diff --git a/apps/web-antd/src/api/mall/product/spu/index.ts b/apps/web-antd/src/api/mall/product/spu/index.ts index 4b37151c6..0c9f4ce94 100644 --- a/apps/web-antd/src/api/mall/product/spu/index.ts +++ b/apps/web-antd/src/api/mall/product/spu/index.ts @@ -62,13 +62,6 @@ export namespace MallSpuApi { valueName?: string; // 属性值名称 } - // TODO @puhui999:这个还要么? - /** 优惠券模板 */ - export interface GiveCouponTemplate { - id?: number; // 优惠券编号 - name?: string; // 优惠券名称 - } - /** 商品状态更新请求 */ export interface SpuStatusUpdateReqVO { id: number; // 商品编号 diff --git a/apps/web-antd/src/api/mall/promotion/bargain/bargainActivity.ts b/apps/web-antd/src/api/mall/promotion/bargain/bargainActivity.ts index 97917ec2d..f13b64916 100644 --- a/apps/web-antd/src/api/mall/promotion/bargain/bargainActivity.ts +++ b/apps/web-antd/src/api/mall/promotion/bargain/bargainActivity.ts @@ -1,7 +1,5 @@ import type { PageParam, PageResult } from '@vben/request'; -import type { MallSpuApi } from '#/api/mall/product/spu'; - import { requestClient } from '#/api/request'; export namespace MallBargainActivityApi { @@ -32,17 +30,6 @@ export namespace MallBargainActivityApi { bargainMinPrice: number; // 砍价底价 stock: number; // 活动库存 } - - // TODO @puhui999:要不要删除? - /** 扩展 SKU 配置 */ - export type SkuExtension = { - productConfig: BargainProduct; // 砍价活动配置 - } & MallSpuApi.Sku; - - /** 扩展 SPU 配置 */ - export interface SpuExtension extends MallSpuApi.Spu { - skus: SkuExtension[]; // SKU 列表 - } } /** 查询砍价活动列表 */ 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 00471bd65..e497ae204 100644 --- a/apps/web-antd/src/api/mall/promotion/combination/combinationActivity.ts +++ b/apps/web-antd/src/api/mall/promotion/combination/combinationActivity.ts @@ -1,7 +1,5 @@ import type { PageParam, PageResult } from '@vben/request'; -import type { MallSpuApi } from '#/api/mall/product/spu'; - import { requestClient } from '#/api/request'; export namespace MallCombinationActivityApi { @@ -25,23 +23,12 @@ export namespace MallCombinationActivityApi { products: CombinationProduct[]; // 商品列表 } - // TODO @puhui999:要不要删除? /** 拼团活动所需属性 */ export interface CombinationProduct { spuId: number; // 商品 SPU 编号 skuId: number; // 商品 SKU 编号 combinationPrice: number; // 拼团价格 } - - /** 扩展 SKU 配置 */ - export type SkuExtension = { - productConfig: CombinationProduct; // 拼团活动配置 - } & MallSpuApi.Sku; - - /** 扩展 SPU 配置 */ - export interface SpuExtension extends MallSpuApi.Spu { - skus: SkuExtension[]; // SKU 列表 - } } /** 查询拼团活动列表 */ diff --git a/apps/web-antd/src/api/mall/promotion/discount/discountActivity.ts b/apps/web-antd/src/api/mall/promotion/discount/discountActivity.ts index 3beaa1aef..1236140f4 100644 --- a/apps/web-antd/src/api/mall/promotion/discount/discountActivity.ts +++ b/apps/web-antd/src/api/mall/promotion/discount/discountActivity.ts @@ -1,7 +1,5 @@ import type { PageParam, PageResult } from '@vben/request'; -import type { MallSpuApi } from '#/api/mall/product/spu'; - import { requestClient } from '#/api/request'; export namespace MallDiscountActivityApi { @@ -25,17 +23,6 @@ export namespace MallDiscountActivityApi { endTime?: Date; // 结束时间 products?: DiscountProduct[]; // 商品列表 } - - // TODO @puhui999:要不要删除? - /** 扩展 SKU 配置 */ - export type SkuExtension = { - productConfig: DiscountProduct; // 限时折扣配置 - } & MallSpuApi.Sku; - - /** 扩展 SPU 配置 */ - export interface SpuExtension extends MallSpuApi.Spu { - skus: SkuExtension[]; // SKU 列表 - } } /** 查询限时折扣活动列表 */ diff --git a/apps/web-antd/src/api/mall/promotion/point/index.ts b/apps/web-antd/src/api/mall/promotion/point/index.ts index 99f39bced..3cf24a4b2 100644 --- a/apps/web-antd/src/api/mall/promotion/point/index.ts +++ b/apps/web-antd/src/api/mall/promotion/point/index.ts @@ -36,17 +36,6 @@ export namespace MallPointActivityApi { price: number; // 兑换金额,单位:分 } - // TODO @puhui999:这些还需要么? - /** 扩展 SKU 配置 */ - export type SkuExtension = { - productConfig: PointProduct; // 积分商城商品配置 - } & MallSpuApi.Sku; - - /** 扩展 SPU 配置 */ - export interface SpuExtension extends MallSpuApi.Spu { - skus: SkuExtension[]; // SKU 列表 - } - /** 扩展 SPU 配置(带积分信息) */ export interface SpuExtensionWithPoint extends MallSpuApi.Spu { pointStock: number; // 积分商城活动库存 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 51da7d275..82297effd 100644 --- a/apps/web-antd/src/api/mall/promotion/seckill/seckillActivity.ts +++ b/apps/web-antd/src/api/mall/promotion/seckill/seckillActivity.ts @@ -1,7 +1,5 @@ import type { PageParam, PageResult } from '@vben/request'; -import type { MallSpuApi } from '#/api/mall/product/spu'; - import { requestClient } from '#/api/request'; export namespace MallSeckillActivityApi { @@ -34,17 +32,6 @@ export namespace MallSeckillActivityApi { seckillPrice?: number; // 秒杀价格 products?: SeckillProduct[]; // 秒杀商品列表 } - - // TODO @puhui999:这些还需要么? - /** 扩展 SKU 配置 */ - export type SkuExtension = { - productConfig: SeckillProduct; // 秒杀商品配置 - } & MallSpuApi.Sku; - - /** 扩展 SPU 配置 */ - export interface SpuExtension extends MallSpuApi.Spu { - skus: SkuExtension[]; // SKU 列表 - } } /** 查询秒杀活动列表 */ diff --git a/apps/web-antd/src/views/mall/product/comment/data.ts b/apps/web-antd/src/views/mall/product/comment/data.ts index 62f9f956d..71d88a8a6 100644 --- a/apps/web-antd/src/views/mall/product/comment/data.ts +++ b/apps/web-antd/src/views/mall/product/comment/data.ts @@ -3,7 +3,6 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { MallCommentApi } from '#/api/mall/product/comment'; import { z } from '#/adapter/form'; -import { getSpuSimpleList } from '#/api/mall/product/spu'; import { getRangePickerDefaultProps } from '#/utils'; /** 新增/修改的表单 */ @@ -21,13 +20,13 @@ export function useFormSchema(): VbenFormSchema[] { { fieldName: 'spuId', label: '商品', - component: 'ApiSelect', + component: 'Input', componentProps: { - api: getSpuSimpleList, - labelField: 'name', - valueField: 'id', placeholder: '请选择商品', }, + renderComponentContent: () => ({ + default: () => null, + }), rules: 'required', }, { @@ -41,6 +40,9 @@ export function useFormSchema(): VbenFormSchema[] { triggerFields: ['spuId'], show: (values) => !!values.spuId, }, + renderComponentContent: () => ({ + default: () => null, + }), rules: 'required', }, { diff --git a/apps/web-antd/src/views/mall/product/comment/modules/form.vue b/apps/web-antd/src/views/mall/product/comment/modules/form.vue index e00983689..b756e00d4 100644 --- a/apps/web-antd/src/views/mall/product/comment/modules/form.vue +++ b/apps/web-antd/src/views/mall/product/comment/modules/form.vue @@ -1,21 +1,31 @@ - + diff --git a/apps/web-antd/src/views/mall/product/spu/components/spu-and-sku-list.vue b/apps/web-antd/src/views/mall/product/spu/components/spu-and-sku-list.vue new file mode 100644 index 000000000..971843c2c --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/spu-and-sku-list.vue @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + {{ formatToFraction(row.price) }} + + + + + + + 删除 + + + + diff --git a/apps/web-antd/src/views/mall/product/spu/components/spu-select-data.ts b/apps/web-antd/src/views/mall/product/spu/components/spu-select-data.ts new file mode 100644 index 000000000..01ef4c1a2 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/spu-select-data.ts @@ -0,0 +1,129 @@ +import type { Ref } from 'vue'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { MallCategoryApi } from '#/api/mall/product/category'; + +import { computed } from 'vue'; + +import { formatToFraction } from '@vben/utils'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** + * @description: 列表的搜索表单 + */ +export function useGridFormSchema( + categoryTreeList: Ref, +): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '商品名称', + component: 'Input', + componentProps: { + placeholder: '请输入商品名称', + allowClear: true, + }, + }, + { + fieldName: 'categoryId', + label: '商品分类', + component: 'TreeSelect', + componentProps: { + treeData: computed(() => categoryTreeList.value), + fieldNames: { + label: 'name', + value: 'id', + }, + treeCheckStrictly: true, + placeholder: '请选择商品分类', + allowClear: true, + showSearch: true, + treeNodeFilterProp: 'name', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** + * @description: 列表的字段 + */ +export function useGridColumns( + isSelectSku: boolean, +): VxeTableGridOptions['columns'] { + return [ + { + type: 'expand', + width: 30, + visible: isSelectSku, + slots: { content: 'expand_content' }, + }, + { type: 'checkbox', width: 55 }, + { + field: 'id', + title: '商品编号', + minWidth: 100, + align: 'center', + }, + { + field: 'picUrl', + title: '商品图', + width: 100, + align: 'center', + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'name', + title: '商品名称', + minWidth: 300, + showOverflow: 'tooltip', + }, + { + field: 'price', + title: '商品售价', + minWidth: 90, + align: 'center', + formatter: ({ cellValue }) => { + // 格式化价格显示(价格以分为单位存储) + return formatToFraction(cellValue); + }, + }, + { + field: 'salesCount', + title: '销量', + minWidth: 90, + align: 'center', + }, + { + field: 'stock', + title: '库存', + minWidth: 90, + align: 'center', + }, + { + field: 'sort', + title: '排序', + minWidth: 70, + align: 'center', + }, + { + field: 'createTime', + title: '创建时间', + width: 180, + align: 'center', + formatter: 'formatDateTime', + }, + ] as VxeTableGridOptions['columns']; +} diff --git a/apps/web-antd/src/views/mall/product/spu/components/spu-select.vue b/apps/web-antd/src/views/mall/product/spu/components/spu-select.vue new file mode 100644 index 000000000..bddc5f840 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/spu-select.vue @@ -0,0 +1,325 @@ + + + + + + + + + + + + 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 6647ae649..c9064f2de 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 @@ -5,11 +5,12 @@ import type { VxeGridProps } from '#/adapter/vxe-table'; import type { MallCategoryApi } from '#/api/mall/product/category'; import type { MallSpuApi } from '#/api/mall/product/spu'; -import { computed, onMounted, ref } from 'vue'; +import { computed, nextTick, onMounted, ref } from 'vue'; -import { useVbenModal } from '@vben/common-ui'; import { handleTree } from '@vben/utils'; +import { Modal } from 'ant-design-vue'; + import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { getCategoryList } from '#/api/mall/product/category'; import { getSpuPage } from '#/api/mall/product/spu'; @@ -30,12 +31,16 @@ const emit = defineEmits<{ const categoryList = ref([]); // 分类列表 const categoryTreeList = ref([]); // 分类树 +/** 弹窗显示状态 */ +const visible = ref(false); +const initData = ref(); + /** 单选:处理选中变化 */ function handleRadioChange() { const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Spu; if (selectedRow) { emit('change', selectedRow); - modalApi.close(); + closeModal(); } } @@ -159,25 +164,16 @@ const [Grid, gridApi] = useVbenVxeGrid({ }, }); -const [Modal, modalApi] = useVbenModal({ - destroyOnClose: true, - showConfirmButton: props.multiple, // 特殊:radio 单选情况下,走 handleRadioChange 处理。 - onConfirm: () => { - const selectedRows = gridApi.grid.getCheckboxRecords() as MallSpuApi.Spu[]; - emit('change', selectedRows); - modalApi.close(); - }, - async onOpenChange(isOpen: boolean) { - if (!isOpen) { - await gridApi.grid.clearCheckboxRow(); - await gridApi.grid.clearRadioRow(); - return; - } - +/** 打开弹窗 */ +async function openModal(data?: MallSpuApi.Spu | MallSpuApi.Spu[]) { + initData.value = data; + visible.value = true; + // 等待 Grid 组件完全初始化后再查询数据 + await nextTick(); + if (gridApi.grid) { // 1. 先查询数据 await gridApi.query(); // 2. 设置已选中行 - const data = modalApi.getData(); if (props.multiple && Array.isArray(data) && data.length > 0) { setTimeout(() => { const tableData = gridApi.grid.getTableData().fullData; @@ -201,14 +197,27 @@ const [Modal, modalApi] = useVbenModal({ } }, 300); } - }, -}); + } +} + +/** 关闭弹窗 */ +async function closeModal() { + visible.value = false; + await gridApi.grid.clearCheckboxRow(); + await gridApi.grid.clearRadioRow(); + initData.value = undefined; +} + +/** 确认选择(多选模式) */ +function handleConfirm() { + const selectedRows = gridApi.grid.getCheckboxRecords() as MallSpuApi.Spu[]; + emit('change', selectedRows); + closeModal(); +} /** 对外暴露的方法 */ defineExpose({ - open: (data?: MallSpuApi.Spu | MallSpuApi.Spu[]) => { - modalApi.setData(data).open(); - }, + open: openModal, }); /** 初始化分类数据 */ @@ -219,7 +228,15 @@ onMounted(async () => { - + diff --git a/apps/web-antd/src/views/mall/product/spu/components/type.ts b/apps/web-antd/src/views/mall/product/spu/components/type.ts new file mode 100644 index 000000000..ba3ffc292 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/type.ts @@ -0,0 +1,28 @@ +/** 商品属性及其值的树形结构(用于前端展示和操作) */ +export interface PropertyAndValues { + id: number; + name: string; + values?: PropertyAndValues[]; +} + +export interface RuleConfig { + // 需要校验的字段 + // 例:name: 'name' 则表示校验 sku.name 的值 + // 例:name: 'productConfig.stock' 则表示校验 sku.productConfig.name 的值,此处 productConfig 表示我在 Sku 上扩展的属性 + name: string; + // 校验规格为一个毁掉函数,其中 arg 为需要校验的字段的值。 + // 例:需要校验价格必须大于0.01 + // { + // name:'price', + // rule:(arg: number) => arg > 0.01 + // } + rule: (arg: any) => boolean; + // 校验不通过时的消息提示 + message: string; +} + +export interface SpuProperty { + propertyList: PropertyAndValues[]; + spuDetail: T; + spuId: number; +} diff --git a/apps/web-antd/src/views/mall/product/spu/form/index.ts b/apps/web-antd/src/views/mall/product/spu/form/index.ts deleted file mode 100644 index 3ba57c4d9..000000000 --- a/apps/web-antd/src/views/mall/product/spu/form/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { MallSpuApi } from '#/api/mall/product/spu'; - -// TODO @puhui999:这个是不是 api 后端有定义类似的?如果是,是不是放到 api 哈? -export interface PropertyAndValues { - id: number; - name: string; - values?: PropertyAndValues[]; -} - -export interface RuleConfig { - // 需要校验的字段 - // 例:name: 'name' 则表示校验 sku.name 的值 - // 例:name: 'productConfig.stock' 则表示校验 sku.productConfig.name 的值,此处 productConfig 表示我在 Sku 上扩展的属性 - name: string; - // 校验规格为一个毁掉函数,其中 arg 为需要校验的字段的值。 - // 例:需要校验价格必须大于0.01 - // { - // name:'price', - // rule:(arg: number) => arg > 0.01 - // } - rule: (arg: any) => boolean; - // 校验不通过时的消息提示 - message: string; -} - -// TODO @puhui999:这个是只有 index.ts 在用么?还是别的模块也会用 -/** 获得商品的规格列表 - 商品相关的公共函数 */ -const getPropertyList = (spu: MallSpuApi.Spu): PropertyAndValues[] => { - // 直接拿返回的 skus 属性逆向生成出 propertyList - const properties: PropertyAndValues[] = []; - // 只有是多规格才处理 - if (spu.specType) { - spu.skus?.forEach((sku) => { - sku.properties?.forEach( - ({ propertyId, propertyName, valueId, valueName }) => { - // 添加属性 - if (!properties?.some((item) => item.id === propertyId)) { - properties.push({ - id: propertyId!, - name: propertyName!, - values: [], - }); - } - // 添加属性值 - const index = properties?.findIndex((item) => item.id === propertyId); - if ( - !properties[index]?.values?.some((value) => value.id === valueId) - ) { - properties[index]?.values?.push({ id: valueId!, name: valueName! }); - } - }, - ); - }); - } - return properties; -}; - -export { getPropertyList }; - -// 导出组件 -// TODO @puhui999:如果 sku-list.vue 要对外,可以考虑在 spu 下面,搞个 components 模块;(目前看,别的模块应该会用到哈。);modules 是当前模块用到的,components 是跨模块要用到的。 -export { default as SkuList } from './modules/sku-list.vue'; diff --git a/apps/web-antd/src/views/mall/product/spu/form/index.vue b/apps/web-antd/src/views/mall/product/spu/form/index.vue index 78c5d6a4b..7048776bb 100644 --- a/apps/web-antd/src/views/mall/product/spu/form/index.vue +++ b/apps/web-antd/src/views/mall/product/spu/form/index.vue @@ -1,7 +1,9 @@ - - - - - - - 选择商品 - + + + + + + + + 选择商品 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - +