[色子游戏]玩家抽奖记录-新增字段保存中奖详情记录信息

This commit is contained in:
2026-03-04 10:21:05 +08:00
parent 5b39efc7a3
commit 894a562eb4
7 changed files with 269 additions and 10 deletions

View File

@@ -62,6 +62,18 @@
{{ row.is_win === 0 ? '无' : row.is_win === 1 ? '中奖' : '-' }} {{ row.is_win === 0 ? '无' : row.is_win === 1 ? '中奖' : '-' }}
</ElTag> </ElTag>
</template> </template>
<!-- 方向 tag -->
<template #direction="{ row }">
<ElTag size="small" :type="row.direction === 0 ? 'primary' : 'warning'">
{{ row.direction === 0 ? '顺时针' : row.direction === 1 ? '逆时针' : '-' }}
</ElTag>
</template>
<!-- 摇取点数 tag -->
<template #roll_array="{ row }">
<ElTag size="small">
{{ formatRollArray(row.roll_array) }}
</ElTag>
</template>
<!-- 操作列 --> <!-- 操作列 -->
<template #operation="{ row }"> <template #operation="{ row }">
<div class="flex gap-2"> <div class="flex gap-2">
@@ -106,7 +118,8 @@
win_coin_min: undefined, win_coin_min: undefined,
win_coin_max: undefined, win_coin_max: undefined,
reward_ui_text: undefined, reward_ui_text: undefined,
reward_tier: undefined reward_tier: undefined,
direction: undefined
}) })
// 搜索处理 // 搜索处理
@@ -122,6 +135,21 @@
const rewardTierFormatter = (row: Record<string, any>) => const rewardTierFormatter = (row: Record<string, any>) =>
row?.diceRewardConfig?.tier ?? row?.reward_config_id ?? '-' row?.diceRewardConfig?.tier ?? row?.reward_config_id ?? '-'
/** 摇取点数格式化为 1,3,4,5,6,6 */
function formatRollArray(val: unknown): string {
if (val == null || val === '') return '-'
if (Array.isArray(val)) return val.join(',')
if (typeof val === 'string') {
try {
const arr = JSON.parse(val)
return Array.isArray(arr) ? arr.join(',') : val
} catch {
return val
}
}
return String(val)
}
// 表格配置 // 表格配置
const { const {
columns, columns,
@@ -156,6 +184,10 @@
{ prop: 'lottery_type', label: '抽奖类型', width: 100, useSlot: true }, { prop: 'lottery_type', label: '抽奖类型', width: 100, useSlot: true },
{ prop: 'is_win', label: '中奖', width: 80, useSlot: true }, { prop: 'is_win', label: '中奖', width: 80, useSlot: true },
{ prop: 'win_coin', label: '赢取平台币' }, { prop: 'win_coin', label: '赢取平台币' },
{ prop: 'direction', label: '方向', width: 90, useSlot: true },
{ prop: 'start_index', label: '起始索引', width: 90 },
{ prop: 'target_index', label: '终点索引', width: 90 },
{ prop: 'roll_array', label: '摇取点数', width: 140, useSlot: true },
{ {
prop: 'reward_config_id', prop: 'reward_config_id',
label: '奖励配置', label: '奖励配置',

View File

@@ -75,6 +75,53 @@
:disabled="dialogType === 'edit'" :disabled="dialogType === 'edit'"
/> />
</el-form-item> </el-form-item>
<el-form-item label="方向" prop="direction">
<el-select
v-model="formData.direction"
placeholder="请选择方向"
clearable
style="width: 100%"
:disabled="dialogType === 'edit'"
>
<el-option label="顺时针" :value="0" />
<el-option label="逆时针" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="起始索引" prop="start_index">
<el-input-number
v-model="formData.start_index"
placeholder="起始索引"
:min="0"
style="width: 100%"
:disabled="dialogType === 'edit'"
/>
</el-form-item>
<el-form-item label="终点索引" prop="target_index">
<el-input-number
v-model="formData.target_index"
placeholder="终点索引"
:min="0"
style="width: 100%"
:disabled="dialogType === 'edit'"
/>
</el-form-item>
<el-form-item label="摇取点数" prop="rollArrayItems">
<div class="roll-array-wrap">
<el-input-number
v-for="(_, i) in 6"
:key="i"
v-model="formData.rollArrayItems[i]"
:min="1"
:max="6"
:precision="0"
controls-position="right"
placeholder=""
class="roll-array-input"
:disabled="dialogType === 'edit'"
/>
</div>
<div class="roll-array-hint">固定 6 个数每个 16</div>
</el-form-item>
<el-form-item label="奖励配置" prop="reward_config_id"> <el-form-item label="奖励配置" prop="reward_config_id">
<el-select <el-select
v-model="formData.reward_config_id" v-model="formData.reward_config_id"
@@ -141,6 +188,23 @@
lottery_type: [{ required: true, message: '请选择抽奖类型', trigger: 'change' }], lottery_type: [{ required: true, message: '请选择抽奖类型', trigger: 'change' }],
is_win: [{ required: true, message: '请选择中奖', trigger: 'change' }], is_win: [{ required: true, message: '请选择中奖', trigger: 'change' }],
win_coin: [{ required: true, message: '赢取平台币必填', trigger: 'blur' }], win_coin: [{ required: true, message: '赢取平台币必填', trigger: 'blur' }],
rollArrayItems: [
{
validator: (_rule: any, value: (number | null)[], callback: (e?: Error) => void) => {
if (!value || value.length !== 6) {
callback(new Error('摇取点数必须为 6 个数'))
return
}
const ok = value.every((n) => n != null && n >= 1 && n <= 6)
if (!ok) {
callback(new Error('摇取点数必须填写 6 个数,每个 16'))
return
}
callback()
},
trigger: 'change'
}
],
reward_config_id: [{ required: true, message: '请选择奖励配置', trigger: 'change' }] reward_config_id: [{ required: true, message: '请选择奖励配置', trigger: 'change' }]
}) })
@@ -155,10 +219,20 @@
lottery_type: null as number | null, lottery_type: null as number | null,
is_win: null as number | null, is_win: null as number | null,
win_coin: null as number | null, win_coin: null as number | null,
direction: null as number | null,
start_index: null as number | null,
target_index: null as number | null,
roll_array: null as string | number[] | null,
reward_config_id: null as number | null reward_config_id: null as number | null
} }
const formData = reactive({ ...initialFormData }) /** 摇取点数固定 6 位 [n0..n5],每项 16 */
const rollArrayItemsDefault = (): (number | null)[] => [null, null, null, null, null, null]
const formData = reactive({
...initialFormData,
rollArrayItems: rollArrayItemsDefault() as (number | null)[]
})
watch( watch(
() => props.modelValue, () => props.modelValue,
@@ -188,7 +262,7 @@
) )
const initPage = async () => { const initPage = async () => {
Object.assign(formData, { ...initialFormData }) Object.assign(formData, { ...initialFormData, rollArrayItems: rollArrayItemsDefault() })
if (props.data) { if (props.data) {
await nextTick() await nextTick()
initForm() initForm()
@@ -204,16 +278,47 @@
'lottery_type', 'lottery_type',
'is_win', 'is_win',
'win_coin', 'win_coin',
'direction',
'start_index',
'target_index',
'roll_array',
'reward_config_id' 'reward_config_id'
] ]
keys.forEach((key) => { keys.forEach((key) => {
const val = props.data![key] const val = props.data![key]
if (val != null && val !== undefined) { if (val != null && val !== undefined) {
if (key === 'roll_array') {
formData.roll_array = val
formData.rollArrayItems = parseRollArrayToItems(val)
} else {
;(formData as Record<string, unknown>)[key] = val ;(formData as Record<string, unknown>)[key] = val
} }
}
}) })
} }
/** 将接口的 roll_array 转为固定 6 项数组,不足补 null */
function parseRollArrayToItems(val: unknown): (number | null)[] {
let arr: number[] = []
if (Array.isArray(val)) {
arr = val.map((n) => (typeof n === 'number' && !Number.isNaN(n) ? n : 0)).slice(0, 6)
} else if (typeof val === 'string') {
try {
const parsed = JSON.parse(val)
arr = Array.isArray(parsed) ? parsed.slice(0, 6).map((n: any) => Number(n) || 0) : []
} catch {
arr = val
.split(',')
.map((n) => parseInt(n, 10))
.filter((n) => !Number.isNaN(n))
.slice(0, 6)
}
}
const items: (number | null)[] = [...arr]
while (items.length < 6) items.push(null)
return items.slice(0, 6)
}
const handleClose = () => { const handleClose = () => {
visible.value = false visible.value = false
formRef.value?.resetFields() formRef.value?.resetFields()
@@ -223,19 +328,56 @@
if (!formRef.value) return if (!formRef.value) return
try { try {
await formRef.value.validate() await formRef.value.validate()
const payload = { ...formData } as Record<string, unknown>
// 将 6 个输入值拼成 [1,2,3,4,5,6] 格式,确保每项为 16 的整数
const items = formData.rollArrayItems
payload.roll_array = items.map((n) => {
const v = n != null ? Number(n) : 1
return Math.min(6, Math.max(1, Number.isNaN(v) ? 1 : Math.floor(v)))
})
delete payload.rollArrayItems
if (props.dialogType === 'add') { if (props.dialogType === 'add') {
const rest = { ...formData } as Record<string, unknown> delete payload.id
delete rest.id await api.save(payload)
await api.save(rest)
ElMessage.success('新增成功') ElMessage.success('新增成功')
} else { } else {
await api.update(formData) await api.update(payload)
ElMessage.success('修改成功') ElMessage.success('修改成功')
} }
emit('success') emit('success')
handleClose() handleClose()
} catch (error) { } catch (error: any) {
console.log('表单验证失败:', error) let msg = '表单验证失败,请检查必填项与格式'
if (error?.message) {
msg = error.message
} else if (typeof error === 'string') {
msg = error
} else if (error && typeof error === 'object') {
const first = Object.values(error).find((v: any) => v?.[0]?.message)
if (first && Array.isArray(first)) {
msg = (first[0] as any).message || msg
}
}
ElMessage.warning(msg)
} }
} }
</script> </script>
<style lang="scss" scoped>
.roll-array-wrap {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.roll-array-input {
width: 72px;
}
.roll-array-hint {
margin-top: 6px;
font-size: 12px;
color: var(--el-text-color-secondary);
}
</style>

View File

@@ -34,6 +34,14 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col v-bind="setSpan(6)">
<el-form-item label="方向" prop="direction">
<el-select v-model="formData.direction" placeholder="全部" clearable style="width: 100%">
<el-option label="顺时针" :value="0" />
<el-option label="逆时针" :value="1" />
</el-select>
</el-form-item>
</el-col>
<el-col v-bind="setSpan(6)"> <el-col v-bind="setSpan(6)">
<el-form-item label="赢取平台币" prop="win_coin_min"> <el-form-item label="赢取平台币" prop="win_coin_min">
<div class="range-wrap"> <div class="range-wrap">

View File

@@ -48,6 +48,7 @@ class DicePlayRecordController extends BaseController
['win_coin_max', ''], ['win_coin_max', ''],
['reward_ui_text', ''], ['reward_ui_text', ''],
['reward_tier', ''], ['reward_tier', ''],
['direction', ''],
]); ]);
$query = $this->logic->search($where); $query = $this->logic->search($where);
$query->with([ $query->with([

View File

@@ -24,4 +24,36 @@ class DicePlayRecordLogic extends BaseLogic
$this->model = new DicePlayRecord(); $this->model = new DicePlayRecord();
} }
/**
* 添加前roll_array 转为 JSON 字符串(数据库为 string 类型)
*/
public function add(array $data): mixed
{
$data = $this->normalizeRollArray($data);
return parent::add($data);
}
/**
* 修改前roll_array 转为 JSON 字符串(数据库为 string 类型)
*/
public function edit($id, array $data): mixed
{
$data = $this->normalizeRollArray($data);
return parent::edit($id, $data);
}
/**
* 将 roll_array 从数组转为 JSON 字符串
*/
private function normalizeRollArray(array $data): array
{
if (!array_key_exists('roll_array', $data)) {
return $data;
}
$val = $data['roll_array'];
if (is_array($val)) {
$data['roll_array'] = json_encode($val, JSON_UNESCAPED_UNICODE);
}
return $data;
}
} }

View File

@@ -23,8 +23,12 @@ use think\model\relation\BelongsTo;
* @property $lottery_type 抽奖类型 * @property $lottery_type 抽奖类型
* @property $is_win 中奖 * @property $is_win 中奖
* @property $win_coin 赢取平台币 * @property $win_coin 赢取平台币
* @property $direction 方向:0=顺时针,1=逆时针
* @property $reward_config_id 奖励配置id * @property $reward_config_id 奖励配置id
* @property $lottery_id 奖池 * @property $lottery_id 奖池
* @property $start_index 起始索引
* @property $target_index 结束索引
* @property $roll_array 摇取点数,格式:[1,2,3,4,5,6]
* @property $lottery_name 奖池名 * @property $lottery_name 奖池名
* @property $create_time 创建时间 * @property $create_time 创建时间
* @property $update_time 修改时间 * @property $update_time 修改时间
@@ -157,4 +161,12 @@ class DicePlayRecord extends BaseModel
$query->whereRaw('1=0'); $query->whereRaw('1=0');
} }
} }
/** 方向 0=顺时针 1=逆时针 */
public function searchDirectionAttr($query, $value)
{
if ($value !== '' && $value !== null) {
$query->where('direction', '=', $value);
}
}
} }

View File

@@ -23,6 +23,7 @@ class DicePlayRecordValidate extends BaseValidate
'is_win' => 'require', 'is_win' => 'require',
'win_coin' => 'require', 'win_coin' => 'require',
'reward_config_id' => 'require', 'reward_config_id' => 'require',
'roll_array' => 'require|checkRollArray',
]; ];
/** /**
@@ -35,6 +36,7 @@ class DicePlayRecordValidate extends BaseValidate
'is_win' => '中奖必须填写', 'is_win' => '中奖必须填写',
'win_coin' => '赢取平台币必须填写', 'win_coin' => '赢取平台币必须填写',
'reward_config_id' => '奖励配置必须填写', 'reward_config_id' => '奖励配置必须填写',
'roll_array.require' => '摇取点数必须填写',
]; ];
/** /**
@@ -48,6 +50,7 @@ class DicePlayRecordValidate extends BaseValidate
'is_win', 'is_win',
'win_coin', 'win_coin',
'reward_config_id', 'reward_config_id',
'roll_array',
], ],
'update' => [ 'update' => [
'player_id', 'player_id',
@@ -56,7 +59,36 @@ class DicePlayRecordValidate extends BaseValidate
'is_win', 'is_win',
'win_coin', 'win_coin',
'reward_config_id', 'reward_config_id',
'roll_array',
], ],
]; ];
/**
* 验证 roll_array必须为 6 个元素,每个值在 16 之间
* @param mixed $value
* @param mixed $rule
* @param array $data
* @param string $field
* @return bool|string
*/
protected function checkRollArray($value, $rule = '', array $data = [], string $field = '')
{
if (is_string($value)) {
$decoded = json_decode($value, true);
$value = is_array($decoded) ? $decoded : [];
}
if (!is_array($value)) {
return '摇取点数必须为数组';
}
if (count($value) !== 6) {
return '摇取点数必须为 6 个数';
}
foreach ($value as $i => $n) {
$v = is_numeric($n) ? (int) $n : null;
if ($v === null || $v < 1 || $v > 6) {
return '摇取点数第' . ($i + 1) . '个值必须在 16 之间';
}
}
return true;
}
} }