[色子游戏]玩家-优化样式

This commit is contained in:
2026-03-03 14:36:04 +08:00
parent fc5f8bb1ca
commit a54f4623c5
8 changed files with 276 additions and 95 deletions

View File

@@ -61,5 +61,15 @@ export default {
url: '/dice/player/DicePlayer/destroy', url: '/dice/player/DicePlayer/destroy',
data: params data: params
}) })
},
/**
* 仅更新状态(列表内开关用)
*/
updateStatus(params: { id: number | string; status: number }) {
return request.put<any>({
url: '/dice/player/DicePlayer/updateStatus',
data: params
})
} }
} }

View File

@@ -42,6 +42,19 @@
@pagination:size-change="handleSizeChange" @pagination:size-change="handleSizeChange"
@pagination:current-change="handleCurrentChange" @pagination:current-change="handleCurrentChange"
> >
<!-- 状态开关直接修改 -->
<template #status="{ row }">
<ElSwitch
v-permission="'dice:player:index:update'"
:model-value="row.status === 1"
:loading="row._statusLoading"
@change="(v: boolean) => handleStatusChange(row, v ? 1 : 0)"
/>
</template>
<!-- 平台币tag 展示 -->
<template #coin="{ row }">
<ElTag type="info" size="small">{{ row.coin ?? 0 }}</ElTag>
</template>
<!-- 操作列 --> <!-- 操作列 -->
<template #operation="{ row }"> <template #operation="{ row }">
<div class="flex gap-2"> <div class="flex gap-2">
@@ -77,11 +90,13 @@
import TableSearch from './modules/table-search.vue' import TableSearch from './modules/table-search.vue'
import EditDialog from './modules/edit-dialog.vue' import EditDialog from './modules/edit-dialog.vue'
// 搜索表单 // 搜索表单
const searchForm = ref({ const searchForm = ref({
username: undefined, username: undefined,
name: undefined, name: undefined,
status: undefined,
coin: undefined,
is_up: undefined
}) })
// 搜索处理 // 搜索处理
@@ -90,6 +105,14 @@
getData() getData()
} }
// 权重列带 % 的 formatter
const weightFormatter = (_row: any, _column: any, cellValue: unknown) =>
cellValue != null && cellValue !== '' ? `${cellValue}%` : '-'
// 倍率列展示0=正常 1=强制杀猪 2=T1高倍率
const isUpFormatter = (_row: any, _column: any, cellValue: unknown) =>
cellValue === 0 ? '正常' : cellValue === 1 ? '强制杀猪' : cellValue === 2 ? 'T1高倍率' : '-'
// 表格配置 // 表格配置
const { const {
columns, columns,
@@ -109,27 +132,39 @@
apiFn: api.list, apiFn: api.list,
columnsFactory: () => [ columnsFactory: () => [
{ type: 'selection' }, { type: 'selection' },
{ prop: 'username', label: '用户名' },
{ prop: 'name', label: '昵称' }, { prop: 'name', label: '昵称' },
{ prop: 'password', label: '密码' }, { prop: 'status', label: '状态', width: 88, useSlot: true },
{ prop: 'status', label: '状态' }, { prop: 'coin', label: '平台币', width: 100, useSlot: true },
{ prop: 'coin', label: '平台币' }, { prop: 'is_up', label: '倍率', width: 80, formatter: isUpFormatter },
{ prop: 'is_up', label: '倍率' }, { prop: 't1_wight', label: 'T1池权重', width: 100, formatter: weightFormatter },
{ prop: 't1_wight', label: 'T1池权重' }, { prop: 't2_wight', label: 'T2池权重', width: 100, formatter: weightFormatter },
{ prop: 't2_wight', label: 'T2池权重' }, { prop: 't3_wight', label: 'T3池权重', width: 100, formatter: weightFormatter },
{ prop: 't3_wight', label: 'T3池权重' }, { prop: 't4_wight', label: 'T4池权重', width: 100, formatter: weightFormatter },
{ prop: 't4_wight', label: 'T4池权重' }, { prop: 't5_wight', label: 'T5池权重', width: 100, formatter: weightFormatter },
{ prop: 't5_wight', label: 'T5池权重' },
{ prop: 'total_draw_count', label: '总抽奖次数' }, { prop: 'total_draw_count', label: '总抽奖次数' },
{ prop: 'paid_draw_count', label: '购买抽奖次数' }, { prop: 'paid_draw_count', label: '购买抽奖次数' },
{ prop: 'free_draw_count', label: '赠送抽奖次数' }, { prop: 'free_draw_count', label: '赠送抽奖次数' },
{ prop: 'created_at', label: '创建时间' }, { prop: 'created_at', label: '创建时间' },
{ prop: 'updated_at', label: '更新时间' }, { prop: 'updated_at', label: '更新时间' },
{ prop: 'deleted_at', label: '删除时间' }, { prop: 'operation', label: '操作', width: 120, fixed: 'right', useSlot: true }
{ prop: 'operation', label: '操作', width: 100, fixed: 'right', useSlot: true }
] ]
} }
}) })
// 状态开关切换(列表内直接修改)
const handleStatusChange = async (row: Record<string, any>, status: number) => {
row._statusLoading = true
try {
await api.updateStatus({ id: row.id, status })
row.status = status
} catch {
refreshData()
} finally {
row._statusLoading = false
}
}
// 编辑配置 // 编辑配置
const { const {
dialogType, dialogType,
@@ -141,5 +176,4 @@
handleSelectionChange, handleSelectionChange,
selectedRows selectedRows
} = useSaiAdmin() } = useSaiAdmin()
</script> </script>

View File

@@ -14,32 +14,56 @@
<el-form-item label="昵称" prop="name"> <el-form-item label="昵称" prop="name">
<el-input v-model="formData.name" placeholder="请输入昵称" /> <el-input v-model="formData.name" placeholder="请输入昵称" />
</el-form-item> </el-form-item>
<el-form-item label="密码" prop="password"> <el-form-item label="密码" prop="password" :rules="passwordRules">
<el-input v-model="formData.password" type="password" placeholder="请输入密码" show-password /> <el-input
v-model="formData.password"
type="password"
placeholder="编辑留空则不修改"
show-password
/>
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<sa-switch v-model="formData.status" /> <sa-switch v-model="formData.status" />
</el-form-item> </el-form-item>
<el-form-item label="平台币" prop="coin"> <el-form-item label="平台币" prop="coin">
<el-input-number v-model="formData.coin" placeholder="请输入平台币" /> <el-input-number
v-model="formData.coin"
:min="0"
:precision="2"
:disabled="dialogType === 'add'"
placeholder="创建时默认0不可改"
style="width: 100%"
/>
</el-form-item> </el-form-item>
<el-form-item label="倍率" prop="is_up"> <el-form-item label="倍率" prop="is_up">
<el-select v-model="formData.is_up" :options="[]" placeholder="请选择倍率" clearable /> <el-select v-model="formData.is_up" placeholder="请选择倍率" clearable style="width: 100%">
<el-option label="正常" :value="0" />
<el-option label="强制杀猪" :value="1" />
<el-option label="T1高倍率" :value="2" />
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="T1池权重" prop="t1_wight"> <el-form-item label="T1池权重(%)" prop="t1_wight">
<el-slider v-model="formData.t1_wight" placeholder="请输入T1池权重" /> <el-slider v-model="formData.t1_wight" :min="0" :max="100" :step="0.01" show-input />
</el-form-item> </el-form-item>
<el-form-item label="T2池权重" prop="t2_wight"> <el-form-item label="T2池权重(%)" prop="t2_wight">
<el-slider v-model="formData.t2_wight" placeholder="请输入T2池权重" /> <el-slider v-model="formData.t2_wight" :min="0" :max="100" :step="0.01" show-input />
</el-form-item> </el-form-item>
<el-form-item label="T3池权重" prop="t3_wight"> <el-form-item label="T3池权重(%)" prop="t3_wight">
<el-slider v-model="formData.t3_wight" placeholder="请输入T3池权重" /> <el-slider v-model="formData.t3_wight" :min="0" :max="100" :step="0.01" show-input />
</el-form-item> </el-form-item>
<el-form-item label="T4池权重" prop="t4_wight"> <el-form-item label="T4池权重(%)" prop="t4_wight">
<el-slider v-model="formData.t4_wight" placeholder="请输入T4池权重" /> <el-slider v-model="formData.t4_wight" :min="0" :max="100" :step="0.01" show-input />
</el-form-item> </el-form-item>
<el-form-item label="T5池权重" prop="t5_wight"> <el-form-item label="T5池权重(%)" prop="t5_wight">
<el-slider v-model="formData.t5_wight" placeholder="请输入T5池权重" /> <el-slider v-model="formData.t5_wight" :min="0" :max="100" :step="0.01" show-input />
</el-form-item>
<el-form-item>
<div class="text-gray-500 text-sm">
五个池权重总和<span :class="Math.abs(weightsSum - 100) > 0.01 ? 'text-red-500' : ''">{{
weightsSum
}}</span
>% / 100%100%
</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
@@ -75,106 +99,112 @@
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
/**
* 弹窗显示状态双向绑定
*/
const visible = computed({ const visible = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: (value) => emit('update:modelValue', value) set: (value) => emit('update:modelValue', value)
}) })
/** const WEIGHT_KEYS = ['t1_wight', 't2_wight', 't3_wight', 't4_wight', 't5_wight'] as const
* 表单验证规则 const weightsSum = computed(() => {
*/ return WEIGHT_KEYS.reduce((sum, key) => sum + Number(formData[key] ?? 0), 0)
})
/** 新增时密码必填,编辑时选填 */
const passwordRules = computed(() =>
props.dialogType === 'add' ? [{ required: true, message: '密码必需填写', trigger: 'blur' }] : []
)
const rules = reactive<FormRules>({ const rules = reactive<FormRules>({
username: [{ required: true, message: '用户名必需填写', trigger: 'blur' }], username: [{ required: true, message: '用户名必需填写', trigger: 'blur' }],
name: [{ required: true, message: '昵称必需填写', trigger: 'blur' }], name: [{ required: true, message: '昵称必需填写', trigger: 'blur' }],
password: [{ required: true, message: '密码必需填写', trigger: 'blur' }],
status: [{ required: true, message: '状态必需填写', trigger: 'blur' }], status: [{ required: true, message: '状态必需填写', trigger: 'blur' }],
coin: [{ required: true, message: '平台币必需填写', trigger: 'blur' }], coin: [{ required: true, message: '平台币必需填写', trigger: 'blur' }]
}) })
/**
* 初始数据
*/
const initialFormData = { const initialFormData = {
id: null, id: null as number | null,
username: '', username: '',
name: '', name: '',
password: '', password: '',
status: 1, status: 1 as number,
coin: '0.00', coin: 0 as number,
is_up: null, is_up: null as number | null,
t1_wight: '', t1_wight: 0 as number,
t2_wight: '', t2_wight: 0 as number,
t3_wight: '', t3_wight: 0 as number,
t4_wight: '', t4_wight: 0 as number,
t5_wight: '', t5_wight: 0 as number
} }
/**
* 表单数据
*/
const formData = reactive({ ...initialFormData }) const formData = reactive({ ...initialFormData })
/**
* 监听弹窗打开,初始化表单数据
*/
watch( watch(
() => props.modelValue, () => props.modelValue,
(newVal) => { (newVal) => {
if (newVal) { if (newVal) initPage()
initPage()
}
} }
) )
/**
* 初始化页面数据
*/
const initPage = async () => { const initPage = async () => {
// 先重置为初始值
Object.assign(formData, initialFormData) Object.assign(formData, initialFormData)
// 如果有数据,则填充数据
if (props.data) { if (props.data) {
await nextTick() await nextTick()
initForm() initForm()
} }
} }
/** const numKeys = [
* 初始化表单数据 'id',
*/ 'status',
'coin',
'is_up',
't1_wight',
't2_wight',
't3_wight',
't4_wight',
't5_wight'
]
const initForm = () => { const initForm = () => {
if (props.data) { if (!props.data) return
for (const key in formData) { for (const key of Object.keys(formData)) {
if (props.data[key] != null && props.data[key] != undefined) { if (!(key in props.data)) continue
;(formData as any)[key] = props.data[key] if (key === 'password') {
;(formData as any).password = ''
continue
} }
const val = props.data[key]
if (numKeys.includes(key)) {
;(formData as any)[key] =
key === 'id' ? (val != null ? Number(val) || null : null) : Number(val) || 0
} else {
;(formData as any)[key] = val ?? ''
} }
} }
} }
/**
* 关闭弹窗并重置表单
*/
const handleClose = () => { const handleClose = () => {
visible.value = false visible.value = false
formRef.value?.resetFields() formRef.value?.resetFields()
} }
/**
* 提交表单
*/
const handleSubmit = async () => { const handleSubmit = async () => {
if (!formRef.value) return if (!formRef.value) return
try { try {
await formRef.value.validate() await formRef.value.validate()
if (Math.abs(weightsSum.value - 100) > 0.01) {
ElMessage.warning('五个池权重总和必须为100%')
return
}
const payload = { ...formData }
if (props.dialogType === 'edit' && !payload.password) {
delete (payload as any).password
}
if (props.dialogType === 'add') { if (props.dialogType === 'add') {
await api.save(formData) await api.save(payload)
ElMessage.success('新增成功') ElMessage.success('新增成功')
} else { } else {
await api.update(formData) await api.update(payload)
ElMessage.success('修改成功') ElMessage.success('修改成功')
} }
emit('success') emit('success')

View File

@@ -18,6 +18,35 @@
<el-input v-model="formData.name" placeholder="请输入昵称" clearable /> <el-input v-model="formData.name" placeholder="请输入昵称" clearable />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col v-bind="setSpan(6)">
<el-form-item label="状态" prop="status">
<el-select v-model="formData.status" placeholder="全部" clearable style="width: 100%">
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
</el-col>
<el-col v-bind="setSpan(6)">
<el-form-item label="平台币" prop="coin">
<el-input-number
v-model="formData.coin"
:min="0"
:precision="2"
placeholder="精确搜索"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col v-bind="setSpan(6)">
<el-form-item label="倍率" prop="is_up">
<el-select v-model="formData.is_up" placeholder="全部" clearable style="width: 100%">
<el-option label="正常" :value="0" />
<el-option label="强制杀猪" :value="1" />
<el-option label="T1高倍率" :value="2" />
</el-select>
</el-form-item>
</el-col>
</sa-search-bar> </sa-search-bar>
</template> </template>
@@ -32,41 +61,33 @@
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
// 展开/收起
const isExpanded = ref<boolean>(false) const isExpanded = ref<boolean>(false)
// 表单数据双向绑定
const searchBarRef = ref() const searchBarRef = ref()
const formData = computed({ const formData = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: (val) => emit('update:modelValue', val) set: (val) => emit('update:modelValue', val)
}) })
// 重置
function handleReset() { function handleReset() {
searchBarRef.value?.ref.resetFields() searchBarRef.value?.ref.resetFields()
emit('reset') emit('reset')
} }
// 搜索
async function handleSearch() { async function handleSearch() {
emit('search', formData.value) emit('search', formData.value)
} }
// 展开/收起
function handleExpand(expanded: boolean) { function handleExpand(expanded: boolean) {
isExpanded.value = expanded isExpanded.value = expanded
} }
// 栅格占据的列数 const setSpan = (span: number) => ({
const setSpan = (span: number) => { span,
return { xs: 24,
span: span, sm: span >= 12 ? span : 12,
xs: 24, // 手机:满宽显示 md: span >= 8 ? span : 8,
sm: span >= 12 ? span : 12, // 平板大于等于12保持否则用半宽
md: span >= 8 ? span : 8, // 中等屏幕大于等于8保持否则用三分之一宽
lg: span, lg: span,
xl: span xl: span
} })
}
</script> </script>

View File

@@ -39,6 +39,9 @@ class DicePlayerController extends BaseController
$where = $request->more([ $where = $request->more([
['username', ''], ['username', ''],
['name', ''], ['name', ''],
['status', ''],
['coin', ''],
['is_up', ''],
]); ]);
$query = $this->logic->search($where); $query = $this->logic->search($where);
$data = $this->logic->getList($query); $data = $this->logic->getList($query);
@@ -99,6 +102,26 @@ class DicePlayerController extends BaseController
} }
} }
/**
* 仅更新状态(列表内开关用)
* @param Request $request
* @return Response
*/
#[Permission('大富翁-玩家修改', 'dice:player:index:update')]
public function updateStatus(Request $request): Response
{
$id = $request->input('id');
$status = $request->input('status');
if ($id === null || $id === '') {
return $this->fail('缺少 id');
}
if ($status === null || $status === '') {
return $this->fail('缺少 status');
}
$this->logic->edit($id, ['status' => (int) $status]);
return $this->success('修改成功');
}
/** /**
* 删除数据 * 删除数据
* @param Request $request * @param Request $request

View File

@@ -16,6 +16,9 @@ use app\dice\model\player\DicePlayer;
*/ */
class DicePlayerLogic extends BaseLogic class DicePlayerLogic extends BaseLogic
{ {
/** 密码加密盐(可与 config 统一) */
private const PASSWORD_SALT = 'dice_player_salt_2024';
/** /**
* 构造函数 * 构造函数
*/ */
@@ -24,4 +27,35 @@ class DicePlayerLogic extends BaseLogic
$this->model = new DicePlayer(); $this->model = new DicePlayer();
} }
/**
* 添加数据(密码 md5+salt 加密)
*/
public function add(array $data): mixed
{
if (!empty($data['password'])) {
$data['password'] = $this->hashPassword($data['password']);
}
return parent::add($data);
}
/**
* 修改数据(仅当传入 password 时用 md5+salt 加密后更新)
*/
public function edit($id, array $data): mixed
{
if (isset($data['password']) && $data['password'] !== '') {
$data['password'] = $this->hashPassword($data['password']);
} else {
unset($data['password']);
}
return parent::edit($id, $data);
}
/**
* 密码加密md5(salt . password)
*/
private function hashPassword(string $password): string
{
return md5(self::PASSWORD_SALT . $password);
}
} }

View File

@@ -62,4 +62,34 @@ class DicePlayer extends BaseModel
$query->where('name', 'like', '%'.$value.'%'); $query->where('name', 'like', '%'.$value.'%');
} }
/**
* 状态 搜索
*/
public function searchStatusAttr($query, $value)
{
if ($value !== '' && $value !== null) {
$query->where('status', '=', $value);
}
}
/**
* 平台币 搜索
*/
public function searchCoinAttr($query, $value)
{
if ($value !== '' && $value !== null) {
$query->where('coin', '=', $value);
}
}
/**
* 倍率 搜索
*/
public function searchIs_upAttr($query, $value)
{
if ($value !== '' && $value !== null) {
$query->where('is_up', '=', $value);
}
}
} }

View File

@@ -36,7 +36,7 @@ class DicePlayerValidate extends BaseValidate
]; ];
/** /**
* 定义场景 * 定义场景update 时密码可选,不填则不修改)
*/ */
protected $scene = [ protected $scene = [
'save' => [ 'save' => [
@@ -49,7 +49,6 @@ class DicePlayerValidate extends BaseValidate
'update' => [ 'update' => [
'username', 'username',
'name', 'name',
'password',
'status', 'status',
'coin', 'coin',
], ],