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

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',
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: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 }">
<div class="flex gap-2">
@@ -77,11 +90,13 @@
import TableSearch from './modules/table-search.vue'
import EditDialog from './modules/edit-dialog.vue'
// 搜索表单
const searchForm = ref({
username: undefined,
name: undefined,
status: undefined,
coin: undefined,
is_up: undefined
})
// 搜索处理
@@ -90,6 +105,14 @@
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 {
columns,
@@ -109,27 +132,39 @@
apiFn: api.list,
columnsFactory: () => [
{ type: 'selection' },
{ prop: 'username', label: '用户名' },
{ prop: 'name', label: '昵称' },
{ prop: 'password', label: '密码' },
{ prop: 'status', label: '状态' },
{ prop: 'coin', label: '平台币' },
{ prop: 'is_up', label: '倍率' },
{ prop: 't1_wight', label: 'T1池权重' },
{ prop: 't2_wight', label: 'T2池权重' },
{ prop: 't3_wight', label: 'T3池权重' },
{ prop: 't4_wight', label: 'T4池权重' },
{ prop: 't5_wight', label: 'T5池权重' },
{ prop: 'status', label: '状态', width: 88, useSlot: true },
{ prop: 'coin', label: '平台币', width: 100, useSlot: true },
{ prop: 'is_up', label: '倍率', width: 80, formatter: isUpFormatter },
{ prop: 't1_wight', label: 'T1池权重', width: 100, formatter: weightFormatter },
{ prop: 't2_wight', label: 'T2池权重', width: 100, formatter: weightFormatter },
{ prop: 't3_wight', label: 'T3池权重', width: 100, formatter: weightFormatter },
{ prop: 't4_wight', label: 'T4池权重', width: 100, formatter: weightFormatter },
{ prop: 't5_wight', label: 'T5池权重', width: 100, formatter: weightFormatter },
{ prop: 'total_draw_count', label: '总抽奖次数' },
{ prop: 'paid_draw_count', label: '购买抽奖次数' },
{ prop: 'free_draw_count', label: '赠送抽奖次数' },
{ prop: 'created_at', label: '创建时间' },
{ prop: 'updated_at', label: '更新时间' },
{ prop: 'deleted_at', label: '删除时间' },
{ prop: 'operation', label: '操作', width: 100, fixed: 'right', useSlot: true }
{ prop: 'operation', label: '操作', width: 120, 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 {
dialogType,
@@ -141,5 +176,4 @@
handleSelectionChange,
selectedRows
} = useSaiAdmin()
</script>

View File

@@ -14,32 +14,56 @@
<el-form-item label="昵称" prop="name">
<el-input v-model="formData.name" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="formData.password" type="password" placeholder="请输入密码" show-password />
<el-form-item label="密码" prop="password" :rules="passwordRules">
<el-input
v-model="formData.password"
type="password"
placeholder="编辑留空则不修改"
show-password
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<sa-switch v-model="formData.status" />
</el-form-item>
<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 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 label="T1池权重" prop="t1_wight">
<el-slider v-model="formData.t1_wight" placeholder="请输入T1池权重" />
<el-form-item label="T1池权重(%)" prop="t1_wight">
<el-slider v-model="formData.t1_wight" :min="0" :max="100" :step="0.01" show-input />
</el-form-item>
<el-form-item label="T2池权重" prop="t2_wight">
<el-slider v-model="formData.t2_wight" placeholder="请输入T2池权重" />
<el-form-item label="T2池权重(%)" prop="t2_wight">
<el-slider v-model="formData.t2_wight" :min="0" :max="100" :step="0.01" show-input />
</el-form-item>
<el-form-item label="T3池权重" prop="t3_wight">
<el-slider v-model="formData.t3_wight" placeholder="请输入T3池权重" />
<el-form-item label="T3池权重(%)" prop="t3_wight">
<el-slider v-model="formData.t3_wight" :min="0" :max="100" :step="0.01" show-input />
</el-form-item>
<el-form-item label="T4池权重" prop="t4_wight">
<el-slider v-model="formData.t4_wight" placeholder="请输入T4池权重" />
<el-form-item label="T4池权重(%)" prop="t4_wight">
<el-slider v-model="formData.t4_wight" :min="0" :max="100" :step="0.01" show-input />
</el-form-item>
<el-form-item label="T5池权重" prop="t5_wight">
<el-slider v-model="formData.t5_wight" placeholder="请输入T5池权重" />
<el-form-item label="T5池权重(%)" prop="t5_wight">
<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>
<template #footer>
@@ -75,106 +99,112 @@
const formRef = ref<FormInstance>()
/**
* 弹窗显示状态双向绑定
*/
const visible = computed({
get: () => props.modelValue,
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>({
username: [{ required: true, message: '用户名必需填写', trigger: 'blur' }],
name: [{ required: true, message: '昵称必需填写', trigger: 'blur' }],
password: [{ 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 = {
id: null,
id: null as number | null,
username: '',
name: '',
password: '',
status: 1,
coin: '0.00',
is_up: null,
t1_wight: '',
t2_wight: '',
t3_wight: '',
t4_wight: '',
t5_wight: '',
status: 1 as number,
coin: 0 as number,
is_up: null as number | null,
t1_wight: 0 as number,
t2_wight: 0 as number,
t3_wight: 0 as number,
t4_wight: 0 as number,
t5_wight: 0 as number
}
/**
* 表单数据
*/
const formData = reactive({ ...initialFormData })
/**
* 监听弹窗打开,初始化表单数据
*/
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
initPage()
}
if (newVal) initPage()
}
)
/**
* 初始化页面数据
*/
const initPage = async () => {
// 先重置为初始值
Object.assign(formData, initialFormData)
// 如果有数据,则填充数据
if (props.data) {
await nextTick()
initForm()
}
}
/**
* 初始化表单数据
*/
const numKeys = [
'id',
'status',
'coin',
'is_up',
't1_wight',
't2_wight',
't3_wight',
't4_wight',
't5_wight'
]
const initForm = () => {
if (props.data) {
for (const key in formData) {
if (props.data[key] != null && props.data[key] != undefined) {
;(formData as any)[key] = props.data[key]
}
if (!props.data) return
for (const key of Object.keys(formData)) {
if (!(key in props.data)) continue
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 = () => {
visible.value = false
formRef.value?.resetFields()
}
/**
* 提交表单
*/
const handleSubmit = async () => {
if (!formRef.value) return
try {
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') {
await api.save(formData)
await api.save(payload)
ElMessage.success('新增成功')
} else {
await api.update(formData)
await api.update(payload)
ElMessage.success('修改成功')
}
emit('success')

View File

@@ -18,6 +18,35 @@
<el-input v-model="formData.name" placeholder="请输入昵称" clearable />
</el-form-item>
</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>
</template>
@@ -32,41 +61,33 @@
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
// 展开/收起
const isExpanded = ref<boolean>(false)
// 表单数据双向绑定
const searchBarRef = ref()
const formData = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
// 重置
function handleReset() {
searchBarRef.value?.ref.resetFields()
emit('reset')
}
// 搜索
async function handleSearch() {
emit('search', formData.value)
}
// 展开/收起
function handleExpand(expanded: boolean) {
isExpanded.value = expanded
}
// 栅格占据的列数
const setSpan = (span: number) => {
return {
span: span,
xs: 24, // 手机:满宽显示
sm: span >= 12 ? span : 12, // 平板大于等于12保持否则用半宽
md: span >= 8 ? span : 8, // 中等屏幕大于等于8保持否则用三分之一宽
lg: span,
xl: span
}
}
const setSpan = (span: number) => ({
span,
xs: 24,
sm: span >= 12 ? span : 12,
md: span >= 8 ? span : 8,
lg: span,
xl: span
})
</script>

View File

@@ -39,6 +39,9 @@ class DicePlayerController extends BaseController
$where = $request->more([
['username', ''],
['name', ''],
['status', ''],
['coin', ''],
['is_up', ''],
]);
$query = $this->logic->search($where);
$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

View File

@@ -16,6 +16,9 @@ use app\dice\model\player\DicePlayer;
*/
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();
}
/**
* 添加数据(密码 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.'%');
}
/**
* 状态 搜索
*/
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 = [
'save' => [
@@ -49,7 +49,6 @@ class DicePlayerValidate extends BaseValidate
'update' => [
'username',
'name',
'password',
'status',
'coin',
],