1.新增默认彩金池配置

2.优化关联彩金池配置的名称显示
3.优化一键测试权重
4.优化底注配置
This commit is contained in:
2026-06-04 12:21:57 +08:00
parent 5d316ef7d6
commit dfb37dd33a
40 changed files with 845 additions and 177 deletions

View File

@@ -27,6 +27,8 @@
"labelIsDefault": "Default Ante",
"placeholderName": "Please enter name",
"placeholderTitle": "Please enter title",
"placeholderNameAuto": "Auto from multiplier, e.g. x5",
"placeholderTitleAuto": "Auto from multiplier, e.g. x5",
"ruleNameRequired": "Please enter name",
"ruleTitleRequired": "Please enter title",
"ruleMultRequired": "Please enter ante multiplier",

View File

@@ -5,11 +5,13 @@
"dialogTitleEdit": "Edit Lottery Pool Config",
"placeholderName": "Please enter name",
"placeholderRemark": "Please enter remark",
"placeholderPoolName": "Pool display name, e.g. Normal pool",
"placeholderConfigNote": "Optional notes about this pool",
"poolType": "Pool Type",
"placeholderPoolType": "Please select pool type",
"poolTypeNormal": "Normal",
"poolTypeFree": "Free",
"poolTypeKill": "Kill",
"poolTypeKill": "Force score kill",
"poolTypeT1": "T1 High",
"safetyLine": "Safety Line",
"t1Weight": "T1 Pool Weight (%)",
@@ -59,11 +61,13 @@
"placeholderPoolType": "Please select pool type",
"poolTypeNormal": "Normal",
"poolTypeFree": "Free",
"poolTypeKill": "Force Kill",
"poolTypeKill": "Force Score Kill",
"poolTypeT1": "T1 High Rate"
},
"table": {
"name": "Name",
"name": "Code",
"poolName": "Pool Name",
"configNote": "Remark",
"poolType": "Pool Type",
"safetyLine": "Safety Line",
"safetyLineNotUsed": "Not used for kill",

View File

@@ -22,6 +22,8 @@
"placeholderLotteryPool": "Leave empty for custom weights below, or select pool",
"currentConfig": "Current Config",
"configLabelName": "Name",
"configLabelPoolName": "Pool name",
"configLabelCode": "Code",
"configLabelType": "Type",
"configLabelWeights": "T1T5 Weights",
"configLabelRemark": "Remark",

View File

@@ -73,8 +73,10 @@
"labelLotteryTypeFree": "Free tier pool",
"labelAnte": "Ante",
"placeholderAnte": "Select ante config",
"placeholderPaidPool": "Leave empty for custom tier odds below (default: default)",
"placeholderFreePool": "Leave empty for custom tier odds below (default: free pool)",
"anteRandomOption": "Random (pick from channel ante configs)",
"placeholderPaidPool": "Leave empty to set T1T5 weights manually",
"placeholderFreePool": "Leave empty to set T1T5 weights manually",
"selectedPoolHint": "Selected pool: {name}",
"tierProbHint": "Custom tier odds (T1T5), each 0100%, sum of five must not exceed 100%",
"tierFieldLabel": "Tier {tier} (%)",
"tierSumError": "Current sum of five tiers is {sum}%, cannot exceed 100%",

View File

@@ -17,6 +17,8 @@
"chainModeNo": "No",
"paidPlannedSpins": "Planned paid spins",
"ante": "Ante",
"anteRandom": "Random",
"testSafetyLine": "Safety line",
"playAgainCount": "Play-again count",
"progressDraws": "{over} done",
"progressFailed": "{over} before fail",
@@ -51,11 +53,13 @@
"testCountProgress": "In progress: {over} done",
"testCountFailed": "{over} before failure",
"chainModeLabel": "Chain play-again",
"killModeOff": "Kill mode off",
"paidPlannedSpins": "Planned paid spins",
"testSafetyLine": "Test safety line",
"createTime": "Created at",
"admin": "Operator",
"paidPoolId": "Paid lottery pool config ID",
"freePoolId": "Free lottery pool config ID",
"paidPoolId": "Paid lottery pool",
"freePoolId": "Free lottery pool",
"bigwinSnapshot": "BIGWIN weight snapshot",
"sectionPaidTier": "Paid draw tier odds (T1T5, used in test)",
"sectionFreeTier": "Free draw tier odds (T1T5, used in test)",

View File

@@ -27,6 +27,8 @@
"labelIsDefault": "默认底注",
"placeholderName": "请输入名称",
"placeholderTitle": "请输入标题",
"placeholderNameAuto": "随底注倍率自动生成,如 x5",
"placeholderTitleAuto": "随底注倍率自动生成,如 x5",
"ruleNameRequired": "请输入名称",
"ruleTitleRequired": "请输入标题",
"ruleMultRequired": "请输入底注倍率",

View File

@@ -5,11 +5,14 @@
"dialogTitleEdit": "编辑色子奖池配置",
"placeholderName": "请输入名称",
"placeholderRemark": "请输入备注",
"placeholderPoolName": "请输入奖池名称,如:正常池",
"placeholderConfigNote": "选填,用于说明该奖池用途或规则",
"poolType": "奖池类型",
"poolName": "奖池名称",
"placeholderPoolType": "请选择奖池类型",
"poolTypeNormal": "正常",
"poolTypeFree": "免费",
"poolTypeKill": "强制杀",
"poolTypeKill": "强制杀",
"poolTypeT1": "T1高倍率",
"safetyLine": "安全线",
"t1Weight": "T1池权重(%)",
@@ -29,7 +32,7 @@
"tierRuleContent": "比较对象为 default 奖池的 profit_amount非单个玩家盈利。当 profit_amount 低于安全线或未开启杀分时,付费按玩家 T*_weight 抽档;当 profit_amount 高于或等于安全线且已开启杀分时,付费按 killScore 奖池抽档。免费抽奖始终按本渠道 name=free 奖池权重(无 free 时回退 default与安全线无关。",
"enableKillScore": "开启杀分",
"killScoreWeights": "杀分权重killScore",
"killWeightNote": "杀分权重请在列表中编辑 name=killScore强制杀)记录;本弹窗仅配置 default 奖池的安全线与杀分开关。",
"killWeightNote": "杀分权重请在列表中编辑 name=killScore强制杀)记录;本弹窗仅配置 default 奖池的安全线与杀分开关。",
"btnResetProfit": "重置彩金池累计盈利",
"btnSaveSafetyLine": "保存安全线与杀分开关",
"safetyLineDefaultOnlyHint": "仅 name=default正常奖池的安全线参与杀分判定其它奖池类型请勿在此配置安全线。",
@@ -55,15 +58,18 @@
},
"search": {
"poolType": "奖池类型",
"poolName": "奖池名称",
"placeholderName": "请输入名称",
"placeholderPoolType": "请选择奖池类型",
"poolTypeNormal": "正常",
"poolTypeFree": "免费",
"poolTypeKill": "强制杀",
"poolTypeKill": "强制杀",
"poolTypeT1": "T1高倍率"
},
"table": {
"name": "名称",
"name": "内部标识",
"poolName": "奖池名称",
"configNote": "备注",
"poolType": "奖池类型",
"safetyLine": "安全线",
"safetyLineNotUsed": "不参与杀分判定",

View File

@@ -22,6 +22,8 @@
"placeholderLotteryPool": "留空则使用下方自定义权重,或选择彩金池",
"currentConfig": "当前配置",
"configLabelName": "名称",
"configLabelPoolName": "奖池名称",
"configLabelCode": "内部标识",
"configLabelType": "类型",
"configLabelWeights": "T1T5 权重",
"configLabelRemark": "备注",

View File

@@ -73,8 +73,10 @@
"labelLotteryTypeFree": "免费档位奖池",
"labelAnte": "底注",
"placeholderAnte": "请选择底注配置",
"placeholderPaidPool": "不选则下方自定义档位概率(默认 default",
"placeholderFreePool": "不选则下方自定义档位概率(默认 free 免费奖池)",
"anteRandomOption": "随机(从当前渠道底注配置中抽取",
"placeholderPaidPool": "不选则下方手动设定 T1T5 档位权重",
"placeholderFreePool": "不选则下方手动设定 T1T5 档位权重",
"selectedPoolHint": "已选奖池:{name}",
"tierProbHint": "自定义档位概率T1T5每档 0-100%,五档之和不能超过 100%",
"tierFieldLabel": "档位 {tier}%",
"tierSumError": "当前五档之和为 {sum}%,不能超过 100%",

View File

@@ -17,6 +17,8 @@
"chainModeNo": "否",
"paidPlannedSpins": "计划付费次数",
"ante": "底注",
"anteRandom": "随机",
"testSafetyLine": "安全线",
"playAgainCount": "再来一次次数",
"progressDraws": "已完成 {over} 次",
"progressFailed": "失败前 {over} 次",
@@ -51,11 +53,13 @@
"testCountProgress": "进行中:已完成 {over} 次",
"testCountFailed": "失败前 {over} 次",
"chainModeLabel": "链式再来一次",
"killModeOff": "未开启杀分",
"paidPlannedSpins": "计划付费次数",
"testSafetyLine": "测试安全线",
"createTime": "创建时间",
"admin": "执行管理员",
"paidPoolId": "付费奖池配置ID",
"freePoolId": "免费奖池配置ID",
"paidPoolId": "付费彩金池",
"freePoolId": "免费彩金池",
"bigwinSnapshot": "BIGWIN 权重快照",
"sectionPaidTier": "付费抽奖档位概率T1-T5测试时使用",
"sectionFreeTier": "免费抽奖档位概率T1-T5测试时使用",

View File

@@ -9,13 +9,19 @@
>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
<el-form-item :label="$t('page.form.labelName')" prop="name">
<el-input v-model="formData.name" :placeholder="$t('page.form.placeholderName')" />
<el-input v-model="formData.name" disabled :placeholder="$t('page.form.placeholderNameAuto')" />
</el-form-item>
<el-form-item :label="$t('page.form.labelTitle')" prop="title">
<el-input v-model="formData.title" :placeholder="$t('page.form.placeholderTitle')" />
<el-input v-model="formData.title" disabled :placeholder="$t('page.form.placeholderTitleAuto')" />
</el-form-item>
<el-form-item :label="$t('page.form.labelMult')" prop="mult">
<el-input-number v-model="formData.mult" :min="1" :step="1" style="width: 100%" />
<el-input-number
v-model="formData.mult"
:min="1"
:step="1"
style="width: 100%"
@update:model-value="syncNameTitleFromMult"
/>
</el-form-item>
<el-form-item :label="$t('page.form.labelIsDefault')" prop="is_default">
<el-radio-group v-model="formData.is_default">
@@ -87,6 +93,13 @@
const formData = reactive({ ...initialFormData })
function syncNameTitleFromMult() {
const mult = Number(formData.mult) || 1
const label = `x${mult}`
formData.name = label
formData.title = label
}
watch(
() => props.modelValue,
async (newVal) => {
@@ -99,6 +112,7 @@
if (typeof props.data.title === 'string') formData.title = props.data.title
formData.mult = Number(props.data.mult ?? 1) || 1
formData.is_default = Number(props.data.is_default ?? 0) === 1 ? 1 : 0
syncNameTitleFromMult()
}
)

View File

@@ -1,4 +1,16 @@
import request from '@/utils/http'
import {
normalizeLotteryPoolOption,
type LotteryPoolOption
} from '@/views/plugin/dice/utils/lotteryPoolDisplay'
export type LotteryPoolConfigOption = LotteryPoolOption & {
t1_weight: number
t2_weight: number
t3_weight: number
t4_weight: number
t5_weight: number
}
/**
* 色子奖池配置 API 接口
@@ -20,32 +32,24 @@ export default {
* 获取 DiceLotteryPoolConfig 列表数据,含 id、name、t1_weightt5_weight用于一键测试权重档位类型下拉
* name 映射default=原 type=0killScore=原 type=1up=原 type=2
*/
async getOptions(params?: Record<string, unknown>): Promise<
Array<{
id: number
name: string
t1_weight: number
t2_weight: number
t3_weight: number
t4_weight: number
t5_weight: number
}>
> {
async getOptions(params?: Record<string, unknown>): Promise<LotteryPoolConfigOption[]> {
const res = await request.get<any>({
url: '/core/dice/lottery_pool_config/DiceLotteryPoolConfig/getOptions',
params
})
const rows = Array.isArray(res) ? res : (Array.isArray((res as any)?.data) ? (res as any).data : [])
if (!Array.isArray(rows)) return []
return rows.map((r: any) => ({
id: Number(r.id),
name: String(r.name ?? r.id ?? ''),
t1_weight: Number(r.t1_weight ?? 0),
t2_weight: Number(r.t2_weight ?? 0),
t3_weight: Number(r.t3_weight ?? 0),
t4_weight: Number(r.t4_weight ?? 0),
t5_weight: Number(r.t5_weight ?? 0)
}))
return rows.map((r: Record<string, unknown>) => {
const base = normalizeLotteryPoolOption(r)
return {
...base,
t1_weight: Number(r.t1_weight ?? 0),
t2_weight: Number(r.t2_weight ?? 0),
t3_weight: Number(r.t3_weight ?? 0),
t4_weight: Number(r.t4_weight ?? 0),
t5_weight: Number(r.t5_weight ?? 0)
}
})
},
/**

View File

@@ -1,4 +1,5 @@
import request from '@/utils/http'
import { normalizeLotteryPoolOption, type LotteryPoolOption } from '@/views/plugin/dice/utils/lotteryPoolDisplay'
/**
* 玩家抽奖记录 API接口
@@ -59,11 +60,13 @@ export default {
})
},
/** 获取彩金池配置选项(id、name */
getLotteryConfigOptions(params?: Record<string, unknown>) {
return request.get<{ id: number; name: string }[]>({
/** 获取彩金池配置选项(含奖池名称 */
async getLotteryConfigOptions(params?: Record<string, unknown>): Promise<LotteryPoolOption[]> {
const res = await request.get<any>({
url: '/core/dice/play_record/DicePlayRecord/getLotteryConfigOptions',
params
})
const rows = (Array.isArray(res) ? res : (res?.data ?? [])) as Array<Record<string, unknown>>
return rows.map((r) => normalizeLotteryPoolOption(r))
}
}

View File

@@ -1,4 +1,5 @@
import request from '@/utils/http'
import { normalizeLotteryPoolOption } from '@/views/plugin/dice/utils/lotteryPoolDisplay'
/**
* 大富翁-玩家 API接口
@@ -84,16 +85,15 @@ export default {
},
/**
* 获取彩金池配置选项DiceLotteryPoolConfig.id、name,供 lottery_config_id 下拉使用
* @returns [ { id, name } ]
* 获取彩金池配置选项,供 lottery_config_id 下拉使用(含奖池名称 display_name
*/
async getLotteryConfigOptions(params?: Record<string, unknown>): Promise<Array<{ id: number; name: string }>> {
async getLotteryConfigOptions(params?: Record<string, unknown>) {
const res = await request.get<any>({
url: '/core/dice/player/DicePlayer/getLotteryConfigOptions',
params
})
const rows = (Array.isArray(res) ? res : (res?.data ?? [])) as Array<{ id: number; name: string }>
return rows.map((r) => ({ id: Number(r.id), name: String(r.name ?? r.id ?? '') }))
const rows = (Array.isArray(res) ? res : (res?.data ?? [])) as Array<Record<string, unknown>>
return rows.map((r) => normalizeLotteryPoolOption(r))
},
/**

View File

@@ -87,14 +87,10 @@
getData()
}
// 奖池类型展示:按 name 映射
const typeFormatter = (row: Record<string, unknown>) => {
const n = String(row.name ?? '')
if (n === 'default') return t('page.search.poolTypeNormal')
if (n === 'free') return t('page.search.poolTypeFree')
if (n === 'killScore') return t('page.search.poolTypeKill')
if (n === 'up') return t('page.search.poolTypeT1')
return n || '-'
const poolNameFormatter = (row: Record<string, unknown>) => {
const remark = String(row.remark ?? '').trim()
if (remark) return remark
return String(row.name ?? '').trim() || '-'
}
// 权重列带 %
@@ -132,8 +128,19 @@
core: {
apiFn: api.list,
columnsFactory: () => [
{ prop: 'name', label: 'page.table.name', align: 'center' },
{ prop: 'name', label: 'page.table.poolType', width: 100, align: 'center', formatter: typeFormatter },
{ prop: 'remark', label: 'page.table.poolName', minWidth: 120, align: 'center', formatter: poolNameFormatter },
{
prop: 'config_note',
label: 'page.table.configNote',
minWidth: 140,
align: 'center',
showOverflowTooltip: true,
formatter: (row: Record<string, unknown>) => {
const v = String(row.config_note ?? '').trim()
return v || '-'
}
},
{ prop: 'name', label: 'page.table.name', width: 110, align: 'center' },
{
prop: 'safety_line',
label: 'page.table.safetyLine',

View File

@@ -15,12 +15,20 @@
:disabled="dialogType === 'edit'"
/>
</el-form-item>
<el-form-item :label="$t('form.labelRemark')" prop="remark">
<el-form-item :label="$t('page.form.poolName')" prop="remark">
<el-input
v-model="formData.remark"
:placeholder="$t('page.form.placeholderPoolName')"
maxlength="200"
show-word-limit
/>
</el-form-item>
<el-form-item :label="$t('form.labelRemark')" prop="config_note">
<el-input
v-model="formData.config_note"
type="textarea"
:rows="3"
:placeholder="$t('page.form.placeholderRemark')"
:placeholder="$t('page.form.placeholderConfigNote')"
maxlength="500"
show-word-limit
/>
@@ -159,6 +167,7 @@
dept_id: undefined as number | undefined,
name: '',
remark: '',
config_note: '',
safety_line: 0 as number,
t1_weight: 0 as number,
t2_weight: 0 as number,

View File

@@ -143,6 +143,7 @@
import api from '../../api/play_record/index'
import TableSearch from './modules/table-search.vue'
import EditDialog from './modules/edit-dialog.vue'
import { lotteryPoolRowLabel } from '@/views/plugin/dice/utils/lotteryPoolDisplay'
const { t } = useI18n()
// 搜索表单
@@ -177,8 +178,7 @@
const usernameFormatter = (row: Record<string, any>) =>
row?.dicePlayer?.username ?? row?.player_id ?? '-'
const lotteryConfigNameFormatter = (row: Record<string, any>) =>
row?.diceLotteryPoolConfig?.name ?? row?.lottery_config_id ?? '-'
const lotteryConfigNameFormatter = (row: Record<string, any>) => lotteryPoolRowLabel(row)
const rewardTierFormatter = (row: Record<string, any>) => row?.reward_tier ?? '-'
/** 摇取点数格式化为 1,3,4,5,6,6 */

View File

@@ -37,7 +37,7 @@
<el-option
v-for="item in lotteryConfigOptions"
:key="item.id"
:label="item.name"
:label="lotteryPoolOptionLabel(item)"
:value="item.id"
/>
</el-select>
@@ -180,6 +180,10 @@
<script setup lang="ts">
import api from '../../../api/play_record/index'
import { getChannelDeptRequestParams } from '@/composables/useChannelDeptScope'
import {
lotteryPoolOptionLabel,
type LotteryPoolOption
} from '@/views/plugin/dice/utils/lotteryPoolDisplay'
import type { FormInstance } from 'element-plus'
interface Props {
@@ -209,7 +213,7 @@
})
const playerOptions = ref<Array<{ id: number; username: string }>>([])
const lotteryConfigOptions = ref<Array<{ id: number; name: string }>>([])
const lotteryConfigOptions = ref<LotteryPoolOption[]>([])
const initialFormData = {
id: null as number | null,

View File

@@ -155,6 +155,7 @@
import TableSearch from './modules/table-search.vue'
import EditDialog from './modules/edit-dialog.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { lotteryPoolRowLabel } from '@/views/plugin/dice/utils/lotteryPoolDisplay'
const { t } = useI18n()
// 搜索表单(与 play_record 对齐:方向、赢取平台币范围、是否中大奖、中奖档位、点数和)
@@ -181,8 +182,7 @@
return res
}
const lotteryConfigNameFormatter = (row: Record<string, any>) =>
row?.diceLotteryPoolConfig?.name ?? row?.lottery_config_id ?? '-'
const lotteryConfigNameFormatter = (row: Record<string, any>) => lotteryPoolRowLabel(row)
const rewardTierFormatter = (row: Record<string, any>) => row?.reward_tier ?? '-'
/** 摇取点数格式化为 1,3,4,5,6 */

View File

@@ -148,6 +148,11 @@
getChannelDeptRequestParams,
useInjectedChannelDept
} from '@/composables/useChannelDeptScope'
import {
filterLotteryPoolOptionsByQuery,
lotteryPoolOptionLabel,
type LotteryPoolOption
} from '@/views/plugin/dice/utils/lotteryPoolDisplay'
interface Props {
modelValue: Record<string, any>
@@ -162,8 +167,8 @@
const isExpanded = ref<boolean>(false)
const channelScope = useInjectedChannelDept()
const lotteryPoolAllOptions = ref<Array<{ id: number; name: string }>>([])
const lotteryPoolOptions = ref<Array<{ id: number; name: string }>>([])
const lotteryPoolAllOptions = ref<LotteryPoolOption[]>([])
const lotteryPoolOptions = ref<LotteryPoolOption[]>([])
const lotteryPoolLoading = ref(false)
function resolveDeptParams(): Record<string, unknown> {
@@ -177,11 +182,6 @@
return {}
}
function lotteryPoolOptionLabel(item: { id: number; name: string }): string {
const name = (item.name || '').trim()
return name ? `${name} (#${item.id})` : `#${item.id}`
}
async function loadLotteryPoolOptions() {
lotteryPoolLoading.value = true
try {
@@ -197,15 +197,7 @@
}
function filterLotteryPoolOptions(query: string) {
const q = (query || '').trim().toLowerCase()
if (!q) {
lotteryPoolOptions.value = [...lotteryPoolAllOptions.value]
return
}
lotteryPoolOptions.value = lotteryPoolAllOptions.value.filter((item) => {
const name = (item.name || '').toLowerCase()
return name.includes(q) || String(item.id).includes(q)
})
lotteryPoolOptions.value = filterLotteryPoolOptionsByQuery(lotteryPoolAllOptions.value, query)
}
function onLotteryPoolDropdownVisible(visible: boolean) {

View File

@@ -115,6 +115,7 @@
import TableSearch from './modules/table-search.vue'
import EditDialog from './modules/edit-dialog.vue'
import WalletOperateDialog from './modules/WalletOperateDialog.vue'
import { lotteryPoolRowLabel } from '@/views/plugin/dice/utils/lotteryPoolDisplay'
const { t } = useI18n()
const { copy } = useClipboard()
@@ -141,10 +142,13 @@
return cellValue != null && cellValue !== '' ? `${cellValue}%` : '-'
}
// 根据 lottery_config_id 显示彩金池配置名称
const lotteryConfigNameFormatter = (row: any) =>
row?.diceLotteryPoolConfig?.name ??
(row?.lottery_config_id ? `#${row.lottery_config_id}` : t('page.table.customConfig'))
const lotteryConfigNameFormatter = (row: any) => {
const label = lotteryPoolRowLabel(row)
if (label === '-' && !row?.lottery_config_id) {
return t('page.table.customConfig')
}
return label
}
// 表格
const {

View File

@@ -88,7 +88,7 @@
<el-option
v-for="item in lotteryConfigOptions"
:key="item.id"
:label="(item.name && String(item.name).trim()) || `#${item.id}`"
:label="lotteryPoolOptionLabel(item)"
:value="item.id"
/>
</el-select>
@@ -97,12 +97,12 @@
<el-form-item v-if="currentLotteryConfig" :label="$t('page.form.currentConfig')" class="current-config-block">
<div class="current-lottery-config">
<div class="config-row">
<span class="config-label">{{ $t('page.form.configLabelName') }}</span>
<span>{{ currentLotteryConfig.name ?? '-' }}</span>
<span class="config-label">{{ $t('page.form.configLabelPoolName') }}</span>
<span>{{ lotteryPoolDisplayLabel(currentLotteryConfig) }}</span>
</div>
<div class="config-row">
<span class="config-label">{{ $t('page.form.configLabelType') }}</span>
<span>{{ lotteryConfigTypeText(currentLotteryConfig.name) }}</span>
<span class="config-label">{{ $t('page.form.configLabelCode') }}</span>
<span>{{ currentLotteryConfig.name ?? '-' }}</span>
</div>
<div class="config-row">
<span class="config-label">{{ $t('page.form.configLabelWeights') }}</span>
@@ -185,6 +185,7 @@
import api from '../../../api/player/index'
import lotteryConfigApi from '../../../api/lottery_pool_config/index'
import { useI18n } from 'vue-i18n'
import { lotteryPoolDisplayLabel, lotteryPoolOptionLabel } from '@/views/plugin/dice/utils/lotteryPoolDisplay'
import { getChannelDeptRequestParams, withChannelDeptParams } from '@/composables/useChannelDeptScope'
import { isSuperAdminUser } from '@/utils/channelLayout'
import { ElMessage } from 'element-plus'

View File

@@ -54,7 +54,7 @@
<el-option
v-for="item in lotteryConfigOptions"
:key="item.id"
:label="item.name"
:label="lotteryPoolOptionLabel(item)"
:value="item.id"
/>
</el-select>
@@ -65,6 +65,7 @@
<script setup lang="ts">
import api from '../../../api/player/index'
import { lotteryPoolOptionLabel } from '@/views/plugin/dice/utils/lotteryPoolDisplay'
interface Props {
modelValue: Record<string, any>

View File

@@ -29,6 +29,10 @@
style="width: 100%"
@change="syncAnteFromSelect"
>
<ElOption
:label="$t('page.weightTest.anteRandomOption')"
:value="RANDOM_ANTE_CONFIG_ID"
/>
<ElOption
v-for="item in anteOptions"
:key="item.id"
@@ -85,12 +89,18 @@
style="width: 100%"
>
<ElOption
v-for="item in paidLotteryOptions"
:key="item.id"
:label="item.name"
v-for="item in lotteryOptions"
:key="'paid-pool-' + item.id"
:label="lotteryPoolOptionLabel(item)"
:value="item.id"
/>
</ElSelect>
<div v-if="selectedPaidPool" class="pool-selected-hint">
{{ $t('page.weightTest.selectedPoolHint', { name: lotteryPoolDisplayLabel(selectedPaidPool) }) }}
</div>
<div v-if="selectedPaidPool" class="pool-weights-preview">
{{ poolTierWeightsText(selectedPaidPool) }}
</div>
</ElFormItem>
<template v-if="form.paid_lottery_config_id == null">
<div class="tier-label">{{ $t('page.weightTest.tierProbHint') }}</div>
@@ -155,12 +165,18 @@
style="width: 100%"
>
<ElOption
v-for="item in freeLotteryOptions"
:key="item.id"
:label="item.name"
v-for="item in lotteryOptions"
:key="'free-pool-' + item.id"
:label="lotteryPoolOptionLabel(item)"
:value="item.id"
/>
</ElSelect>
<div v-if="selectedFreePool" class="pool-selected-hint">
{{ $t('page.weightTest.selectedPoolHint', { name: lotteryPoolDisplayLabel(selectedFreePool) }) }}
</div>
<div v-if="selectedFreePool" class="pool-weights-preview">
{{ poolTierWeightsText(selectedFreePool) }}
</div>
</ElFormItem>
<template v-if="form.free_lottery_config_id == null">
<div class="tier-label">{{ $t('page.weightTest.tierProbHintFreeChain') }}</div>
@@ -218,6 +234,13 @@
useInjectedChannelDept,
withChannelDeptParams
} from '@/composables/useChannelDeptScope'
import {
lotteryPoolDisplayLabel,
lotteryPoolOptionLabel
} from '@/views/plugin/dice/utils/lotteryPoolDisplay'
/** 底注下拉「随机」选项值(非真实 ante_config.id */
const RANDOM_ANTE_CONFIG_ID = -1
const props = defineProps<{
/** 父页面渠道栏选中值(弹窗 teleport 后 inject 可能失效) */
@@ -247,16 +270,26 @@
paid_s_count: 100,
paid_n_count: 100,
kill_mode_enabled: false,
test_safety_line: 5000
test_safety_line: 0
})
const lotteryOptions = ref<Array<{ id: number; name: string }>>([])
const paidLotteryOptions = computed(() =>
lotteryOptions.value.filter((r) => r.name === 'default')
type LotteryPoolOption = {
id: number
name: string
remark?: string
display_name?: string
t1_weight?: number
t2_weight?: number
t3_weight?: number
t4_weight?: number
t5_weight?: number
}
const lotteryOptions = ref<LotteryPoolOption[]>([])
const selectedPaidPool = computed(() =>
lotteryOptions.value.find((r) => r.id === form.paid_lottery_config_id) ?? null
)
const selectedFreePool = computed(() =>
lotteryOptions.value.find((r) => r.id === form.free_lottery_config_id) ?? null
)
const freeLotteryOptions = computed(() => {
const list = lotteryOptions.value.filter((r) => r.name === 'free')
return list.length > 0 ? list : lotteryOptions.value.filter((r) => r.name === 'default')
})
const defaultPoolInfo = ref<{ safety_line: number; kill_enabled: number; profit_amount: number } | null>(null)
const running = ref(false)
@@ -322,6 +355,15 @@
return label ? `${label} (×${item.mult})` : `×${item.mult}`
}
function poolTierWeightsText(pool: LotteryPoolOption): string {
const parts = tierKeys.map((t) => {
const key = `${t.toLowerCase()}_weight` as keyof LotteryPoolOption
const v = pool[key]
return `${t} ${v ?? 0}%`
})
return parts.join(' · ')
}
function syncAnteFromSelect() {
const opt = anteOptions.value.find((o) => o.id === form.ante_config_id)
if (opt) {
@@ -350,11 +392,13 @@
async function loadDefaultPoolInfo() {
try {
const pool = await lotteryPoolApi.getCurrentPool(resolveDeptParams())
const safetyLine = Number(pool?.safety_line ?? 0)
defaultPoolInfo.value = {
safety_line: Number(pool?.safety_line ?? 0),
safety_line: safetyLine,
kill_enabled: Number(pool?.kill_enabled ?? 1),
profit_amount: Number(pool?.profit_amount ?? 0)
}
form.test_safety_line = safetyLine
} catch {
defaultPoolInfo.value = null
}
@@ -363,12 +407,34 @@
async function loadLotteryOptions() {
try {
const list = await lotteryPoolApi.getOptions(resolveDeptParams())
lotteryOptions.value = list.map((r: { id: number; name: string }) => ({
id: r.id,
name: r.name
}))
lotteryOptions.value = list.map(
(r: {
id: number
name: string
remark?: string
display_name?: string
t1_weight?: number
t2_weight?: number
t3_weight?: number
t4_weight?: number
t5_weight?: number
}) => ({
id: r.id,
name: r.name,
remark: r.remark,
display_name: r.display_name,
t1_weight: r.t1_weight,
t2_weight: r.t2_weight,
t3_weight: r.t3_weight,
t4_weight: r.t4_weight,
t5_weight: r.t5_weight
})
)
const playerDefault = list.find((r: { name?: string }) => r.name === 'playerDefault')
const normal = list.find((r: { name?: string }) => r.name === 'default')
if (normal) {
if (playerDefault) {
form.paid_lottery_config_id = playerDefault.id
} else if (normal) {
form.paid_lottery_config_id = normal.id
}
const freePool = list.find((r: { name?: string }) => r.name === 'free')
@@ -385,7 +451,6 @@
function buildPayload() {
const payload: Record<string, unknown> = {
ante: form.ante,
ante_config_id: form.ante_config_id,
paid_s_count: form.paid_s_count,
paid_n_count: form.paid_n_count,
free_s_count: 0,
@@ -394,6 +459,11 @@
kill_mode_enabled: form.kill_mode_enabled,
test_safety_line: form.test_safety_line
}
if (form.ante_config_id === RANDOM_ANTE_CONFIG_ID) {
payload.ante_random = true
} else {
payload.ante_config_id = form.ante_config_id
}
if (form.paid_lottery_config_id != null) {
payload.paid_lottery_config_id = form.paid_lottery_config_id
} else {
@@ -408,12 +478,15 @@
}
function validateForm(): boolean {
if (form.ante_config_id == null || form.ante_config_id <= 0) {
const isRandomAnte = form.ante_config_id === RANDOM_ANTE_CONFIG_ID
if (!isRandomAnte && (form.ante_config_id == null || form.ante_config_id <= 0)) {
ElMessage.warning(t('page.weightTest.warnAnte'))
return false
}
syncAnteFromSelect()
if (form.ante == null || form.ante <= 0) {
if (!isRandomAnte) {
syncAnteFromSelect()
}
if (!isRandomAnte && (form.ante == null || form.ante <= 0)) {
ElMessage.warning(t('page.weightTest.warnAnte'))
return false
}
@@ -563,6 +636,20 @@
border-top: 1px dashed var(--el-border-color);
}
.pool-selected-hint {
margin-top: 6px;
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: 1.4;
}
.pool-weights-preview {
margin-top: 4px;
font-size: 12px;
color: var(--el-text-color-regular);
font-family: ui-monospace, monospace;
}
.kill-mode-field {
max-width: 280px;
}

View File

@@ -143,6 +143,33 @@
return Number(row.paid_n_count ?? 0)
}
function formatTestSafetyLine(row: Record<string, unknown>): string {
const dash = t('page.detail.dash')
if (Number(row.kill_mode_enabled ?? 0) !== 1) {
return dash
}
const line = row.test_safety_line
if (line === null || line === undefined || line === '') {
return dash
}
const n = Number(line)
return Number.isFinite(n) ? String(n) : dash
}
function formatAnteCell(row: Record<string, unknown>): string {
const ante = row.ante
if (ante === null || ante === undefined || ante === '') {
return t('page.detail.dash')
}
const snap = row.tier_weights_snapshot
const isRandom =
snap &&
typeof snap === 'object' &&
(snap as { ante_random?: boolean }).ante_random === true
const base = String(ante)
return isRandom ? `${base} (${t('page.table.anteRandom')})` : base
}
// 平台赚取金额展示(未完成或空显示 —)
function formatPlatformProfit(v: unknown): string {
const dash = t('page.detail.dash')
@@ -228,8 +255,16 @@
{
prop: 'ante',
label: 'page.table.ante',
width: 90,
align: 'center'
width: 100,
align: 'center',
formatter: (row: Record<string, unknown>) => formatAnteCell(row)
},
{
prop: 'test_safety_line',
label: 'page.table.testSafetyLine',
width: 100,
align: 'center',
formatter: (row: Record<string, unknown>) => formatTestSafetyLine(row)
},
{
prop: 'play_again_count',

View File

@@ -20,6 +20,12 @@
<el-descriptions-item :label="$t('page.detail.paidPlannedSpins')">
{{ record.paid_planned_spins ?? $t('page.detail.dash') }}
</el-descriptions-item>
<el-descriptions-item :label="$t('page.detail.testSafetyLine')">
{{ formatTestSafetyLineDetail(record) }}
</el-descriptions-item>
<el-descriptions-item :label="$t('page.table.ante')">
{{ formatAnteDetail(record) }}
</el-descriptions-item>
<el-descriptions-item :label="$t('page.detail.testCount')">
{{ formatTestCountDisplay(record) }}
</el-descriptions-item>
@@ -30,10 +36,10 @@
{{ record.admin_name ?? record.admin_id ?? $t('page.detail.dash') }}
</el-descriptions-item>
<el-descriptions-item :label="$t('page.detail.paidPoolId')">
{{ record.paid_lottery_config_id ?? record.lottery_config_id ?? $t('page.detail.dash') }}
{{ formatRecordPaidPoolName(record) }}
</el-descriptions-item>
<el-descriptions-item :label="$t('page.detail.freePoolId')">
{{ record.free_lottery_config_id ?? $t('page.detail.dash') }}
{{ formatRecordFreePoolName(record) }}
</el-descriptions-item>
<el-descriptions-item :label="$t('page.detail.bigwinSnapshot')">
<template v-if="bigwinWeightDisplay.length">
@@ -182,7 +188,7 @@
<el-option
v-for="opt in paidLotteryOptions"
:key="opt.id"
:label="opt.name"
:label="lotteryPoolOptionLabel(opt)"
:value="opt.id"
/>
</el-select>
@@ -199,7 +205,7 @@
<el-option
v-for="opt in freeLotteryOptions"
:key="opt.id"
:label="opt.name"
:label="lotteryPoolOptionLabel(opt)"
:value="opt.id"
/>
</el-select>
@@ -224,6 +230,11 @@
import ArtBarChart from '@/components/core/charts/art-bar-chart/index.vue'
import recordApi from '../../../api/reward_config_record/index'
import lotteryConfigApi from '../../../api/lottery_pool_config/index'
import {
lotteryPoolLabelById,
lotteryPoolOptionLabel,
type LotteryPoolOption
} from '@/views/plugin/dice/utils/lotteryPoolDisplay'
import { ElMessage } from 'element-plus'
import { useI18n } from 'vue-i18n'
@@ -242,6 +253,9 @@
over_play_count?: number
chain_free_mode?: number | boolean | string
paid_planned_spins?: number
ante?: number
kill_mode_enabled?: number
test_safety_line?: number
create_time?: string
admin_id?: number | null
admin_name?: string
@@ -278,6 +292,33 @@
return t('page.table.chainModeNo')
}
function formatTestSafetyLineDetail(record: RecordRow | null): string {
if (!record) return t('page.detail.dash')
if (Number(record.kill_mode_enabled ?? 0) !== 1) {
return t('page.detail.killModeOff')
}
const line = record.test_safety_line
if (line === null || line === undefined || line === '') {
return t('page.detail.dash')
}
return String(line)
}
function formatAnteDetail(record: RecordRow | null): string {
if (!record) return t('page.detail.dash')
const ante = record.ante
if (ante === null || ante === undefined || ante === '') {
return t('page.detail.dash')
}
const snap = record.tier_weights_snapshot
const isRandom =
snap &&
typeof snap === 'object' &&
(snap as { ante_random?: boolean }).ante_random === true
const base = String(ante)
return isRandom ? `${base} (${t('page.table.anteRandom')})` : base
}
function formatTestCountDisplay(record: RecordRow | null): string {
if (!record) return t('page.detail.dash')
const status = Number(record.status)
@@ -316,7 +357,18 @@
const importing = ref(false)
const importPaidLotteryConfigId = ref<number | null>(null)
const importFreeLotteryConfigId = ref<number | null>(null)
const lotteryConfigOptions = ref<Array<{ id: number; name: string }>>([])
const lotteryConfigOptions = ref<LotteryPoolOption[]>([])
function formatRecordPaidPoolName(record: RecordRow | null): string {
if (!record) return t('page.detail.dash')
const id = record.paid_lottery_config_id ?? record.lottery_config_id ?? null
return lotteryPoolLabelById(id, lotteryConfigOptions.value)
}
function formatRecordFreePoolName(record: RecordRow | null): string {
if (!record) return t('page.detail.dash')
return lotteryPoolLabelById(record.free_lottery_config_id ?? null, lotteryConfigOptions.value)
}
function tierWeightsToTableData(weightsMap: Record<string, number> | null | undefined) {
const dash = t('page.detail.dash')
@@ -496,6 +548,15 @@
}
}
watch(
() => props.modelValue,
(open) => {
if (open) {
void loadLotteryOptions()
}
}
)
function openImport() {
importPaidLotteryConfigId.value =
props.record?.paid_lottery_config_id ?? props.record?.lottery_config_id ?? null

View File

@@ -0,0 +1,99 @@
/** 彩金池选项/关联行通用结构 */
export interface LotteryPoolOption {
id: number
name?: string
remark?: string
display_name?: string
}
/** 彩金池后台展示名(奖池名称):优先 display_name / remark不用内部 name 作为首选 */
export function lotteryPoolDisplayLabel(item?: LotteryPoolOption | null): string {
if (!item) {
return '-'
}
const display = String(item.display_name ?? '').trim()
if (display) {
return display
}
const remark = String(item.remark ?? '').trim()
if (remark) {
return remark
}
return String(item.name ?? '').trim() || '-'
}
/** 下拉选项文案默认仅奖池名称withId=true 时附带 ID */
export function lotteryPoolOptionLabel(
item: LotteryPoolOption,
options?: { withId?: boolean }
): string {
const label = lotteryPoolDisplayLabel(item)
if (options?.withId && item.id > 0) {
return label !== '-' ? `${label} (#${item.id})` : `#${item.id}`
}
return label !== '-' ? label : `#${item.id}`
}
/** 列表行关联彩金池(含 diceLotteryPoolConfig 关联) */
export function lotteryPoolRowLabel(row?: {
diceLotteryPoolConfig?: LotteryPoolOption | null
lottery_config_id?: number | null | string
} | null): string {
if (!row) {
return '-'
}
const pool = row.diceLotteryPoolConfig
if (pool && (pool.id || pool.remark || pool.name || pool.display_name)) {
return lotteryPoolDisplayLabel(pool)
}
const id = row.lottery_config_id
if (id !== null && id !== undefined && id !== '') {
return `#${id}`
}
return '-'
}
/** 规范化接口返回的彩金池选项 */
export function normalizeLotteryPoolOption(raw: Record<string, unknown>): LotteryPoolOption {
const id = Number(raw.id ?? 0)
const name = String(raw.name ?? '')
const remark = String(raw.remark ?? '')
const displayName = String(raw.display_name ?? '').trim()
return {
id,
name,
remark,
display_name: displayName !== '' ? displayName : remark !== '' ? remark : name
}
}
/** 按奖池名称 / 内部标识 / ID 过滤下拉 */
export function filterLotteryPoolOptionsByQuery(
list: LotteryPoolOption[],
query: string
): LotteryPoolOption[] {
const q = (query || '').trim().toLowerCase()
if (!q) {
return [...list]
}
return list.filter((item) => {
const label = lotteryPoolDisplayLabel(item).toLowerCase()
const code = String(item.name ?? '').toLowerCase()
return label.includes(q) || code.includes(q) || String(item.id).includes(q)
})
}
/** 根据 ID 从选项列表解析奖池名称 */
export function lotteryPoolLabelById(
poolId: number | null | undefined,
options: LotteryPoolOption[]
): string {
if (poolId == null || poolId <= 0) {
return '-'
}
const found = options.find((o) => o.id === poolId)
if (found) {
return lotteryPoolDisplayLabel(found)
}
return `#${poolId}`
}