根据对接实施方案文档修改

This commit is contained in:
2026-03-20 18:11:49 +08:00
parent ed5665cb85
commit 5d8a0564b4
14 changed files with 1320 additions and 16 deletions

View File

@@ -0,0 +1,45 @@
<template>
<div>{{ formattedValue }}</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { TableColumnCtx } from 'element-plus'
import { getCellValue } from '/@/components/table/index'
interface Props {
row: TableRow
field: TableColumn
column: TableColumnCtx<TableRow>
index: number
}
const props = defineProps<Props>()
const cellValue = getCellValue(props.row, props.field, props.column, props.index)
const formattedValue = computed(() => {
if (cellValue === null || cellValue === undefined || cellValue === '') return '-'
// PlayX “业务日期”在后端为 YYYY-MM-DD或 YYYY-MM-DDTHH:mm:ssZ
// 这里尽量“按字符串原样”处理,避免 new Date('YYYY-MM-DD') 的时区差导致日期偏移 1 天。
const s = typeof cellValue === 'string' ? cellValue : String(cellValue)
// 常见 ISO/日期字符串截取
const m = s.match(/^(\d{4}-\d{2}-\d{2})/)
if (m) return m[1]
// 如果后端返回的是秒级时间戳,做兜底:用本地日期组件拼出 YYYY-MM-DD
const n = Number(cellValue)
if (Number.isFinite(n) && s.length === 10) {
const d = new Date(n * 1000)
const y = d.getFullYear()
const mm = String(d.getMonth() + 1).padStart(2, '0')
const dd = String(d.getDate()).padStart(2, '0')
return `${y}-${mm}-${dd}`
}
return s
})
</script>

View File

@@ -8,6 +8,10 @@ export default {
'type 1': 'type 1',
'type 2': 'type 2',
'type 3': 'type 3',
amount: 'amount',
multiplier: 'multiplier',
category: 'category',
category_title: 'category_title',
admin_id: 'admin_id',
admin__username: 'username',
image: 'show image',

View File

@@ -5,9 +5,13 @@ export default {
remark: '备注',
score: '兑换积分',
type: '类型',
'type 1': '奖励',
'type 2': '充值',
'type 3': '实物',
'type 1': '红利(BONUS)',
'type 2': '实物(PHYSICAL)',
'type 3': '提现(WITHDRAW)',
amount: '现金面值',
multiplier: '流水倍数',
category: '红利业务类别',
category_title: '类别展示名',
admin_id: '创建管理员',
admin__username: '创建管理员',
image: '展示图',

View File

@@ -1,11 +1,5 @@
<template>
<el-dialog
class="ba-operate-dialog"
:close-on-click-modal="false"
:model-value="!!row"
:title="t('channel.manage.whitelist')"
@close="close"
>
<el-dialog class="ba-operate-dialog" :close-on-click-modal="false" :model-value="!!row" :title="t('channel.manage.whitelist')" @close="close">
<el-scrollbar v-loading="loading" class="ba-table-form-scrollbar">
<div class="ba-operate-form">
<div class="ba-ip-white-list">
@@ -47,7 +41,7 @@ const submitLoading = ref(false)
/** 将后端数据转为 IP 字符串数组(兼容旧格式 [{key,value}] 和新格式 [ip] */
function normalizeIpWhite(val: unknown): string[] {
if (!val || !Array.isArray(val)) return []
return val.map((item) => (typeof item === 'string' ? item : item?.value ?? ''))
return val.map((item) => (typeof item === 'string' ? item : (item?.value ?? '')))
}
const ipWhiteList = ref<string[]>([])

View File

@@ -67,6 +67,7 @@ const baTable = new baTableClass(
label: t('mall.item.type'),
prop: 'type',
align: 'center',
minWidth: 140,
effect: 'dark',
custom: { 1: 'success', 2: 'primary', 3: 'info' },
operator: 'eq',
@@ -74,6 +75,36 @@ const baTable = new baTableClass(
render: 'tag',
replaceValue: { '1': t('mall.item.type 1'), '2': t('mall.item.type 2'), '3': t('mall.item.type 3') },
},
{
label: t('mall.item.amount'),
prop: 'amount',
align: 'center',
sortable: false,
operator: 'RANGE',
},
{
label: t('mall.item.multiplier'),
prop: 'multiplier',
align: 'center',
sortable: false,
operator: 'eq',
},
{
label: t('mall.item.category'),
prop: 'category',
align: 'center',
sortable: false,
operator: 'LIKE',
operatorPlaceholder: t('Fuzzy query'),
},
{
label: t('mall.item.category_title'),
prop: 'category_title',
align: 'center',
sortable: false,
operator: 'LIKE',
operatorPlaceholder: t('Fuzzy query'),
},
{
label: t('mall.item.status'),
prop: 'status',
@@ -139,7 +170,15 @@ const baTable = new baTableClass(
width: 160,
timeFormat: 'yyyy-mm-dd hh:MM:ss',
},
{ label: t('Operate'), align: 'center', width: 100, render: 'buttons', buttons: optButtons, operator: false },
{
label: t('Operate'),
align: 'center',
width: 100,
render: 'buttons',
buttons: optButtons,
operator: false,
fixed: 'right',
},
],
dblClickNotEditColumn: [undefined, 'status'],
},

View File

@@ -72,6 +72,40 @@
:input-attr="{ content: { '1': t('mall.item.type 1'), '2': t('mall.item.type 2'), '3': t('mall.item.type 3') } }"
:placeholder="t('Please select field', { field: t('mall.item.type') })"
/>
<FormItem
:label="t('mall.item.amount')"
type="number"
v-model="baTable.form.items!.amount"
prop="amount"
:input-attr="{ step: 0.01 }"
:placeholder="t('Please input field', { field: t('mall.item.amount') })"
v-if="isBonusOrWithdraw"
/>
<FormItem
:label="t('mall.item.multiplier')"
type="number"
v-model="baTable.form.items!.multiplier"
prop="multiplier"
:input-attr="{ step: 1 }"
:placeholder="t('Please input field', { field: t('mall.item.multiplier') })"
v-if="isBonusOrWithdraw"
/>
<FormItem
:label="t('mall.item.category')"
type="string"
v-model="baTable.form.items!.category"
prop="category"
:placeholder="t('Please input field', { field: t('mall.item.category') })"
v-if="isBonusOrWithdraw"
/>
<FormItem
:label="t('mall.item.category_title')"
type="string"
v-model="baTable.form.items!.category_title"
prop="category_title"
:placeholder="t('Please input field', { field: t('mall.item.category_title') })"
v-if="isBonusOrWithdraw"
/>
<FormItem :label="t('mall.item.image')" type="image" v-model="baTable.form.items!.image" />
<FormItem
:label="t('mall.item.stock')"
@@ -80,6 +114,7 @@
prop="stock"
:input-attr="{ step: 1 }"
:placeholder="t('Please input field', { field: t('mall.item.stock') })"
v-if="isPhysical"
/>
<FormItem
:label="t('mall.item.sort')"
@@ -112,7 +147,7 @@
<script setup lang="ts">
import type { FormItemRule } from 'element-plus'
import { inject, reactive, useTemplateRef } from 'vue'
import { computed, inject, reactive, useTemplateRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import FormItem from '/@/components/formItem/index.vue'
import { useConfig } from '/@/stores/config'
@@ -125,6 +160,35 @@ const baTable = inject('baTable') as baTableClass
const { t } = useI18n()
const itemType = computed(() => baTable.form.items!.type)
const isBonus = computed(() => itemType.value === 1 || itemType.value === '1')
const isPhysical = computed(() => itemType.value === 2 || itemType.value === '2')
const isWithdraw = computed(() => itemType.value === 3 || itemType.value === '3')
const isBonusOrWithdraw = computed(() => isBonus.value || isWithdraw.value)
// 切换类型后,清理不适用的字段,避免“隐藏字段仍保留上一次的值”导致提交脏数据
watch(
itemType,
(n, o) => {
if (o === undefined) return
if (!baTable.form.items) return
const typeNum = Number(n)
if (!Number.isFinite(typeNum)) return
if (typeNum === 2) {
baTable.form.items.amount = 0
baTable.form.items.multiplier = 0
baTable.form.items.category = ''
baTable.form.items.category_title = ''
} else {
// BONUS / WITHDRAW
baTable.form.items.stock = 0
}
},
{ flush: 'post' },
)
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
title: [buildValidatorData({ name: 'required', title: t('mall.item.title') })],
description: [buildValidatorData({ name: 'required', title: t('mall.item.description') })],
@@ -133,6 +197,87 @@ const rules: Partial<Record<string, FormItemRule[]>> = reactive({
buildValidatorData({ name: 'required', title: t('mall.item.score') }),
],
type: [buildValidatorData({ name: 'required', title: t('mall.item.type') })],
amount: [
{
validator: (rule: any, val: any, callback: Function) => {
if (!isBonusOrWithdraw.value) return callback()
if (val === '' || val === null || val === undefined) {
return callback(new Error(t('Please input field', { field: t('mall.item.amount') })))
}
const num = Number(val)
if (!Number.isFinite(num)) {
return callback(new Error(t('Please enter the correct field', { field: t('mall.item.amount') })))
}
if (num < 0) {
return callback(new Error(t('Please enter the correct field', { field: t('mall.item.amount') })))
}
return callback()
},
trigger: 'blur',
},
],
multiplier: [
{
validator: (rule: any, val: any, callback: Function) => {
if (!isBonusOrWithdraw.value) return callback()
if (val === '' || val === null || val === undefined) {
return callback(new Error(t('Please input field', { field: t('mall.item.multiplier') })))
}
const num = Number(val)
if (!Number.isFinite(num) || !Number.isInteger(num)) {
return callback(new Error(t('Please enter the correct field', { field: t('mall.item.multiplier') })))
}
if (num < 0) {
return callback(new Error(t('Please enter the correct field', { field: t('mall.item.multiplier') })))
}
return callback()
},
trigger: 'blur',
},
],
category: [
{
validator: (rule: any, val: any, callback: Function) => {
if (!isBonusOrWithdraw.value) return callback()
if (!val) {
return callback(new Error(t('Please input field', { field: t('mall.item.category') })))
}
return callback()
},
trigger: 'blur',
},
],
category_title: [
{
validator: (rule: any, val: any, callback: Function) => {
if (!isBonusOrWithdraw.value) return callback()
if (!val) {
return callback(new Error(t('Please input field', { field: t('mall.item.category_title') })))
}
return callback()
},
trigger: 'blur',
},
],
stock: [
{
validator: (rule: any, val: any, callback: Function) => {
if (!isPhysical.value) return callback()
if (val === '' || val === null || val === undefined) {
return callback(new Error(t('Please input field', { field: t('mall.item.stock') })))
}
const num = Number(val)
if (!Number.isFinite(num) || !Number.isInteger(num)) {
return callback(new Error(t('Please enter the correct field', { field: t('mall.item.stock') })))
}
if (num < 0) {
return callback(new Error(t('Please enter the correct field', { field: t('mall.item.stock') })))
}
return callback()
},
trigger: 'blur',
},
],
sort: [buildValidatorData({ name: 'number', title: t('mall.item.sort') })],
create_time: [buildValidatorData({ name: 'date', title: t('mall.item.create_time') })],
update_time: [buildValidatorData({ name: 'date', title: t('mall.item.update_time') })],