@@ -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([])
diff --git a/web/src/views/backend/mall/item/index.vue b/web/src/views/backend/mall/item/index.vue
index 324f193..fbca1a5 100644
--- a/web/src/views/backend/mall/item/index.vue
+++ b/web/src/views/backend/mall/item/index.vue
@@ -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'],
},
diff --git a/web/src/views/backend/mall/item/popupForm.vue b/web/src/views/backend/mall/item/popupForm.vue
index 16a3887..5396c36 100644
--- a/web/src/views/backend/mall/item/popupForm.vue
+++ b/web/src/views/backend/mall/item/popupForm.vue
@@ -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') })"
/>
+
+
+
+
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> = 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> = 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') })],