重新设置抽奖底注金额为1,优化页面样式
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
"poolName": "Pool Name",
|
"poolName": "Pool Name",
|
||||||
"playerProfit": "Player Total Profit (profit_amount):",
|
"playerProfit": "Player Total Profit (profit_amount):",
|
||||||
"realtime": "Live",
|
"realtime": "Live",
|
||||||
"profitCalcHint": "Profit per round: paid = win_coin (incl. BIGWIN) - paid_amount (= ante×100); free = win_coin. Refreshes every 2s while open.",
|
"profitCalcHint": "Profit per round: paid = win_coin (incl. BIGWIN) - paid_amount (= ante×1); free = win_coin. Refreshes every 2s while open.",
|
||||||
"tierRuleTitle": "Tier Rule",
|
"tierRuleTitle": "Tier Rule",
|
||||||
"tierRuleContent": "When player profit in this pool is below safety line, use player T*_weight; when above or equal, use pool T*_weight (kill).",
|
"tierRuleContent": "When player profit in this pool is below safety line, use player T*_weight; when above or equal, use pool T*_weight (kill).",
|
||||||
"killScoreWeights": "Kill weights",
|
"killScoreWeights": "Kill weights",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"poolName": "池子名称",
|
"poolName": "池子名称",
|
||||||
"playerProfit": "玩家累计盈利(profit_amount):",
|
"playerProfit": "玩家累计盈利(profit_amount):",
|
||||||
"realtime": "实时",
|
"realtime": "实时",
|
||||||
"profitCalcHint": "计算方式:付费每局按“赢取平台币 win_coin(含 BIGWIN)减去付费金额 压注金额paid_amount(= 压注倍数ante×100)”累加;免费每局按“玩家赢得平台币win_coin”累加。弹窗打开期间每 2 秒自动刷新",
|
"profitCalcHint": "计算方式:付费每局按“赢取平台币 win_coin(含 BIGWIN)减去付费金额 压注金额paid_amount(= 压注倍数ante×1)”累加;免费每局按“玩家赢得平台币win_coin”累加。弹窗打开期间每 2 秒自动刷新",
|
||||||
"tierRuleTitle": "抽奖档位规则",
|
"tierRuleTitle": "抽奖档位规则",
|
||||||
"tierRuleContent": "当玩家在当前彩金池的累计盈利 低于安全线 时,按 玩家 的 T*_weight 权重抽取档位;当累计盈利 高于或等于安全线 时,按 当前彩金池 的 T*_weight 权重抽取档位(杀分)。",
|
"tierRuleContent": "当玩家在当前彩金池的累计盈利 低于安全线 时,按 玩家 的 T*_weight 权重抽取档位;当累计盈利 高于或等于安全线 时,按 当前彩金池 的 T*_weight 权重抽取档位(杀分)。",
|
||||||
"killScoreWeights": "杀分权重",
|
"killScoreWeights": "杀分权重",
|
||||||
|
|||||||
@@ -6,7 +6,11 @@
|
|||||||
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
|
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
|
||||||
<template #left>
|
<template #left>
|
||||||
<ElSpace wrap>
|
<ElSpace wrap>
|
||||||
<ElButton v-permission="'dice:ante_config:index:save'" @click="showDialog('add')" v-ripple>
|
<ElButton
|
||||||
|
v-permission="'dice:ante_config:index:save'"
|
||||||
|
@click="showDialog('add')"
|
||||||
|
v-ripple
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<ArtSvgIcon icon="ri:add-fill" />
|
<ArtSvgIcon icon="ri:add-fill" />
|
||||||
</template>
|
</template>
|
||||||
@@ -40,7 +44,7 @@
|
|||||||
@pagination:current-change="handleCurrentChange"
|
@pagination:current-change="handleCurrentChange"
|
||||||
>
|
>
|
||||||
<template #is_default="{ row }">
|
<template #is_default="{ row }">
|
||||||
<ElTag :type="row.is_default === 1 ? 'success' : 'info'" size="small">
|
<ElTag :type="row.is_default === 1 ? 'primary' : 'warning'" size="small">
|
||||||
{{ row.is_default === 1 ? $t('page.table.defaultYes') : $t('page.table.defaultNo') }}
|
{{ row.is_default === 1 ? $t('page.table.defaultYes') : $t('page.table.defaultNo') }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</template>
|
</template>
|
||||||
@@ -111,7 +115,13 @@
|
|||||||
{ prop: 'name', label: 'page.table.name', align: 'center' },
|
{ prop: 'name', label: 'page.table.name', align: 'center' },
|
||||||
{ prop: 'title', label: 'page.table.title', align: 'center' },
|
{ prop: 'title', label: 'page.table.title', align: 'center' },
|
||||||
{ prop: 'mult', label: 'page.table.mult', align: 'center' },
|
{ prop: 'mult', label: 'page.table.mult', align: 'center' },
|
||||||
{ prop: 'is_default', label: 'page.table.isDefault', width: 110, align: 'center', useSlot: true },
|
{
|
||||||
|
prop: 'is_default',
|
||||||
|
label: 'page.table.isDefault',
|
||||||
|
width: 110,
|
||||||
|
align: 'center',
|
||||||
|
useSlot: true
|
||||||
|
},
|
||||||
{ prop: 'create_time', label: 'page.table.createTime', width: 170, align: 'center' },
|
{ prop: 'create_time', label: 'page.table.createTime', width: 170, align: 'center' },
|
||||||
{ prop: 'update_time', label: 'page.table.updateTime', width: 170, align: 'center' },
|
{ prop: 'update_time', label: 'page.table.updateTime', width: 170, align: 'center' },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -99,6 +99,9 @@
|
|||||||
<template #reward_win_coin="{ row }">
|
<template #reward_win_coin="{ row }">
|
||||||
<span>{{ formatPlatformCoin(row?.reward_win_coin) }}</span>
|
<span>{{ formatPlatformCoin(row?.reward_win_coin) }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
<template #paid_amount="{ row }">
|
||||||
|
<span>{{ formatPlatformCoin(row?.paid_amount) }}</span>
|
||||||
|
</template>
|
||||||
<!-- 摇取点数 tag -->
|
<!-- 摇取点数 tag -->
|
||||||
<template #roll_array="{ row }">
|
<template #roll_array="{ row }">
|
||||||
<ElTag size="small">
|
<ElTag size="small">
|
||||||
@@ -157,7 +160,7 @@
|
|||||||
direction: undefined
|
direction: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 当前筛选下平台总盈利(付费抽奖次数×100 - 玩家总收益) */
|
/** 当前筛选下平台总盈利(付费金额 paid_amount 求和 - 玩家总收益) */
|
||||||
const totalWinCoin = ref<number | null>(null)
|
const totalWinCoin = ref<number | null>(null)
|
||||||
|
|
||||||
const listApi = async (params: Record<string, any>) => {
|
const listApi = async (params: Record<string, any>) => {
|
||||||
@@ -197,7 +200,7 @@
|
|||||||
if (val === '' || val === null || val === undefined) return '-'
|
if (val === '' || val === null || val === undefined) return '-'
|
||||||
const n = typeof val === 'number' ? val : Number(val)
|
const n = typeof val === 'number' ? val : Number(val)
|
||||||
if (!Number.isFinite(n)) return '-'
|
if (!Number.isFinite(n)) return '-'
|
||||||
return String(Math.trunc(n))
|
return n.toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格配置
|
// 表格配置
|
||||||
@@ -235,7 +238,7 @@
|
|||||||
{ prop: 'lottery_type', label: 'page.table.drawType', width: 100, useSlot: true },
|
{ prop: 'lottery_type', label: 'page.table.drawType', width: 100, useSlot: true },
|
||||||
{ prop: 'direction', label: 'page.table.direction', width: 90, useSlot: true },
|
{ prop: 'direction', label: 'page.table.direction', width: 90, useSlot: true },
|
||||||
{ prop: 'ante', label: 'page.table.ante', width: 80, align: 'center' },
|
{ prop: 'ante', label: 'page.table.ante', width: 80, align: 'center' },
|
||||||
{ prop: 'paid_amount', label: 'page.table.paidAmount', width: 110, align: 'center' },
|
{ prop: 'paid_amount', label: 'page.table.paidAmount', width: 110, align: 'center', useSlot: true },
|
||||||
{ prop: 'is_win', label: 'page.table.isBigWin', width: 100, useSlot: true },
|
{ prop: 'is_win', label: 'page.table.isBigWin', width: 100, useSlot: true },
|
||||||
{ prop: 'win_coin', label: 'page.table.winCoin', width: 110, useSlot: true },
|
{ prop: 'win_coin', label: 'page.table.winCoin', width: 110, useSlot: true },
|
||||||
{ prop: 'super_win_coin', label: 'page.table.superWinCoin', width: 120, useSlot: true },
|
{ prop: 'super_win_coin', label: 'page.table.superWinCoin', width: 120, useSlot: true },
|
||||||
|
|||||||
@@ -171,7 +171,7 @@
|
|||||||
roll_number: undefined
|
roll_number: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
// 当前筛选下平台总盈利(付费抽奖次数×100 - 玩家总收益)
|
// 当前筛选下平台总盈利(付费金额 paid_amount 求和 - 玩家总收益)
|
||||||
const totalWinCoin = ref<number | null>(null)
|
const totalWinCoin = ref<number | null>(null)
|
||||||
|
|
||||||
const listApi = async (params: Record<string, any>) => {
|
const listApi = async (params: Record<string, any>) => {
|
||||||
@@ -203,7 +203,7 @@
|
|||||||
if (val === '' || val === null || val === undefined) return '-'
|
if (val === '' || val === null || val === undefined) return '-'
|
||||||
const n = typeof val === 'number' ? val : Number(val)
|
const n = typeof val === 'number' ? val : Number(val)
|
||||||
if (!Number.isFinite(n)) return '-'
|
if (!Number.isFinite(n)) return '-'
|
||||||
return String(Math.trunc(n))
|
return n.toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClearAll = async () => {
|
const handleClearAll = async () => {
|
||||||
|
|||||||
@@ -167,7 +167,7 @@
|
|||||||
{
|
{
|
||||||
prop: 'coin',
|
prop: 'coin',
|
||||||
label: 'page.table.coin',
|
label: 'page.table.coin',
|
||||||
width: 100,
|
width: 110,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
useSlot: true
|
useSlot: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -146,11 +146,11 @@
|
|||||||
return player?.username ?? row.player_id ?? '-'
|
return player?.username ?? row.player_id ?? '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatInteger(val: unknown): string {
|
function formatMoney2(val: unknown): string {
|
||||||
if (val === '' || val === null || val === undefined) return '-'
|
if (val === '' || val === null || val === undefined) return '-'
|
||||||
const n = typeof val === 'number' ? val : Number(val)
|
const n = typeof val === 'number' ? val : Number(val)
|
||||||
if (!Number.isFinite(n)) return '-'
|
if (!Number.isFinite(n)) return '-'
|
||||||
return String(Math.trunc(n))
|
return n.toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格配置
|
// 表格配置
|
||||||
@@ -202,14 +202,14 @@
|
|||||||
label: 'page.table.walletBefore',
|
label: 'page.table.walletBefore',
|
||||||
width: 110,
|
width: 110,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
formatter: (row: Record<string, any>) => formatInteger(row?.wallet_before)
|
formatter: (row: Record<string, any>) => formatMoney2(row?.wallet_before)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'wallet_after',
|
prop: 'wallet_after',
|
||||||
label: 'page.table.walletAfter',
|
label: 'page.table.walletAfter',
|
||||||
width: 110,
|
width: 110,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
formatter: (row: Record<string, any>) => formatInteger(row?.wallet_after)
|
formatter: (row: Record<string, any>) => formatMoney2(row?.wallet_after)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'remark',
|
prop: 'remark',
|
||||||
|
|||||||
@@ -45,7 +45,8 @@
|
|||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="formData.coin"
|
v-model="formData.coin"
|
||||||
:placeholder="$t('page.form.placeholderCoinChange')"
|
:placeholder="$t('page.form.placeholderCoinChange')"
|
||||||
:precision="0"
|
:precision="2"
|
||||||
|
:step="1"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="onCoinChange"
|
@change="onCoinChange"
|
||||||
:disabled="dialogType === 'edit'"
|
:disabled="dialogType === 'edit'"
|
||||||
@@ -55,7 +56,7 @@
|
|||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="formData.wallet_before"
|
v-model="formData.wallet_before"
|
||||||
:placeholder="$t('page.form.placeholderWalletBefore')"
|
:placeholder="$t('page.form.placeholderWalletBefore')"
|
||||||
:precision="0"
|
:precision="2"
|
||||||
disabled
|
disabled
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
@@ -64,7 +65,7 @@
|
|||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="formData.wallet_after"
|
v-model="formData.wallet_after"
|
||||||
:placeholder="$t('page.form.placeholderWalletAfter')"
|
:placeholder="$t('page.form.placeholderWalletAfter')"
|
||||||
:precision="0"
|
:precision="2"
|
||||||
disabled
|
disabled
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
@@ -178,7 +179,7 @@
|
|||||||
function calcWalletAfter() {
|
function calcWalletAfter() {
|
||||||
const before = Number(formData.wallet_before) || 0
|
const before = Number(formData.wallet_before) || 0
|
||||||
const coin = Number(formData.coin) || 0
|
const coin = Number(formData.coin) || 0
|
||||||
formData.wallet_after = Math.trunc(before + coin)
|
formData.wallet_after = Number((before + coin).toFixed(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -204,11 +205,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeInteger(val: unknown, fallback: number): number {
|
function normalizeMoney2(val: unknown, fallback: number): number {
|
||||||
if (val === '' || val === null || val === undefined) return fallback
|
if (val === '' || val === null || val === undefined) return fallback
|
||||||
const n = typeof val === 'number' ? val : Number(val)
|
const n = typeof val === 'number' ? val : Number(val)
|
||||||
if (!Number.isFinite(n)) return fallback
|
if (!Number.isFinite(n)) return fallback
|
||||||
return Math.trunc(n)
|
return Number(n.toFixed(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
const initForm = () => {
|
const initForm = () => {
|
||||||
@@ -218,9 +219,9 @@
|
|||||||
formData.player_id =
|
formData.player_id =
|
||||||
props.data.player_id != null && props.data.player_id !== '' ? Number(props.data.player_id) : null
|
props.data.player_id != null && props.data.player_id !== '' ? Number(props.data.player_id) : null
|
||||||
formData.type = props.data.type != null && props.data.type !== '' ? Number(props.data.type) : null
|
formData.type = props.data.type != null && props.data.type !== '' ? Number(props.data.type) : null
|
||||||
formData.coin = normalizeInteger(props.data.coin, 0)
|
formData.coin = normalizeMoney2(props.data.coin, 0)
|
||||||
formData.wallet_before = normalizeInteger(props.data.wallet_before, 0)
|
formData.wallet_before = normalizeMoney2(props.data.wallet_before, 0)
|
||||||
formData.wallet_after = normalizeInteger(props.data.wallet_after, 0)
|
formData.wallet_after = normalizeMoney2(props.data.wallet_after, 0)
|
||||||
formData.remark = props.data.remark ?? ''
|
formData.remark = props.data.remark ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,11 +70,11 @@
|
|||||||
return api.list({ ...params, direction: currentDirection.value })
|
return api.list({ ...params, direction: currentDirection.value })
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatInteger(val: unknown): string {
|
function formatMoney2(val: unknown): string {
|
||||||
if (val === '' || val === null || val === undefined) return '-'
|
if (val === '' || val === null || val === undefined) return '-'
|
||||||
const n = typeof val === 'number' ? val : Number(val)
|
const n = typeof val === 'number' ? val : Number(val)
|
||||||
if (!Number.isFinite(n)) return '-'
|
if (!Number.isFinite(n)) return '-'
|
||||||
return String(Math.trunc(n))
|
return n.toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearch = (params: Record<string, any>) => {
|
const handleSearch = (params: Record<string, any>) => {
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
label: 'page.table.realEv',
|
label: 'page.table.realEv',
|
||||||
width: 110,
|
width: 110,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
formatter: (row: Record<string, any>) => formatInteger(row?.real_ev)
|
formatter: (row: Record<string, any>) => formatMoney2(row?.real_ev)
|
||||||
},
|
},
|
||||||
{ prop: 'remark', label: 'page.table.remark', minWidth: 80, align: 'center', showOverflowTooltip: true },
|
{ prop: 'remark', label: 'page.table.remark', minWidth: 80, align: 'center', showOverflowTooltip: true },
|
||||||
{ prop: 'weight', label: 'page.table.weight', width: 110, align: 'center' }
|
{ prop: 'weight', label: 'page.table.weight', width: 110, align: 'center' }
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span>{{ formatInteger(row?.real_ev) }}</span>
|
<span>{{ formatMoney2(row?.real_ev) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@@ -254,11 +254,11 @@
|
|||||||
import api from '../../../api/reward/index'
|
import api from '../../../api/reward/index'
|
||||||
import ArtBarChart from '@/components/core/charts/art-bar-chart/index.vue'
|
import ArtBarChart from '@/components/core/charts/art-bar-chart/index.vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
function formatInteger(val: unknown): string {
|
function formatMoney2(val: unknown): string {
|
||||||
if (val === '' || val === null || val === undefined) return '-'
|
if (val === '' || val === null || val === undefined) return '-'
|
||||||
const n = typeof val === 'number' ? val : Number(val)
|
const n = typeof val === 'number' ? val : Number(val)
|
||||||
if (!Number.isFinite(n)) return '-'
|
if (!Number.isFinite(n)) return '-'
|
||||||
return String(Math.trunc(n))
|
return n.toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span>{{ formatInteger(row?.real_ev) }}</span>
|
<span>{{ formatMoney2(row?.real_ev) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@@ -319,11 +319,11 @@
|
|||||||
import ArtBarChart from '@/components/core/charts/art-bar-chart/index.vue'
|
import ArtBarChart from '@/components/core/charts/art-bar-chart/index.vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
function formatInteger(val: unknown): string {
|
function formatMoney2(val: unknown): string {
|
||||||
if (val === '' || val === null || val === undefined) return '-'
|
if (val === '' || val === null || val === undefined) return '-'
|
||||||
const n = typeof val === 'number' ? val : Number(val)
|
const n = typeof val === 'number' ? val : Number(val)
|
||||||
if (!Number.isFinite(n)) return '-'
|
if (!Number.isFinite(n)) return '-'
|
||||||
return String(Math.trunc(n))
|
return n.toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -81,13 +81,14 @@
|
|||||||
@change="handleRealEvChange(row)"
|
@change="handleRealEvChange(row)"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
size="small"
|
size="small"
|
||||||
|
:step="1"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn :label="$t('page.configPage.colRealReward')" min-width="130" align="center">
|
<ElTableColumn :label="$t('page.configPage.colRealReward')" min-width="130" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span>{{ calcRealReward(row.real_ev) }}</span>
|
<span>{{ formatMoney2(calcRealReward(row.real_ev)) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn :label="$t('page.configPage.colTier')" width="100" align="center">
|
<ElTableColumn :label="$t('page.configPage.colTier')" width="100" align="center">
|
||||||
@@ -171,6 +172,7 @@
|
|||||||
@change="handleRealEvChange(row)"
|
@change="handleRealEvChange(row)"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
size="small"
|
size="small"
|
||||||
|
:step="1"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -501,11 +503,18 @@
|
|||||||
typeof row.real_ev === 'number' && !Number.isNaN(row.real_ev)
|
typeof row.real_ev === 'number' && !Number.isNaN(row.real_ev)
|
||||||
? row.real_ev
|
? row.real_ev
|
||||||
: Number(row.real_ev)
|
: Number(row.real_ev)
|
||||||
const text = Number.isNaN(n) ? '' : String(n)
|
const text = Number.isNaN(n) ? '' : Number(n).toFixed(2)
|
||||||
row.ui_text = text
|
row.ui_text = text
|
||||||
row.ui_text_en = text
|
row.ui_text_en = text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatMoney2(val: unknown): string {
|
||||||
|
if (val === '' || val === null || val === undefined) return '-'
|
||||||
|
const n = typeof val === 'number' ? val : Number(val)
|
||||||
|
if (!Number.isFinite(n)) return '-'
|
||||||
|
return n.toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
async function handleCreateRewardReference() {
|
async function handleCreateRewardReference() {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await ElMessageBox.confirm(
|
||||||
|
|||||||
@@ -278,13 +278,13 @@ function uiTextByTierWhenStandards(
|
|||||||
if (tier === 'T5') {
|
if (tier === 'T5') {
|
||||||
return { ui_text: '再来一次', ui_text_en: 'Once again' }
|
return { ui_text: '再来一次', ui_text_en: 'Once again' }
|
||||||
}
|
}
|
||||||
const value = String(realEv)
|
const value = Number.isFinite(realEv) ? realEv.toFixed(2) : String(realEv)
|
||||||
return { ui_text: value, ui_text_en: value }
|
return { ui_text: value, ui_text_en: value }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 展示文案:直接使用真实结算值(中英文相同) */
|
/** 展示文案:直接使用真实结算值(中英文相同) */
|
||||||
function uiTextFromRealEv(realEv: number): { ui_text: string; ui_text_en: string } {
|
function uiTextFromRealEv(realEv: number): { ui_text: string; ui_text_en: string } {
|
||||||
const value = String(realEv)
|
const value = Number.isFinite(realEv) ? realEv.toFixed(2) : String(realEv)
|
||||||
return { ui_text: value, ui_text_en: value }
|
return { ui_text: value, ui_text_en: value }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,7 @@
|
|||||||
if (v === null || v === undefined || v === '') return dash
|
if (v === null || v === undefined || v === '') return dash
|
||||||
const n = Number(v)
|
const n = Number(v)
|
||||||
if (Number.isNaN(n)) return dash
|
if (Number.isNaN(n)) return dash
|
||||||
return String(n)
|
return n.toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 链式再来一次:1=是(新库字段),JSON 旧数据用 tier_weights_snapshot.chain_free_mode */
|
/** 链式再来一次:1=是(新库字段),JSON 旧数据用 tier_weights_snapshot.chain_free_mode */
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class GameController extends BaseController
|
|||||||
* 购买抽奖券
|
* 购买抽奖券
|
||||||
* POST /api/game/buyLotteryTickets
|
* POST /api/game/buyLotteryTickets
|
||||||
* header: token(由 TokenMiddleware 注入 request->player_id)
|
* header: token(由 TokenMiddleware 注入 request->player_id)
|
||||||
* body: count = 1 | 5 | 10(1次/100coin, 5次/500coin, 10次/1000coin)
|
* body: count = 1 | 5 | 10(1次/1coin, 5次/5coin, 10次/10coin)
|
||||||
*/
|
*/
|
||||||
// public function buyLotteryTickets(Request $request): Response
|
// public function buyLotteryTickets(Request $request): Response
|
||||||
// {
|
// {
|
||||||
@@ -217,6 +217,7 @@ class GameController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$data['tier'] = $data['reward_tier'] ?? '';
|
||||||
|
|
||||||
return $this->success($data);
|
return $this->success($data);
|
||||||
} catch (ApiException $e) {
|
} catch (ApiException $e) {
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ use support\think\Db;
|
|||||||
class GameLogic
|
class GameLogic
|
||||||
{
|
{
|
||||||
public const PACKAGES = [
|
public const PACKAGES = [
|
||||||
1 => ['coin' => 100, 'paid' => 1, 'free' => 0], // 1次/100coin
|
1 => ['coin' => 1, 'paid' => 1, 'free' => 0], // 1次/1coin
|
||||||
5 => ['coin' => 500, 'paid' => 5, 'free' => 1], // 5张/500coin(5购买+1赠送,共6次)
|
5 => ['coin' => 5, 'paid' => 5, 'free' => 1], // 5张/5coin(5购买+1赠送,共6次)
|
||||||
10 => ['coin' => 1000, 'paid' => 10, 'free' => 3], // 10张/1000coin(10购买+3赠送,共13次)
|
10 => ['coin' => 10, 'paid' => 10, 'free' => 3], // 10张/10coin(10购买+3赠送,共13次)
|
||||||
];
|
];
|
||||||
|
|
||||||
/** 钱包流水类型:购买抽奖次数 */
|
/** 钱包流水类型:购买抽奖次数 */
|
||||||
@@ -52,7 +52,7 @@ class GameLogic
|
|||||||
throw new ApiException('Insufficient balance');
|
throw new ApiException('Insufficient balance');
|
||||||
}
|
}
|
||||||
|
|
||||||
$coinAfter = $coinBefore - $cost;
|
$coinAfter = round($coinBefore - $cost, 2);
|
||||||
$totalBefore = (int) ($player->total_ticket_count ?? 0);
|
$totalBefore = (int) ($player->total_ticket_count ?? 0);
|
||||||
$paidBefore = (int) ($player->paid_ticket_count ?? 0);
|
$paidBefore = (int) ($player->paid_ticket_count ?? 0);
|
||||||
$freeBefore = (int) ($player->free_ticket_count ?? 0);
|
$freeBefore = (int) ($player->free_ticket_count ?? 0);
|
||||||
@@ -94,7 +94,7 @@ class GameLogic
|
|||||||
DicePlayerWalletRecord::create([
|
DicePlayerWalletRecord::create([
|
||||||
'player_id' => $playerId,
|
'player_id' => $playerId,
|
||||||
'admin_id' => $adminId,
|
'admin_id' => $adminId,
|
||||||
'coin' => -$cost,
|
'coin' => round(-$cost, 2),
|
||||||
'type' => self::WALLET_TYPE_BUY_DRAW,
|
'type' => self::WALLET_TYPE_BUY_DRAW,
|
||||||
'wallet_before' => $coinBefore,
|
'wallet_before' => $coinBefore,
|
||||||
'wallet_after' => $coinAfter,
|
'wallet_after' => $coinAfter,
|
||||||
@@ -107,7 +107,7 @@ class GameLogic
|
|||||||
DicePlayerTicketRecord::create([
|
DicePlayerTicketRecord::create([
|
||||||
'player_id' => $playerId,
|
'player_id' => $playerId,
|
||||||
'admin_id' => $adminId,
|
'admin_id' => $adminId,
|
||||||
'use_coins' => $cost,
|
'use_coins' => round($cost, 2),
|
||||||
'ante' => 1,
|
'ante' => 1,
|
||||||
'total_ticket_count' => $addTotal,
|
'total_ticket_count' => $addTotal,
|
||||||
'paid_ticket_count' => $addPaid,
|
'paid_ticket_count' => $addPaid,
|
||||||
@@ -121,7 +121,7 @@ class GameLogic
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'coin' => (float) $coinAfter,
|
'coin' => round((float) $coinAfter, 2),
|
||||||
'total_ticket_count' => (int) $totalAfter,
|
'total_ticket_count' => (int) $totalAfter,
|
||||||
'paid_ticket_count' => (int) $paidAfter,
|
'paid_ticket_count' => (int) $paidAfter,
|
||||||
'free_ticket_count' => (int) $freeAfter,
|
'free_ticket_count' => (int) $freeAfter,
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ class PlayStartLogic
|
|||||||
/** 对局状态:超时/失败 */
|
/** 对局状态:超时/失败 */
|
||||||
public const RECORD_STATUS_TIMEOUT = 0;
|
public const RECORD_STATUS_TIMEOUT = 0;
|
||||||
|
|
||||||
/** 单注费用(对应原票价 100) */
|
/** 单注费用(抽奖券基础费用) */
|
||||||
private const UNIT_COST = 100;
|
private const UNIT_COST = 1.0;
|
||||||
/** 免费抽奖注数缓存 key 前缀(用于强制下一局注数一致) */
|
/** 免费抽奖注数缓存 key 前缀(用于强制下一局注数一致) */
|
||||||
private const FREE_ANTE_KEY_PREFIX = 'api:game:free_ante:';
|
private const FREE_ANTE_KEY_PREFIX = 'api:game:free_ante:';
|
||||||
/** 免费抽奖注数缓存过期(秒) */
|
/** 免费抽奖注数缓存过期(秒) */
|
||||||
@@ -122,8 +122,8 @@ class PlayStartLogic
|
|||||||
throw new ApiException('未达抽奖余额 ' . $needMinBalance . ',无法开始游戏');
|
throw new ApiException('未达抽奖余额 ' . $needMinBalance . ',无法开始游戏');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 付费抽奖:开始前扣除费用 ante * 100,不足则提示余额不足
|
// 付费抽奖:开始前扣除费用 ante * UNIT_COST,不足则提示余额不足
|
||||||
$paidAmount = $ticketType === self::LOTTERY_TYPE_PAID ? ($ante * self::UNIT_COST) : 0;
|
$paidAmount = $ticketType === self::LOTTERY_TYPE_PAID ? round($ante * self::UNIT_COST, 2) : 0.0;
|
||||||
if ($ticketType === self::LOTTERY_TYPE_PAID && $coin < $paidAmount) {
|
if ($ticketType === self::LOTTERY_TYPE_PAID && $coin < $paidAmount) {
|
||||||
throw new ApiException('余额不足');
|
throw new ApiException('余额不足');
|
||||||
}
|
}
|
||||||
@@ -188,12 +188,12 @@ class PlayStartLogic
|
|||||||
if ($isTierT5 === false && (string) ($tier ?? '') === 'T5') {
|
if ($isTierT5 === false && (string) ($tier ?? '') === 'T5') {
|
||||||
$isTierT5 = true;
|
$isTierT5 = true;
|
||||||
}
|
}
|
||||||
// 摇色子中奖:按 dice_reward_config.real_ev 直接结算(已乘 ante);不再叠加票价 100
|
// 摇色子中奖:按 dice_reward_config.real_ev 直接结算(已乘 ante)
|
||||||
$rewardWinCoin = $realEv * $ante;
|
$rewardWinCoin = round($realEv * $ante, 2);
|
||||||
|
|
||||||
// 豹子判定:5/30 必豹子;10/15/20/25 按 DiceRewardConfig 中 BIGWIN 该点数的 weight 判定(0-10000,10000=100%)
|
// 豹子判定:5/30 必豹子;10/15/20/25 按 DiceRewardConfig 中 BIGWIN 该点数的 weight 判定(0-10000,10000=100%)
|
||||||
// 杀分档位:不触发豹子,5/30 已在上方抽取时排除,10/15/20/25 仅生成非豹子组合
|
// 杀分档位:不触发豹子,5/30 已在上方抽取时排除,10/15/20/25 仅生成非豹子组合
|
||||||
$superWinCoin = 0;
|
$superWinCoin = 0.0;
|
||||||
$isWin = 0;
|
$isWin = 0;
|
||||||
$bigWinRealEv = 0.0;
|
$bigWinRealEv = 0.0;
|
||||||
if (in_array($rollNumber, self::SUPER_WIN_GRID_NUMBERS, true)) {
|
if (in_array($rollNumber, self::SUPER_WIN_GRID_NUMBERS, true)) {
|
||||||
@@ -223,10 +223,10 @@ class PlayStartLogic
|
|||||||
$rollArray = $this->getSuperWinRollArray($rollNumber);
|
$rollArray = $this->getSuperWinRollArray($rollNumber);
|
||||||
$isWin = 1;
|
$isWin = 1;
|
||||||
$bigWinEv = $bigWinRealEv > 0 ? $bigWinRealEv : self::SUPER_WIN_BONUS;
|
$bigWinEv = $bigWinRealEv > 0 ? $bigWinRealEv : self::SUPER_WIN_BONUS;
|
||||||
$superWinCoin = $bigWinEv * $ante;
|
$superWinCoin = round($bigWinEv * $ante, 2);
|
||||||
// 中 BIGWIN 豹子:不走原奖励流程,不记录原奖励,不触发 T5 再来一次,仅发放豹子奖金
|
// 中 BIGWIN 豹子:不走原奖励流程,不记录原奖励,不触发 T5 再来一次,仅发放豹子奖金
|
||||||
$rewardWinCoin = 0;
|
$rewardWinCoin = 0.0;
|
||||||
$realEv = 0;
|
$realEv = 0.0;
|
||||||
$isTierT5 = false;
|
$isTierT5 = false;
|
||||||
} else {
|
} else {
|
||||||
$rollArray = $this->generateNonSuperWinRollArrayWithSum($rollNumber);
|
$rollArray = $this->generateNonSuperWinRollArrayWithSum($rollNumber);
|
||||||
@@ -243,7 +243,7 @@ class PlayStartLogic
|
|||||||
$startIndex,
|
$startIndex,
|
||||||
$targetIndex
|
$targetIndex
|
||||||
));
|
));
|
||||||
$winCoin = $superWinCoin + $rewardWinCoin; // 赢取平台币 = 中大奖 + 摇色子中奖(豹子时 rewardWinCoin 已为 0)
|
$winCoin = round($superWinCoin + $rewardWinCoin, 2); // 赢取平台币 = 中大奖 + 摇色子中奖(豹子时 rewardWinCoin 已为 0)
|
||||||
|
|
||||||
$record = null;
|
$record = null;
|
||||||
$configId = (int) $config->id;
|
$configId = (int) $config->id;
|
||||||
@@ -302,7 +302,7 @@ class PlayStartLogic
|
|||||||
}
|
}
|
||||||
$coinBefore = (float) $p->coin;
|
$coinBefore = (float) $p->coin;
|
||||||
// 开始前先扣付费金额,再加中奖金额(免费抽奖 paid_amount=0)
|
// 开始前先扣付费金额,再加中奖金额(免费抽奖 paid_amount=0)
|
||||||
$coinAfter = $coinBefore - $paidAmount + $winCoin;
|
$coinAfter = round($coinBefore - $paidAmount + $winCoin, 2);
|
||||||
$p->coin = $coinAfter;
|
$p->coin = $coinAfter;
|
||||||
// 免费抽奖消耗:优先消耗 free_ticket.count,耗尽则清空 free_ticket;否则兼容旧 free_ticket_count
|
// 免费抽奖消耗:优先消耗 free_ticket.count,耗尽则清空 free_ticket;否则兼容旧 free_ticket_count
|
||||||
if ($ticketType === self::LOTTERY_TYPE_FREE) {
|
if ($ticketType === self::LOTTERY_TYPE_FREE) {
|
||||||
@@ -400,13 +400,13 @@ class PlayStartLogic
|
|||||||
$p->save();
|
$p->save();
|
||||||
|
|
||||||
// 彩金池累计盈利累加在 name=default 彩金池上:
|
// 彩金池累计盈利累加在 name=default 彩金池上:
|
||||||
// 付费:每局按「本局赢取平台币 win_coin - 抽奖费用 paid_amount(ante*100)」
|
// 付费:每局按「本局赢取平台币 win_coin - 抽奖费用 paid_amount(ante*UNIT_COST)」
|
||||||
// 免费券:paid_amount=0,只计入 win_coin
|
// 免费券:paid_amount=0,只计入 win_coin
|
||||||
$perPlayProfit = ($ticketType === self::LOTTERY_TYPE_PAID) ? ($winCoin - (float) $paidAmount) : $winCoin;
|
$perPlayProfit = ($ticketType === self::LOTTERY_TYPE_PAID) ? ($winCoin - $paidAmount) : $winCoin;
|
||||||
$addProfit = $perPlayProfit;
|
$addProfit = round($perPlayProfit, 2);
|
||||||
try {
|
try {
|
||||||
DiceLotteryPoolConfig::where('id', $type0ConfigId)->update([
|
DiceLotteryPoolConfig::where('id', $type0ConfigId)->update([
|
||||||
'profit_amount' => Db::raw('IFNULL(profit_amount,0) + ' . (float) $addProfit),
|
'profit_amount' => Db::raw('IFNULL(profit_amount,0) + ' . sprintf('%.2f', $addProfit)),
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
Log::warning('彩金池盈利累加失败', [
|
Log::warning('彩金池盈利累加失败', [
|
||||||
@@ -418,11 +418,11 @@ class PlayStartLogic
|
|||||||
|
|
||||||
// 钱包流水拆分:先记录购券扣费,再记录抽奖结果(中奖/惩罚)
|
// 钱包流水拆分:先记录购券扣费,再记录抽奖结果(中奖/惩罚)
|
||||||
if ($paidAmount > 0) {
|
if ($paidAmount > 0) {
|
||||||
$walletAfterBuy = $coinBefore - $paidAmount;
|
$walletAfterBuy = round($coinBefore - $paidAmount, 2);
|
||||||
DicePlayerWalletRecord::create([
|
DicePlayerWalletRecord::create([
|
||||||
'player_id' => $playerId,
|
'player_id' => $playerId,
|
||||||
'admin_id' => $adminId,
|
'admin_id' => $adminId,
|
||||||
'coin' => -$paidAmount,
|
'coin' => round(-$paidAmount, 2),
|
||||||
'type' => self::WALLET_TYPE_BUY_DRAW,
|
'type' => self::WALLET_TYPE_BUY_DRAW,
|
||||||
'wallet_before' => $coinBefore,
|
'wallet_before' => $coinBefore,
|
||||||
'wallet_after' => $walletAfterBuy,
|
'wallet_after' => $walletAfterBuy,
|
||||||
@@ -437,7 +437,7 @@ class PlayStartLogic
|
|||||||
'admin_id' => $adminId,
|
'admin_id' => $adminId,
|
||||||
'coin' => $winCoin,
|
'coin' => $winCoin,
|
||||||
'type' => self::WALLET_TYPE_DRAW,
|
'type' => self::WALLET_TYPE_DRAW,
|
||||||
'wallet_before' => $walletBeforeDraw,
|
'wallet_before' => round($walletBeforeDraw, 2),
|
||||||
'wallet_after' => $coinAfter,
|
'wallet_after' => $coinAfter,
|
||||||
'remark' => $drawRemark,
|
'remark' => $drawRemark,
|
||||||
]);
|
]);
|
||||||
@@ -484,9 +484,9 @@ class PlayStartLogic
|
|||||||
$arr['roll_number'] = is_array($arr['roll_array'] ?? null) ? array_sum($arr['roll_array']) : 0;
|
$arr['roll_number'] = is_array($arr['roll_array'] ?? null) ? array_sum($arr['roll_array']) : 0;
|
||||||
$arr['reward_tier'] = ($isWin === 1 && $superWinCoin > 0) ? 'BIGWIN' : (string) ($tier ?? '');
|
$arr['reward_tier'] = ($isWin === 1 && $superWinCoin > 0) ? 'BIGWIN' : (string) ($tier ?? '');
|
||||||
// 记录完数据后返回当前玩家余额与抽奖次数
|
// 记录完数据后返回当前玩家余额与抽奖次数
|
||||||
$arr['coin'] = $updated ? (float) $updated->coin : 0;
|
$arr['coin'] = $updated ? round((float) $updated->coin, 2) : 0.0;
|
||||||
// 本局从玩家货币中扣除的金额:付费抽奖为 ante*UNIT_COST,免费抽奖为 0(与 paid_amount 一致)
|
// 本局从玩家货币中扣除的金额:付费抽奖为 ante*UNIT_COST,免费抽奖为 0(与 paid_amount 一致)
|
||||||
$arr['use_coin'] = $paidAmount;
|
$arr['use_coin'] = round($paidAmount, 2);
|
||||||
return $arr;
|
return $arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,9 +669,9 @@ class PlayStartLogic
|
|||||||
$rollNumber = (int) ($chosen['grid_number'] ?? 0);
|
$rollNumber = (int) ($chosen['grid_number'] ?? 0);
|
||||||
$realEv = (float) ($chosen['real_ev'] ?? 0);
|
$realEv = (float) ($chosen['real_ev'] ?? 0);
|
||||||
// 摇色子中奖:按 real_ev 直接结算(与正式抽奖 run() 一致)
|
// 摇色子中奖:按 real_ev 直接结算(与正式抽奖 run() 一致)
|
||||||
$rewardWinCoin = $realEv * $ante;
|
$rewardWinCoin = round($realEv * $ante, 2);
|
||||||
|
|
||||||
$superWinCoin = 0;
|
$superWinCoin = 0.0;
|
||||||
$isWin = 0;
|
$isWin = 0;
|
||||||
$bigWinRealEv = 0.0;
|
$bigWinRealEv = 0.0;
|
||||||
if (in_array($rollNumber, self::SUPER_WIN_GRID_NUMBERS, true)) {
|
if (in_array($rollNumber, self::SUPER_WIN_GRID_NUMBERS, true)) {
|
||||||
@@ -699,8 +699,8 @@ class PlayStartLogic
|
|||||||
$rollArray = $this->getSuperWinRollArray($rollNumber);
|
$rollArray = $this->getSuperWinRollArray($rollNumber);
|
||||||
$isWin = 1;
|
$isWin = 1;
|
||||||
$bigWinEv = $bigWinRealEv > 0 ? $bigWinRealEv : self::SUPER_WIN_BONUS;
|
$bigWinEv = $bigWinRealEv > 0 ? $bigWinRealEv : self::SUPER_WIN_BONUS;
|
||||||
$superWinCoin = $bigWinEv * $ante;
|
$superWinCoin = round($bigWinEv * $ante, 2);
|
||||||
$rewardWinCoin = 0;
|
$rewardWinCoin = 0.0;
|
||||||
// 中豹子时不走原奖励流程
|
// 中豹子时不走原奖励流程
|
||||||
$realEv = 0.0;
|
$realEv = 0.0;
|
||||||
} else {
|
} else {
|
||||||
@@ -711,11 +711,11 @@ class PlayStartLogic
|
|||||||
$rollArray = $this->generateRollArrayFromSum($rollNumber);
|
$rollArray = $this->generateRollArrayFromSum($rollNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
$winCoin = $superWinCoin + $rewardWinCoin;
|
$winCoin = round($superWinCoin + $rewardWinCoin, 2);
|
||||||
$configId = $config !== null ? (int) $config->id : 0;
|
$configId = $config !== null ? (int) $config->id : 0;
|
||||||
$configName = $config !== null ? (string) ($config->name ?? '') : '自定义';
|
$configName = $config !== null ? (string) ($config->name ?? '') : '自定义';
|
||||||
$costRealEv = $realEv + ($isWin === 1 ? $bigWinRealEv : 0.0);
|
$costRealEv = $realEv + ($isWin === 1 ? $bigWinRealEv : 0.0);
|
||||||
$paidAmount = $lotteryType === 0 ? ($ante * self::UNIT_COST) : 0;
|
$paidAmount = $lotteryType === 0 ? round($ante * self::UNIT_COST, 2) : 0.0;
|
||||||
$rewardTier = ($isWin === 1 && $superWinCoin > 0) ? 'BIGWIN' : (string) ($tier ?? '');
|
$rewardTier = ($isWin === 1 && $superWinCoin > 0) ? 'BIGWIN' : (string) ($tier ?? '');
|
||||||
// 与写入记录的 reward_tier 完全一致:仅当展示档位为 T5(非豹子大奖)时触发「再来一次」链式免费局
|
// 与写入记录的 reward_tier 完全一致:仅当展示档位为 T5(非豹子大奖)时触发「再来一次」链式免费局
|
||||||
$grantsFreeTicket = ($rewardTier === 'T5');
|
$grantsFreeTicket = ($rewardTier === 'T5');
|
||||||
|
|||||||
@@ -59,12 +59,12 @@ class DicePlayRecordController extends BaseController
|
|||||||
'diceLotteryPoolConfig',
|
'diceLotteryPoolConfig',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 按当前筛选条件统计:平台总盈利 = 付费抽奖(lottery_type=0)次数×100 - 玩家总收益(win_coin 求和)
|
// 按当前筛选条件统计:平台总盈利 = 付费金额(paid_amount 求和) - 玩家总收益(win_coin 求和)
|
||||||
$sumQuery = clone $query;
|
$sumQuery = clone $query;
|
||||||
$playerTotalWin = (float) $sumQuery->sum('win_coin');
|
$playerTotalWin = (float) $sumQuery->sum('win_coin');
|
||||||
$paidAmountQuery = clone $query;
|
$paidAmountQuery = clone $query;
|
||||||
$paidAmount = (float) $paidAmountQuery->where('lottery_type', 0)->sum('paid_amount');
|
$paidAmount = (float) $paidAmountQuery->where('lottery_type', 0)->sum('paid_amount');
|
||||||
$totalWinCoin = $paidAmount - $playerTotalWin;
|
$totalWinCoin = round($paidAmount - $playerTotalWin, 2);
|
||||||
|
|
||||||
$data = $this->logic->getList($query);
|
$data = $this->logic->getList($query);
|
||||||
$data['total_win_coin'] = $totalWinCoin;
|
$data['total_win_coin'] = $totalWinCoin;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class DicePlayRecordTestController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据列表,并在结果中附带当前筛选条件下测试数据的平台总盈利 total_win_coin(付费抽奖次数×100 - 玩家总收益)
|
* 数据列表,并在结果中附带当前筛选条件下测试数据的平台总盈利 total_win_coin(付费金额 paid_amount 求和 - 玩家总收益)
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
@@ -57,7 +57,7 @@ class DicePlayRecordTestController extends BaseController
|
|||||||
$playerTotalWin = (float) $sumQuery->sum('win_coin');
|
$playerTotalWin = (float) $sumQuery->sum('win_coin');
|
||||||
$paidAmountQuery = clone $query;
|
$paidAmountQuery = clone $query;
|
||||||
$paidAmount = (float) $paidAmountQuery->where('lottery_type', 0)->sum('paid_amount');
|
$paidAmount = (float) $paidAmountQuery->where('lottery_type', 0)->sum('paid_amount');
|
||||||
$totalWinCoin = $paidAmount - $playerTotalWin;
|
$totalWinCoin = round($paidAmount - $playerTotalWin, 2);
|
||||||
|
|
||||||
$data = $this->logic->getList($query);
|
$data = $this->logic->getList($query);
|
||||||
$data['total_win_coin'] = $totalWinCoin;
|
$data['total_win_coin'] = $totalWinCoin;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use think\model\relation\BelongsTo;
|
|||||||
* @property $lottery_config_id 彩金池配置
|
* @property $lottery_config_id 彩金池配置
|
||||||
* @property $lottery_type 抽奖类型
|
* @property $lottery_type 抽奖类型
|
||||||
* @property $ante 底注/注数(dice_ante_config.mult)
|
* @property $ante 底注/注数(dice_ante_config.mult)
|
||||||
* @property $paid_amount 付费金额(付费局=ante*100,免费局=0)
|
* @property $paid_amount 付费金额(付费局=ante*1,免费局=0)
|
||||||
* @property $is_win 是否中大奖:豹子号[1,1,1,1,1]~[6,6,6,6,6]为1,否则0
|
* @property $is_win 是否中大奖:豹子号[1,1,1,1,1]~[6,6,6,6,6]为1,否则0
|
||||||
* @property $win_coin 赢取平台币(= super_win_coin + reward_win_coin)
|
* @property $win_coin 赢取平台币(= super_win_coin + reward_win_coin)
|
||||||
* @property $super_win_coin 中大奖平台币(豹子时发放)
|
* @property $super_win_coin 中大奖平台币(豹子时发放)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use think\model\relation\BelongsTo;
|
|||||||
* @property $is_win 中大奖:0=无,1=中奖
|
* @property $is_win 中大奖:0=无,1=中奖
|
||||||
* @property $win_coin 赢取平台币
|
* @property $win_coin 赢取平台币
|
||||||
* @property int|null $ante 底注/注数(dice_ante_config.mult)
|
* @property int|null $ante 底注/注数(dice_ante_config.mult)
|
||||||
* @property int|null $paid_amount 付费金额(付费局=ante*100,免费局=0)
|
* @property int|null $paid_amount 付费金额(付费局=ante*1,免费局=0)
|
||||||
* @property $direction 方向:0=顺时针,1=逆时针
|
* @property $direction 方向:0=顺时针,1=逆时针
|
||||||
* @property $reward_tier 中奖档位:T1,T2,T3,T4,T5,BIGWIN
|
* @property $reward_tier 中奖档位:T1,T2,T3,T4,T5,BIGWIN
|
||||||
* @property $create_time 创建时间
|
* @property $create_time 创建时间
|
||||||
@@ -113,7 +113,7 @@ class DicePlayRecordTest extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 付费金额(付费局=ante*100,免费局=0) */
|
/** 付费金额(付费局=ante*1,免费局=0) */
|
||||||
public function searchPaidAmountAttr($query, $value)
|
public function searchPaidAmountAttr($query, $value)
|
||||||
{
|
{
|
||||||
if ($value !== '' && $value !== null) {
|
if ($value !== '' && $value !== null) {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ use think\model\relation\HasMany;
|
|||||||
* @property array|null $free_tier_weights 免费自定义档位权重 T1-T5
|
* @property array|null $free_tier_weights 免费自定义档位权重 T1-T5
|
||||||
* @property array $result_counts 落点统计 grid_number=>出现次数
|
* @property array $result_counts 落点统计 grid_number=>出现次数
|
||||||
* @property array|null $tier_counts 档位出现次数 T1=>count
|
* @property array|null $tier_counts 档位出现次数 T1=>count
|
||||||
* @property float|null $platform_profit 平台赚取金额(付费抽取次数×100-玩家总收益)
|
* @property float|null $platform_profit 平台赚取金额(付费金额 paid_amount 求和-玩家总收益)
|
||||||
* @property array|null $bigwin_weight 测试时 BIGWIN 档位权重快照(JSON:grid_number=>weight)
|
* @property array|null $bigwin_weight 测试时 BIGWIN 档位权重快照(JSON:grid_number=>weight)
|
||||||
* @property int|null $admin_id 执行测试的管理员ID
|
* @property int|null $admin_id 执行测试的管理员ID
|
||||||
* @property string|null $create_time 创建时间
|
* @property string|null $create_time 创建时间
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
-- DicePlayRecord 新增注数与付费金额字段
|
-- DicePlayRecord 新增注数与付费金额字段
|
||||||
ALTER TABLE `dice_play_record`
|
ALTER TABLE `dice_play_record`
|
||||||
ADD COLUMN `ante` int unsigned NOT NULL DEFAULT 1 COMMENT '底注/注数(必须为 dice_ante_config.mult 中存在的值)' AFTER `lottery_type`,
|
ADD COLUMN `ante` int unsigned NOT NULL DEFAULT 1 COMMENT '底注/注数(必须为 dice_ante_config.mult 中存在的值)' AFTER `lottery_type`,
|
||||||
ADD COLUMN `paid_amount` int unsigned NOT NULL DEFAULT 0 COMMENT '付费金额(付费局=ante*100,免费局=0)' AFTER `ante`;
|
ADD COLUMN `paid_amount` int unsigned NOT NULL DEFAULT 0 COMMENT '付费金额(付费局=ante*1,免费局=0)' AFTER `ante`;
|
||||||
|
|||||||
24
项目文档.md
24
项目文档.md
@@ -18,11 +18,11 @@
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **平台币 `coin`** | 玩家钱包余额;付费开局、购买套餐、中奖结算均围绕该字段。 |
|
| **平台币 `coin`** | 玩家钱包余额;付费开局、购买套餐、中奖结算均围绕该字段。 |
|
||||||
| **注数 `ante`** | 倍数因子,须存在于表 `dice_ante_config` 的 `mult` 中;接口 `/api/game/anteConfig` 返回可选注数。 |
|
| **注数 `ante`** | 倍数因子,须存在于表 `dice_ante_config` 的 `mult` 中;接口 `/api/game/anteConfig` 返回可选注数。 |
|
||||||
| **单注费用** | 付费抽奖时,开局前扣除 **`ante × 100`** 平台币(代码常量 `UNIT_COST = 100`,即「单注 100 币」口径)。 |
|
| **单注费用** | 付费抽奖时,开局前扣除 **`ante × 1`** 平台币(代码常量 `UNIT_COST = 1`,即「单注 1 币」口径)。 |
|
||||||
| **方向 `direction`** | 开局参数:`0` 与 `1` 对应两套奖励数据(顺时针/逆时针或「无 / 中奖」分支,由前端与配置表共同约定);服务端在 **档位确定后**,按当前方向从 `DiceReward` 缓存结构中取该档位下的条目再按权重抽取。 |
|
| **方向 `direction`** | 开局参数:`0` 与 `1` 对应两套奖励数据(顺时针/逆时针或「无 / 中奖」分支,由前端与配置表共同约定);服务端在 **档位确定后**,按当前方向从 `DiceReward` 缓存结构中取该档位下的条目再按权重抽取。 |
|
||||||
| **档位 T1–T5** | 中奖层级;先抽档位,再在该档位 + 当前方向下按 `weight` 抽一条奖励配置。 |
|
| **档位 T1–T5** | 中奖层级;先抽档位,再在该档位 + 当前方向下按 `weight` 抽一条奖励配置。 |
|
||||||
| **`grid_number`(5–30)** | 与「五颗骰子点数之和」一致:最小 5(全 1),最大 30(全 6);用于关联奖励行与后续生成 `roll_array`。 |
|
| **`grid_number`(5–30)** | 与「五颗骰子点数之和」一致:最小 5(全 1),最大 30(全 6);用于关联奖励行与后续生成 `roll_array`。 |
|
||||||
| **`real_ev`** | 奖励配置中的期望调节项;**普通中奖**结算为 **`(100 + real_ev) × ante`**(付费局在开局已扣 `ante×100`,净效果依 `real_ev` 而定)。 |
|
| **`real_ev`** | 奖励配置中的期望调节项;**普通中奖**结算为 **`real_ev × ante`**(付费局在开局已扣 `ante×1`,净效果依 `real_ev` 而定)。 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -33,9 +33,9 @@
|
|||||||
|
|
||||||
2. **(可选)购买「抽奖券」套餐**
|
2. **(可选)购买「抽奖券」套餐**
|
||||||
`POST /api/game/buyLotteryTickets`,`count` 仅支持 `1`、`5`、`10`:
|
`POST /api/game/buyLotteryTickets`,`count` 仅支持 `1`、`5`、`10`:
|
||||||
- 1:100 币 → 1 次付费计数 + 0 次赠送
|
- 1:1 币 → 1 次付费计数 + 0 次赠送
|
||||||
- 5:500 币 → 5 次付费 + **1 次赠送**(共 6 次计入总次数)
|
- 5:5 币 → 5 次付费 + **1 次赠送**(共 6 次计入总次数)
|
||||||
- 10:1000 币 → 10 次付费 + **3 次赠送**(共 13 次)
|
- 10:10 币 → 10 次付费 + **3 次赠送**(共 13 次)
|
||||||
|
|
||||||
会更新玩家身上的 `total_ticket_count` / `paid_ticket_count` / `free_ticket_count`,并记钱包与券流水。
|
会更新玩家身上的 `total_ticket_count` / `paid_ticket_count` / `free_ticket_count`,并记钱包与券流水。
|
||||||
|
|
||||||
@@ -43,8 +43,8 @@
|
|||||||
`POST /api/game/playStart`,需传 **`direction`(0 或 1)** 与 **`ante`(正整数,且须在底注配置中)**。
|
`POST /api/game/playStart`,需传 **`direction`(0 或 1)** 与 **`ante`(正整数,且须在底注配置中)**。
|
||||||
|
|
||||||
4. **付费 vs 免费**
|
4. **付费 vs 免费**
|
||||||
- **免费抽奖**:当 `free_ticket_count > 0` 时,本局视为免费类型:不扣 `ante×100`,但会消耗 **1 次** `free_ticket_count`。
|
- **免费抽奖**:当 `free_ticket_count > 0` 时,本局视为免费类型:不扣 `ante×1`,但会消耗 **1 次** `free_ticket_count`。
|
||||||
- **付费抽奖**:不依赖「券张数是否大于 0」;只要非免费局,开局前扣 **`ante × 100`**。
|
- **付费抽奖**:不依赖「券张数是否大于 0」;只要非免费局,开局前扣 **`ante × 1`**。
|
||||||
|
|
||||||
> **重要**:当前实现已**不再用「抽奖券张数」作为能否开局的条件**;`buyLotteryTickets` 更新的是统计与赠送次数,**真正开局仍看余额、注数、免费次数等规则**(见下节)。
|
> **重要**:当前实现已**不再用「抽奖券张数」作为能否开局的条件**;`buyLotteryTickets` 更新的是统计与赠送次数,**真正开局仍看余额、注数、免费次数等规则**(见下节)。
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
|
|
||||||
- 用户存在;`ante` 合法。
|
- 用户存在;`ante` 合法。
|
||||||
- **最低余额**:`coin ≥ abs(min_real_ev) × ante`(`min_real_ev` 来自全表 `DiceRewardConfig` 缓存),防止极端负 EV 下余额不足以覆盖风险口径。
|
- **最低余额**:`coin ≥ abs(min_real_ev) × ante`(`min_real_ev` 来自全表 `DiceRewardConfig` 缓存),防止极端负 EV 下余额不足以覆盖风险口径。
|
||||||
- 付费局:`coin ≥ ante × 100`。
|
- 付费局:`coin ≥ ante × 1`。
|
||||||
|
|
||||||
### 4.2 使用哪套「档位权重」:默认奖池 vs 杀分奖池
|
### 4.2 使用哪套「档位权重」:默认奖池 vs 杀分奖池
|
||||||
|
|
||||||
@@ -89,12 +89,12 @@
|
|||||||
|
|
||||||
### 4.4 普通奖与「豹子 / BIGWIN」
|
### 4.4 普通奖与「豹子 / BIGWIN」
|
||||||
|
|
||||||
- 若本次抽中的 `grid_number` **不是**「豹子集合」`{5,10,15,20,25,30}`:按点数和生成 5 个 1–6 的骰子(和为 `grid_number`),**普通奖金** = **`(100 + real_ev) × ante`**(付费局已预先扣除 `ante×100`)。
|
- 若本次抽中的 `grid_number` **不是**「豹子集合」`{5,10,15,20,25,30}`:按点数和生成 5 个 1–6 的骰子(和为 `grid_number`),**普通奖金** = **`real_ev × ante`**(付费局已预先扣除 `ante×1`)。
|
||||||
|
|
||||||
- 若点数和落在豹子集合:
|
- 若点数和落在豹子集合:
|
||||||
- **`grid_number` 为 5 或 30**:若**非**杀分路径,**必定**按豹子结算(五颗相同点数)。
|
- **`grid_number` 为 5 或 30**:若**非**杀分路径,**必定**按豹子结算(五颗相同点数)。
|
||||||
- **10 / 15 / 20 / 25**:读取 `DiceRewardConfig` 中 **`tier = BIGWIN`** 且对应该 `grid_number` 的配置,用其 **`weight`(0–10000,10000=100%)** 随机决定是否视为真豹子;否则生成**非豹子**但点数和不变的骰子组合。
|
- **10 / 15 / 20 / 25**:读取 `DiceRewardConfig` 中 **`tier = BIGWIN`** 且对应该 `grid_number` 的配置,用其 **`weight`(0–10000,10000=100%)** 随机决定是否视为真豹子;否则生成**非豹子**但点数和不变的骰子组合。
|
||||||
- **真豹子**时:奖金按 **`(100 + big_win_real_ev) × ante`** 发放(`big_win_real_ev` 来自 BIGWIN 配置;若未配则用代码兜底常量);并**不计入**当次普通 `reward_win` 那条配置(与「中豹子不走普通奖」逻辑一致,详见代码注释)。
|
- **真豹子**时:奖金按 **`big_win_real_ev × ante`** 发放(`big_win_real_ev` 来自 BIGWIN 配置;若未配则用代码兜底常量);并**不计入**当次普通 `reward_win` 那条配置(与「中豹子不走普通奖」逻辑一致,详见代码注释)。
|
||||||
|
|
||||||
杀分路径下:**不触发**豹子奖,仅展示非豹子组合。
|
杀分路径下:**不触发**豹子奖,仅展示非豹子组合。
|
||||||
|
|
||||||
@@ -106,12 +106,12 @@
|
|||||||
|
|
||||||
在 **`default`** 那条池子上更新 **`profit_amount`**:
|
在 **`default`** 那条池子上更新 **`profit_amount`**:
|
||||||
|
|
||||||
- **付费局**:本局贡献 `+= (本局总中奖 win_coin) - (本局付费 paid_amount)`,其中 `paid_amount = ante × 100`。
|
- **付费局**:本局贡献 `+= (本局总中奖 win_coin) - (本局付费 paid_amount)`,其中 `paid_amount = ante × 1`。
|
||||||
- **免费局**:`+= win_coin`(无票价成本,`paid_amount = 0`)。
|
- **免费局**:`+= win_coin`(无票价成本,`paid_amount = 0`)。
|
||||||
|
|
||||||
该累计值与 **`safety_line`、 `kill_enabled`** 共同决定下一局付费是否进入 **killScore** 档位权重(见 4.2)。
|
该累计值与 **`safety_line`、 `kill_enabled`** 共同决定下一局付费是否进入 **killScore** 档位权重(见 4.2)。
|
||||||
|
|
||||||
> 注意:仓库中部分数据库迁移脚本对 `profit_amount` 的注释可能仍沿用旧口径(例如按 `100-real_ev` 解释)。当前线上行为应以 `PlayStartLogic` 中对 `profit_amount` 的实际累加逻辑为准。
|
> 注意:仓库中部分数据库迁移脚本对 `profit_amount` 的注释可能仍沿用旧口径。当前行为应以 `PlayStartLogic` 中对 `profit_amount` 的实际累加逻辑为准。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user