Compare commits
5 Commits
cc7e2d9a1a
...
b79904f75e
| Author | SHA1 | Date | |
|---|---|---|---|
| b79904f75e | |||
| 6b21626878 | |||
| 7445dc4cb0 | |||
| e8620998ae | |||
| 3182d04956 |
@@ -49,10 +49,7 @@ export default {
|
|||||||
/**
|
/**
|
||||||
* 权重编辑弹窗:批量更新当前方向的权重(单方向)
|
* 权重编辑弹窗:批量更新当前方向的权重(单方向)
|
||||||
*/
|
*/
|
||||||
batchUpdateWeightsByDirection(
|
batchUpdateWeightsByDirection(direction: 0 | 1, items: Array<{ id: number; weight: number }>) {
|
||||||
direction: 0 | 1,
|
|
||||||
items: Array<{ id: number; weight: number }>
|
|
||||||
) {
|
|
||||||
return request.post<any>({
|
return request.post<any>({
|
||||||
url: '/core/dice/reward/DiceReward/batchUpdateWeightsByDirection',
|
url: '/core/dice/reward/DiceReward/batchUpdateWeightsByDirection',
|
||||||
data: { direction, items }
|
data: { direction, items }
|
||||||
|
|||||||
@@ -63,6 +63,16 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新奖励索引配置(第一页:id、grid_number、ui_text、real_ev、tier、remark)
|
||||||
|
*/
|
||||||
|
batchUpdate(items: Array<{ id: number; grid_number?: number; ui_text?: string; real_ev?: number; tier?: string; remark?: string }>) {
|
||||||
|
return request.post<any>({
|
||||||
|
url: '/core/dice/reward_config/DiceRewardConfig/batchUpdate',
|
||||||
|
data: { items }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* T1-T5、BIGWIN 权重配比:按档位分组获取配置列表
|
* T1-T5、BIGWIN 权重配比:按档位分组获取配置列表
|
||||||
*/
|
*/
|
||||||
@@ -83,6 +93,16 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大奖权重:按 grid_number 批量保存 BIGWIN 权重(无需 reward id,不存在则自动创建)
|
||||||
|
*/
|
||||||
|
saveBigwinWeightsByGrid(items: Array<{ grid_number: number; weight: number }>) {
|
||||||
|
return request.post<any>({
|
||||||
|
url: '/core/dice/reward_config/DiceRewardConfig/saveBigwinWeightsByGrid',
|
||||||
|
data: { items }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建奖励对照:按当前奖励配置为顺时针(0)、逆时针(1)生成所有色子可能对应的 dice_reward 记录,权重默认 1,可在奖励对照页权重编辑中调整
|
* 创建奖励对照:按当前奖励配置为顺时针(0)、逆时针(1)生成所有色子可能对应的 dice_reward 记录,权重默认 1,可在奖励对照页权重编辑中调整
|
||||||
*/
|
*/
|
||||||
@@ -95,22 +115,5 @@ export default {
|
|||||||
}>({
|
}>({
|
||||||
url: '/core/dice/reward_config/DiceRewardConfig/createRewardReference'
|
url: '/core/dice/reward_config/DiceRewardConfig/createRewardReference'
|
||||||
})
|
})
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 权重配比测试:按当前配置模拟 N 次抽奖,返回各 grid_number 落点次数
|
|
||||||
* @param test_count 100 | 500 | 1000
|
|
||||||
* @param save_record 是否保存到 dice_reward_config_record
|
|
||||||
* @param lottery_config_id 奖池配置ID(DiceLotteryPoolConfig),用于设定 T1-T5 档位概率;不传则使用 type=0 或均等
|
|
||||||
*/
|
|
||||||
runWeightTest(params: {
|
|
||||||
test_count: number
|
|
||||||
save_record?: boolean
|
|
||||||
lottery_config_id?: number | null
|
|
||||||
}) {
|
|
||||||
return request.post<{ data: { counts: Record<string, number>; record_id: number | null } }>({
|
|
||||||
url: '/core/dice/reward_config/DiceRewardConfig/runWeightTest',
|
|
||||||
data: params
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
>
|
>
|
||||||
<div class="global-tip">
|
<div class="global-tip">
|
||||||
编辑的是<strong>奖励对照表(dice_reward / DiceReward 模型)</strong>的权重,按<strong>结束索引(end_index)</strong>区分
|
编辑的是<strong>奖励对照表(dice_reward / DiceReward 模型)</strong
|
||||||
|
>的权重,按<strong>结束索引(end_index)</strong>区分
|
||||||
<strong>顺时针</strong>与<strong>逆时针</strong>两套权重;抽奖时按当前方向取对应权重。
|
<strong>顺时针</strong>与<strong>逆时针</strong>两套权重;抽奖时按当前方向取对应权重。
|
||||||
</div>
|
</div>
|
||||||
<div v-loading="loading" class="dialog-body">
|
<div v-loading="loading" class="dialog-body">
|
||||||
@@ -39,11 +40,35 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="weight-sum weight-sum-t4t5" v-else>T4、T5 仅单一结果,无需配置权重。</div>
|
<div class="weight-sum weight-sum-t4t5" v-else>T4、T5 仅单一结果,无需配置权重。</div>
|
||||||
<el-table :data="getTierItems(t)" border size="small" class="weight-table">
|
<el-table :data="getTierItems(t)" border size="small" class="weight-table">
|
||||||
<el-table-column label="结束索引(id)" prop="id" width="90" align="center" show-overflow-tooltip />
|
<el-table-column
|
||||||
|
label="结束索引(id)"
|
||||||
|
prop="id"
|
||||||
|
width="90"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
<el-table-column label="色子点数" prop="grid_number" width="80" align="center" />
|
<el-table-column label="色子点数" prop="grid_number" width="80" align="center" />
|
||||||
<el-table-column label="实际中奖金额" prop="real_ev" width="90" align="center" show-overflow-tooltip />
|
<el-table-column
|
||||||
<el-table-column label="显示文本" prop="ui_text" min-width="70" align="center" show-overflow-tooltip />
|
label="实际中奖金额"
|
||||||
<el-table-column label="备注" prop="remark" min-width="70" align="center" show-overflow-tooltip />
|
prop="real_ev"
|
||||||
|
width="90"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="显示文本"
|
||||||
|
prop="ui_text"
|
||||||
|
min-width="70"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="备注"
|
||||||
|
prop="remark"
|
||||||
|
min-width="70"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
<el-table-column label="顺时针权重(direction=0)" min-width="160" align="center">
|
<el-table-column label="顺时针权重(direction=0)" min-width="160" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="weight-cell-vertical">
|
<div class="weight-cell-vertical">
|
||||||
@@ -58,7 +83,12 @@
|
|||||||
class="weight-slider"
|
class="weight-slider"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(v: number | number[]) =>
|
(v: number | number[]) =>
|
||||||
setItemWeightByRow(t, row, 'clockwise', Array.isArray(v) ? (v[0] ?? 1) : (v ?? 1))
|
setItemWeightByRow(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
'clockwise',
|
||||||
|
Array.isArray(v) ? (v[0] ?? 1) : (v ?? 1)
|
||||||
|
)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,8 +97,16 @@
|
|||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
:disabled="isWeightDisabled(row, t) || getItemWeight(row, 'clockwise') <= 1"
|
:disabled="isWeightDisabled(row, t) || getItemWeight(row, 'clockwise') <= 1"
|
||||||
@click="setItemWeightByRow(t, row, 'clockwise', Math.max(1, getItemWeight(row, 'clockwise') - 1))"
|
@click="
|
||||||
>-</el-button>
|
setItemWeightByRow(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
'clockwise',
|
||||||
|
Math.max(1, getItemWeight(row, 'clockwise') - 1)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>-</el-button
|
||||||
|
>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
:model-value="getItemWeight(row, 'clockwise')"
|
:model-value="getItemWeight(row, 'clockwise')"
|
||||||
:min="1"
|
:min="1"
|
||||||
@@ -91,9 +129,19 @@
|
|||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
:disabled="isWeightDisabled(row, t) || getItemWeight(row, 'clockwise') >= 10000"
|
:disabled="
|
||||||
@click="setItemWeightByRow(t, row, 'clockwise', Math.min(10000, getItemWeight(row, 'clockwise') + 1))"
|
isWeightDisabled(row, t) || getItemWeight(row, 'clockwise') >= 10000
|
||||||
>+</el-button>
|
"
|
||||||
|
@click="
|
||||||
|
setItemWeightByRow(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
'clockwise',
|
||||||
|
Math.min(10000, getItemWeight(row, 'clockwise') + 1)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>+</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -112,7 +160,12 @@
|
|||||||
class="weight-slider"
|
class="weight-slider"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(v: number | number[]) =>
|
(v: number | number[]) =>
|
||||||
setItemWeightByRow(t, row, 'counterclockwise', Array.isArray(v) ? (v[0] ?? 1) : (v ?? 1))
|
setItemWeightByRow(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
'counterclockwise',
|
||||||
|
Array.isArray(v) ? (v[0] ?? 1) : (v ?? 1)
|
||||||
|
)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -120,9 +173,19 @@
|
|||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
:disabled="isWeightDisabled(row, t) || getItemWeight(row, 'counterclockwise') <= 1"
|
:disabled="
|
||||||
@click="setItemWeightByRow(t, row, 'counterclockwise', Math.max(1, getItemWeight(row, 'counterclockwise') - 1))"
|
isWeightDisabled(row, t) || getItemWeight(row, 'counterclockwise') <= 1
|
||||||
>-</el-button>
|
"
|
||||||
|
@click="
|
||||||
|
setItemWeightByRow(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
'counterclockwise',
|
||||||
|
Math.max(1, getItemWeight(row, 'counterclockwise') - 1)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>-</el-button
|
||||||
|
>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
:model-value="getItemWeight(row, 'counterclockwise')"
|
:model-value="getItemWeight(row, 'counterclockwise')"
|
||||||
:min="1"
|
:min="1"
|
||||||
@@ -145,9 +208,20 @@
|
|||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
:disabled="isWeightDisabled(row, t) || getItemWeight(row, 'counterclockwise') >= 10000"
|
:disabled="
|
||||||
@click="setItemWeightByRow(t, row, 'counterclockwise', Math.min(10000, getItemWeight(row, 'counterclockwise') + 1))"
|
isWeightDisabled(row, t) ||
|
||||||
>+</el-button>
|
getItemWeight(row, 'counterclockwise') >= 10000
|
||||||
|
"
|
||||||
|
@click="
|
||||||
|
setItemWeightByRow(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
'counterclockwise',
|
||||||
|
Math.min(10000, getItemWeight(row, 'counterclockwise') + 1)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>+</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -170,6 +244,8 @@
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const TIER_KEYS = ['T1', 'T2', 'T3', 'T4', 'T5'] as const
|
const TIER_KEYS = ['T1', 'T2', 'T3', 'T4', 'T5'] as const
|
||||||
|
/** 供模板 v-for 使用 */
|
||||||
|
const tierKeys = TIER_KEYS
|
||||||
type DirectionKey = 'clockwise' | 'counterclockwise'
|
type DirectionKey = 'clockwise' | 'counterclockwise'
|
||||||
|
|
||||||
interface WeightRow {
|
interface WeightRow {
|
||||||
@@ -251,8 +327,7 @@
|
|||||||
const list = grouped.value[tier]
|
const list = grouped.value[tier]
|
||||||
if (!list) return
|
if (!list) return
|
||||||
const key = dir === 'clockwise' ? 'weight_clockwise' : 'weight_counterclockwise'
|
const key = dir === 'clockwise' ? 'weight_clockwise' : 'weight_counterclockwise'
|
||||||
const rid =
|
const rid = dir === 'clockwise' ? row.reward_id_clockwise : row.reward_id_counterclockwise
|
||||||
dir === 'clockwise' ? row.reward_id_clockwise : row.reward_id_counterclockwise
|
|
||||||
const idx = list.findIndex(
|
const idx = list.findIndex(
|
||||||
(r) =>
|
(r) =>
|
||||||
r === row ||
|
r === row ||
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
>
|
>
|
||||||
<div class="global-tip">
|
<div class="global-tip">
|
||||||
配置<strong>奖励对照表(dice_reward)</strong>的权重,一级按<strong>方向</strong>(顺时针/逆时针),二级按<strong>档位</strong>(T1-T5);各条权重 1-10000,档位内按权重比抽取。
|
配置<strong>奖励对照表(dice_reward)</strong>的权重,一级按<strong>方向</strong>(顺时针/逆时针),二级按<strong>档位</strong>(T1-T5);各条权重
|
||||||
|
1-10000,档位内按权重比抽取。
|
||||||
</div>
|
</div>
|
||||||
<div v-loading="loading" class="dialog-body">
|
<div v-loading="loading" class="dialog-body">
|
||||||
<!-- 一级:方向;二级档位放在各方向 pane 内,切换方向时二级能正常显示 -->
|
<!-- 一级:方向;二级档位放在各方向 pane 内,切换方向时二级能正常显示 -->
|
||||||
@@ -30,14 +31,50 @@
|
|||||||
当前档位权重合计:<strong>{{ getTierSumForCurrentDirection(t) }}</strong>
|
当前档位权重合计:<strong>{{ getTierSumForCurrentDirection(t) }}</strong>
|
||||||
(各条 1-10000,档位内按权重比抽取,和不限制)
|
(各条 1-10000,档位内按权重比抽取,和不限制)
|
||||||
</div>
|
</div>
|
||||||
<div class="weight-sum weight-sum-t4t5" v-else>T4、T5 仅单一结果,无需配置权重。</div>
|
<div class="weight-sum weight-sum-t4t5" v-else
|
||||||
|
>T4、T5 仅单一结果,无需配置权重。</div
|
||||||
|
>
|
||||||
<el-table :data="getTierItems(t)" border size="small" class="weight-table">
|
<el-table :data="getTierItems(t)" border size="small" class="weight-table">
|
||||||
<el-table-column label="点数(grid_number)" prop="grid_number" width="110" align="center" show-overflow-tooltip />
|
<el-table-column
|
||||||
<el-table-column label="结束索引(id)" prop="id" width="90" align="center" show-overflow-tooltip />
|
label="点数(grid_number)"
|
||||||
<el-table-column label="实际中奖金额" prop="real_ev" width="90" align="center" show-overflow-tooltip />
|
prop="grid_number"
|
||||||
<el-table-column label="显示文本" prop="ui_text" min-width="70" align="center" show-overflow-tooltip />
|
width="110"
|
||||||
<el-table-column label="备注" prop="remark" min-width="70" align="center" show-overflow-tooltip />
|
align="center"
|
||||||
<el-table-column :label="currentDirectionLabel + ' 权重(1-10000)'" min-width="200" align="center">
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="结束索引(id)"
|
||||||
|
prop="id"
|
||||||
|
width="90"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="实际中奖金额"
|
||||||
|
prop="real_ev"
|
||||||
|
width="90"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="显示文本"
|
||||||
|
prop="ui_text"
|
||||||
|
min-width="70"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="备注"
|
||||||
|
prop="remark"
|
||||||
|
min-width="70"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
:label="currentDirectionLabel + ' 权重(1-10000)'"
|
||||||
|
min-width="200"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="weight-cell-vertical">
|
<div class="weight-cell-vertical">
|
||||||
<div class="weight-slider-wrap">
|
<div class="weight-slider-wrap">
|
||||||
@@ -49,16 +86,32 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:disabled="isWeightDisabled(row, t)"
|
:disabled="isWeightDisabled(row, t)"
|
||||||
class="weight-slider"
|
class="weight-slider"
|
||||||
@update:model-value="(v: number | number[]) => setItemWeightForCurrentDirection(t, row, Array.isArray(v) ? (v[0] ?? 1) : (v ?? 1))"
|
@update:model-value="
|
||||||
|
(v: number | number[]) =>
|
||||||
|
setItemWeightForCurrentDirection(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
Array.isArray(v) ? (v[0] ?? 1) : (v ?? 1)
|
||||||
|
)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="weight-input-wrap">
|
<div class="weight-input-wrap">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
:disabled="isWeightDisabled(row, t) || getItemWeightForCurrentDirection(row) <= 1"
|
:disabled="
|
||||||
@click="setItemWeightForCurrentDirection(t, row, Math.max(1, getItemWeightForCurrentDirection(row) - 1))"
|
isWeightDisabled(row, t) || getItemWeightForCurrentDirection(row) <= 1
|
||||||
>-</el-button>
|
"
|
||||||
|
@click="
|
||||||
|
setItemWeightForCurrentDirection(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
Math.max(1, getItemWeightForCurrentDirection(row) - 1)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>-</el-button
|
||||||
|
>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
:model-value="getItemWeightForCurrentDirection(row)"
|
:model-value="getItemWeightForCurrentDirection(row)"
|
||||||
:min="1"
|
:min="1"
|
||||||
@@ -68,14 +121,31 @@
|
|||||||
controls-position="right"
|
controls-position="right"
|
||||||
size="small"
|
size="small"
|
||||||
class="weight-input"
|
class="weight-input"
|
||||||
@update:model-value="(v: number | string | undefined) => setItemWeightForCurrentDirection(t, row, typeof v === 'number' && !Number.isNaN(v) ? v : Number(v) || 1)"
|
@update:model-value="
|
||||||
|
(v: number | string | undefined) =>
|
||||||
|
setItemWeightForCurrentDirection(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
typeof v === 'number' && !Number.isNaN(v) ? v : Number(v) || 1
|
||||||
|
)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
:disabled="isWeightDisabled(row, t) || getItemWeightForCurrentDirection(row) >= 10000"
|
:disabled="
|
||||||
@click="setItemWeightForCurrentDirection(t, row, Math.min(10000, getItemWeightForCurrentDirection(row) + 1))"
|
isWeightDisabled(row, t) ||
|
||||||
>+</el-button>
|
getItemWeightForCurrentDirection(row) >= 10000
|
||||||
|
"
|
||||||
|
@click="
|
||||||
|
setItemWeightForCurrentDirection(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
Math.min(10000, getItemWeightForCurrentDirection(row) + 1)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>+</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -85,7 +155,7 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="逆时针 (direction=1)" name="1">
|
<el-tab-pane label="逆时针" name="1">
|
||||||
<el-tabs v-model="activeTier" type="card" class="tier-tabs">
|
<el-tabs v-model="activeTier" type="card" class="tier-tabs">
|
||||||
<el-tab-pane v-for="t in tierKeys" :key="'ccw-' + t" :label="t" :name="t">
|
<el-tab-pane v-for="t in tierKeys" :key="'ccw-' + t" :label="t" :name="t">
|
||||||
<div v-if="getTierItems(t).length === 0" class="empty-tip">该档位暂无配置数据</div>
|
<div v-if="getTierItems(t).length === 0" class="empty-tip">该档位暂无配置数据</div>
|
||||||
@@ -102,14 +172,50 @@
|
|||||||
当前档位权重合计:<strong>{{ getTierSumForCurrentDirection(t) }}</strong>
|
当前档位权重合计:<strong>{{ getTierSumForCurrentDirection(t) }}</strong>
|
||||||
(各条 1-10000,档位内按权重比抽取,和不限制)
|
(各条 1-10000,档位内按权重比抽取,和不限制)
|
||||||
</div>
|
</div>
|
||||||
<div class="weight-sum weight-sum-t4t5" v-else>T4、T5 仅单一结果,无需配置权重。</div>
|
<div class="weight-sum weight-sum-t4t5" v-else
|
||||||
|
>T4、T5 仅单一结果,无需配置权重。</div
|
||||||
|
>
|
||||||
<el-table :data="getTierItems(t)" border size="small" class="weight-table">
|
<el-table :data="getTierItems(t)" border size="small" class="weight-table">
|
||||||
<el-table-column label="点数(grid_number)" prop="grid_number" width="110" align="center" show-overflow-tooltip />
|
<el-table-column
|
||||||
<el-table-column label="结束索引(id)" prop="id" width="90" align="center" show-overflow-tooltip />
|
label="点数(grid_number)"
|
||||||
<el-table-column label="实际中奖金额" prop="real_ev" width="90" align="center" show-overflow-tooltip />
|
prop="grid_number"
|
||||||
<el-table-column label="显示文本" prop="ui_text" min-width="70" align="center" show-overflow-tooltip />
|
width="110"
|
||||||
<el-table-column label="备注" prop="remark" min-width="70" align="center" show-overflow-tooltip />
|
align="center"
|
||||||
<el-table-column :label="currentDirectionLabel + ' 权重(1-10000)'" min-width="200" align="center">
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="结束索引(id)"
|
||||||
|
prop="id"
|
||||||
|
width="90"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="实际中奖金额"
|
||||||
|
prop="real_ev"
|
||||||
|
width="90"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="显示文本"
|
||||||
|
prop="ui_text"
|
||||||
|
min-width="70"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="备注"
|
||||||
|
prop="remark"
|
||||||
|
min-width="70"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
:label="currentDirectionLabel + ' 权重(1-10000)'"
|
||||||
|
min-width="200"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="weight-cell-vertical">
|
<div class="weight-cell-vertical">
|
||||||
<div class="weight-slider-wrap">
|
<div class="weight-slider-wrap">
|
||||||
@@ -121,16 +227,32 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:disabled="isWeightDisabled(row, t)"
|
:disabled="isWeightDisabled(row, t)"
|
||||||
class="weight-slider"
|
class="weight-slider"
|
||||||
@update:model-value="(v: number | number[]) => setItemWeightForCurrentDirection(t, row, Array.isArray(v) ? (v[0] ?? 1) : (v ?? 1))"
|
@update:model-value="
|
||||||
|
(v: number | number[]) =>
|
||||||
|
setItemWeightForCurrentDirection(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
Array.isArray(v) ? (v[0] ?? 1) : (v ?? 1)
|
||||||
|
)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="weight-input-wrap">
|
<div class="weight-input-wrap">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
:disabled="isWeightDisabled(row, t) || getItemWeightForCurrentDirection(row) <= 1"
|
:disabled="
|
||||||
@click="setItemWeightForCurrentDirection(t, row, Math.max(1, getItemWeightForCurrentDirection(row) - 1))"
|
isWeightDisabled(row, t) || getItemWeightForCurrentDirection(row) <= 1
|
||||||
>-</el-button>
|
"
|
||||||
|
@click="
|
||||||
|
setItemWeightForCurrentDirection(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
Math.max(1, getItemWeightForCurrentDirection(row) - 1)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>-</el-button
|
||||||
|
>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
:model-value="getItemWeightForCurrentDirection(row)"
|
:model-value="getItemWeightForCurrentDirection(row)"
|
||||||
:min="1"
|
:min="1"
|
||||||
@@ -140,14 +262,31 @@
|
|||||||
controls-position="right"
|
controls-position="right"
|
||||||
size="small"
|
size="small"
|
||||||
class="weight-input"
|
class="weight-input"
|
||||||
@update:model-value="(v: number | string | undefined) => setItemWeightForCurrentDirection(t, row, typeof v === 'number' && !Number.isNaN(v) ? v : Number(v) || 1)"
|
@update:model-value="
|
||||||
|
(v: number | string | undefined) =>
|
||||||
|
setItemWeightForCurrentDirection(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
typeof v === 'number' && !Number.isNaN(v) ? v : Number(v) || 1
|
||||||
|
)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
:disabled="isWeightDisabled(row, t) || getItemWeightForCurrentDirection(row) >= 10000"
|
:disabled="
|
||||||
@click="setItemWeightForCurrentDirection(t, row, Math.min(10000, getItemWeightForCurrentDirection(row) + 1))"
|
isWeightDisabled(row, t) ||
|
||||||
>+</el-button>
|
getItemWeightForCurrentDirection(row) >= 10000
|
||||||
|
"
|
||||||
|
@click="
|
||||||
|
setItemWeightForCurrentDirection(
|
||||||
|
t,
|
||||||
|
row,
|
||||||
|
Math.min(10000, getItemWeightForCurrentDirection(row) + 1)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>+</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,90 +1,228 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="art-full-height">
|
<div class="art-full-height reward-config-form">
|
||||||
<!-- 搜索面板 -->
|
<ElCard shadow="never" class="form-card">
|
||||||
<TableSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams" />
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>游戏奖励配置</span>
|
||||||
|
<ElButton
|
||||||
|
v-permission="'dice:reward_config:index:update'"
|
||||||
|
type="warning"
|
||||||
|
:loading="createRewardLoading"
|
||||||
|
@click="handleCreateRewardReference"
|
||||||
|
v-ripple
|
||||||
|
title="按规则:start_index=config(grid_number).id;顺时针 end_index=(start_index+grid_number)%26;逆时针 end_index=start_index-grid_number≥0?start_index-grid_number:26+start_index-grid_number"
|
||||||
|
>
|
||||||
|
创建奖励对照
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<ElCard class="art-table-card" shadow="never">
|
<ElTabs v-model="activeTab" type="card" class="top-tabs">
|
||||||
<!-- 表格头部 -->
|
<ElTabPane label="奖励索引" name="index">
|
||||||
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
|
<div class="tab-panel">
|
||||||
<template #left>
|
<div class="panel-tip">仅显示 T1~T5 档位(不含 BIGWIN)。本表单独立提交,仅提交本表数据;色子点数须在 5~30 之间且本表内不重复。</div>
|
||||||
<ElSpace wrap>
|
<div class="table-scroll-wrap">
|
||||||
<ElButton
|
<ElTable
|
||||||
v-permission="'dice:reward_config:index:update'"
|
v-loading="loading"
|
||||||
type="warning"
|
:data="indexRowsExcludeBigwin"
|
||||||
:loading="createRewardLoading"
|
border
|
||||||
@click="handleCreateRewardReference"
|
size="default"
|
||||||
v-ripple
|
class="config-table"
|
||||||
title="按规则:start_index=config(grid_number).id;顺时针 end_index=(start_index+grid_number)%26;逆时针 end_index=start_index-grid_number≥0?start_index-grid_number:26+start_index-grid_number"
|
>
|
||||||
>
|
<ElTableColumn label="索引(id)" prop="id" width="60" align="center">
|
||||||
创建奖励对照
|
<template #default="{ row }">
|
||||||
</ElButton>
|
<span>{{ row.id }}</span>
|
||||||
<ElButton
|
</template>
|
||||||
v-permission="'dice:reward_config:index:index'"
|
</ElTableColumn>
|
||||||
type="success"
|
<ElTableColumn label="色子点数" min-width="100" align="center">
|
||||||
@click="weightTestVisible = true"
|
<template #default="{ row }">
|
||||||
v-ripple
|
<ElInputNumber
|
||||||
>
|
v-model="row.grid_number"
|
||||||
测试中奖
|
:min="5"
|
||||||
</ElButton>
|
:max="30"
|
||||||
</ElSpace>
|
controls-position="right"
|
||||||
</template>
|
size="small"
|
||||||
</ArtTableHeader>
|
class="full-width"
|
||||||
|
/>
|
||||||
<!-- 表格 -->
|
</template>
|
||||||
<ArtTable
|
</ElTableColumn>
|
||||||
ref="tableRef"
|
<ElTableColumn label="显示文本" min-width="100" align="center">
|
||||||
rowKey="id"
|
<template #default="{ row }">
|
||||||
:loading="loading"
|
<ElInput v-model="row.ui_text" size="small" placeholder="显示文本" />
|
||||||
:data="data"
|
</template>
|
||||||
:columns="columns"
|
</ElTableColumn>
|
||||||
:pagination="pagination"
|
<ElTableColumn label="真实结算" min-width="110" align="center">
|
||||||
@sort-change="handleSortChange"
|
<template #default="{ row }">
|
||||||
@selection-change="handleSelectionChange"
|
<ElInputNumber
|
||||||
@pagination:size-change="handleSizeChange"
|
v-model="row.real_ev"
|
||||||
@pagination:current-change="handleCurrentChange"
|
controls-position="right"
|
||||||
>
|
size="small"
|
||||||
<!-- 操作列 -->
|
class="full-width"
|
||||||
<template #operation="{ row }">
|
/>
|
||||||
<div class="flex gap-2">
|
</template>
|
||||||
<SaButton
|
</ElTableColumn>
|
||||||
v-permission="'dice:reward_config:index:update'"
|
<ElTableColumn label="所属档位" width="100" align="center">
|
||||||
type="secondary"
|
<template #default="{ row }">
|
||||||
@click="showDialog('edit', row)"
|
<ElSelect
|
||||||
/>
|
v-model="row.tier"
|
||||||
<!-- <SaButton-->
|
placeholder="档位"
|
||||||
<!-- v-permission="'dice:reward_config:index:destroy'"-->
|
clearable
|
||||||
<!-- type="error"-->
|
size="small"
|
||||||
<!-- @click="deleteRow(row, api.delete, refreshData)"-->
|
class="full-width"
|
||||||
<!-- />-->
|
>
|
||||||
|
<ElOption label="T1" value="T1" />
|
||||||
|
<ElOption label="T2" value="T2" />
|
||||||
|
<ElOption label="T3" value="T3" />
|
||||||
|
<ElOption label="T4" value="T4" />
|
||||||
|
<ElOption label="T5" value="T5" />
|
||||||
|
</ElSelect>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="备注" min-width="140" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElInput v-model="row.remark" size="small" placeholder="备注" />
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
</div>
|
||||||
|
<div class="tab-footer">
|
||||||
|
<ElButton type="primary" :loading="savingIndex" @click="handleSaveIndex"
|
||||||
|
>保存</ElButton
|
||||||
|
>
|
||||||
|
<ElButton @click="handleResetIndex">重置</ElButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</ElTabPane>
|
||||||
</ArtTable>
|
<ElTabPane label="大奖权重" name="bigwin">
|
||||||
|
<div class="tab-panel">
|
||||||
|
<div class="panel-tip">从左至右:中大奖点数(不可改)、显示信息、实际中奖、备注、权重(0~10000)。点数 5、30 权重固定 100%。本表单独立提交,仅提交大奖权重。</div>
|
||||||
|
<div class="table-scroll-wrap">
|
||||||
|
<ElTable
|
||||||
|
v-loading="loading"
|
||||||
|
:data="bigwinRows"
|
||||||
|
border
|
||||||
|
size="default"
|
||||||
|
class="config-table bigwin-table"
|
||||||
|
>
|
||||||
|
<ElTableColumn label="中大奖点数" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="readonly-value">{{ row.grid_number }}</span>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="显示信息" min-width="140" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElInput v-model="row.ui_text" size="small" placeholder="显示信息" />
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="实际中奖" min-width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="row.real_ev"
|
||||||
|
controls-position="right"
|
||||||
|
size="small"
|
||||||
|
class="full-width"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="备注" min-width="140" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElInput v-model="row.remark" size="small" placeholder="备注" />
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="权重(0-10000)" min-width="220" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="weight-cell">
|
||||||
|
<ElSlider
|
||||||
|
v-model="row.weight"
|
||||||
|
:min="0"
|
||||||
|
:max="10000"
|
||||||
|
:step="100"
|
||||||
|
:disabled="isBigwinWeightDisabled(row)"
|
||||||
|
/>
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="row.weight"
|
||||||
|
:min="0"
|
||||||
|
:max="10000"
|
||||||
|
:step="100"
|
||||||
|
:disabled="isBigwinWeightDisabled(row)"
|
||||||
|
controls-position="right"
|
||||||
|
size="small"
|
||||||
|
class="weight-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span v-if="isBigwinWeightDisabled(row)" class="weight-tip"
|
||||||
|
>点数 5、30 固定 100%</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
</div>
|
||||||
|
<div v-if="bigwinRows.length === 0 && !loading" class="empty-tip">
|
||||||
|
暂无 BIGWIN 档位配置,请在「奖励索引」中设置 tier 为 BIGWIN。
|
||||||
|
</div>
|
||||||
|
<div class="tab-footer">
|
||||||
|
<ElButton type="primary" :loading="savingBigwin" @click="handleSaveBigwin"
|
||||||
|
>保存</ElButton
|
||||||
|
>
|
||||||
|
<ElButton @click="handleResetBigwin">重置</ElButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElTabPane>
|
||||||
|
</ElTabs>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
|
|
||||||
<!-- 编辑弹窗 -->
|
|
||||||
<EditDialog
|
|
||||||
v-model="dialogVisible"
|
|
||||||
:dialog-type="dialogType"
|
|
||||||
:data="dialogData"
|
|
||||||
@success="refreshData"
|
|
||||||
/>
|
|
||||||
<!-- 权重配比测试弹窗 -->
|
|
||||||
<WeightTestDialog v-model="weightTestVisible" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { useTable } from '@/hooks/core/useTable'
|
|
||||||
import { useSaiAdmin } from '@/composables/useSaiAdmin'
|
|
||||||
import api from '../../api/reward_config/index'
|
import api from '../../api/reward_config/index'
|
||||||
import TableSearch from './modules/table-search.vue'
|
|
||||||
import EditDialog from './modules/edit-dialog.vue'
|
|
||||||
import WeightTestDialog from './modules/weight-test-dialog.vue'
|
|
||||||
|
|
||||||
const weightTestVisible = ref(false)
|
/** 第一页:奖励索引行(来自 DiceRewardConfig 表) */
|
||||||
|
interface IndexRow {
|
||||||
|
id: number
|
||||||
|
grid_number: number
|
||||||
|
ui_text: string
|
||||||
|
real_ev: number
|
||||||
|
tier: string
|
||||||
|
remark: string
|
||||||
|
weight: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeTab = ref<'index' | 'bigwin'>('index')
|
||||||
|
const loading = ref(false)
|
||||||
|
const savingIndex = ref(false)
|
||||||
|
const savingBigwin = ref(false)
|
||||||
const createRewardLoading = ref(false)
|
const createRewardLoading = ref(false)
|
||||||
|
|
||||||
|
/** 第一页数据(来自 api.list,即 DiceRewardConfig 表) */
|
||||||
|
const indexRows = ref<IndexRow[]>([])
|
||||||
|
/** 奖励索引 Tab:排除 tier=BIGWIN,仅显示 T1~T5 */
|
||||||
|
const indexRowsExcludeBigwin = computed(() =>
|
||||||
|
indexRows.value.filter((r) => r.tier !== 'BIGWIN')
|
||||||
|
)
|
||||||
|
/** 第二页 BIGWIN 数据:来自同一张表 DiceRewardConfig,过滤 tier===BIGWIN */
|
||||||
|
const bigwinRows = computed(() => indexRows.value.filter((r) => r.tier === 'BIGWIN'))
|
||||||
|
/** 原始 list 快照,用于重置 */
|
||||||
|
let indexRowsSnapshot: IndexRow[] = []
|
||||||
|
|
||||||
|
function toWeight(v: unknown): number {
|
||||||
|
const n = typeof v === 'number' && !Number.isNaN(v) ? v : Number(v)
|
||||||
|
if (Number.isNaN(n)) return 0
|
||||||
|
return Math.max(0, Math.min(10000, Math.floor(n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeIndexRow(raw: Record<string, unknown>): IndexRow {
|
||||||
|
return {
|
||||||
|
id: Number(raw.id) ?? 0,
|
||||||
|
grid_number: Number(raw.grid_number) ?? 0,
|
||||||
|
ui_text: String(raw.ui_text ?? ''),
|
||||||
|
real_ev: Number(raw.real_ev) ?? 0,
|
||||||
|
tier: String(raw.tier ?? ''),
|
||||||
|
remark: String(raw.remark ?? ''),
|
||||||
|
weight: toWeight(raw.weight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleCreateRewardReference() {
|
async function handleCreateRewardReference() {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await ElMessageBox.confirm(
|
||||||
@@ -108,6 +246,7 @@
|
|||||||
? `已按 5-30 共26个点数、顺时针+逆时针创建:顺时针新增 ${data.created_clockwise ?? 0} 条、逆时针新增 ${data.created_counterclockwise ?? 0} 条;顺时针更新 ${data.updated_clockwise ?? 0} 条、逆时针更新 ${data.updated_counterclockwise ?? 0} 条${(data.skipped ?? 0) > 0 ? `;${data.skipped} 个点数使用兜底起始索引` : ''}`
|
? `已按 5-30 共26个点数、顺时针+逆时针创建:顺时针新增 ${data.created_clockwise ?? 0} 条、逆时针新增 ${data.created_counterclockwise ?? 0} 条;顺时针更新 ${data.updated_clockwise ?? 0} 条、逆时针更新 ${data.updated_counterclockwise ?? 0} 条${(data.skipped ?? 0) > 0 ? `;${data.skipped} 个点数使用兜底起始索引` : ''}`
|
||||||
: '创建成功'
|
: '创建成功'
|
||||||
ElMessage.success(msg)
|
ElMessage.success(msg)
|
||||||
|
loadIndexList()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
ElMessage.error(e?.message ?? '创建奖励对照失败')
|
ElMessage.error(e?.message ?? '创建奖励对照失败')
|
||||||
} finally {
|
} finally {
|
||||||
@@ -115,70 +254,266 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索表单
|
function loadIndexList() {
|
||||||
const searchForm = ref<Record<string, unknown>>({
|
loading.value = true
|
||||||
grid_number_min: undefined,
|
return api
|
||||||
grid_number_max: undefined,
|
.list({ limit: 200 })
|
||||||
ui_text: undefined,
|
.then((res: any) => {
|
||||||
real_ev_min: undefined,
|
const list = res?.data?.records ?? res?.records ?? res?.data ?? []
|
||||||
real_ev_max: undefined,
|
const rows = Array.isArray(list)
|
||||||
tier: undefined
|
? list.map((r: Record<string, unknown>) => normalizeIndexRow(r))
|
||||||
})
|
: []
|
||||||
|
indexRows.value = rows
|
||||||
// 搜索处理
|
indexRowsSnapshot = rows.map((r) => ({ ...r }))
|
||||||
const handleSearch = (params: Record<string, any>) => {
|
})
|
||||||
Object.assign(searchParams, params)
|
.catch(() => {
|
||||||
getData()
|
ElMessage.error('获取奖励索引配置失败')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格配置(默认 100 条/页)
|
function isBigwinWeightDisabled(row: IndexRow): boolean {
|
||||||
const {
|
return row.grid_number === 5 || row.grid_number === 30
|
||||||
columns,
|
}
|
||||||
columnChecks,
|
|
||||||
data,
|
|
||||||
loading,
|
|
||||||
getData,
|
|
||||||
searchParams,
|
|
||||||
pagination,
|
|
||||||
resetSearchParams,
|
|
||||||
handleSortChange,
|
|
||||||
handleSizeChange,
|
|
||||||
handleCurrentChange,
|
|
||||||
refreshData
|
|
||||||
} = useTable({
|
|
||||||
core: {
|
|
||||||
apiFn: api.list,
|
|
||||||
apiParams: { limit: 100 },
|
|
||||||
columnsFactory: () => [
|
|
||||||
// { type: 'selection' },
|
|
||||||
{ prop: 'grid_number', label: '色子点数', align: 'center' },
|
|
||||||
{ prop: 'ui_text', label: '前端显示文本', align: 'center' },
|
|
||||||
{ prop: 'real_ev', label: '真实资金结算', align: 'center' },
|
|
||||||
{ prop: 'tier', label: '所属档位', sortable: true, align: 'center' },
|
|
||||||
{ prop: 'weight', label: '权重(1-10000)', sortable: true, align: 'center' },
|
|
||||||
// 权重已迁移至「权重配比」弹窗(dice_reward 表,区分顺时针/逆时针)
|
|
||||||
// { prop: 'create_time', label: '创建时间', sortable: true, align: 'center' },
|
|
||||||
{
|
|
||||||
prop: 'operation',
|
|
||||||
label: '操作',
|
|
||||||
width: 60,
|
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
|
||||||
useSlot: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 编辑配置
|
const GRID_NUMBER_MIN = 5
|
||||||
const {
|
const GRID_NUMBER_MAX = 30
|
||||||
dialogType,
|
|
||||||
dialogVisible,
|
/** 找出数组中出现多于一次的值 */
|
||||||
dialogData,
|
function findDuplicateValues(arr: number[]): number[] {
|
||||||
showDialog,
|
const count = new Map<number, number>()
|
||||||
// deleteRow,
|
for (const v of arr) {
|
||||||
// deleteSelectedRows,
|
count.set(v, (count.get(v) ?? 0) + 1)
|
||||||
handleSelectionChange
|
}
|
||||||
// selectedRows
|
const duplicates: number[] = []
|
||||||
} = useSaiAdmin()
|
count.forEach((c, v) => {
|
||||||
|
if (c > 1) duplicates.push(v)
|
||||||
|
})
|
||||||
|
return duplicates.sort((a, b) => a - b)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 奖励索引表单校验:仅对本表内的行(不含 BIGWIN)校验,点数 5~30 且本批内不重复 */
|
||||||
|
function validateIndexFormForSave(): string | null {
|
||||||
|
const toSave = indexRows.value.filter((r) => r.tier !== 'BIGWIN')
|
||||||
|
if (toSave.length === 0) {
|
||||||
|
return '暂无奖励索引数据可保存'
|
||||||
|
}
|
||||||
|
const nums = toSave.map((r) => Number(r.grid_number))
|
||||||
|
const outOfRange = nums.filter(
|
||||||
|
(n) => Number.isNaN(n) || n < GRID_NUMBER_MIN || n > GRID_NUMBER_MAX
|
||||||
|
)
|
||||||
|
if (outOfRange.length > 0) {
|
||||||
|
return `色子点数必须在 ${GRID_NUMBER_MIN}~${GRID_NUMBER_MAX} 之间`
|
||||||
|
}
|
||||||
|
const duplicates = findDuplicateValues(nums)
|
||||||
|
if (duplicates.length > 0) {
|
||||||
|
return `色子点数在本表内不能重复,重复的点数为:${duplicates.join('、')}`
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 奖励索引表单:仅提交本表数据(T1~T5),不包含大奖权重 */
|
||||||
|
async function handleSaveIndex() {
|
||||||
|
const err = validateIndexFormForSave()
|
||||||
|
if (err) {
|
||||||
|
ElMessage.warning(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const toSave = indexRows.value.filter((r) => r.tier !== 'BIGWIN')
|
||||||
|
savingIndex.value = true
|
||||||
|
try {
|
||||||
|
const indexPayload = toSave.map((r) => ({
|
||||||
|
id: r.id,
|
||||||
|
grid_number: r.grid_number,
|
||||||
|
ui_text: r.ui_text,
|
||||||
|
real_ev: r.real_ev,
|
||||||
|
tier: r.tier,
|
||||||
|
remark: r.remark
|
||||||
|
}))
|
||||||
|
await api.batchUpdate(indexPayload)
|
||||||
|
ElMessage.success('保存成功')
|
||||||
|
indexRowsSnapshot = indexRows.value.map((r) => ({ ...r }))
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.message ?? '保存失败')
|
||||||
|
} finally {
|
||||||
|
savingIndex.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 奖励索引页:重置为本页数据(重新拉取列表) */
|
||||||
|
function handleResetIndex() {
|
||||||
|
loadIndexList()
|
||||||
|
ElMessage.info('已重新加载奖励索引,恢复为服务器最新数据')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 大奖权重表单校验:点数在本表内不重复 */
|
||||||
|
function validateBigwinFormForSave(): string | null {
|
||||||
|
const rows = bigwinRows.value
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return '暂无 BIGWIN 档位配置可保存'
|
||||||
|
}
|
||||||
|
const nums = rows.map((r) => Number(r.grid_number))
|
||||||
|
const outOfRange = nums.filter(
|
||||||
|
(n) => Number.isNaN(n) || n < GRID_NUMBER_MIN || n > GRID_NUMBER_MAX
|
||||||
|
)
|
||||||
|
if (outOfRange.length > 0) {
|
||||||
|
return `色子点数必须在 ${GRID_NUMBER_MIN}~${GRID_NUMBER_MAX} 之间`
|
||||||
|
}
|
||||||
|
const duplicates = findDuplicateValues(nums)
|
||||||
|
if (duplicates.length > 0) {
|
||||||
|
return `大奖权重本表内点数不能重复,重复的点数为:${duplicates.join('、')}`
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 大奖权重表单:仅提交本表数据(BIGWIN 权重),不包含奖励索引 */
|
||||||
|
async function handleSaveBigwin() {
|
||||||
|
const rows = bigwinRows.value
|
||||||
|
if (rows.length === 0) {
|
||||||
|
ElMessage.info('暂无 BIGWIN 档位配置,请先在「奖励索引」中设置 tier 为 BIGWIN')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const err = validateBigwinFormForSave()
|
||||||
|
if (err) {
|
||||||
|
ElMessage.warning(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
savingBigwin.value = true
|
||||||
|
try {
|
||||||
|
const items = rows.map((r) => ({
|
||||||
|
grid_number: r.grid_number,
|
||||||
|
weight: isBigwinWeightDisabled(r)
|
||||||
|
? 10000
|
||||||
|
: Math.max(0, Math.min(10000, Math.floor(r.weight)))
|
||||||
|
}))
|
||||||
|
await api.saveBigwinWeightsByGrid(items)
|
||||||
|
ElMessage.success('保存成功')
|
||||||
|
loadIndexList()
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.message ?? '保存失败')
|
||||||
|
} finally {
|
||||||
|
savingBigwin.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 大奖权重页:重置(重新拉取列表,BIGWIN 数据随之更新) */
|
||||||
|
function handleResetBigwin() {
|
||||||
|
loadIndexList()
|
||||||
|
ElMessage.info('已重新加载,大奖权重恢复为服务器最新数据')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadIndexList()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.reward-config-form {
|
||||||
|
padding: 16px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.form-card {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.top-tabs {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
:deep(.el-tabs__header) {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__content) {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
:deep(.el-tab-pane) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tab-panel {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.table-scroll-wrap {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.tab-footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-top: 1px solid var(--el-border-color-lighter);
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
background: var(--el-bg-color);
|
||||||
|
}
|
||||||
|
.panel-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.config-table {
|
||||||
|
width: 100%;
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.weight-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 0 8px;
|
||||||
|
.el-slider {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.weight-input {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.weight-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
.readonly-value {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
.bigwin-table .full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.empty-tip {
|
||||||
|
padding: 24px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,227 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog
|
|
||||||
v-model="visible"
|
|
||||||
title="权重配比测试"
|
|
||||||
width="720px"
|
|
||||||
align-center
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
destroy-on-close
|
|
||||||
@close="handleClose"
|
|
||||||
>
|
|
||||||
<div v-if="!result" class="test-form">
|
|
||||||
<el-form :model="form" label-width="120px">
|
|
||||||
<el-form-item label="奖池配置">
|
|
||||||
<el-select
|
|
||||||
v-model="form.lottery_config_id"
|
|
||||||
placeholder="选择 T1-T5 档位概率来源,不选则使用默认"
|
|
||||||
clearable
|
|
||||||
style="width: 320px"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="opt in lotteryConfigOptions"
|
|
||||||
:key="opt.id"
|
|
||||||
:label="opt.name"
|
|
||||||
:value="opt.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<div class="form-tip">
|
|
||||||
{{
|
|
||||||
selectedTierSummary || '选定奖池的 t1_weight~t5_weight 将作为测试时 T1-T5 的抽取概率'
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="测试次数">
|
|
||||||
<el-radio-group v-model="form.test_count">
|
|
||||||
<el-radio :value="100">100 次</el-radio>
|
|
||||||
<el-radio :value="500">500 次</el-radio>
|
|
||||||
<el-radio :value="1000">1000 次</el-radio>
|
|
||||||
<el-radio :value="5000">5000 次</el-radio>
|
|
||||||
<el-radio :value="10000">10000 次</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="保存记录">
|
|
||||||
<el-switch v-model="form.save_record" />
|
|
||||||
<span class="form-tip">保存到「测试记录表」便于后续对比</span>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
<div v-else class="test-result">
|
|
||||||
<div class="result-summary">
|
|
||||||
共模拟 <strong>{{ form.test_count }}</strong> 次抽奖,落点分布如下:
|
|
||||||
</div>
|
|
||||||
<div class="chart-wrap">
|
|
||||||
<ArtBarChart
|
|
||||||
x-axis-name="色子点数 (grid_number)"
|
|
||||||
:x-axis-data="chartLabels"
|
|
||||||
:data="chartData"
|
|
||||||
height="320px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-if="result.record_id" class="record-tip"
|
|
||||||
>已保存至测试记录,记录 ID:{{ result.record_id }}</div
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<template v-if="!result">
|
|
||||||
<el-button @click="handleClose">取消</el-button>
|
|
||||||
<el-button type="primary" :loading="submitting" @click="handleRun">开始测试</el-button>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<el-button type="primary" @click="handleReset">再测一次</el-button>
|
|
||||||
<el-button @click="handleClose">关闭</el-button>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import api from '../../../api/reward_config/index'
|
|
||||||
import lotteryConfigApi from '../../../api/lottery_pool_config/index'
|
|
||||||
import ArtBarChart from '@/components/core/charts/art-bar-chart/index.vue'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
|
|
||||||
const GRID_NUMBERS = [
|
|
||||||
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
|
||||||
30
|
|
||||||
]
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
modelValue: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Emits {
|
|
||||||
(e: 'update:modelValue', value: boolean): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
modelValue: false
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits<Emits>()
|
|
||||||
|
|
||||||
const visible = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: (v) => emit('update:modelValue', v)
|
|
||||||
})
|
|
||||||
|
|
||||||
const form = ref({
|
|
||||||
lottery_config_id: null as number | null,
|
|
||||||
test_count: 100 as 100 | 500 | 1000 | 5000 | 10000,
|
|
||||||
save_record: true
|
|
||||||
})
|
|
||||||
|
|
||||||
const lotteryConfigOptions = ref<
|
|
||||||
Array<{
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
t1_weight: number
|
|
||||||
t2_weight: number
|
|
||||||
t3_weight: number
|
|
||||||
t4_weight: number
|
|
||||||
t5_weight: number
|
|
||||||
}>
|
|
||||||
>([])
|
|
||||||
const submitting = ref(false)
|
|
||||||
const result = ref<{ counts: Record<string, number>; record_id: number | null } | null>(null)
|
|
||||||
|
|
||||||
const selectedLotteryConfig = computed(
|
|
||||||
() => lotteryConfigOptions.value.find((opt) => opt.id === form.value.lottery_config_id) || null
|
|
||||||
)
|
|
||||||
|
|
||||||
const selectedTierSummary = computed(() => {
|
|
||||||
const opt = selectedLotteryConfig.value
|
|
||||||
if (!opt) return ''
|
|
||||||
const t1 = opt.t1_weight
|
|
||||||
const t2 = opt.t2_weight
|
|
||||||
const t3 = opt.t3_weight
|
|
||||||
const t4 = opt.t4_weight
|
|
||||||
const t5 = opt.t5_weight
|
|
||||||
const total = t1 + t2 + t3 + t4 + t5
|
|
||||||
if (!total) {
|
|
||||||
return `当前奖池 T1-T5 权重:T1=${t1} T2=${t2} T3=${t3} T4=${t4} T5=${t5}(总和为 0,测试时会按均等档位概率)`
|
|
||||||
}
|
|
||||||
const p = (v: number) => ((v / total) * 100).toFixed(1)
|
|
||||||
return `当前奖池 T1-T5 权重:T1=${t1} (${p(t1)}%),T2=${t2} (${p(t2)}%),T3=${t3} (${p(t3)}%),T4=${t4} (${p(t4)}%),T5=${t5} (${p(t5)}%)`
|
|
||||||
})
|
|
||||||
|
|
||||||
const chartLabels = computed(() => GRID_NUMBERS.map((n) => String(n)))
|
|
||||||
|
|
||||||
const chartData = computed(() => {
|
|
||||||
if (!result.value?.counts) return GRID_NUMBERS.map(() => 0)
|
|
||||||
const counts = result.value.counts
|
|
||||||
return GRID_NUMBERS.map((n) => {
|
|
||||||
const v = counts[String(n)] ?? counts[n]
|
|
||||||
return typeof v === 'number' && !Number.isNaN(v) ? v : 0
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
async function loadLotteryOptions() {
|
|
||||||
try {
|
|
||||||
const list = await lotteryConfigApi.getOptions()
|
|
||||||
lotteryConfigOptions.value = Array.isArray(list) ? list : []
|
|
||||||
} catch {
|
|
||||||
lotteryConfigOptions.value = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleRun() {
|
|
||||||
submitting.value = true
|
|
||||||
result.value = null
|
|
||||||
try {
|
|
||||||
const res = await api.runWeightTest({
|
|
||||||
test_count: form.value.test_count,
|
|
||||||
save_record: form.value.save_record,
|
|
||||||
lottery_config_id: form.value.lottery_config_id ?? undefined
|
|
||||||
})
|
|
||||||
const data = (res as any)?.data ?? res
|
|
||||||
result.value = {
|
|
||||||
counts: data.counts ?? {},
|
|
||||||
record_id: data.record_id ?? null
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
ElMessage.error(e?.message ?? '测试请求失败')
|
|
||||||
} finally {
|
|
||||||
submitting.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleReset() {
|
|
||||||
result.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClose() {
|
|
||||||
visible.value = false
|
|
||||||
result.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(visible, (open) => {
|
|
||||||
if (open) {
|
|
||||||
loadLotteryOptions()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.test-form {
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
|
||||||
.form-tip {
|
|
||||||
margin-left: 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
.test-result {
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
|
||||||
.result-summary {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.chart-wrap {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.record-tip {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -104,6 +104,29 @@ class DiceRewardConfigController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新奖励索引配置(第一页:id、grid_number、ui_text、real_ev、tier、remark)
|
||||||
|
* @param Request $request items: [{ id, grid_number?, ui_text?, real_ev?, tier?, remark? }, ...]
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
#[Permission('奖励配置修改', 'dice:reward_config:index:update')]
|
||||||
|
public function batchUpdate(Request $request): Response
|
||||||
|
{
|
||||||
|
$items = $request->post('items', []);
|
||||||
|
if (! is_array($items)) {
|
||||||
|
return $this->fail('参数 items 必须为数组');
|
||||||
|
}
|
||||||
|
$err = $this->logic->validateBatchUpdateItems($items);
|
||||||
|
if ($err !== null) {
|
||||||
|
return $this->fail($err);
|
||||||
|
}
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$this->validate('batch_update', array_merge($item, ['id' => $item['id']]));
|
||||||
|
}
|
||||||
|
$this->logic->batchUpdate($items);
|
||||||
|
return $this->success('保存成功');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除数据
|
* 删除数据
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
@@ -159,6 +182,27 @@ class DiceRewardConfigController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大奖权重:按 grid_number 批量保存 BIGWIN 权重(仅更新 dice_reward_config 表,不操作 dice_reward)
|
||||||
|
* items: [ { grid_number: 5-30, weight: 0-10000 }, ... ]
|
||||||
|
* @param Request $request
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
#[Permission('奖励配置修改', 'dice:reward_config:index:update')]
|
||||||
|
public function saveBigwinWeightsByGrid(Request $request): Response
|
||||||
|
{
|
||||||
|
$items = $request->post('items', []);
|
||||||
|
if (! is_array($items)) {
|
||||||
|
return $this->fail('参数 items 必须为数组');
|
||||||
|
}
|
||||||
|
$err = $this->logic->validateBigwinWeightItems($items);
|
||||||
|
if ($err !== null) {
|
||||||
|
return $this->fail($err);
|
||||||
|
}
|
||||||
|
$this->logic->batchUpdateBigwinWeight($items);
|
||||||
|
return $this->success('保存成功');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建奖励对照:按当前 dice_reward_config 为两种方向(顺时针0、逆时针1)生成所有色子可能对应的 dice_reward 记录
|
* 创建奖励对照:按当前 dice_reward_config 为两种方向(顺时针0、逆时针1)生成所有色子可能对应的 dice_reward 记录
|
||||||
* 权重默认 1,可在「奖励对照」页的权重编辑弹窗中调整
|
* 权重默认 1,可在「奖励对照」页的权重编辑弹窗中调整
|
||||||
|
|||||||
@@ -226,37 +226,46 @@ class DiceRewardLogic
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新 BIGWIN 档位某点数的权重(顺/逆时针同时更新);0=0% 中奖,10000=100% 中奖
|
* 更新 BIGWIN 档位某点数的权重(顺/逆时针同时更新);0=0% 中奖,10000=100% 中奖
|
||||||
|
* 表 dice_reward 唯一键为 (direction, grid_number),同一点数同一方向仅一条记录,故先按该键查找再更新,避免重复插入
|
||||||
*/
|
*/
|
||||||
public function updateBigwinWeight(int $gridNumber, int $weight): void
|
public function updateBigwinWeight(int $gridNumber, int $weight): void
|
||||||
{
|
{
|
||||||
$weight = min(self::BIGWIN_WEIGHT_MAX, max(0, $weight));
|
$weight = min(self::BIGWIN_WEIGHT_MAX, max(0, $weight));
|
||||||
|
$config = DiceRewardConfig::where('tier', 'BIGWIN')
|
||||||
|
->where('grid_number', $gridNumber)
|
||||||
|
->find();
|
||||||
|
if (! $config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$configArr = $config->toArray();
|
||||||
foreach ([DiceReward::DIRECTION_CLOCKWISE, DiceReward::DIRECTION_COUNTERCLOCKWISE] as $direction) {
|
foreach ([DiceReward::DIRECTION_CLOCKWISE, DiceReward::DIRECTION_COUNTERCLOCKWISE] as $direction) {
|
||||||
// 优先更新已存在记录
|
// 按唯一键 (direction, grid_number) 查找,存在则更新,不存在则插入
|
||||||
$affected = DiceReward::where('tier', 'BIGWIN')
|
$row = DiceReward::where('direction', $direction)
|
||||||
->where('direction', $direction)
|
|
||||||
->where('grid_number', $gridNumber)
|
->where('grid_number', $gridNumber)
|
||||||
->update(['weight' => $weight]);
|
->find();
|
||||||
|
if ($row) {
|
||||||
// 若不存在 BIGWIN 记录,则按当前 BIGWIN 配置懒加载创建一条 dice_reward 记录
|
$row->tier = 'BIGWIN';
|
||||||
if ($affected === 0) {
|
$row->weight = $weight > 0 ? $weight : self::WEIGHT_MIN;
|
||||||
$config = DiceRewardConfig::where('tier', 'BIGWIN')
|
$row->start_index = (int) ($configArr['id'] ?? $row->start_index);
|
||||||
->where('grid_number', $gridNumber)
|
$row->end_index = (int) ($configArr['id'] ?? $row->end_index);
|
||||||
->find();
|
$row->ui_text = (string) ($configArr['ui_text'] ?? $row->ui_text);
|
||||||
if ($config) {
|
$row->real_ev = (float) ($configArr['real_ev'] ?? $row->real_ev);
|
||||||
$m = new DiceReward();
|
$row->remark = (string) ($configArr['remark'] ?? $row->remark);
|
||||||
$m->tier = 'BIGWIN';
|
$row->type = $configArr['type'] ?? $row->type;
|
||||||
$m->direction = $direction;
|
$row->save();
|
||||||
$m->grid_number = (int) $gridNumber;
|
} else {
|
||||||
// 对于 BIGWIN,仅需保证 real_ev、weight、grid_number,start_index/end_index 取当前配置 id 即可
|
$m = new DiceReward();
|
||||||
$m->start_index = (int) $config->id;
|
$m->tier = 'BIGWIN';
|
||||||
$m->end_index = (int) $config->id;
|
$m->direction = $direction;
|
||||||
$m->ui_text = (string) ($config->ui_text ?? '');
|
$m->grid_number = (int) $gridNumber;
|
||||||
$m->real_ev = (float) ($config->real_ev ?? 0);
|
$m->start_index = (int) ($configArr['id'] ?? 0);
|
||||||
$m->remark = (string) ($config->remark ?? '');
|
$m->end_index = (int) ($configArr['id'] ?? 0);
|
||||||
$m->type = $config->type ?? null;
|
$m->ui_text = (string) ($configArr['ui_text'] ?? '');
|
||||||
$m->weight = $weight > 0 ? $weight : self::WEIGHT_MIN;
|
$m->real_ev = (float) ($configArr['real_ev'] ?? 0);
|
||||||
$m->save();
|
$m->remark = (string) ($configArr['remark'] ?? '');
|
||||||
}
|
$m->type = $configArr['type'] ?? null;
|
||||||
|
$m->weight = $weight > 0 ? $weight : self::WEIGHT_MIN;
|
||||||
|
$m->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DiceReward::refreshCache();
|
DiceReward::refreshCache();
|
||||||
|
|||||||
@@ -68,6 +68,158 @@ class DiceRewardConfigLogic extends BaseLogic
|
|||||||
return $listResult;
|
return $listResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 奖励索引必须为 26 条,id 为 0~25,点数 5~30 各出现一次 */
|
||||||
|
private const BATCH_INDEX_COUNT = 26;
|
||||||
|
private const INDEX_ID_MIN = 0;
|
||||||
|
private const INDEX_ID_MAX = 25;
|
||||||
|
private const GRID_NUMBER_MIN = 5;
|
||||||
|
private const GRID_NUMBER_MAX = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验批量更新项(奖励索引表单独立提交,可能只含非 BIGWIN 的若干条)
|
||||||
|
* - 每项必须包含 id、grid_number;grid_number 须在 5~30,提交项内 grid_number 不能重复
|
||||||
|
* - 若为 26 条则额外校验:id 为 0~25 各一、grid_number 为 5~30 各一
|
||||||
|
* @return string|null 校验失败返回错误信息,通过返回 null
|
||||||
|
*/
|
||||||
|
public function validateBatchUpdateItems(array $items): ?string
|
||||||
|
{
|
||||||
|
if (count($items) === 0) {
|
||||||
|
return '提交数据不能为空';
|
||||||
|
}
|
||||||
|
$ids = [];
|
||||||
|
$gridNumbers = [];
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (! array_key_exists('id', $item) || $item['id'] === null || $item['id'] === '') {
|
||||||
|
return '每项必须包含 id';
|
||||||
|
}
|
||||||
|
$id = (int) $item['id'];
|
||||||
|
$ids[] = $id;
|
||||||
|
if (! array_key_exists('grid_number', $item)) {
|
||||||
|
return '每项必须包含 grid_number';
|
||||||
|
}
|
||||||
|
$gn = (int) $item['grid_number'];
|
||||||
|
if ($gn < self::GRID_NUMBER_MIN || $gn > self::GRID_NUMBER_MAX) {
|
||||||
|
return '色子点数 grid_number 只能为 ' . self::GRID_NUMBER_MIN . '~' . self::GRID_NUMBER_MAX . ',当前存在 ' . $gn;
|
||||||
|
}
|
||||||
|
$gridNumbers[] = $gn;
|
||||||
|
}
|
||||||
|
$gridDuplicates = $this->findDuplicateValues($gridNumbers);
|
||||||
|
if ($gridDuplicates !== []) {
|
||||||
|
sort($gridDuplicates);
|
||||||
|
return '色子点数在本批内不能重复,重复的点数为:' . implode('、', $gridDuplicates);
|
||||||
|
}
|
||||||
|
$cnt = count($items);
|
||||||
|
if ($cnt === self::BATCH_INDEX_COUNT) {
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
if ($id < self::INDEX_ID_MIN || $id > self::INDEX_ID_MAX) {
|
||||||
|
return '索引 id 只能为 ' . self::INDEX_ID_MIN . '~' . self::INDEX_ID_MAX . ',当前存在 id=' . $id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$idDuplicates = $this->findDuplicateValues($ids);
|
||||||
|
if ($idDuplicates !== []) {
|
||||||
|
sort($idDuplicates);
|
||||||
|
return '索引 id 必须为 0~25 各出现一次不能重复,重复的 id 为:' . implode('、', $idDuplicates);
|
||||||
|
}
|
||||||
|
$requiredIds = range(self::INDEX_ID_MIN, self::INDEX_ID_MAX);
|
||||||
|
if (array_diff($requiredIds, $ids) !== [] || array_diff($ids, $requiredIds) !== []) {
|
||||||
|
return '索引 id 必须且只能为 0~25 各一个';
|
||||||
|
}
|
||||||
|
$requiredGrid = range(self::GRID_NUMBER_MIN, self::GRID_NUMBER_MAX);
|
||||||
|
if (array_diff($requiredGrid, $gridNumbers) !== [] || array_diff($gridNumbers, $requiredGrid) !== []) {
|
||||||
|
return '色子点数必须且只能为 5~30 各一个';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 找出数组中出现多于一次的值
|
||||||
|
* @param array $arr
|
||||||
|
* @return array 重复出现的值(去重)
|
||||||
|
*/
|
||||||
|
private function findDuplicateValues(array $arr): array
|
||||||
|
{
|
||||||
|
$counts = array_count_values($arr);
|
||||||
|
$duplicates = [];
|
||||||
|
foreach ($counts as $value => $count) {
|
||||||
|
if ($count > 1) {
|
||||||
|
$duplicates[] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $duplicates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新奖励索引配置:grid_number、ui_text、real_ev、tier、remark(不含 weight,BIGWIN 权重单独接口)
|
||||||
|
* @param array $items 每项 [id, grid_number?, ui_text?, real_ev?, tier?, remark?]
|
||||||
|
*/
|
||||||
|
public function batchUpdate(array $items): void
|
||||||
|
{
|
||||||
|
foreach ($items as $row) {
|
||||||
|
if (! array_key_exists('id', $row) || $row['id'] === null || $row['id'] === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$id = (int) $row['id'];
|
||||||
|
$data = [];
|
||||||
|
foreach (['grid_number', 'ui_text', 'real_ev', 'tier', 'remark'] as $field) {
|
||||||
|
if (array_key_exists($field, $row)) {
|
||||||
|
$data[$field] = $row[$field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! empty($data)) {
|
||||||
|
parent::edit($id, $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DiceRewardConfig::refreshCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验大奖权重提交项:点数 5~30,本批内 grid_number 不能重复
|
||||||
|
* @return string|null 校验失败返回错误信息(含重复的点数),通过返回 null
|
||||||
|
*/
|
||||||
|
public function validateBigwinWeightItems(array $items): ?string
|
||||||
|
{
|
||||||
|
if (count($items) === 0) {
|
||||||
|
return '提交数据不能为空';
|
||||||
|
}
|
||||||
|
$gridNumbers = [];
|
||||||
|
foreach ($items as $row) {
|
||||||
|
$gn = isset($row['grid_number']) ? (int) $row['grid_number'] : 0;
|
||||||
|
if ($gn < self::GRID_NUMBER_MIN || $gn > self::GRID_NUMBER_MAX) {
|
||||||
|
return '色子点数 grid_number 只能为 ' . self::GRID_NUMBER_MIN . '~' . self::GRID_NUMBER_MAX . ',当前存在 ' . $gn;
|
||||||
|
}
|
||||||
|
$gridNumbers[] = $gn;
|
||||||
|
}
|
||||||
|
$duplicates = $this->findDuplicateValues($gridNumbers);
|
||||||
|
if ($duplicates !== []) {
|
||||||
|
sort($duplicates);
|
||||||
|
return '大奖权重本批内点数不能重复,重复的点数为:' . implode('、', $duplicates);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新 BIGWIN 档位权重(仅写 dice_reward_config 表,不操作 dice_reward)
|
||||||
|
* @param array $items 每项 [grid_number => 5-30, weight => 0-10000]
|
||||||
|
*/
|
||||||
|
public function batchUpdateBigwinWeight(array $items): void
|
||||||
|
{
|
||||||
|
$weightMin = 0;
|
||||||
|
$weightMax = 10000;
|
||||||
|
foreach ($items as $row) {
|
||||||
|
$gridNumber = isset($row['grid_number']) ? (int) $row['grid_number'] : 0;
|
||||||
|
$weight = isset($row['weight']) ? (int) $row['weight'] : 0;
|
||||||
|
if ($gridNumber < 5 || $gridNumber > 30) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$weight = max($weightMin, min($weightMax, $weight));
|
||||||
|
$this->model->where('tier', 'BIGWIN')
|
||||||
|
->where('grid_number', $gridNumber)
|
||||||
|
->update(['weight' => $weight]);
|
||||||
|
}
|
||||||
|
DiceRewardConfig::refreshCache();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除后刷新缓存
|
* 删除后刷新缓存
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,25 +13,31 @@ use plugin\saiadmin\basic\BaseValidate;
|
|||||||
*/
|
*/
|
||||||
class DiceRewardConfigValidate extends BaseValidate
|
class DiceRewardConfigValidate extends BaseValidate
|
||||||
{
|
{
|
||||||
|
/** 色子点数范围:5~30 共 26 个点数 */
|
||||||
|
public const GRID_NUMBER_MIN = 5;
|
||||||
|
public const GRID_NUMBER_MAX = 30;
|
||||||
|
|
||||||
protected $rule = [
|
protected $rule = [
|
||||||
'grid_number' => 'require',
|
'grid_number' => 'require|integer|between:5,30',
|
||||||
'ui_text' => 'require',
|
'ui_text' => 'require',
|
||||||
'real_ev' => 'require',
|
'real_ev' => 'require',
|
||||||
'tier' => 'require',
|
'tier' => 'require',
|
||||||
'type' => 'number',
|
'type' => 'number',
|
||||||
'weight' => 'number|between:0,10000', // BIGWIN 大奖权重,仅档位为 BIGWIN 时使用
|
'weight' => 'number|between:0,10000', // BIGWIN 大奖权重,仅档位为 BIGWIN 时使用
|
||||||
|
'remark' => 'max:500',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $message = [
|
protected $message = [
|
||||||
'grid_number' => '色子点数必须填写',
|
'grid_number' => '色子点数必须为 5~30 之间的整数(共26个点数)',
|
||||||
'ui_text' => '前端显示文本必须填写',
|
'ui_text' => '前端显示文本必须填写',
|
||||||
'real_ev' => '真实资金结算必须填写',
|
'real_ev' => '真实资金结算必须填写',
|
||||||
'tier' => '所属档位必须填写',
|
'tier' => '所属档位必须填写',
|
||||||
'type' => '奖励类型须为数字',
|
'type' => '奖励类型须为数字',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $scene = [
|
protected $scene = [
|
||||||
'save' => ['grid_number', 'ui_text', 'real_ev', 'tier', 'type'],
|
'save' => ['grid_number', 'ui_text', 'real_ev', 'tier', 'type'],
|
||||||
'update' => ['grid_number', 'ui_text', 'real_ev', 'tier', 'type', 'weight'],
|
'update' => ['grid_number', 'ui_text', 'real_ev', 'tier', 'type', 'weight'],
|
||||||
|
'batch_update' => ['grid_number', 'ui_text', 'real_ev', 'tier', 'remark'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ Route::group('/core', function () {
|
|||||||
fastRoute('dice/reward_config/DiceRewardConfig', \app\dice\controller\reward_config\DiceRewardConfigController::class);
|
fastRoute('dice/reward_config/DiceRewardConfig', \app\dice\controller\reward_config\DiceRewardConfigController::class);
|
||||||
Route::get('/dice/reward_config/DiceRewardConfig/weightRatioList', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'weightRatioList']);
|
Route::get('/dice/reward_config/DiceRewardConfig/weightRatioList', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'weightRatioList']);
|
||||||
Route::post('/dice/reward_config/DiceRewardConfig/batchUpdateWeights', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'batchUpdateWeights']);
|
Route::post('/dice/reward_config/DiceRewardConfig/batchUpdateWeights', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'batchUpdateWeights']);
|
||||||
|
Route::post('/dice/reward_config/DiceRewardConfig/saveBigwinWeightsByGrid', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'saveBigwinWeightsByGrid']);
|
||||||
|
Route::post('/dice/reward_config/DiceRewardConfig/batchUpdate', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'batchUpdate']);
|
||||||
Route::post('/dice/reward_config/DiceRewardConfig/createRewardReference', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'createRewardReference']);
|
Route::post('/dice/reward_config/DiceRewardConfig/createRewardReference', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'createRewardReference']);
|
||||||
Route::post('/dice/reward_config/DiceRewardConfig/runWeightTest', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'runWeightTest']);
|
Route::post('/dice/reward_config/DiceRewardConfig/runWeightTest', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'runWeightTest']);
|
||||||
fastRoute('dice/lottery_pool_config/DiceLotteryPoolConfig', \app\dice\controller\lottery_pool_config\DiceLotteryPoolConfigController::class);
|
fastRoute('dice/lottery_pool_config/DiceLotteryPoolConfig', \app\dice\controller\lottery_pool_config\DiceLotteryPoolConfigController::class);
|
||||||
|
|||||||
Reference in New Issue
Block a user