diff --git a/apps/web-ele/src/views/mall/promotion/seckill/activity/data.ts b/apps/web-ele/src/views/mall/promotion/seckill/activity/data.ts index b27413519..4d22656ca 100644 --- a/apps/web-ele/src/views/mall/promotion/seckill/activity/data.ts +++ b/apps/web-ele/src/views/mall/promotion/seckill/activity/data.ts @@ -4,6 +4,117 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import { DICT_TYPE } from '@vben/constants'; import { getDictOptions } from '@vben/hooks'; +import { z } from '#/adapter/form'; +import { getSimpleSeckillConfigList } from '#/api/mall/promotion/seckill/seckillConfig'; + +/** 新增/编辑的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '秒杀活动名称', + component: 'Input', + componentProps: { + placeholder: '请输入活动名称', + }, + rules: 'required', + formItemClass: 'col-span-2', + }, + { + fieldName: 'startTime', + label: '活动开始时间', + component: 'DatePicker', + componentProps: { + placeholder: '请选择活动开始时间', + showTime: false, + format: 'YYYY-MM-DD', + valueFormat: 'x', + class: 'w-full', + }, + rules: 'required', + }, + { + fieldName: 'endTime', + label: '活动结束时间', + component: 'DatePicker', + componentProps: { + placeholder: '请选择活动结束时间', + showTime: false, + format: 'YYYY-MM-DD', + valueFormat: 'x', + class: 'w-full', + }, + rules: 'required', + }, + { + fieldName: 'configIds', + label: '秒杀时段', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择秒杀时段', + mode: 'multiple', + api: getSimpleSeckillConfigList, + labelField: 'name', + valueField: 'id', + class: 'w-full', + }, + rules: 'required', + formItemClass: 'col-span-2', + }, + { + fieldName: 'totalLimitCount', + label: '总限购数量', + component: 'InputNumber', + componentProps: { + placeholder: '请输入总限购数量', + min: 0, + class: 'w-full', + }, + rules: z.number().min(0).default(0), + }, + { + fieldName: 'singleLimitCount', + label: '单次限购数量', + component: 'InputNumber', + componentProps: { + placeholder: '请输入单次限购数量', + min: 0, + class: 'w-full', + }, + rules: z.number().min(0).default(0), + }, + { + fieldName: 'sort', + label: '排序', + component: 'InputNumber', + componentProps: { + placeholder: '请输入排序', + min: 0, + class: 'w-full', + }, + rules: z.number().min(0).default(0), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 4, + }, + formItemClass: 'col-span-2', + }, + ]; +} + /** 列表的搜索表单 */ export function useGridFormSchema(): VbenFormSchema[] { return [ diff --git a/apps/web-ele/src/views/mall/promotion/seckill/activity/modules/form.vue b/apps/web-ele/src/views/mall/promotion/seckill/activity/modules/form.vue index 3de767ce9..07b9b071e 100644 --- a/apps/web-ele/src/views/mall/promotion/seckill/activity/modules/form.vue +++ b/apps/web-ele/src/views/mall/promotion/seckill/activity/modules/form.vue @@ -8,12 +8,16 @@ import { useVbenModal } from '@vben/common-ui'; import { ElMessage } from 'element-plus'; import { useVbenForm } from '#/adapter/form'; +import { getSpu } from '#/api/mall/product/spu'; import { createSeckillActivity, getSeckillActivity, updateSeckillActivity, } from '#/api/mall/promotion/seckill/seckillActivity'; import { $t } from '#/locales'; +import { SpuSkuSelect } from '#/views/mall/product/spu/components'; + +import { useFormSchema } from '../data'; const emit = defineEmits(['success']); const formData = ref(); @@ -23,51 +27,47 @@ const getTitle = computed(() => { : $t('ui.actionTitle.create', ['秒杀活动']); }); -// 简化的表单配置,实际项目中应该有完整的字段配置 -const formSchema = [ - { - fieldName: 'id', - component: 'Input', - dependencies: { - triggerFields: [''], - show: () => false, - }, - }, - { - fieldName: 'name', - label: '活动名称', - component: 'Input', - componentProps: { - placeholder: '请输入活动名称', - }, - rules: 'required', - }, - { - fieldName: 'status', - label: '活动状态', - component: 'Select', - componentProps: { - placeholder: '请选择活动状态', - options: [ - { label: '开启', value: 0 }, - { label: '关闭', value: 1 }, - ], - }, - rules: 'required', - }, -]; +const spuId = ref(); +const spuName = ref(''); +const skuTableData = ref([]); +const spuSkuSelectRef = ref(); + +const handleSelectProduct = () => { + spuSkuSelectRef.value?.open(); +}; + +async function handleSpuSelected(selectedSpuId: number, skuIds?: number[]) { + const spu = await getSpu(selectedSpuId); + if (!spu) return; + + spuId.value = spu.id; + spuName.value = spu.name || ''; + + const selectedSkus = skuIds + ? spu.skus?.filter((sku) => skuIds.includes(sku.id!)) + : spu.skus; + + skuTableData.value = + selectedSkus?.map((sku) => ({ + skuId: sku.id!, + skuName: sku.name || '', + picUrl: sku.picUrl || spu.picUrl || '', + price: sku.price || 0, + stock: 0, + seckillPrice: 0, + })) || []; +} const [Form, formApi] = useVbenForm({ commonConfig: { componentProps: { class: 'w-full', }, - formItemClass: 'col-span-2', - labelWidth: 80, }, layout: 'horizontal', - schema: formSchema, + schema: useFormSchema(), showDefaultActions: false, + wrapperClass: 'grid-cols-2', }); const [Modal, modalApi] = useVbenModal({ @@ -76,15 +76,38 @@ const [Modal, modalApi] = useVbenModal({ if (!valid) { return; } + + if (!spuId.value) { + ElMessage.error('请选择秒杀商品'); + return; + } + if (skuTableData.value.length === 0) { + ElMessage.error('请至少配置一个 SKU'); + return; + } + const hasInvalidSku = skuTableData.value.some( + (sku) => sku.stock < 1 || sku.seckillPrice < 0.01, + ); + if (hasInvalidSku) { + ElMessage.error('请正确配置 SKU 的秒杀库存(≥1)和秒杀价格(≥0.01)'); + return; + } + modalApi.lock(); - // 提交表单 - const data = - (await formApi.getValues()) as MallSeckillActivityApi.SeckillActivity; try { + const values = await formApi.getValues(); + const data: any = { + ...values, + spuId: spuId.value, + products: skuTableData.value.map((sku) => ({ + skuId: sku.skuId, + stock: sku.stock, + seckillPrice: Math.round(sku.seckillPrice * 100), + })), + }; await (formData.value?.id ? updateSeckillActivity(data) : createSeckillActivity(data)); - // 关闭并提示 await modalApi.close(); emit('success'); ElMessage.success($t('ui.actionMessage.operationSuccess')); @@ -95,9 +118,11 @@ const [Modal, modalApi] = useVbenModal({ async onOpenChange(isOpen: boolean) { if (!isOpen) { formData.value = undefined; + spuId.value = undefined; + spuName.value = ''; + skuTableData.value = []; return; } - // 加载数据 const data = modalApi.getData(); if (!data || !data.id) { return; @@ -105,8 +130,29 @@ const [Modal, modalApi] = useVbenModal({ modalApi.lock(); try { formData.value = await getSeckillActivity(data.id); - // 设置到 values await formApi.setValues(formData.value); + if (formData.value.spuId) { + const spu = await getSpu(formData.value.spuId); + if (spu) { + spuId.value = spu.id; + spuName.value = spu.name || ''; + const products = formData.value.products || []; + skuTableData.value = + spu.skus + ?.filter((sku) => products.some((p) => p.skuId === sku.id)) + .map((sku) => { + const product = products.find((p) => p.skuId === sku.id); + return { + skuId: sku.id!, + skuName: sku.name || '', + picUrl: sku.picUrl || spu.picUrl || '', + price: sku.price || 0, + stock: product?.stock || 0, + seckillPrice: (product?.seckillPrice || 0) / 100, + }; + }) || []; + } + } } finally { modalApi.unlock(); } @@ -115,7 +161,73 @@ const [Modal, modalApi] = useVbenModal({