diff --git a/apps/web-ele/src/views/mall/promotion/point/activity/data.ts b/apps/web-ele/src/views/mall/promotion/point/activity/data.ts index dcb750cbb..51db7b0ed 100644 --- a/apps/web-ele/src/views/mall/promotion/point/activity/data.ts +++ b/apps/web-ele/src/views/mall/promotion/point/activity/data.ts @@ -3,50 +3,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import { DICT_TYPE } from '@vben/constants'; import { getDictOptions } from '@vben/hooks'; - -/** 表单配置 */ -export function useFormSchema(): VbenFormSchema[] { - return [ - { - fieldName: 'id', - component: 'Input', - dependencies: { - triggerFields: [''], - show: () => false, - }, - }, - { - fieldName: 'spuId', - label: '积分商城活动商品', - component: 'Input', - componentProps: { - placeholder: '请选择商品', - }, - rules: 'required', - }, - { - fieldName: 'sort', - label: '排序', - component: 'InputNumber', - componentProps: { - placeholder: '请输入排序', - min: 0, - controlsPosition: 'right', - class: '!w-full', - }, - rules: 'required', - }, - { - fieldName: 'remark', - label: '备注', - component: 'Textarea', - componentProps: { - placeholder: '请输入备注', - rows: 4, - }, - }, - ]; -} +import { fenToYuan } from '@vben/utils'; /** 列表的搜索表单 */ export function useGridFormSchema(): VbenFormSchema[] { @@ -56,15 +13,15 @@ export function useGridFormSchema(): VbenFormSchema[] { label: '活动状态', component: 'Select', componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), placeholder: '请选择活动状态', clearable: true, - options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), }, }, ]; } -/** 列表的字段 */ +/** 列表的表格列 */ export function useGridColumns(): VxeTableGridOptions['columns'] { return [ { @@ -78,6 +35,9 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { minWidth: 80, cellRender: { name: 'CellImage', + props: { + height: 40, + }, }, }, { @@ -89,18 +49,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { field: 'marketPrice', title: '原价', minWidth: 100, - formatter: 'formatFenToYuanAmount', - }, - { - field: 'point', - title: '兑换积分', - minWidth: 100, - }, - { - field: 'price', - title: '兑换金额', - minWidth: 100, - formatter: 'formatFenToYuanAmount', + formatter: ({ row }) => `¥${fenToYuan(row.marketPrice)}`, }, { field: 'status', @@ -125,7 +74,9 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { field: 'redeemedQuantity', title: '已兑换数量', minWidth: 100, - slots: { default: 'redeemedQuantity' }, + formatter: ({ row }) => { + return (row.totalStock || 0) - (row.stock || 0); + }, }, { field: 'createTime', @@ -141,3 +92,48 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, ]; } + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'sort', + label: '排序', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入排序', + controlsPosition: 'right', + class: '!w-full', + }, + defaultValue: 0, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 4, + }, + formItemClass: 'col-span-2', + }, + // TODO @puhui999:商品图太大了。 + { + fieldName: 'spuId', + label: '活动商品', + component: 'Input', + rules: 'required', + formItemClass: 'col-span-2', + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/promotion/point/activity/index.vue b/apps/web-ele/src/views/mall/promotion/point/activity/index.vue index 4fcebfad2..dc8b10534 100644 --- a/apps/web-ele/src/views/mall/promotion/point/activity/index.vue +++ b/apps/web-ele/src/views/mall/promotion/point/activity/index.vue @@ -1,10 +1,7 @@ - - - - - - - {{ getRedeemedQuantity(row) }} - +import type { MallSpuApi } from '#/api/mall/product/spu'; import type { MallPointActivityApi } from '#/api/mall/promotion/point'; +import type { + RuleConfig, + SpuProperty, +} from '#/views/mall/product/spu/components/type'; import { computed, ref } from 'vue'; -import { useVbenForm, useVbenModal } from '@vben/common-ui'; +import { useVbenModal } from '@vben/common-ui'; +import { cloneDeep, convertToInteger, formatToFraction } from '@vben/utils'; -import { ElMessage } from 'element-plus'; +import { ElButton, ElInputNumber, ElMessage } from 'element-plus'; +import { useVbenForm } from '#/adapter/form'; +import { VxeColumn } from '#/adapter/vxe-table'; +import { getSpu } from '#/api/mall/product/spu'; import { createPointActivity, getPointActivity, updatePointActivity, } from '#/api/mall/promotion/point'; import { $t } from '#/locales'; +import { + SpuAndSkuList, + SpuSkuSelect, +} from '#/views/mall/product/spu/components'; +import { getPropertyList } from '#/views/mall/product/spu/components/property-util'; import { useFormSchema } from '../data'; @@ -29,13 +43,110 @@ const [Form, formApi] = useVbenForm({ componentProps: { class: 'w-full', }, - labelWidth: 120, + formItemClass: 'col-span-2', + labelWidth: 100, }, layout: 'horizontal', schema: useFormSchema(), showDefaultActions: false, }); +// ================= 商品选择相关 ================= + +const spuSkuSelectRef = ref(); // 商品和属性选择 Ref +const spuAndSkuListRef = ref(); // SPU 和 SKU 列表组件 Ref + +const ruleConfig: RuleConfig[] = [ + { + name: 'productConfig.stock', + rule: (arg) => arg >= 1, + message: '商品可兑换库存必须大于等于 1 !!!', + }, + { + name: 'productConfig.point', + rule: (arg) => arg >= 1, + message: '商品所需兑换积分必须大于等于 1 !!!', + }, + { + name: 'productConfig.count', + rule: (arg) => arg >= 1, + message: '商品可兑换次数必须大于等于 1 !!!', + }, +]; // SKU 规则配置 + +const spuList = ref([]); // 选择的 SPU 列表 +const spuPropertyList = ref[]>([]); // SPU 属性列表 + +/** 打开商品选择器 */ +function openSpuSelect() { + spuSkuSelectRef.value?.open(); +} + +/** 选择商品后的回调 */ +async function handleSpuSelected(spuId: number, skuIds?: number[]) { + await formApi.setFieldValue('spuId', spuId); + await getSpuDetails(spuId, skuIds); +} + +/** 获取 SPU 详情 */ +async function getSpuDetails( + spuId: number, + skuIds?: number[], + products?: MallPointActivityApi.PointProduct[], +) { + const res = await getSpu(spuId); + if (!res) { + return; + } + + spuList.value = []; + + // 筛选指定的 SKU + const selectSkus = + skuIds === undefined + ? res.skus + : res.skus?.filter((sku) => skuIds.includes(sku.id!)); + // 为每个 SKU 配置积分商城相关的配置 + selectSkus?.forEach((sku) => { + let config: MallPointActivityApi.PointProduct = { + skuId: sku.id!, + stock: 0, + price: 0, + point: 0, + count: 0, + }; + // 如果是编辑模式,回填已有配置 + if (products !== undefined) { + const product = products.find((item) => item.skuId === sku.id); + if (product) { + product.price = formatToFraction(product.price) as unknown as number; + } + config = product || config; + } + // 动态添加 productConfig 属性到 SKU + ( + sku as MallSpuApi.Sku & { + productConfig: MallPointActivityApi.PointProduct; + } + ).productConfig = config; + }); + res.skus = selectSkus; + + const spuProperties: SpuProperty[] = [ + { + spuId: res.id!, + spuDetail: res, + propertyList: getPropertyList(res), + }, + ]; // 构建 SPU 属性列表 + + // 直接赋值,因为每次只选择一个 SPU + spuList.value = [res]; + spuPropertyList.value = spuProperties; +} + +// ================= end ================= + const [Modal, modalApi] = useVbenModal({ async onConfirm() { const { valid } = await formApi.validate(); @@ -43,19 +154,19 @@ const [Modal, modalApi] = useVbenModal({ return; } modalApi.lock(); - // 提交表单 - const data = - (await formApi.getValues()) as MallPointActivityApi.PointActivity; - - // 确保必要的默认值 - if (!data.products) { - data.products = []; - } - if (!data.sort) { - data.sort = 0; - } - try { + // 获取积分商城商品配置(深拷贝避免直接修改原对象) + const products: MallPointActivityApi.PointProduct[] = cloneDeep( + spuAndSkuListRef.value?.getSkuConfigs('productConfig') || [], + ); + // 价格需要转为分 + products.forEach((item) => { + item.price = convertToInteger(item.price); + }); + // 提交表单 + const data = + (await formApi.getValues()) as MallPointActivityApi.PointActivity; + data.products = products; await (formData.value?.id ? updatePointActivity(data) : createPointActivity(data)); @@ -69,17 +180,27 @@ const [Modal, modalApi] = useVbenModal({ }, async onOpenChange(isOpen: boolean) { if (!isOpen) { + // 重置表单数据(新增和编辑模式都需要) formData.value = undefined; + spuList.value = []; + spuPropertyList.value = []; return; } + // 加载数据 const data = modalApi.getData(); if (!data || !data.id) { return; } + // 加载数据 modalApi.lock(); try { formData.value = await getPointActivity(data.id); + await getSpuDetails( + formData.value.spuId, + formData.value.products?.map((sku) => sku.skuId), + formData.value.products, + ); // 设置到 values await formApi.setValues(formData.value); } finally { @@ -90,16 +211,85 @@ const [Modal, modalApi] = useVbenModal({ - - - - - 注意: - 积分活动涉及复杂的商品选择和SKU配置,当前为简化版本。 - 完整的商品选择和积分配置功能需要在后续版本中完善。 - - - - - + + + + + + + + 选择商品 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/mall/promotion/point/components/index.ts b/apps/web-ele/src/views/mall/promotion/point/components/index.ts index bb0194057..4271f4e17 100644 --- a/apps/web-ele/src/views/mall/promotion/point/components/index.ts +++ b/apps/web-ele/src/views/mall/promotion/point/components/index.ts @@ -1 +1,2 @@ export { default as PointShowcase } from './showcase.vue'; +export { default as PointTableSelect } from './table-select.vue';
- 注意: - 积分活动涉及复杂的商品选择和SKU配置,当前为简化版本。 - 完整的商品选择和积分配置功能需要在后续版本中完善。 -