优化彩金池累加计算方式

This commit is contained in:
2026-03-11 09:54:49 +08:00
parent 84d499145d
commit bb166350fd
4 changed files with 72 additions and 42 deletions

View File

@@ -21,7 +21,7 @@
<span class="realtime-badge">实时</span> <span class="realtime-badge">实时</span>
</div> </div>
<div class="profit-calc-hint"> <div class="profit-calc-hint">
计算方式每局抽奖累加 (100 该局中奖档位 real_ev)弹窗打开期间每 2 秒自动刷新 计算方式每局抽奖扣除本局发放成本普通档位 real_ev + 中大奖时 BIGWIN.real_ev弹窗打开期间每 2 秒自动刷新
</div> </div>
</div> </div>
<div class="tip-block"> <div class="tip-block">

View File

@@ -1,10 +1,10 @@
<template> <template>
<div class="art-full-height"> <div class="art-full-height">
<!-- ???? --> <!-- 搜索条件 -->
<TableSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams" /> <TableSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams" />
<ElCard class="art-table-card" shadow="never"> <ElCard class="art-table-card" shadow="never">
<!-- ???? --> <!-- 表格操作 -->
<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>
@@ -12,7 +12,7 @@
<template #icon> <template #icon>
<ArtSvgIcon icon="ri:add-fill" /> <ArtSvgIcon icon="ri:add-fill" />
</template> </template>
?? 新增
</ElButton> </ElButton>
<ElButton <ElButton
v-permission="'dice:player:index:destroy'" v-permission="'dice:player:index:destroy'"
@@ -23,13 +23,13 @@
<template #icon> <template #icon>
<ArtSvgIcon icon="ri:delete-bin-5-line" /> <ArtSvgIcon icon="ri:delete-bin-5-line" />
</template> </template>
?? 删除
</ElButton> </ElButton>
</ElSpace> </ElSpace>
</template> </template>
</ArtTableHeader> </ArtTableHeader>
<!-- ?? --> <!-- 表格 -->
<ArtTable <ArtTable
ref="tableRef" ref="tableRef"
rowKey="id" rowKey="id"
@@ -42,7 +42,7 @@
@pagination:size-change="handleSizeChange" @pagination:size-change="handleSizeChange"
@pagination:current-change="handleCurrentChange" @pagination:current-change="handleCurrentChange"
> >
<!-- ????????? --> <!-- 状态列 -->
<template #status="{ row }"> <template #status="{ row }">
<ElSwitch <ElSwitch
v-permission="'dice:player:index:update'" v-permission="'dice:player:index:update'"
@@ -51,7 +51,7 @@
@change="(v: string | number | boolean) => handleStatusChange(row, v ? 1 : 0)" @change="(v: string | number | boolean) => handleStatusChange(row, v ? 1 : 0)"
/> />
</template> </template>
<!-- ????tag ??????????? --> <!-- 平台币点击可操作 -->
<template #coin="{ row }"> <template #coin="{ row }">
<ElTag <ElTag
type="info" type="info"
@@ -62,7 +62,7 @@
{{ row.coin ?? 0 }} {{ row.coin ?? 0 }}
</ElTag> </ElTag>
</template> </template>
<!-- ??? --> <!-- 操作 -->
<template #operation="{ row }"> <template #operation="{ row }">
<div class="flex gap-2"> <div class="flex gap-2">
<SaButton <SaButton
@@ -80,7 +80,7 @@
</ArtTable> </ArtTable>
</ElCard> </ElCard>
<!-- ???? --> <!-- 编辑弹窗 -->
<EditDialog <EditDialog
v-model="dialogVisible" v-model="dialogVisible"
:dialog-type="dialogType" :dialog-type="dialogType"
@@ -88,7 +88,7 @@
@success="refreshData" @success="refreshData"
/> />
<!-- ?????????/??? --> <!-- 钱包操作弹窗 -->
<WalletOperateDialog <WalletOperateDialog
v-model="walletDialogVisible" v-model="walletDialogVisible"
:player="walletOperatePlayer" :player="walletOperatePlayer"
@@ -105,7 +105,7 @@
import EditDialog from './modules/edit-dialog.vue' import EditDialog from './modules/edit-dialog.vue'
import WalletOperateDialog from './modules/WalletOperateDialog.vue' import WalletOperateDialog from './modules/WalletOperateDialog.vue'
// ???? // 搜索表单
const searchForm = ref({ const searchForm = ref({
username: undefined, username: undefined,
name: undefined, name: undefined,
@@ -115,23 +115,23 @@
lottery_config_id: undefined lottery_config_id: undefined
}) })
// ???? // 搜索
const handleSearch = (params: Record<string, any>) => { const handleSearch = (params: Record<string, any>) => {
Object.assign(searchParams, params) Object.assign(searchParams, params)
getData() getData()
} }
// ???? % ? formatter?ColumnOption.formatter ??? row? // 权重列显示为百分比
const weightFormatter = (prop: string) => (row: any) => { const weightFormatter = (prop: string) => (row: any) => {
const cellValue = row[prop] const cellValue = row[prop]
return cellValue != null && cellValue !== '' ? `${cellValue}%` : '-' return cellValue != null && cellValue !== '' ? `${cellValue}%` : '-'
} }
// ???????lottery_config_id ?? DiceLotteryPoolConfig??? name // 根据 lottery_config_id 显示彩金池配置名称
const lotteryConfigNameFormatter = (row: any) => const lotteryConfigNameFormatter = (row: any) =>
row?.diceLotteryPoolConfig?.name ?? (row?.lottery_config_id ? `#${row.lottery_config_id}` : '???') row?.diceLotteryPoolConfig?.name ?? (row?.lottery_config_id ? `#${row.lottery_config_id}` : '未知')
// ???? // 表格
const { const {
columns, columns,
columnChecks, columnChecks,
@@ -150,73 +150,73 @@
apiFn: api.list, apiFn: api.list,
columnsFactory: () => [ columnsFactory: () => [
{ type: 'selection' }, { type: 'selection' },
{ prop: 'username', label: '???', align: 'center' }, { prop: 'username', label: '用户名', align: 'center' },
{ prop: 'phone', label: '???', align: 'center' }, { prop: 'phone', label: '手机号', align: 'center' },
{ prop: 'name', label: '??', align: 'center' }, { prop: 'name', label: '昵称', align: 'center' },
{ {
prop: 'status', prop: 'status',
label: '??', label: '状态',
width: 88, width: 88,
align: 'center', align: 'center',
useSlot: true useSlot: true
}, },
{ {
prop: 'coin', prop: 'coin',
label: '???', label: '平台币',
width: 100, width: 100,
align: 'center', align: 'center',
useSlot: true useSlot: true
}, },
{ {
prop: 'lottery_config_id', prop: 'lottery_config_id',
label: '?????', label: '彩金池配置',
width: 120, width: 120,
align: 'center', align: 'center',
formatter: (row: any) => lotteryConfigNameFormatter(row) formatter: (row: any) => lotteryConfigNameFormatter(row)
}, },
{ {
prop: 't1_weight', prop: 't1_weight',
label: 'T1???', label: 'T1权重',
width: 80, width: 80,
align: 'center', align: 'center',
formatter: weightFormatter('t1_weight') formatter: weightFormatter('t1_weight')
}, },
{ {
prop: 't2_weight', prop: 't2_weight',
label: 'T2???', label: 'T2权重',
width: 100, width: 100,
align: 'center', align: 'center',
formatter: weightFormatter('t2_weight') formatter: weightFormatter('t2_weight')
}, },
{ {
prop: 't3_weight', prop: 't3_weight',
label: 'T3???', label: 'T3权重',
width: 100, width: 100,
align: 'center', align: 'center',
formatter: weightFormatter('t3_weight') formatter: weightFormatter('t3_weight')
}, },
{ {
prop: 't4_weight', prop: 't4_weight',
label: 'T4???', label: 'T4权重',
width: 100, width: 100,
align: 'center', align: 'center',
formatter: weightFormatter('t4_weight') formatter: weightFormatter('t4_weight')
}, },
{ {
prop: 't5_weight', prop: 't5_weight',
label: 'T5???', label: 'T5权重',
width: 100, width: 100,
align: 'center', align: 'center',
formatter: weightFormatter('t5_weight') formatter: weightFormatter('t5_weight')
}, },
{ prop: 'total_ticket_count', label: '?????', align: 'center' }, { prop: 'total_ticket_count', label: '总抽奖次数', align: 'center' },
{ prop: 'paid_ticket_count', label: '??????', align: 'center' }, { prop: 'paid_ticket_count', label: '购买抽奖次数', align: 'center' },
{ prop: 'free_ticket_count', label: '??????', align: 'center' }, { prop: 'free_ticket_count', label: '赠送抽奖次数', align: 'center' },
{ prop: 'create_time', label: '????', align: 'center' }, { prop: 'create_time', label: '创建时间', align: 'center' },
{ prop: 'update_time', label: '????', align: 'center' }, { prop: 'update_time', label: '更新时间', align: 'center' },
{ {
prop: 'operation', prop: 'operation',
label: '??', label: '操作',
width: 100, width: 100,
align: 'center', align: 'center',
fixed: 'right', fixed: 'right',
@@ -226,7 +226,7 @@
} }
}) })
// ??????????????? // 状态切换
const handleStatusChange = async (row: Record<string, any>, status: number) => { const handleStatusChange = async (row: Record<string, any>, status: number) => {
row._statusLoading = true row._statusLoading = true
try { try {
@@ -239,7 +239,7 @@
} }
} }
// ???? // 弹窗与删除
const { const {
dialogType, dialogType,
dialogVisible, dialogVisible,
@@ -251,7 +251,7 @@
selectedRows selectedRows
} = useSaiAdmin() } = useSaiAdmin()
// ??????????? tag ????? // 钱包操作弹窗
const walletDialogVisible = ref(false) const walletDialogVisible = ref(false)
type WalletPlayer = { id: number; username?: string; coin?: number } type WalletPlayer = { id: number; username?: string; coin?: number }
const walletOperatePlayer = ref<WalletPlayer | null>(null) const walletOperatePlayer = ref<WalletPlayer | null>(null)

View File

@@ -102,8 +102,8 @@
set: (value) => emit('update:modelValue', value) set: (value) => emit('update:modelValue', value)
}) })
/** BIGWIN 且 grid_number 为 5 或 30 时豹子概率不可修改 */ /** BIGWIN 且 grid_number 为 5 或 30 时豹子概率固定为 100%(禁止手动调整) */
const isWeightDisabled = computed( const isWeightFixed100 = computed(
() => () =>
formData.tier === 'BIGWIN' && formData.tier === 'BIGWIN' &&
(formData.grid_number === 5 || formData.grid_number === 30) (formData.grid_number === 5 || formData.grid_number === 30)

View File

@@ -101,7 +101,7 @@ class PlayStartLogic
} }
$maxRewardRetry = count($tierRewards); $maxRewardRetry = count($tierRewards);
for ($attempt = 0; $attempt < $maxRewardRetry; $attempt++) { for ($attempt = 0; $attempt < $maxRewardRetry; $attempt++) {
$chosen = $tierRewards[array_rand($tierRewards)]; $chosen = self::drawRewardByWeight($tierRewards);
$chosenId = (int) ($chosen['id'] ?? 0); $chosenId = (int) ($chosen['id'] ?? 0);
if ($direction === 0) { if ($direction === 0) {
$startCandidates = DiceRewardConfig::getCachedBySEndIndex($chosenId); $startCandidates = DiceRewardConfig::getCachedBySEndIndex($chosenId);
@@ -246,10 +246,10 @@ class PlayStartLogic
$p->save(); $p->save();
// 彩金池盈利累加:每局累加 (100 - 本局总 real_ev),其中本局总 real_ev = 普通档位 real_ev + BIGWIN.real_ev如触发) // 彩金池盈利:每局扣除本局发放的真实成本(普通档位 real_ev + BIGWIN.real_ev 如触发),不额外加 100
// 需确保表有 profit_amount 字段(见 db/dice_lottery_config_add_profit_amount.sql // 需确保表有 profit_amount 字段(见 db/dice_lottery_config_add_profit_amount.sql
$totalRealEv = $realEv + $bigWinRealEv; $totalRealEv = $realEv + $bigWinRealEv;
$addProfit = 100 - $totalRealEv; $addProfit = -$totalRealEv;
try { try {
DiceLotteryPoolConfig::where('id', $configId)->update([ DiceLotteryPoolConfig::where('id', $configId)->update([
'profit_amount' => Db::raw('IFNULL(profit_amount,0) + ' . (float) $addProfit), 'profit_amount' => Db::raw('IFNULL(profit_amount,0) + ' . (float) $addProfit),
@@ -322,6 +322,36 @@ class PlayStartLogic
return $arr; return $arr;
} }
/**
* T1-T5 档位内按 DiceRewardConfig.weight 抽取一条配置;权重均为 0 或未设时等权随机
* @param array<int, array> $rewards 该档位配置列表(每条含 weight、id、real_ev 等)
* @return array 抽中的一条配置
*/
private static function drawRewardByWeight(array $rewards): array
{
if (empty($rewards)) {
throw new \InvalidArgumentException('rewards 不能为空');
}
$weights = [];
foreach ($rewards as $i => $row) {
$w = isset($row['weight']) ? max(0, (float) $row['weight']) : 0;
$weights[$i] = $w;
}
$total = array_sum($weights);
if ($total <= 0) {
return $rewards[array_rand($rewards)];
}
$r = mt_rand(1, (int) $total);
$acc = 0;
foreach ($weights as $i => $w) {
$acc += $w;
if ($r <= $acc) {
return $rewards[$i];
}
}
return $rewards[array_key_last($rewards)];
}
/** /**
* 根据摇取点数5-30生成 5 个色子数组,每个 1-6总和为 $sum * 根据摇取点数5-30生成 5 个色子数组,每个 1-6总和为 $sum
* @return int[] 如 [1,2,3,4,5] * @return int[] 如 [1,2,3,4,5]