From a5b51f45da2d69133c29c8f88d6f3da946f6f923 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 1 Dec 2025 18:37:05 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E3=80=90antd=E3=80=91=E3=80=90mal?= =?UTF-8?q?l=E3=80=91=E5=95=86=E5=9F=8E=E6=B4=BB=E5=8A=A8=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../combination/combinationActivity.ts | 1 + .../mall/promotion/seckill/seckillActivity.ts | 1 + .../mall/promotion/bargain/activity/data.ts | 6 ++ .../bargain/activity/modules/form.vue | 20 +++-- .../promotion/combination/activity/data.ts | 2 +- .../combination/activity/modules/form.vue | 21 +++-- .../combination/components/showcase.vue | 1 - .../mall/promotion/coupon/template/data.ts | 8 -- .../coupon/template/modules/form.vue | 24 +++++- .../mall/promotion/discountActivity/data.ts | 9 +- .../discountActivity/modules/form.vue | 16 +++- .../mall/promotion/point/activity/data.ts | 4 - .../mall/promotion/rewardActivity/index.vue | 1 - .../promotion/rewardActivity/modules/form.vue | 3 +- .../seckill/activity/modules/form.vue | 82 ++++++++++++++----- .../promotion/seckill/components/showcase.vue | 1 - packages/locales/src/langs/en-US/ui.json | 4 +- packages/locales/src/langs/zh-CN/ui.json | 4 +- 18 files changed, 149 insertions(+), 59 deletions(-) diff --git a/apps/web-antd/src/api/mall/promotion/combination/combinationActivity.ts b/apps/web-antd/src/api/mall/promotion/combination/combinationActivity.ts index e497ae204..19af95e95 100644 --- a/apps/web-antd/src/api/mall/promotion/combination/combinationActivity.ts +++ b/apps/web-antd/src/api/mall/promotion/combination/combinationActivity.ts @@ -21,6 +21,7 @@ export namespace MallCombinationActivityApi { limitDuration?: number; // 限制时长 combinationPrice?: number; // 拼团价格 products: CombinationProduct[]; // 商品列表 + picUrl?: any; } /** 拼团活动所需属性 */ diff --git a/apps/web-antd/src/api/mall/promotion/seckill/seckillActivity.ts b/apps/web-antd/src/api/mall/promotion/seckill/seckillActivity.ts index 82297effd..1c18c5e3b 100644 --- a/apps/web-antd/src/api/mall/promotion/seckill/seckillActivity.ts +++ b/apps/web-antd/src/api/mall/promotion/seckill/seckillActivity.ts @@ -31,6 +31,7 @@ export namespace MallSeckillActivityApi { totalStock?: number; // 秒杀总库存 seckillPrice?: number; // 秒杀价格 products?: SeckillProduct[]; // 秒杀商品列表 + picUrl?: any; } } diff --git a/apps/web-antd/src/views/mall/promotion/bargain/activity/data.ts b/apps/web-antd/src/views/mall/promotion/bargain/activity/data.ts index a013b6f8d..d6bd1b209 100644 --- a/apps/web-antd/src/views/mall/promotion/bargain/activity/data.ts +++ b/apps/web-antd/src/views/mall/promotion/bargain/activity/data.ts @@ -133,6 +133,12 @@ export function useFormSchema(): VbenFormSchema[] { placeholder: '请输入最大砍价金额', }, }, + { + fieldName: 'spuId', + label: '砍价商品', + component: 'Input', + rules: 'required', + }, ]; } diff --git a/apps/web-antd/src/views/mall/promotion/bargain/activity/modules/form.vue b/apps/web-antd/src/views/mall/promotion/bargain/activity/modules/form.vue index 52aa55de4..71e91a979 100644 --- a/apps/web-antd/src/views/mall/promotion/bargain/activity/modules/form.vue +++ b/apps/web-antd/src/views/mall/promotion/bargain/activity/modules/form.vue @@ -14,6 +14,7 @@ import { updateBargainActivity, } from '#/api/mall/promotion/bargain/bargainActivity'; import { $t } from '#/locales'; +import { SpuShowcase } from '#/views/mall/product/spu/components'; import { useFormSchema } from '../data'; @@ -21,7 +22,7 @@ defineOptions({ name: 'PromotionBargainActivityForm' }); const emit = defineEmits(['success']); -const formData = ref(); +const formData = ref>({}); const getTitle = computed(() => { return formData.value?.id ? $t('ui.actionTitle.edit', ['砍价活动']) @@ -49,8 +50,11 @@ const [Modal, modalApi] = useVbenModal({ } modalApi.lock(); // 提交表单 - const data = - (await formApi.getValues()) as MallBargainActivityApi.BargainActivity; + const values = await formApi.getValues(); + const data = { + ...values, + spuId: formData.value.spuId, + } as MallBargainActivityApi.BargainActivity; try { await (formData.value?.id ? updateBargainActivity(data) @@ -65,7 +69,7 @@ const [Modal, modalApi] = useVbenModal({ }, async onOpenChange(isOpen: boolean) { if (!isOpen) { - formData.value = undefined; + formData.value = {}; return; } // 加载数据 @@ -86,8 +90,12 @@ const [Modal, modalApi] = useVbenModal({ diff --git a/apps/web-antd/src/views/mall/promotion/combination/activity/data.ts b/apps/web-antd/src/views/mall/promotion/combination/activity/data.ts index 08e36bab2..3cb04435b 100644 --- a/apps/web-antd/src/views/mall/promotion/combination/activity/data.ts +++ b/apps/web-antd/src/views/mall/promotion/combination/activity/data.ts @@ -106,10 +106,10 @@ export function useFormSchema(): VbenFormSchema[] { }, }, { - // TODO fieldName: 'spuId', label: '拼团商品', component: 'Input', + rules: 'required', }, ]; } diff --git a/apps/web-antd/src/views/mall/promotion/combination/activity/modules/form.vue b/apps/web-antd/src/views/mall/promotion/combination/activity/modules/form.vue index 92047c4ab..1cb3952fc 100644 --- a/apps/web-antd/src/views/mall/promotion/combination/activity/modules/form.vue +++ b/apps/web-antd/src/views/mall/promotion/combination/activity/modules/form.vue @@ -13,13 +13,16 @@ import { updateCombinationActivity, } from '#/api/mall/promotion/combination/combinationActivity'; import { $t } from '#/locales'; +import { SpuShowcase } from '#/views/mall/product/spu/components'; import { useFormSchema } from '../data'; defineOptions({ name: 'CombinationActivityForm' }); const emit = defineEmits(['success']); -const formData = ref(); +const formData = ref>( + {}, +); const getTitle = computed(() => { return formData.value?.id ? $t('ui.actionTitle.edit', ['拼团活动']) @@ -47,8 +50,11 @@ const [Modal, modalApi] = useVbenModal({ } modalApi.lock(); // 提交表单 - const data = - (await formApi.getValues()) as MallCombinationActivityApi.CombinationActivity; + const values = await formApi.getValues(); + const data = { + ...values, + spuId: formData.value.spuId, + } as MallCombinationActivityApi.CombinationActivity; try { await (formData.value?.id ? updateCombinationActivity(data) @@ -63,7 +69,7 @@ const [Modal, modalApi] = useVbenModal({ }, async onOpenChange(isOpen: boolean) { if (!isOpen) { - formData.value = undefined; + formData.value = {}; return; } // 加载数据 @@ -86,6 +92,11 @@ const [Modal, modalApi] = useVbenModal({ diff --git a/apps/web-antd/src/views/mall/promotion/combination/components/showcase.vue b/apps/web-antd/src/views/mall/promotion/combination/components/showcase.vue index 1c7e999a0..167a979bf 100644 --- a/apps/web-antd/src/views/mall/promotion/combination/components/showcase.vue +++ b/apps/web-antd/src/views/mall/promotion/combination/components/showcase.vue @@ -111,7 +111,6 @@ function emitActivityChange() { >
- @@ -84,14 +80,10 @@ export function useFormSchema(): VbenFormSchema[] { }, rules: 'required', }, - // TODO @puhui999: 商品分类选择器优化 { fieldName: 'productCategoryIds', label: '商品分类', component: 'Input', - componentProps: { - placeholder: '请选择商品分类', - }, dependencies: { triggerFields: ['productScope', 'productScopeValues'], show: (model) => diff --git a/apps/web-antd/src/views/mall/promotion/coupon/template/modules/form.vue b/apps/web-antd/src/views/mall/promotion/coupon/template/modules/form.vue index 753f26893..b015eb7f4 100644 --- a/apps/web-antd/src/views/mall/promotion/coupon/template/modules/form.vue +++ b/apps/web-antd/src/views/mall/promotion/coupon/template/modules/form.vue @@ -16,11 +16,18 @@ import { updateCouponTemplate, } from '#/api/mall/promotion/coupon/couponTemplate'; import { $t } from '#/locales'; +import { ProductCategorySelect } from '#/views/mall/product/category/components'; +import { SpuShowcase } from '#/views/mall/product/spu/components'; import { useFormSchema } from '../data'; const emit = defineEmits(['success']); -const formData = ref(); +const formData = ref< + Partial & { + productCategoryIds?: number | number[]; + productSpuIds?: number[]; + } +>({}); const getTitle = computed(() => { return formData.value?.id ? $t('ui.actionTitle.edit', ['优惠券模板']) @@ -64,7 +71,7 @@ const [Modal, modalApi] = useVbenModal({ }, async onOpenChange(isOpen: boolean) { if (!isOpen) { - formData.value = undefined; + formData.value = {}; return; } // 加载数据 @@ -75,7 +82,7 @@ const [Modal, modalApi] = useVbenModal({ modalApi.lock(); try { formData.value = await getCouponTemplate(data.id); - const processedData = await processLoadData(formData.value); + const processedData = await processLoadData(formData.value as any); // 设置到表单 await formApi.setValues(processedData); } finally { @@ -144,6 +151,15 @@ async function processLoadData( diff --git a/apps/web-antd/src/views/mall/promotion/discountActivity/data.ts b/apps/web-antd/src/views/mall/promotion/discountActivity/data.ts index 5a3b39cf9..f9f170845 100644 --- a/apps/web-antd/src/views/mall/promotion/discountActivity/data.ts +++ b/apps/web-antd/src/views/mall/promotion/discountActivity/data.ts @@ -67,8 +67,15 @@ export function useFormSchema(): VbenFormSchema[] { placeholder: '请输入备注', rows: 4, }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'spuIds', + label: '活动商品', + component: 'Input', + rules: 'required', + formItemClass: 'col-span-2', }, - // TODO @puhui999:少了商品选择~ ]; } diff --git a/apps/web-antd/src/views/mall/promotion/discountActivity/modules/form.vue b/apps/web-antd/src/views/mall/promotion/discountActivity/modules/form.vue index 260c7a98f..6f8bc9a47 100644 --- a/apps/web-antd/src/views/mall/promotion/discountActivity/modules/form.vue +++ b/apps/web-antd/src/views/mall/promotion/discountActivity/modules/form.vue @@ -13,13 +13,18 @@ import { updateDiscountActivity, } from '#/api/mall/promotion/discount/discountActivity'; import { $t } from '#/locales'; +import { SpuShowcase } from '#/views/mall/product/spu/components'; import { useFormSchema } from '../data'; defineOptions({ name: 'DiscountActivityForm' }); const emit = defineEmits(['success']); -const formData = ref(); +const formData = ref< + Partial & { + spuIds?: number[]; + } +>({}); const getTitle = computed(() => { return formData.value?.id ? $t('ui.actionTitle.edit', ['限时折扣活动']) @@ -69,7 +74,7 @@ const [Modal, modalApi] = useVbenModal({ }, async onOpenChange(isOpen: boolean) { if (!isOpen) { - formData.value = undefined; + formData.value = {}; return; } // 加载数据 @@ -91,6 +96,11 @@ const [Modal, modalApi] = useVbenModal({ diff --git a/apps/web-antd/src/views/mall/promotion/point/activity/data.ts b/apps/web-antd/src/views/mall/promotion/point/activity/data.ts index 5883ec911..558535544 100644 --- a/apps/web-antd/src/views/mall/promotion/point/activity/data.ts +++ b/apps/web-antd/src/views/mall/promotion/point/activity/data.ts @@ -133,10 +133,6 @@ export function useFormSchema(): VbenFormSchema[] { component: 'Input', rules: 'required', formItemClass: 'col-span-2', - // TODO @puhui999:貌似 renderComponentContent 不需要哇? - renderComponentContent: () => ({ - default: () => null, - }), }, ]; } diff --git a/apps/web-antd/src/views/mall/promotion/rewardActivity/index.vue b/apps/web-antd/src/views/mall/promotion/rewardActivity/index.vue index 77d038161..20e892a12 100644 --- a/apps/web-antd/src/views/mall/promotion/rewardActivity/index.vue +++ b/apps/web-antd/src/views/mall/promotion/rewardActivity/index.vue @@ -42,7 +42,6 @@ function handleEdit(row: MallRewardActivityApi.RewardActivity) { /** 关闭满减送活动 */ async function handleClose(row: MallRewardActivityApi.RewardActivity) { - // TODO @puhui999:这个国际化,需要加下哈;closing、closeSuccess; const hideLoading = message.loading({ content: $t('ui.actionMessage.closing', [row.name]), duration: 0, diff --git a/apps/web-antd/src/views/mall/promotion/rewardActivity/modules/form.vue b/apps/web-antd/src/views/mall/promotion/rewardActivity/modules/form.vue index fb3105c1c..ca56e951e 100644 --- a/apps/web-antd/src/views/mall/promotion/rewardActivity/modules/form.vue +++ b/apps/web-antd/src/views/mall/promotion/rewardActivity/modules/form.vue @@ -29,10 +29,10 @@ const emit = defineEmits(['success']); const formData = ref>({ conditionType: PromotionConditionTypeEnum.PRICE.type, + productScope: PromotionProductScopeEnum.ALL.scope, rules: [], }); -// TODO @puhui999:点击“编辑”后,会出现 Cannot read properties of null (reading 'type') 报错; const getTitle = computed(() => { return formData.value?.id ? $t('ui.actionTitle.edit', ['满减送']) @@ -78,6 +78,7 @@ const [Modal, modalApi] = useVbenModal({ switch (data.productScope) { case PromotionProductScopeEnum.CATEGORY.scope: { const categoryIds = data.productCategoryIds; + data.productScopeValues = Array.isArray(categoryIds) ? categoryIds : categoryIds diff --git a/apps/web-antd/src/views/mall/promotion/seckill/activity/modules/form.vue b/apps/web-antd/src/views/mall/promotion/seckill/activity/modules/form.vue index 971908dec..b2b95b542 100644 --- a/apps/web-antd/src/views/mall/promotion/seckill/activity/modules/form.vue +++ b/apps/web-antd/src/views/mall/promotion/seckill/activity/modules/form.vue @@ -8,12 +8,14 @@ import { useVbenModal } from '@vben/common-ui'; import { Button, message } from 'ant-design-vue'; 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'; @@ -31,25 +33,37 @@ const spuId = ref(); const spuName = ref(''); const skuTableData = ref([]); -// 选择商品(占位函数,实际需要对接商品选择组件) +const spuSkuSelectRef = ref(); // 商品选择弹窗 Ref + +/** 打开商品选择弹窗 */ const handleSelectProduct = () => { - message.info('商品选择功能需要对接商品选择组件'); - // TODO: 打开商品选择弹窗 - // 实际使用时需要: - // 1. 打开商品选择弹窗 - // 2. 选择商品后调用以下逻辑设置数据: - // spuId.value = selectedSpu.id; - // spuName.value = selectedSpu.name; - // skuTableData.value = selectedSkus.map(sku => ({ - // skuId: sku.id, - // skuName: sku.name || '', - // picUrl: sku.picUrl || selectedSpu.picUrl || '', - // price: sku.price || 0, - // stock: 0, - // seckillPrice: 0, - // })); + 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 || ''; + + // 筛选指定的 SKU + 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, + })) || []; +} + // ================= end ================= const [Form, formApi] = useVbenForm({ @@ -137,10 +151,30 @@ const [Modal, modalApi] = useVbenModal({ await nextTick(); await formApi.setValues(formData.value); - // TODO: 加载商品和 SKU 信息 - // 需要调用商品 API 获取 SPU 详情 - // spuId.value = formData.value.spuId; - // await loadProductDetails(formData.value.spuId, formData.value.products); + // 加载商品和 SKU 信息 + if (formData.value.spuId) { + const spu = await getSpu(formData.value.spuId); + if (spu) { + spuId.value = spu.id; + spuName.value = spu.name || ''; + // 回填 SKU 配置 + 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(); } @@ -154,7 +188,6 @@ const [Modal, modalApi] = useVbenModal({
-
秒杀活动商品: @@ -218,4 +251,11 @@ const [Modal, modalApi] = useVbenModal({
+ + + diff --git a/apps/web-antd/src/views/mall/promotion/seckill/components/showcase.vue b/apps/web-antd/src/views/mall/promotion/seckill/components/showcase.vue index 9de0df553..d5d414bdf 100644 --- a/apps/web-antd/src/views/mall/promotion/seckill/components/showcase.vue +++ b/apps/web-antd/src/views/mall/promotion/seckill/components/showcase.vue @@ -133,7 +133,6 @@ function emitActivityChange() { class="flex h-[60px] w-[60px] cursor-pointer items-center justify-center rounded-lg border border-dashed border-gray-300 hover:border-blue-400" @click="handleOpenActivitySelect" > -
diff --git a/packages/locales/src/langs/en-US/ui.json b/packages/locales/src/langs/en-US/ui.json index 08722ab81..34e2664b6 100644 --- a/packages/locales/src/langs/en-US/ui.json +++ b/packages/locales/src/langs/en-US/ui.json @@ -36,7 +36,9 @@ "downloadTemplateFail": "Download template failed", "updating": "Updating {0}...", "updateSuccess": "Update {0} successfully", - "updateFailed": "Update {0} failed" + "updateFailed": "Update {0} failed", + "closing": "Closing {0} ...", + "closeSuccess": "{0} closed successfully" }, "placeholder": { "input": "Please enter", diff --git a/packages/locales/src/langs/zh-CN/ui.json b/packages/locales/src/langs/zh-CN/ui.json index 3b1bc453a..a8bbb4d9b 100644 --- a/packages/locales/src/langs/zh-CN/ui.json +++ b/packages/locales/src/langs/zh-CN/ui.json @@ -36,7 +36,9 @@ "downloadTemplateFail": "下载模板失败", "updating": "正在更新 {0}...", "updateSuccess": "更新 {0} 成功", - "updateFailed": "更新 {0} 失败" + "updateFailed": "更新 {0} 失败", + "closing": "正在关闭 {0} ...", + "closeSuccess": "{0} 关闭成功" }, "placeholder": { "input": "请输入",