893 lines
33 KiB
Vue
893 lines
33 KiB
Vue
<template>
|
||
<div class="default-main ba-table-box">
|
||
<el-card shadow="never" class="reward-form-card" v-loading="pageLoading">
|
||
<template #header>
|
||
<span class="card-title">{{ t('game.rewardConfigForm.title') }}</span>
|
||
</template>
|
||
|
||
<el-alert
|
||
v-if="showTemplateIntro"
|
||
class="intro-alert"
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
>
|
||
<template #title>{{ t('game.rewardConfigForm.intro') }}</template>
|
||
</el-alert>
|
||
|
||
<el-form
|
||
ref="formRef"
|
||
class="reward-form-body"
|
||
@submit.prevent=""
|
||
:model="formModel"
|
||
:label-position="config.layout.shrink ? 'top' : 'right'"
|
||
:label-width="formLabelWidth + 'px'"
|
||
:rules="rules"
|
||
>
|
||
<template v-if="isSuperAdmin">
|
||
<el-form-item :label="t('game.rewardConfigForm.super_scope_label')">
|
||
<el-radio-group v-model="superEditScope" @change="onSuperScopeChange">
|
||
<el-radio-button value="template">{{ t('game.rewardConfigForm.super_scope_template') }}</el-radio-button>
|
||
<el-radio-button value="channel">{{ t('game.rewardConfigForm.super_scope_channel') }}</el-radio-button>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-alert
|
||
v-if="superEditScope === 'channel'"
|
||
class="scope-hint"
|
||
type="warning"
|
||
:closable="false"
|
||
:title="t('game.rewardConfigForm.super_scope_hint')"
|
||
/>
|
||
<div v-if="superEditScope === 'channel'" class="channel-bar">
|
||
<div class="channel-picker">
|
||
<FormItem
|
||
:label="t('game.rewardConfig.game_channel_id')"
|
||
type="remoteSelect"
|
||
v-model="formModel.game_channel_id"
|
||
prop="game_channel_id"
|
||
:input-attr="channelRemoteAttr"
|
||
:placeholder="t('Please select field', { field: t('game.rewardConfig.game_channel_id') })"
|
||
/>
|
||
</div>
|
||
<el-button type="primary" @click="loadData" :disabled="!formModel.game_channel_id">{{ t('Refresh') }}</el-button>
|
||
</div>
|
||
</template>
|
||
|
||
<template v-if="showInner">
|
||
<el-form-item :label="t('game.rewardConfig.tier_reward_form')" prop="tier_reward_form">
|
||
<div class="block-editor tier-editor">
|
||
<div class="line line-head">
|
||
<span>{{ t('game.rewardConfig.grid_number') }}</span>
|
||
<span>{{ t('game.rewardConfig.ui_text') }}</span>
|
||
<span>{{ t('game.rewardConfig.ui_text_en') }}</span>
|
||
<span>{{ t('game.rewardConfig.real_ev') }}</span>
|
||
<span>{{ t('game.rewardConfig.tier') }}</span>
|
||
<span>{{ t('game.rewardConfig.remark') }}</span>
|
||
</div>
|
||
<div v-for="(row, idx) in tierRows" :key="'tier-' + idx" class="line">
|
||
<el-input v-model="row.grid_number" disabled />
|
||
<el-input
|
||
v-model="row.ui_text"
|
||
:placeholder="t('Please input field', { field: t('game.rewardConfig.ui_text') })"
|
||
@input="syncPayload"
|
||
/>
|
||
<el-input
|
||
v-model="row.ui_text_en"
|
||
:placeholder="t('Please input field', { field: t('game.rewardConfig.ui_text_en') })"
|
||
@input="syncPayload"
|
||
/>
|
||
<el-input
|
||
v-model="row.real_ev"
|
||
:placeholder="t('Please input field', { field: t('game.rewardConfig.real_ev') })"
|
||
@input="syncPayload"
|
||
/>
|
||
<el-select v-model="row.tier" style="width: 100px" @change="syncPayload">
|
||
<el-option :label="t('game.rewardConfig.tier_t1')" value="T1" />
|
||
<el-option :label="t('game.rewardConfig.tier_t2')" value="T2" />
|
||
<el-option :label="t('game.rewardConfig.tier_t3')" value="T3" />
|
||
<el-option :label="t('game.rewardConfig.tier_t4')" value="T4" />
|
||
<el-option :label="t('game.rewardConfig.tier_t5')" value="T5" />
|
||
</el-select>
|
||
<el-input
|
||
v-model="row.remark"
|
||
:placeholder="t('Please input field', { field: t('game.rewardConfig.remark') })"
|
||
@input="syncPayload"
|
||
/>
|
||
</div>
|
||
<div class="form-help">{{ t('game.rewardConfig.tier_reward_form_help') }}</div>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item :label="t('game.rewardConfig.bigwin_form')" prop="bigwin_form">
|
||
<div class="block-editor bigwin-editor">
|
||
<div class="line line-head">
|
||
<span>{{ t('game.rewardConfig.grid_number') }}</span>
|
||
<span>{{ t('game.rewardConfig.ui_text') }}</span>
|
||
<span>{{ t('game.rewardConfig.real_ev') }}</span>
|
||
<span>{{ t('game.rewardConfig.tier') }}</span>
|
||
</div>
|
||
<div v-for="(row, idx) in bigwinRows" :key="'bigwin-' + idx" class="line">
|
||
<el-input v-model="row.grid_number" disabled />
|
||
<el-input
|
||
v-model="row.ui_text"
|
||
:placeholder="t('Please input field', { field: t('game.rewardConfig.ui_text') })"
|
||
@input="syncPayload"
|
||
/>
|
||
<el-input
|
||
v-model="row.real_ev"
|
||
:placeholder="t('Please input field', { field: t('game.rewardConfig.real_ev') })"
|
||
@input="syncPayload"
|
||
/>
|
||
<el-input v-model="row.tier" disabled style="width: 120px" />
|
||
</div>
|
||
<div class="form-help">{{ t('game.rewardConfig.bigwin_form_help') }}</div>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item>
|
||
<el-button type="primary" :loading="submitLoading" @click="onSubmit">{{ t('game.rewardConfigForm.btn_add') }}</el-button>
|
||
<el-button @click="onReset">{{ t('game.rewardConfigForm.btn_reset') }}</el-button>
|
||
<el-button v-auth="'generateTierBoard'" @click="openGenTierDialog">{{ t('game.rewardConfigForm.btn_gen_tier') }}</el-button>
|
||
<el-button v-auth="'generateRewardWeight'" :loading="genWeightSubmitting" @click="onGenWeightClick">{{
|
||
t('game.rewardConfigForm.btn_gen_weight')
|
||
}}</el-button>
|
||
</el-form-item>
|
||
</template>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<el-dialog
|
||
v-model="genTierDialogVisible"
|
||
class="gen-tier-dialog"
|
||
:title="t('game.rewardConfigForm.gen_tier_title')"
|
||
:width="genTierDialogWidth"
|
||
:fullscreen="genTierDialogFullscreen"
|
||
:align-center="!genTierDialogFullscreen"
|
||
append-to-body
|
||
:close-on-click-modal="false"
|
||
destroy-on-close
|
||
>
|
||
<el-scrollbar :max-height="genTierScrollMaxHeight" class="gen-tier-scroll">
|
||
<div class="gen-rule">{{ t('game.rewardConfigForm.gen_tier_rule') }}</div>
|
||
<el-form
|
||
:model="genTierForm"
|
||
class="gen-tier-form"
|
||
:label-position="genTierFormLabelPosition"
|
||
:label-width="genTierFormLabelWidth"
|
||
>
|
||
<div class="gen-tier-block">
|
||
<div class="gen-tier-block-title">{{ t('game.rewardConfigForm.gen_t1_label') }}</div>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_t1_fixed') + '(' + t('game.rewardConfigForm.gen_dir_cw') + ')'">
|
||
<el-input-number v-model="genTierForm.t1_fixed_cw" :min="0" :max="26" :step="1" controls-position="right" />
|
||
</el-form-item>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_t1_fixed') + '(' + t('game.rewardConfigForm.gen_dir_ccw') + ')'">
|
||
<el-input-number v-model="genTierForm.t1_fixed_ccw" :min="0" :max="26" :step="1" controls-position="right" />
|
||
</el-form-item>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_settlement')">
|
||
<el-input-number v-model="genTierForm.amt_t1" :min="0" :step="1" controls-position="right" />
|
||
</el-form-item>
|
||
</div>
|
||
<div class="gen-tier-block">
|
||
<div class="gen-tier-block-title">{{ t('game.rewardConfigForm.gen_t2_label') }}</div>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_t2_min') + '(' + t('game.rewardConfigForm.gen_dir_cw') + ')'">
|
||
<el-input-number v-model="genTierForm.t2_min_cw" :min="0" :max="26" :step="1" controls-position="right" />
|
||
</el-form-item>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_t2_min') + '(' + t('game.rewardConfigForm.gen_dir_ccw') + ')'">
|
||
<el-input-number v-model="genTierForm.t2_min_ccw" :min="0" :max="26" :step="1" controls-position="right" />
|
||
</el-form-item>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_settlement')">
|
||
<el-input-number v-model="genTierForm.amt_t2" :min="0" :precision="2" :step="0.1" controls-position="right" />
|
||
</el-form-item>
|
||
</div>
|
||
<div class="gen-tier-block">
|
||
<div class="gen-tier-block-title">{{ t('game.rewardConfigForm.gen_t3_label') }}</div>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_t3_amt_only')">
|
||
<el-input-number v-model="genTierForm.amt_t3" :min="0" :precision="2" :step="0.1" controls-position="right" />
|
||
</el-form-item>
|
||
</div>
|
||
<div class="gen-tier-block">
|
||
<div class="gen-tier-block-title">{{ t('game.rewardConfigForm.gen_t4_label') }}</div>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_t4_fixed') + '(' + t('game.rewardConfigForm.gen_dir_cw') + ')'">
|
||
<el-input-number v-model="genTierForm.t4_fixed_cw" :min="0" :max="26" :step="1" controls-position="right" />
|
||
</el-form-item>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_t4_fixed') + '(' + t('game.rewardConfigForm.gen_dir_ccw') + ')'">
|
||
<el-input-number v-model="genTierForm.t4_fixed_ccw" :min="0" :max="26" :step="1" controls-position="right" />
|
||
</el-form-item>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_settlement')">
|
||
<el-input-number v-model="genTierForm.amt_t4" :precision="2" :step="0.1" controls-position="right" />
|
||
</el-form-item>
|
||
</div>
|
||
<div class="gen-tier-block">
|
||
<div class="gen-tier-block-title">{{ t('game.rewardConfigForm.gen_t5_label') }}</div>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_t5_fixed') + '(' + t('game.rewardConfigForm.gen_dir_cw') + ')'">
|
||
<el-input-number v-model="genTierForm.t5_fixed_cw" :min="0" :max="26" :step="1" controls-position="right" />
|
||
</el-form-item>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_t5_fixed') + '(' + t('game.rewardConfigForm.gen_dir_ccw') + ')'">
|
||
<el-input-number v-model="genTierForm.t5_fixed_ccw" :min="0" :max="26" :step="1" controls-position="right" />
|
||
</el-form-item>
|
||
<el-form-item :label="t('game.rewardConfigForm.gen_settlement')">
|
||
<el-input-number :model-value="0" disabled />
|
||
</el-form-item>
|
||
</div>
|
||
</el-form>
|
||
<div class="gen-tier-footer-hint">{{ t('game.rewardConfigForm.gen_tier_footer_hint') }}</div>
|
||
</el-scrollbar>
|
||
<template #footer>
|
||
<div class="gen-tier-dialog-footer">
|
||
<el-button @click="genTierDialogVisible = false">{{ t('game.rewardConfigForm.gen_tier_cancel') }}</el-button>
|
||
<el-button v-auth="'generateTierBoard'" type="primary" :loading="genTierSubmitting" @click="onGenTierSubmit">{{
|
||
t('game.rewardConfigForm.gen_tier_submit')
|
||
}}</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import type { FormInstance, FormItemRule } from 'element-plus'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { useWindowSize } from '@vueuse/core'
|
||
import { computed, onMounted, reactive, ref, useTemplateRef, watch } from 'vue'
|
||
import { useI18n } from 'vue-i18n'
|
||
import FormItem from '/@/components/formItem/index.vue'
|
||
import { useConfig } from '/@/stores/config'
|
||
import { useAdminInfo } from '/@/stores/adminInfo'
|
||
import createAxios from '/@/utils/axios'
|
||
|
||
defineOptions({
|
||
name: 'game/rewardConfig',
|
||
})
|
||
|
||
type RewardRow = {
|
||
grid_number: string
|
||
ui_text: string
|
||
ui_text_en: string
|
||
real_ev: string
|
||
tier: string
|
||
remark: string
|
||
}
|
||
|
||
const { t } = useI18n()
|
||
const config = useConfig()
|
||
const adminInfo = useAdminInfo()
|
||
const { width: windowWidth } = useWindowSize()
|
||
|
||
/** 生成弹窗:窄屏全屏/宽百分比,宽屏固定最大宽度 */
|
||
const genTierDialogFullscreen = computed(() => windowWidth.value <= 520)
|
||
const genTierDialogWidth = computed(() => {
|
||
if (windowWidth.value <= 520) {
|
||
return '100%'
|
||
}
|
||
if (windowWidth.value <= 768) {
|
||
return '92%'
|
||
}
|
||
return '720px'
|
||
})
|
||
|
||
const genTierFormLabelPosition = computed(() => (windowWidth.value < 640 ? 'top' : 'right'))
|
||
const genTierFormLabelWidth = computed(() => (windowWidth.value < 640 ? 'auto' : '150px'))
|
||
|
||
const genTierScrollMaxHeight = computed(() => (genTierDialogFullscreen.value ? 'calc(100vh - 140px)' : 'min(70vh, 640px)'))
|
||
const formRef = useTemplateRef<FormInstance>('formRef')
|
||
|
||
const TIER_GRIDS = Array.from({ length: 26 }, (_v, i) => String(i + 5))
|
||
const BIGWIN_GRIDS = ['5', '10', '15', '20', '25', '30']
|
||
|
||
const isSuperAdmin = computed(() => adminInfo.super === true)
|
||
const formLabelWidth = computed(() => (config.layout.shrink ? 100 : 140))
|
||
const channelRemoteAttr = { pk: 'game_channel.id', field: 'name', remoteUrl: '/admin/game.Channel/index' }
|
||
|
||
const pageLoading = ref(false)
|
||
const submitLoading = ref(false)
|
||
const genTierDialogVisible = ref(false)
|
||
const genTierSubmitting = ref(false)
|
||
const genWeightSubmitting = ref(false)
|
||
const channelChangeSilent = ref(false)
|
||
const lastAutoLoadChannelId = ref<number | null>(null)
|
||
|
||
const genTierForm = reactive({
|
||
t1_fixed_cw: 3,
|
||
t1_fixed_ccw: 3,
|
||
t2_min_cw: 5,
|
||
t2_min_ccw: 5,
|
||
t4_fixed_cw: 1,
|
||
t4_fixed_ccw: 1,
|
||
t5_fixed_cw: 1,
|
||
t5_fixed_ccw: 1,
|
||
amt_t1: 3,
|
||
amt_t2: 1.5,
|
||
amt_t3: 0.5,
|
||
amt_t4: -0.4,
|
||
})
|
||
/** 超管:template = game_channel_id 0 默认模板;channel = 编辑指定渠道 */
|
||
const superEditScope = ref<'template' | 'channel'>('template')
|
||
/** 仅超管维护「全渠道默认模板」(game_channel_id=0、未绑定具体渠道)时展示顶部说明;渠道管理员或超管选「指定渠道」时不展示 */
|
||
const showTemplateIntro = computed(() => isSuperAdmin.value && superEditScope.value === 'template')
|
||
|
||
const formModel = reactive({
|
||
id: null as number | string | null,
|
||
game_channel_id: 0 as number | string,
|
||
tier_reward_form: '',
|
||
bigwin_form: '',
|
||
})
|
||
|
||
const tierRows = ref<RewardRow[]>(
|
||
TIER_GRIDS.map((g) => ({ grid_number: g, ui_text: '', ui_text_en: '', real_ev: '', tier: 'T1', remark: '' }))
|
||
)
|
||
const bigwinRows = ref<RewardRow[]>(
|
||
BIGWIN_GRIDS.map((g) => ({ grid_number: g, ui_text: '', ui_text_en: '', real_ev: '', tier: 'BIGWIN', remark: '' }))
|
||
)
|
||
|
||
const showInner = computed(() => {
|
||
if (!isSuperAdmin.value) {
|
||
return true
|
||
}
|
||
if (superEditScope.value === 'template') {
|
||
return true
|
||
}
|
||
return !!formModel.game_channel_id
|
||
})
|
||
|
||
function parseRows(raw: unknown): RewardRow[] {
|
||
if (typeof raw !== 'string' || raw.trim() === '') return []
|
||
try {
|
||
const parsed = JSON.parse(raw) as unknown
|
||
if (!Array.isArray(parsed)) return []
|
||
const out: RewardRow[] = []
|
||
for (const item of parsed) {
|
||
if (item === null || typeof item !== 'object' || Array.isArray(item)) continue
|
||
const obj = item as Record<string, unknown>
|
||
out.push({
|
||
grid_number: String(obj.grid_number ?? ''),
|
||
ui_text: String(obj.ui_text ?? ''),
|
||
ui_text_en: String(obj.ui_text_en ?? ''),
|
||
real_ev: String(obj.real_ev ?? ''),
|
||
tier: String(obj.tier ?? ''),
|
||
remark: String(obj.remark ?? ''),
|
||
})
|
||
}
|
||
return out
|
||
} catch {
|
||
return []
|
||
}
|
||
}
|
||
|
||
function toTierRows(raw: unknown): RewardRow[] {
|
||
const parsed = parseRows(raw)
|
||
const map = new Map<string, RewardRow>()
|
||
for (const r of parsed) map.set(r.grid_number, r)
|
||
return TIER_GRIDS.map((g) => {
|
||
const row = map.get(g)
|
||
return {
|
||
grid_number: g,
|
||
ui_text: row?.ui_text ?? '',
|
||
ui_text_en: row?.ui_text_en ?? '',
|
||
real_ev: row?.real_ev ?? '',
|
||
tier: row?.tier && row.tier !== 'BIGWIN' ? row.tier : 'T1',
|
||
remark: row?.remark ?? '',
|
||
}
|
||
})
|
||
}
|
||
|
||
function toBigwinRows(raw: unknown): RewardRow[] {
|
||
const parsed = parseRows(raw)
|
||
const map = new Map<string, RewardRow>()
|
||
for (const r of parsed) map.set(r.grid_number, r)
|
||
return BIGWIN_GRIDS.map((g) => {
|
||
const row = map.get(g)
|
||
return {
|
||
grid_number: g,
|
||
ui_text: row?.ui_text ?? '',
|
||
ui_text_en: row?.ui_text_en ?? '',
|
||
real_ev: row?.real_ev ?? '',
|
||
tier: 'BIGWIN',
|
||
remark: row?.remark ?? '',
|
||
}
|
||
})
|
||
}
|
||
|
||
function syncPayload() {
|
||
formModel.tier_reward_form = JSON.stringify(tierRows.value)
|
||
formModel.bigwin_form = JSON.stringify(bigwinRows.value)
|
||
}
|
||
|
||
function applyRowToForm(row: Record<string, unknown>) {
|
||
channelChangeSilent.value = true
|
||
formModel.id = (row.id as number | string | null | undefined) ?? null
|
||
formModel.game_channel_id = (row.game_channel_id as number | string) ?? 0
|
||
tierRows.value = toTierRows(row.tier_reward_form)
|
||
bigwinRows.value = toBigwinRows(row.bigwin_form)
|
||
syncPayload()
|
||
channelChangeSilent.value = false
|
||
}
|
||
|
||
function validateForms(): string | undefined {
|
||
if (tierRows.value.length !== 26 || bigwinRows.value.length !== 6) {
|
||
return t('Parameter error')
|
||
}
|
||
for (let i = 0; i < tierRows.value.length; i++) {
|
||
const row = tierRows.value[i]
|
||
if (row.grid_number !== TIER_GRIDS[i]) return t('Parameter error')
|
||
if (row.ui_text.trim() === '' || row.real_ev.trim() === '') return t('Please input field', { field: t('game.rewardConfig.ui_text') })
|
||
if (!Number.isFinite(Number(row.real_ev))) return t('game.rewardConfig.real_ev')
|
||
if (!['T1', 'T2', 'T3', 'T4', 'T5'].includes(row.tier)) return t('Parameter error')
|
||
}
|
||
for (let i = 0; i < bigwinRows.value.length; i++) {
|
||
const row = bigwinRows.value[i]
|
||
if (row.grid_number !== BIGWIN_GRIDS[i]) return t('Parameter error')
|
||
if (row.tier !== 'BIGWIN') return t('Parameter error')
|
||
if (row.ui_text.trim() === '' || row.real_ev.trim() === '') return t('Please input field', { field: t('game.rewardConfig.ui_text') })
|
||
if (!Number.isFinite(Number(row.real_ev))) return t('game.rewardConfig.real_ev')
|
||
}
|
||
return undefined
|
||
}
|
||
|
||
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||
game_channel_id: [
|
||
{
|
||
validator: (_rule, _val, callback) => {
|
||
if (!isSuperAdmin.value || superEditScope.value !== 'channel') {
|
||
callback()
|
||
return
|
||
}
|
||
if (!_val && _val !== 0) {
|
||
callback(new Error(t('Please select field', { field: t('game.rewardConfig.game_channel_id') })))
|
||
return
|
||
}
|
||
if (_val === 0 || _val === '0') {
|
||
callback(new Error(t('Please select field', { field: t('game.rewardConfig.game_channel_id') })))
|
||
return
|
||
}
|
||
callback()
|
||
},
|
||
trigger: ['change', 'blur'],
|
||
},
|
||
],
|
||
tier_reward_form: [
|
||
{
|
||
validator: (_rule, _val, callback) => {
|
||
const err = validateForms()
|
||
if (err) return callback(new Error(err))
|
||
callback()
|
||
},
|
||
trigger: ['blur', 'change'],
|
||
},
|
||
],
|
||
bigwin_form: [
|
||
{
|
||
validator: (_rule, _val, callback) => {
|
||
const err = validateForms()
|
||
if (err) return callback(new Error(err))
|
||
callback()
|
||
},
|
||
trigger: ['blur', 'change'],
|
||
},
|
||
],
|
||
})
|
||
|
||
function resolveRequestChannelId(): number | null {
|
||
if (!isSuperAdmin.value) {
|
||
return null
|
||
}
|
||
if (superEditScope.value === 'template') {
|
||
return 0
|
||
}
|
||
const v = formModel.game_channel_id
|
||
if (!v && v !== 0) return null
|
||
const n = Number(v)
|
||
if (!Number.isFinite(n) || n <= 0) return null
|
||
return n
|
||
}
|
||
|
||
async function loadData() {
|
||
if (isSuperAdmin.value && superEditScope.value === 'channel' && !formModel.game_channel_id) {
|
||
return
|
||
}
|
||
pageLoading.value = true
|
||
try {
|
||
const params: Record<string, number> = {}
|
||
const cid = resolveRequestChannelId()
|
||
if (cid !== null) {
|
||
params.game_channel_id = cid
|
||
}
|
||
const res = await createAxios<{ row?: Record<string, unknown> }>(
|
||
{
|
||
url: '/admin/game.RewardConfig/index',
|
||
method: 'get',
|
||
params,
|
||
},
|
||
{ showErrorMessage: true, loading: false }
|
||
)
|
||
const row = res.data?.row
|
||
if (row && typeof row === 'object' && !Array.isArray(row)) {
|
||
applyRowToForm(row as Record<string, unknown>)
|
||
}
|
||
} catch (error: unknown) {
|
||
const err = error as { code?: string; message?: string }
|
||
if (err.code === 'ERR_CANCELED' || String(err.message ?? '').toLowerCase().includes('canceled')) {
|
||
return
|
||
}
|
||
} finally {
|
||
pageLoading.value = false
|
||
}
|
||
}
|
||
|
||
function onSuperScopeChange(val: string | number | boolean | undefined) {
|
||
if (val === 'template') {
|
||
formModel.game_channel_id = 0
|
||
loadData()
|
||
} else {
|
||
formModel.game_channel_id = ''
|
||
lastAutoLoadChannelId.value = null
|
||
formRef.value?.clearValidate(['game_channel_id'])
|
||
}
|
||
}
|
||
|
||
async function onSubmit() {
|
||
const form = formRef.value
|
||
if (!form) return
|
||
syncPayload()
|
||
await form.validate(async (valid) => {
|
||
if (!valid) return
|
||
submitLoading.value = true
|
||
try {
|
||
const body: Record<string, string | number> = {
|
||
tier_reward_form: formModel.tier_reward_form,
|
||
bigwin_form: formModel.bigwin_form,
|
||
}
|
||
const cid = resolveRequestChannelId()
|
||
if (cid !== null) {
|
||
body.game_channel_id = cid
|
||
}
|
||
await createAxios(
|
||
{
|
||
url: '/admin/game.RewardConfig/save',
|
||
method: 'post',
|
||
data: body,
|
||
},
|
||
{ showSuccessMessage: true, loading: false }
|
||
)
|
||
await loadData()
|
||
} finally {
|
||
submitLoading.value = false
|
||
}
|
||
})
|
||
}
|
||
|
||
async function onReset() {
|
||
formRef.value?.clearValidate()
|
||
await loadData()
|
||
}
|
||
|
||
function openGenTierDialog() {
|
||
genTierDialogVisible.value = true
|
||
}
|
||
|
||
async function onGenTierSubmit() {
|
||
genTierSubmitting.value = true
|
||
try {
|
||
const body: Record<string, string | number> = {
|
||
t1_fixed_cw: genTierForm.t1_fixed_cw,
|
||
t1_fixed_ccw: genTierForm.t1_fixed_ccw,
|
||
t2_min_cw: genTierForm.t2_min_cw,
|
||
t2_min_ccw: genTierForm.t2_min_ccw,
|
||
t4_fixed_cw: genTierForm.t4_fixed_cw,
|
||
t4_fixed_ccw: genTierForm.t4_fixed_ccw,
|
||
t5_fixed_cw: genTierForm.t5_fixed_cw,
|
||
t5_fixed_ccw: genTierForm.t5_fixed_ccw,
|
||
amt_t1: genTierForm.amt_t1,
|
||
amt_t2: genTierForm.amt_t2,
|
||
amt_t3: genTierForm.amt_t3,
|
||
amt_t4: genTierForm.amt_t4,
|
||
}
|
||
const cid = resolveRequestChannelId()
|
||
if (cid !== null) {
|
||
body.game_channel_id = cid
|
||
}
|
||
await createAxios(
|
||
{
|
||
url: '/admin/game.RewardConfig/generateTierBoard',
|
||
method: 'post',
|
||
data: body,
|
||
},
|
||
{ showSuccessMessage: true, loading: false }
|
||
)
|
||
genTierDialogVisible.value = false
|
||
await loadData()
|
||
} finally {
|
||
genTierSubmitting.value = false
|
||
}
|
||
}
|
||
|
||
async function onGenWeightClick() {
|
||
if (isSuperAdmin.value && superEditScope.value === 'channel' && !formModel.game_channel_id) {
|
||
ElMessage.warning(t('game.rewardConfigForm.gen_weight_need_channel'))
|
||
return
|
||
}
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
t('game.rewardConfigForm.gen_weight_confirm_body'),
|
||
t('game.rewardConfigForm.gen_weight_confirm_title'),
|
||
{
|
||
type: 'warning',
|
||
confirmButtonText: t('game.rewardConfigForm.gen_weight_confirm_ok'),
|
||
cancelButtonText: t('Cancel'),
|
||
}
|
||
)
|
||
} catch {
|
||
return
|
||
}
|
||
genWeightSubmitting.value = true
|
||
try {
|
||
const body: Record<string, number> = {}
|
||
const cid = resolveRequestChannelId()
|
||
if (cid !== null) {
|
||
body.game_channel_id = cid
|
||
}
|
||
await createAxios(
|
||
{
|
||
url: '/admin/game.RewardConfig/generateRewardWeight',
|
||
method: 'post',
|
||
data: body,
|
||
},
|
||
{ showSuccessMessage: true, loading: false }
|
||
)
|
||
} finally {
|
||
genWeightSubmitting.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
if (isSuperAdmin.value) {
|
||
superEditScope.value = 'template'
|
||
formModel.game_channel_id = 0
|
||
loadData()
|
||
} else {
|
||
loadData()
|
||
}
|
||
})
|
||
|
||
watch(
|
||
() => formModel.game_channel_id,
|
||
(val, oldVal) => {
|
||
if (!isSuperAdmin.value || superEditScope.value !== 'channel') {
|
||
return
|
||
}
|
||
if (channelChangeSilent.value) {
|
||
return
|
||
}
|
||
const nextCid = Number(val)
|
||
const prevCid = Number(oldVal)
|
||
if (Number.isFinite(nextCid) && Number.isFinite(prevCid) && nextCid === prevCid) {
|
||
return
|
||
}
|
||
if (!Number.isFinite(nextCid) || nextCid <= 0) {
|
||
return
|
||
}
|
||
if (lastAutoLoadChannelId.value === nextCid) {
|
||
return
|
||
}
|
||
lastAutoLoadChannelId.value = nextCid
|
||
void loadData()
|
||
}
|
||
)
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.reward-form-card {
|
||
max-width: 1280px;
|
||
}
|
||
.card-title {
|
||
font-weight: 600;
|
||
}
|
||
.intro-alert {
|
||
margin-bottom: 16px;
|
||
}
|
||
.scope-hint {
|
||
margin-bottom: 12px;
|
||
}
|
||
.channel-bar {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 12px;
|
||
margin-bottom: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.channel-picker {
|
||
flex: 1 1 360px;
|
||
min-width: 320px;
|
||
max-width: 520px;
|
||
}
|
||
.channel-picker :deep(.el-form-item) {
|
||
margin-bottom: 0;
|
||
}
|
||
.channel-picker :deep(.el-select) {
|
||
width: 100%;
|
||
}
|
||
.reward-form-body {
|
||
margin-top: 8px;
|
||
}
|
||
.block-editor {
|
||
width: 100%;
|
||
}
|
||
.tier-editor .line {
|
||
display: grid;
|
||
grid-template-columns: 64px minmax(88px, 1fr) minmax(88px, 1fr) minmax(88px, 1fr) minmax(96px, 110px) minmax(124px, 1fr);
|
||
column-gap: 10px;
|
||
row-gap: 8px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.bigwin-editor .line {
|
||
display: grid;
|
||
grid-template-columns: 110px 1fr 1fr 120px;
|
||
gap: 8px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.line-head {
|
||
margin-bottom: 6px;
|
||
color: var(--el-text-color-secondary);
|
||
font-size: 12px;
|
||
line-height: 20px;
|
||
font-weight: 600;
|
||
}
|
||
.form-help {
|
||
margin-top: 6px;
|
||
color: var(--el-text-color-secondary);
|
||
font-size: 12px;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.reward-form-card :deep(.el-card__body) {
|
||
padding: 12px 10px;
|
||
}
|
||
.reward-form-body :deep(.el-form-item) {
|
||
margin-bottom: 12px;
|
||
}
|
||
.channel-picker {
|
||
min-width: 100%;
|
||
max-width: 100%;
|
||
}
|
||
.channel-bar {
|
||
gap: 8px;
|
||
margin-bottom: 10px;
|
||
}
|
||
.channel-bar > .el-button {
|
||
width: 100%;
|
||
}
|
||
|
||
/* 移动端主配置表:保持列宽,允许左右滚动,避免被压扁 */
|
||
.tier-editor,
|
||
.bigwin-editor {
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
padding-bottom: 4px;
|
||
}
|
||
.tier-editor .line {
|
||
min-width: 820px;
|
||
grid-template-columns: 48px 80px 80px 80px 100px 130px;
|
||
column-gap: 10px;
|
||
row-gap: 8px;
|
||
}
|
||
.bigwin-editor .line {
|
||
min-width: 560px;
|
||
grid-template-columns: 48px 80px 80px 100px;
|
||
}
|
||
.line-head {
|
||
font-size: 11px;
|
||
line-height: 16px;
|
||
}
|
||
|
||
.reward-form-body :deep(.el-form-item:last-child .el-form-item__content) {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
.reward-form-body :deep(.el-form-item:last-child .el-button) {
|
||
flex: 1 1 calc(50% - 8px);
|
||
min-width: 128px;
|
||
margin-left: 0;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<!-- 弹窗 append-to-body 后不在当前组件 DOM 内,scoped 样式无法作用,单独写全局选择器 -->
|
||
<style lang="scss">
|
||
.gen-tier-dialog.el-dialog {
|
||
box-sizing: border-box;
|
||
max-width: 100vw;
|
||
}
|
||
|
||
.gen-tier-dialog .el-dialog__header {
|
||
padding: 12px 14px;
|
||
margin-right: 0;
|
||
}
|
||
|
||
.gen-tier-dialog .el-dialog__body {
|
||
padding: 8px 12px 12px;
|
||
box-sizing: border-box;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.gen-tier-dialog .el-dialog__footer {
|
||
padding: 10px 12px 14px;
|
||
}
|
||
|
||
.gen-tier-dialog .gen-tier-scroll {
|
||
padding-right: 4px;
|
||
}
|
||
|
||
.gen-tier-dialog .gen-rule {
|
||
white-space: pre-line;
|
||
overflow-wrap: anywhere;
|
||
word-break: break-word;
|
||
max-width: 100%;
|
||
box-sizing: border-box;
|
||
background: var(--el-fill-color-light);
|
||
padding: 12px 12px;
|
||
border-radius: 8px;
|
||
margin-bottom: 12px;
|
||
font-size: 13px;
|
||
line-height: 1.55;
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
|
||
.gen-tier-dialog .gen-tier-block {
|
||
margin-bottom: 12px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
|
||
.gen-tier-dialog .gen-tier-block:last-of-type {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.gen-tier-dialog .gen-tier-block-title {
|
||
font-weight: 600;
|
||
margin-bottom: 8px;
|
||
color: var(--el-text-color-primary);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.gen-tier-dialog .gen-tier-form .el-form-item {
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
.gen-tier-dialog .gen-tier-form.el-form--label-top .el-form-item__label {
|
||
line-height: 1.4;
|
||
margin-bottom: 6px;
|
||
height: auto;
|
||
white-space: normal;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.gen-tier-dialog .gen-tier-form .el-input-number {
|
||
width: 100%;
|
||
max-width: 320px;
|
||
}
|
||
|
||
.gen-tier-dialog .gen-tier-footer-hint {
|
||
margin-top: 4px;
|
||
padding: 0 2px 8px;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
color: var(--el-text-color-secondary);
|
||
overflow-wrap: anywhere;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.gen-tier-dialog .gen-tier-dialog-footer {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
width: 100%;
|
||
}
|
||
|
||
@media (max-width: 639px) {
|
||
.gen-tier-dialog .gen-tier-form .el-input-number {
|
||
max-width: none;
|
||
}
|
||
}
|
||
</style>
|