From 83d3188477f995a6570125a94ac361b8ce7395de Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 31 Oct 2025 10:44:22 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat=EF=BC=9A=E3=80=90mall=20=E5=95=86?= =?UTF-8?q?=E5=9F=8E=E3=80=91=E7=A7=92=E6=9D=80=E6=B4=BB=E5=8A=A8=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E4=BC=98=E5=8C=96=EF=BC=88antd=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mall/promotion/seckill/activity/data.ts | 112 ++++++++++ .../mall/promotion/seckill/activity/index.vue | 2 +- .../seckill/activity/modules/form.vue | 191 +++++++++++++----- 3 files changed, 258 insertions(+), 47 deletions(-) diff --git a/apps/web-antd/src/views/mall/promotion/seckill/activity/data.ts b/apps/web-antd/src/views/mall/promotion/seckill/activity/data.ts index 77124baed..a5c5a8450 100644 --- a/apps/web-antd/src/views/mall/promotion/seckill/activity/data.ts +++ b/apps/web-antd/src/views/mall/promotion/seckill/activity/data.ts @@ -4,6 +4,9 @@ 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 useGridFormSchema(): VbenFormSchema[] { return [ @@ -29,6 +32,115 @@ export function useGridFormSchema(): VbenFormSchema[] { ]; } +/** 新增/编辑的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + // 隐藏的 ID 字段 + { + 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 useGridColumns(): VxeTableGridOptions['columns'] { return [ diff --git a/apps/web-antd/src/views/mall/promotion/seckill/activity/index.vue b/apps/web-antd/src/views/mall/promotion/seckill/activity/index.vue index 83538bdd1..dfed233c2 100644 --- a/apps/web-antd/src/views/mall/promotion/seckill/activity/index.vue +++ b/apps/web-antd/src/views/mall/promotion/seckill/activity/index.vue @@ -5,7 +5,6 @@ import type { MallSeckillActivityApi } from '#/api/mall/promotion/seckill/seckil import { onMounted } from 'vue'; import { DocAlert, Page, useVbenModal } from '@vben/common-ui'; -import { $t } from '@vben/locales'; import { message, Tag } from 'ant-design-vue'; @@ -16,6 +15,7 @@ import { getSeckillActivityPage, } from '#/api/mall/promotion/seckill/seckillActivity'; import { getSimpleSeckillConfigList } from '#/api/mall/promotion/seckill/seckillConfig'; +import { $t } from '#/locales'; import { useGridColumns, useGridFormSchema } from './data'; import { formatConfigNames, formatTimeRange, setConfigList } from './formatter'; 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 7aef17ae8..ceef1485b 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 @@ -1,11 +1,11 @@ From 3802a87659351fcaee0675fc2589a3d5dcfe605d Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 31 Oct 2025 15:48:18 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat=EF=BC=9A=E3=80=90mall=20=E5=95=86?= =?UTF-8?q?=E5=9F=8E=E3=80=91=E4=BC=98=E6=83=A0=E5=88=B8=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E8=BF=81=E7=A7=BB=EF=BC=88antd=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coupon/components/coupon-select.vue | 116 ++++++++++++++++++ .../mall/promotion/coupon/components/data.ts | 101 +++++++++++++++ .../mall/promotion/coupon/components/index.ts | 1 + 3 files changed, 218 insertions(+) create mode 100644 apps/web-antd/src/views/mall/promotion/coupon/components/coupon-select.vue diff --git a/apps/web-antd/src/views/mall/promotion/coupon/components/coupon-select.vue b/apps/web-antd/src/views/mall/promotion/coupon/components/coupon-select.vue new file mode 100644 index 000000000..e43e3319b --- /dev/null +++ b/apps/web-antd/src/views/mall/promotion/coupon/components/coupon-select.vue @@ -0,0 +1,116 @@ + + + diff --git a/apps/web-antd/src/views/mall/promotion/coupon/components/data.ts b/apps/web-antd/src/views/mall/promotion/coupon/components/data.ts index 35a823b79..2b45327bd 100644 --- a/apps/web-antd/src/views/mall/promotion/coupon/components/data.ts +++ b/apps/web-antd/src/views/mall/promotion/coupon/components/data.ts @@ -1,13 +1,42 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeGridProps } from '#/adapter/vxe-table'; +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + import { discountFormat, remainedCountFormat, + takeLimitCountFormat, usePriceFormat, validityTypeFormat, } from '../formatter'; +/** 优惠券选择弹窗的搜索表单 schema */ +export function useCouponSelectFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'name', + label: '优惠券名称', + componentProps: { + placeholder: '请输入优惠券名称', + allowClear: true, + }, + }, + { + component: 'Select', + fieldName: 'discountType', + label: '优惠类型', + componentProps: { + options: getDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE, 'number'), + placeholder: '请选择优惠类型', + allowClear: true, + }, + }, + ]; +} + /** 搜索表单的 schema */ export function useFormSchema(): VbenFormSchema[] { return [ @@ -23,6 +52,78 @@ export function useFormSchema(): VbenFormSchema[] { ]; } +/** 优惠券选择弹窗的表格列配置 */ +export function useCouponSelectGridColumns(): VxeGridProps['columns'] { + return [ + { type: 'checkbox', width: 55 }, + { + title: '优惠券名称', + field: 'name', + minWidth: 140, + }, + { + title: '类型', + field: 'productScope', + minWidth: 80, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_PRODUCT_SCOPE }, + }, + }, + { + title: '优惠', + field: 'discount', + minWidth: 100, + slots: { default: 'discount' }, + }, + { + title: '领取方式', + field: 'takeType', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE }, + }, + }, + { + title: '使用时间', + field: 'validityType', + minWidth: 185, + align: 'center', + formatter: ({ row }) => validityTypeFormat(row), + }, + { + title: '发放数量', + field: 'totalCount', + align: 'center', + minWidth: 100, + }, + { + title: '剩余数量', + minWidth: 100, + align: 'center', + formatter: ({ row }) => remainedCountFormat(row), + }, + { + title: '领取上限', + field: 'takeLimitCount', + minWidth: 100, + align: 'center', + formatter: ({ row }) => takeLimitCountFormat(row), + }, + { + title: '状态', + field: 'status', + align: 'center', + minWidth: 80, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + ]; +} + /** 表格列配置 */ export function useGridColumns(): VxeGridProps['columns'] { return [ diff --git a/apps/web-antd/src/views/mall/promotion/coupon/components/index.ts b/apps/web-antd/src/views/mall/promotion/coupon/components/index.ts index 00a6c3f32..99fca49f3 100644 --- a/apps/web-antd/src/views/mall/promotion/coupon/components/index.ts +++ b/apps/web-antd/src/views/mall/promotion/coupon/components/index.ts @@ -1,2 +1,3 @@ +export { default as CouponSelect } from './coupon-select.vue'; export * from './data'; export { default as CouponSendForm } from './send-form.vue'; From 0956c79aa50f7a90308ea3ac76326091d0d5162c Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 31 Oct 2025 20:51:25 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat=EF=BC=9A=E3=80=90mall=20=E5=95=86?= =?UTF-8?q?=E5=9F=8E=E3=80=91spu=20sku=20=E9=80=89=E6=8B=A9=E5=99=A8?= =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=88antd=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sku-table-select.vue | 54 ++- .../spu/components/spu-table-select.vue | 225 ++++++++++++ .../spu/form/modules/spu-table-select.vue | 346 ------------------ 3 files changed, 249 insertions(+), 376 deletions(-) rename apps/web-antd/src/views/mall/product/spu/{form/modules => components}/sku-table-select.vue (71%) create mode 100644 apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue delete mode 100644 apps/web-antd/src/views/mall/product/spu/form/modules/spu-table-select.vue diff --git a/apps/web-antd/src/views/mall/product/spu/form/modules/sku-table-select.vue b/apps/web-antd/src/views/mall/product/spu/components/sku-table-select.vue similarity index 71% rename from apps/web-antd/src/views/mall/product/spu/form/modules/sku-table-select.vue rename to apps/web-antd/src/views/mall/product/spu/components/sku-table-select.vue index 0900c360e..3ff82032f 100644 --- a/apps/web-antd/src/views/mall/product/spu/form/modules/sku-table-select.vue +++ b/apps/web-antd/src/views/mall/product/spu/components/sku-table-select.vue @@ -8,7 +8,7 @@ import { computed, ref } from 'vue'; import { useVbenModal } from '@vben/common-ui'; import { fenToYuan } from '@vben/utils'; -import { Input, message } from 'ant-design-vue'; +import { message } from 'ant-design-vue'; import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { getSpu } from '#/api/mall/product/spu'; @@ -21,18 +21,13 @@ const emit = defineEmits<{ change: [sku: MallSpuApi.Sku]; }>(); -const selectedSkuId = ref(); const spuId = ref(); -/** 配置列 */ -// TODO @puhui999:貌似列太宽了? +/** 表格列配置 */ const gridColumns = computed(() => [ { - field: 'id', - title: '#', - width: 60, - align: 'center', - slots: { default: 'radio-column' }, + type: 'radio', + width: 55, }, { field: 'picUrl', @@ -72,6 +67,9 @@ const [Grid, gridApi] = useVbenVxeGrid({ height: 400, border: true, showOverflow: true, + radioConfig: { + reserve: true, + }, proxyConfig: { ajax: { query: async () => { @@ -93,46 +91,42 @@ const [Grid, gridApi] = useVbenVxeGrid({ }, }, }, + gridEvents: { + radioChange: handleRadioChange, + }, }); /** 处理选中 */ -function handleSelected(row: MallSpuApi.Sku) { - emit('change', row); - modalApi.close(); - selectedSkuId.value = undefined; +function handleRadioChange() { + const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Sku; + if (selectedRow) { + emit('change', selectedRow); + modalApi.close(); + } } const [Modal, modalApi] = useVbenModal({ destroyOnClose: true, onOpenChange: async (isOpen: boolean) => { if (!isOpen) { - selectedSkuId.value = undefined; + gridApi.grid.clearRadioRow(); spuId.value = undefined; return; } + const data = modalApi.getData(); - // TODO @puhui999:这里要不 if return,让括号的层级简单点。 - if (data?.spuId) { - spuId.value = data.spuId; - // 触发数据查询 - await gridApi.query(); + if (!data?.spuId) { + return; } + + spuId.value = data.spuId; + await gridApi.query(); }, }); diff --git a/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue b/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue new file mode 100644 index 000000000..0098c9813 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue @@ -0,0 +1,225 @@ + + + + diff --git a/apps/web-antd/src/views/mall/product/spu/form/modules/spu-table-select.vue b/apps/web-antd/src/views/mall/product/spu/form/modules/spu-table-select.vue deleted file mode 100644 index 19cb654d2..000000000 --- a/apps/web-antd/src/views/mall/product/spu/form/modules/spu-table-select.vue +++ /dev/null @@ -1,346 +0,0 @@ - - - - From dd37e58a411639aa6870d10437ae641f5dcff3e6 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 31 Oct 2025 22:59:42 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat=EF=BC=9A=E3=80=90mall=20=E5=95=86?= =?UTF-8?q?=E5=9F=8E=E3=80=91=E5=95=86=E5=93=81=E6=A9=B1=E7=AA=97=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=EF=BC=88antd=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mall/product/spu/components/index.ts | 3 + .../product/spu/components/spu-showcase.vue | 154 ++++++++++++++++++ .../spu/components/spu-table-select.vue | 7 + packages/icons/src/iconify/index.ts | 4 + 4 files changed, 168 insertions(+) create mode 100644 apps/web-antd/src/views/mall/product/spu/components/index.ts create mode 100644 apps/web-antd/src/views/mall/product/spu/components/spu-showcase.vue diff --git a/apps/web-antd/src/views/mall/product/spu/components/index.ts b/apps/web-antd/src/views/mall/product/spu/components/index.ts new file mode 100644 index 000000000..122cbcea0 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/index.ts @@ -0,0 +1,3 @@ +export { default as SkuTableSelect } from './sku-table-select.vue'; +export { default as SpuShowcase } from './spu-showcase.vue'; +export { default as SpuTableSelect } from './spu-table-select.vue'; diff --git a/apps/web-antd/src/views/mall/product/spu/components/spu-showcase.vue b/apps/web-antd/src/views/mall/product/spu/components/spu-showcase.vue new file mode 100644 index 000000000..1d75f8a8f --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/spu-showcase.vue @@ -0,0 +1,154 @@ + + + + + + diff --git a/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue b/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue index 0098c9813..e503a95f8 100644 --- a/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue +++ b/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue @@ -216,6 +216,13 @@ onMounted(async () => { categoryList.value = await getCategoryList({}); categoryTreeList.value = handleTree(categoryList.value, 'id', 'parentId'); }); + +/** 对外暴露的方法 */ +defineExpose({ + open: (data?: MallSpuApi.Spu | MallSpuApi.Spu[]) => { + modalApi.setData(data).open(); + }, +});