优化页面样式

This commit is contained in:
2026-03-13 11:01:21 +08:00
parent 6b21626878
commit b79904f75e
7 changed files with 736 additions and 156 deletions

View File

@@ -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 权重配比:按档位分组获取配置列表
*/
@@ -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可在奖励对照页权重编辑中调整
*/

View File

@@ -1,78 +1,228 @@
<template>
<div class="art-full-height">
<!-- 搜索面板 -->
<TableSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams" />
<div class="art-full-height reward-config-form">
<ElCard shadow="never" class="form-card">
<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">
<!-- 表格头部 -->
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
<template #left>
<ElSpace wrap>
<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>
</ElSpace>
</template>
</ArtTableHeader>
<!-- 表格 -->
<ArtTable
ref="tableRef"
rowKey="id"
:loading="loading"
:data="data"
:columns="columns"
:pagination="pagination"
@sort-change="handleSortChange"
@selection-change="handleSelectionChange"
@pagination:size-change="handleSizeChange"
@pagination:current-change="handleCurrentChange"
>
<!-- 操作列 -->
<template #operation="{ row }">
<div class="flex gap-2">
<SaButton
v-permission="'dice:reward_config:index:update'"
type="secondary"
@click="showDialog('edit', row)"
/>
<!-- <SaButton-->
<!-- v-permission="'dice:reward_config:index:destroy'"-->
<!-- type="error"-->
<!-- @click="deleteRow(row, api.delete, refreshData)"-->
<!-- />-->
<ElTabs v-model="activeTab" type="card" class="top-tabs">
<ElTabPane label="奖励索引" name="index">
<div class="tab-panel">
<div class="panel-tip">仅显示 T1T5 档位不含 BIGWIN本表单独立提交仅提交本表数据色子点数须在 530 之间且本表内不重复</div>
<div class="table-scroll-wrap">
<ElTable
v-loading="loading"
:data="indexRowsExcludeBigwin"
border
size="default"
class="config-table"
>
<ElTableColumn label="索引(id)" prop="id" width="60" align="center">
<template #default="{ row }">
<span>{{ row.id }}</span>
</template>
</ElTableColumn>
<ElTableColumn label="色子点数" min-width="100" align="center">
<template #default="{ row }">
<ElInputNumber
v-model="row.grid_number"
:min="5"
:max="30"
controls-position="right"
size="small"
class="full-width"
/>
</template>
</ElTableColumn>
<ElTableColumn label="显示文本" min-width="100" align="center">
<template #default="{ row }">
<ElInput v-model="row.ui_text" size="small" placeholder="显示文本" />
</template>
</ElTableColumn>
<ElTableColumn label="真实结算" min-width="110" align="center">
<template #default="{ row }">
<ElInputNumber
v-model="row.real_ev"
controls-position="right"
size="small"
class="full-width"
/>
</template>
</ElTableColumn>
<ElTableColumn label="所属档位" width="100" align="center">
<template #default="{ row }">
<ElSelect
v-model="row.tier"
placeholder="档位"
clearable
size="small"
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>
</template>
</ArtTable>
</ElTabPane>
<ElTabPane label="大奖权重" name="bigwin">
<div class="tab-panel">
<div class="panel-tip">从左至右中大奖点数不可改显示信息实际中奖备注权重(0~10000)点数 530 权重固定 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"
>点数 530 固定 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>
<!-- 编辑弹窗 -->
<EditDialog
v-model="dialogVisible"
:dialog-type="dialogType"
:data="dialogData"
@success="refreshData"
/>
</div>
</template>
<script setup lang="ts">
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 TableSearch from './modules/table-search.vue'
import EditDialog from './modules/edit-dialog.vue'
/** 第一页:奖励索引行(来自 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)
/** 第一页数据(来自 api.list即 DiceRewardConfig 表) */
const indexRows = ref<IndexRow[]>([])
/** 奖励索引 Tab排除 tier=BIGWIN仅显示 T1T5 */
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() {
try {
await ElMessageBox.confirm(
@@ -96,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} 个点数使用兜底起始索引` : ''}`
: '创建成功'
ElMessage.success(msg)
loadIndexList()
} catch (e: any) {
ElMessage.error(e?.message ?? '创建奖励对照失败')
} finally {
@@ -103,70 +254,266 @@
}
}
// 搜索表单
const searchForm = ref<Record<string, unknown>>({
grid_number_min: undefined,
grid_number_max: undefined,
ui_text: undefined,
real_ev_min: undefined,
real_ev_max: undefined,
tier: undefined
})
// 搜索处理
const handleSearch = (params: Record<string, any>) => {
Object.assign(searchParams, params)
getData()
function loadIndexList() {
loading.value = true
return api
.list({ limit: 200 })
.then((res: any) => {
const list = res?.data?.records ?? res?.records ?? res?.data ?? []
const rows = Array.isArray(list)
? list.map((r: Record<string, unknown>) => normalizeIndexRow(r))
: []
indexRows.value = rows
indexRowsSnapshot = rows.map((r) => ({ ...r }))
})
.catch(() => {
ElMessage.error('获取奖励索引配置失败')
})
.finally(() => {
loading.value = false
})
}
// 表格配置(默认 100 条/页)
const {
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
}
]
}
})
function isBigwinWeightDisabled(row: IndexRow): boolean {
return row.grid_number === 5 || row.grid_number === 30
}
// 编辑配置
const {
dialogType,
dialogVisible,
dialogData,
showDialog,
// deleteRow,
// deleteSelectedRows,
handleSelectionChange
// selectedRows
} = useSaiAdmin()
const GRID_NUMBER_MIN = 5
const GRID_NUMBER_MAX = 30
/** 找出数组中出现多于一次的值 */
function findDuplicateValues(arr: number[]): number[] {
const count = new Map<number, number>()
for (const v of arr) {
count.set(v, (count.get(v) ?? 0) + 1)
}
const duplicates: number[] = []
count.forEach((c, v) => {
if (c > 1) duplicates.push(v)
})
return duplicates.sort((a, b) => a - b)
}
/** 奖励索引表单校验:仅对本表内的行(不含 BIGWIN校验点数 530 且本批内不重复 */
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
}
/** 奖励索引表单仅提交本表数据T1T5不包含大奖权重 */
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>
<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>