Files
dafuweng-saiadmin6.x/saiadmin-artd/src/views/plugin/dice/reward/index/modules/weight-test-dialog.vue

575 lines
18 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>
<ElDialog
v-model="visible"
:title="$t('page.weightTest.title')"
width="920px"
top="4vh"
class="weight-test-dialog"
:close-on-click-modal="false"
destroy-on-close
@close="onClose"
>
<div class="weight-test-dialog-body">
<ElAlert type="info" :closable="false" show-icon class="weight-test-tip compact-tip">
<div class="tip-lines">
<div>{{ $t('page.weightTest.alertBody') }}</div>
<div>{{ $t('page.weightTest.chainModeHint') }}</div>
<div>{{ $t('page.weightTest.killModeHint') }}</div>
</div>
</ElAlert>
<ElForm :model="form" label-width="108px" class="weight-test-form">
<ElRow :gutter="16">
<ElCol :span="8">
<ElFormItem :label="$t('page.weightTest.labelAnte')" prop="ante_config_id" required>
<ElSelect
v-model="form.ante_config_id"
:placeholder="$t('page.weightTest.placeholderAnte')"
filterable
style="width: 100%"
@change="syncAnteFromSelect"
>
<ElOption
v-for="item in anteOptions"
:key="item.id"
:label="anteOptionLabel(item)"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
</ElCol>
<ElCol :span="8">
<ElFormItem :label="$t('page.weightTest.labelKillModeEnabled')" prop="kill_mode_enabled">
<ElSwitch v-model="form.kill_mode_enabled" />
</ElFormItem>
</ElCol>
<ElCol :span="8">
<ElFormItem :label="$t('page.weightTest.labelTestSafetyLine')">
<ElInput
:model-value="defaultPoolSafetyLineText"
readonly
:disabled="!form.kill_mode_enabled"
/>
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="20" class="section-row">
<ElCol :span="12">
<div class="section-title">{{ $t('page.weightTest.sectionPaid') }}</div>
<ElFormItem :label="$t('page.weightTest.labelLotteryTypePaid')" prop="paid_lottery_config_id">
<ElSelect
v-model="form.paid_lottery_config_id"
:placeholder="$t('page.weightTest.placeholderPaidPool')"
clearable
filterable
style="width: 100%"
>
<ElOption
v-for="item in paidLotteryOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<template v-if="form.paid_lottery_config_id == null">
<div class="tier-label">{{ $t('page.weightTest.tierProbHint') }}</div>
<ElRow :gutter="8" class="tier-row">
<ElCol v-for="t in tierKeys" :key="'paid-' + t" :span="8">
<div class="tier-field">
<label class="tier-field-label">{{
$t('page.weightTest.tierFieldLabel', { tier: t })
}}</label>
<input
type="number"
:value="getPaidTier(t)"
min="0"
max="100"
placeholder="0"
class="tier-input"
@input="setPaidTier(t, $event)"
/>
</div>
</ElCol>
</ElRow>
<div v-if="paidTierSum > 100" class="tier-error">{{
$t('page.weightTest.tierSumError', { sum: paidTierSum })
}}</div>
</template>
<ElFormItem :label="$t('page.weightTest.labelCwCount')" prop="paid_s_count" required>
<ElSelect
v-model="form.paid_s_count"
:placeholder="$t('page.weightTest.placeholderSelect')"
style="width: 100%"
>
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
</ElSelect>
</ElFormItem>
<ElFormItem :label="$t('page.weightTest.labelCcwCount')" prop="paid_n_count" required>
<ElSelect
v-model="form.paid_n_count"
:placeholder="$t('page.weightTest.placeholderSelect')"
style="width: 100%"
>
<ElOption v-for="c in countOptions" :key="'n-' + c" :label="String(c)" :value="c" />
</ElSelect>
</ElFormItem>
</ElCol>
<ElCol :span="12">
<div class="section-title">{{ $t('page.weightTest.sectionFreeAfterPlayAgain') }}</div>
<ElFormItem :label="$t('page.weightTest.labelLotteryTypeFree')" prop="free_lottery_config_id">
<ElSelect
v-model="form.free_lottery_config_id"
:placeholder="$t('page.weightTest.placeholderFreePool')"
clearable
filterable
style="width: 100%"
>
<ElOption
v-for="item in freeLotteryOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<template v-if="form.free_lottery_config_id == null">
<div class="tier-label">{{ $t('page.weightTest.tierProbHintFreeChain') }}</div>
<ElRow :gutter="8" class="tier-row">
<ElCol v-for="t in tierKeys" :key="'free-' + t" :span="8">
<div class="tier-field">
<label class="tier-field-label">{{
$t('page.weightTest.tierFieldLabel', { tier: t })
}}</label>
<input
type="number"
:value="getFreeTier(t)"
min="0"
max="100"
placeholder="0"
class="tier-input"
@input="setFreeTier(t, $event)"
/>
</div>
</ElCol>
</ElRow>
<div v-if="freeTierSum > 100" class="tier-error">{{
$t('page.weightTest.tierSumError', { sum: freeTierSum })
}}</div>
</template>
</ElCol>
</ElRow>
</ElForm>
</div>
<template #footer>
<ElButton
v-permission="'dice:reward:index:startWeightTest'"
type="primary"
:loading="running"
@click="handleStart"
>{{ $t('page.weightTest.btnStart') }}</ElButton
>
<ElButton :disabled="running" @click="visible = false">{{
$t('page.weightTest.btnCancel')
}}</ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import api from '../../../api/reward/index'
import anteConfigApi from '../../../api/ante_config/index'
import lotteryPoolApi from '../../../api/lottery_pool_config/index'
import { ElMessage } from 'element-plus'
import { useI18n } from 'vue-i18n'
import {
getChannelDeptRequestParams,
useInjectedChannelDept,
withChannelDeptParams
} from '@/composables/useChannelDeptScope'
const props = defineProps<{
/** 父页面渠道栏选中值(弹窗 teleport 后 inject 可能失效) */
channelDeptId?: number
}>()
const { t } = useI18n()
const channelScope = useInjectedChannelDept()
const countOptions = [0, 100, 500, 1000, 5000]
const tierKeys = ['T1', 'T2', 'T3', 'T4', 'T5'] as const
const visible = defineModel<boolean>({ default: false })
const emit = defineEmits<{ (e: 'success'): void }>()
const anteOptions = ref<
Array<{ id: number; name: string; title: string; mult: number; is_default: number }>
>([])
const form = reactive({
ante: 1,
ante_config_id: undefined as number | undefined,
paid_lottery_config_id: undefined as number | undefined,
free_lottery_config_id: undefined as number | undefined,
paid_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record<string, number>,
free_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record<string, number>,
paid_s_count: 100,
paid_n_count: 100,
kill_mode_enabled: false
})
const defaultPoolSafetyLineText = computed(() => {
const info = defaultPoolInfo.value
if (!info) {
return '-'
}
const killText = info.kill_enabled === 1
? t('page.weightTest.killEnabledOn')
: t('page.weightTest.killEnabledOff')
return `${info.safety_line}${killText}${t('page.weightTest.poolProfitPrefix')}${info.profit_amount}`
})
const lotteryOptions = ref<Array<{ id: number; name: string }>>([])
const paidLotteryOptions = computed(() =>
lotteryOptions.value.filter((r) => r.name === 'default')
)
const freeLotteryOptions = computed(() => {
const list = lotteryOptions.value.filter((r) => r.name === 'free')
return list.length > 0 ? list : lotteryOptions.value.filter((r) => r.name === 'default')
})
const defaultPoolInfo = ref<{ safety_line: number; kill_enabled: number; profit_amount: number } | null>(null)
const running = ref(false)
function onClose() {
running.value = false
}
function getPaidTier(t: string): string {
const v = form.paid_tier_weights[t]
return v !== undefined && v !== null ? String(v) : ''
}
function setPaidTier(t: string, val: string | Event) {
const raw =
typeof val === 'string'
? val
: ((val as Event & { target: HTMLInputElement }).target?.value ?? '')
const num = raw === '' ? 0 : Math.max(0, Math.min(100, Number(raw) || 0))
form.paid_tier_weights[t] = num
}
const paidTierSum = computed(() =>
tierKeys.reduce((s, t) => s + (form.paid_tier_weights[t] ?? 0), 0)
)
function getFreeTier(t: string): string {
const v = form.free_tier_weights[t]
return v !== undefined && v !== null ? String(v) : ''
}
function setFreeTier(t: string, val: string | Event) {
const raw =
typeof val === 'string'
? val
: ((val as Event & { target: HTMLInputElement }).target?.value ?? '')
const num = raw === '' ? 0 : Math.max(0, Math.min(100, Number(raw) || 0))
form.free_tier_weights[t] = num
}
const freeTierSum = computed(() =>
tierKeys.reduce((s, t) => s + (form.free_tier_weights[t] ?? 0), 0)
)
function resolveDeptParams(): { dept_id?: number } {
if (props.channelDeptId !== undefined && props.channelDeptId !== null) {
return { dept_id: props.channelDeptId }
}
const extra = getChannelDeptRequestParams()
if (extra.dept_id !== undefined) {
return extra
}
if (channelScope) {
return { dept_id: channelScope.selectedDeptId.value }
}
return {}
}
function resolveSubmitDeptId(): number | undefined {
const params = resolveDeptParams()
if (params.dept_id !== undefined) {
return params.dept_id
}
return undefined
}
function anteOptionLabel(item: { name: string; title: string; mult: number }): string {
const label = (item.title || item.name || '').trim()
return label ? `${label} (×${item.mult})` : `×${item.mult}`
}
function syncAnteFromSelect() {
const opt = anteOptions.value.find((o) => o.id === form.ante_config_id)
if (opt) {
form.ante = opt.mult
}
}
async function loadAnteOptions() {
try {
const list = await anteConfigApi.getOptions(resolveDeptParams())
anteOptions.value = list
const def = list.find((i) => i.is_default === 1) ?? list[0]
if (def) {
form.ante_config_id = def.id
form.ante = def.mult
} else {
form.ante_config_id = undefined
form.ante = 1
}
} catch {
anteOptions.value = []
form.ante_config_id = undefined
}
}
async function loadDefaultPoolInfo() {
try {
const pool = await lotteryPoolApi.getCurrentPool(resolveDeptParams())
defaultPoolInfo.value = {
safety_line: Number(pool?.safety_line ?? 0),
kill_enabled: Number(pool?.kill_enabled ?? 1),
profit_amount: Number(pool?.profit_amount ?? 0)
}
} catch {
defaultPoolInfo.value = null
}
}
async function loadLotteryOptions() {
try {
const list = await lotteryPoolApi.getOptions(resolveDeptParams())
lotteryOptions.value = list.map((r: { id: number; name: string }) => ({
id: r.id,
name: r.name
}))
const normal = list.find((r: { name?: string }) => r.name === 'default')
if (normal) {
form.paid_lottery_config_id = normal.id
}
const freePool = list.find((r: { name?: string }) => r.name === 'free')
if (freePool) {
form.free_lottery_config_id = freePool.id
} else if (list.length > 0) {
form.free_lottery_config_id = list[0].id
}
} catch {
lotteryOptions.value = []
}
}
function buildPayload() {
const payload: Record<string, unknown> = {
ante: form.ante,
ante_config_id: form.ante_config_id,
paid_s_count: form.paid_s_count,
paid_n_count: form.paid_n_count,
free_s_count: 0,
free_n_count: 0,
chain_free_mode: true,
kill_mode_enabled: form.kill_mode_enabled,
test_safety_line: defaultPoolInfo.value?.safety_line ?? 0
}
if (form.paid_lottery_config_id != null) {
payload.paid_lottery_config_id = form.paid_lottery_config_id
} else {
payload.paid_tier_weights = { ...form.paid_tier_weights }
}
if (form.free_lottery_config_id != null) {
payload.free_lottery_config_id = form.free_lottery_config_id
} else {
payload.free_tier_weights = { ...form.free_tier_weights }
}
return payload
}
function validateForm(): boolean {
if (form.ante_config_id == null || form.ante_config_id <= 0) {
ElMessage.warning(t('page.weightTest.warnAnte'))
return false
}
syncAnteFromSelect()
if (form.ante == null || form.ante <= 0) {
ElMessage.warning(t('page.weightTest.warnAnte'))
return false
}
if (form.paid_s_count + form.paid_n_count <= 0) {
ElMessage.warning(t('page.weightTest.warnPaidSpins'))
return false
}
const needPaidTier = form.paid_lottery_config_id == null
const needFreeTier = form.free_lottery_config_id == null
if (needPaidTier) {
const sum = paidTierSum.value
if (sum <= 0) {
ElMessage.warning(t('page.weightTest.warnPaidTierSumPositive'))
return false
}
if (sum > 100) {
ElMessage.warning(t('page.weightTest.warnPaidTierSumMax'))
return false
}
}
if (needFreeTier) {
const sum = freeTierSum.value
if (sum <= 0) {
ElMessage.warning(t('page.weightTest.warnFreeTierSumPositive'))
return false
}
if (sum > 100) {
ElMessage.warning(t('page.weightTest.warnFreeTierSumMax'))
return false
}
}
return true
}
async function handleStart() {
if (!validateForm()) return
running.value = true
try {
const payload = buildPayload()
const deptId = resolveSubmitDeptId()
if (deptId !== undefined) {
payload.dept_id = deptId
}
await api.startWeightTest(withChannelDeptParams(payload))
ElMessage.success(t('page.weightTest.successCreated'))
visible.value = false
emit('success')
} catch (e: any) {
ElMessage.error(e?.message || t('page.weightTest.failCreate'))
} finally {
running.value = false
}
}
watch(visible, (v) => {
if (v) {
void loadAnteOptions()
void loadLotteryOptions()
void loadDefaultPoolInfo()
} else {
onClose()
}
})
watch(
() => props.channelDeptId ?? channelScope?.selectedDeptId.value,
() => {
if (visible.value) {
void loadAnteOptions()
void loadLotteryOptions()
void loadDefaultPoolInfo()
}
}
)
</script>
<style lang="scss" scoped>
.weight-test-dialog-body {
max-height: calc(100vh - 168px);
overflow: visible;
}
.compact-tip {
margin-bottom: 12px;
:deep(.el-alert__content) {
line-height: 1.45;
}
}
.tip-lines {
font-size: 12px;
color: var(--el-text-color-regular);
div + div {
margin-top: 4px;
}
}
.weight-test-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
.section-row {
margin-top: 4px;
}
.section-title {
font-size: 13px;
font-weight: 600;
color: var(--el-text-color-primary);
margin: 0 0 10px;
padding-bottom: 4px;
border-bottom: 1px solid var(--el-border-color-lighter);
}
.tier-label {
font-size: 12px;
color: var(--el-text-color-secondary);
margin-bottom: 6px;
}
.tier-row {
margin-bottom: 8px;
}
.tier-field {
margin-bottom: 6px;
}
.tier-field-label {
display: block;
font-size: 12px;
color: var(--el-text-color-regular);
margin-bottom: 2px;
line-height: 1.4;
}
.tier-input {
display: block;
width: 100%;
padding: 4px 8px;
font-size: 13px;
line-height: 1.4;
color: var(--el-text-color-regular);
background-color: var(--el-fill-color-blank);
border: 1px solid var(--el-border-color);
border-radius: var(--el-border-radius-base);
box-sizing: border-box;
}
.tier-input:hover {
border-color: var(--el-border-color-hover);
}
.tier-input:focus {
outline: none;
border-color: var(--el-color-primary);
box-shadow: 0 0 0 2px var(--el-color-primary-light-7);
}
.tier-error {
font-size: 12px;
color: var(--el-color-danger);
margin-bottom: 6px;
}
</style>
<style lang="scss">
.weight-test-dialog.el-dialog {
margin-bottom: 4vh;
}
.weight-test-dialog .el-dialog__body {
padding-top: 12px;
padding-bottom: 8px;
}
</style>