Files
dafuweng-saiadmin6.x/saiadmin-artd/src/views/plugin/dice/reward_config/index/index.vue

544 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="art-full-height reward-config-form">
<ElCard shadow="never" class="form-card">
<template #header>
<div class="card-header">
<span>{{ $t('page.toolbar.gameRewardConfig') }}</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"
>
{{ $t('page.toolbar.createRewardRef') }}
</ElButton>
</div>
</template>
<ElTabs v-model="activeTab" type="card" class="top-tabs">
<ElTabPane label="奖励索引" name="index">
<div class="tab-panel">
<div class="panel-tip">色子点数须在 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="120" align="center">
<template #default="{ row }">
<ElInput v-model="row.ui_text_en" 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>
</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="160" align="center">
<template #default="{ row }">
<ElInput v-model="row.ui_text_en" 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>
</div>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import api from '../../api/reward_config/index'
/** 第一页:奖励索引行(来自 DiceRewardConfig 表) */
interface IndexRow {
id: number
grid_number: number
ui_text: string
ui_text_en: 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 ?? ''),
ui_text_en: String((raw as any).ui_text_en ?? ''),
real_ev: Number(raw.real_ev) ?? 0,
tier: String(raw.tier ?? ''),
remark: String(raw.remark ?? ''),
weight: toWeight((raw as any).weight)
}
}
async function handleCreateRewardReference() {
try {
await ElMessageBox.confirm(
'按规则创建奖励对照:起始索引 start_index=奖励配置中 grid_number 对应格位的 id顺时针 end_index=(start_index+摇取点数)%26逆时针 end_index=start_index-摇取点数≥0 则取该值,否则 26+start_index-摇取点数。先清空现有数据再为 5-30 共 26 个点数、顺/逆时针分别生成。是否继续?',
'创建奖励对照',
{
confirmButtonText: '确定创建',
cancelButtonText: '取消',
type: 'warning'
}
)
} catch {
return
}
createRewardLoading.value = true
try {
const res: any = await api.createRewardReference()
const data = res?.data ?? res
const msg =
typeof data === 'object' && data !== null
? `已按 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 {
createRewardLoading.value = false
}
}
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
})
}
function isBigwinWeightDisabled(row: IndexRow): boolean {
return row.grid_number === 5 || row.grid_number === 30
}
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,
ui_text_en: r.ui_text_en,
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 batchPayload = rows.map((r) => ({
id: r.id,
grid_number: r.grid_number,
ui_text: r.ui_text,
ui_text_en: r.ui_text_en,
real_ev: r.real_ev,
tier: r.tier,
remark: r.remark
}))
await api.batchUpdate(batchPayload)
const weightItems = 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(weightItems)
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>