From 3102eb511f5a83223e8a38a936cc4ad68afd5196 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sun, 14 Dec 2025 16:35:58 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E3=80=90ele=E3=80=91=E3=80=90mall?= =?UTF-8?q?=E3=80=91spu=20=E4=BB=A3=E7=A0=81=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web-ele/src/router/routes/modules/mall.ts | 15 +- .../mall/product/spu/components/index.ts | 5 + .../product/spu/components/property-util.ts | 36 + .../mall/product/spu/components/sku-list.vue | 641 ++++++++++++++++++ .../spu/components/spu-and-sku-list.vue | 166 +++++ .../product/spu/components/spu-select-data.ts | 168 +++++ .../product/spu/components/spu-select.vue | 319 +++++++++ .../views/mall/product/spu/components/type.ts | 32 + .../src/views/mall/product/spu/data.ts | 97 ++- .../{modules/form-data.ts => form/data.ts} | 83 ++- .../src/views/mall/product/spu/form/index.vue | 412 +++++++++++ .../spu/form/modules/product-attributes.vue | 235 +++++++ .../modules/product-property-add-form.vue | 147 ++++ .../src/views/mall/product/spu/index.vue | 127 ++-- .../views/mall/product/spu/modules/detail.vue | 504 -------------- .../views/mall/product/spu/modules/form.vue | 177 ----- 16 files changed, 2339 insertions(+), 825 deletions(-) create mode 100644 apps/web-ele/src/views/mall/product/spu/components/property-util.ts create mode 100644 apps/web-ele/src/views/mall/product/spu/components/sku-list.vue create mode 100644 apps/web-ele/src/views/mall/product/spu/components/spu-and-sku-list.vue create mode 100644 apps/web-ele/src/views/mall/product/spu/components/spu-select-data.ts create mode 100644 apps/web-ele/src/views/mall/product/spu/components/spu-select.vue create mode 100644 apps/web-ele/src/views/mall/product/spu/components/type.ts rename apps/web-ele/src/views/mall/product/spu/{modules/form-data.ts => form/data.ts} (73%) create mode 100644 apps/web-ele/src/views/mall/product/spu/form/index.vue create mode 100644 apps/web-ele/src/views/mall/product/spu/form/modules/product-attributes.vue create mode 100644 apps/web-ele/src/views/mall/product/spu/form/modules/product-property-add-form.vue delete mode 100644 apps/web-ele/src/views/mall/product/spu/modules/detail.vue delete mode 100644 apps/web-ele/src/views/mall/product/spu/modules/form.vue diff --git a/apps/web-ele/src/router/routes/modules/mall.ts b/apps/web-ele/src/router/routes/modules/mall.ts index 1c2f40496..db8fcab5e 100644 --- a/apps/web-ele/src/router/routes/modules/mall.ts +++ b/apps/web-ele/src/router/routes/modules/mall.ts @@ -18,7 +18,7 @@ const routes: RouteRecordRaw[] = [ title: '商品添加', activePath: '/mall/product/spu', }, - component: () => import('#/views/mall/product/spu/modules/form.vue'), + component: () => import('#/views/mall/product/spu/form/index.vue'), }, { path: String.raw`spu/edit/:id(\d+)`, @@ -27,25 +27,16 @@ const routes: RouteRecordRaw[] = [ title: '商品编辑', activePath: '/mall/product/spu', }, - component: () => import('#/views/mall/product/spu/modules/form.vue'), + component: () => import('#/views/mall/product/spu/form/index.vue'), }, { path: String.raw`spu/detail/:id(\d+)`, name: 'ProductSpuDetail', meta: { title: '商品详情', - activePath: '/crm/business', - }, - component: () => import('#/views/mall/product/spu/modules/detail.vue'), - }, - { - path: '/product/spu', - name: 'ProductSpu', - meta: { - title: '商品列表', activePath: '/mall/product/spu', }, - component: () => import('#/views/mall/product/spu/index.vue'), + component: () => import('#/views/mall/product/spu/form/index.vue'), }, ], }, diff --git a/apps/web-ele/src/views/mall/product/spu/components/index.ts b/apps/web-ele/src/views/mall/product/spu/components/index.ts index 122cbcea0..bedd27a45 100644 --- a/apps/web-ele/src/views/mall/product/spu/components/index.ts +++ b/apps/web-ele/src/views/mall/product/spu/components/index.ts @@ -1,3 +1,8 @@ +export * from './property-util'; +export { default as SkuList } from './sku-list.vue'; export { default as SkuTableSelect } from './sku-table-select.vue'; +export { default as SpuAndSkuList } from './spu-and-sku-list.vue'; +export { default as SpuSkuSelect } from './spu-select.vue'; export { default as SpuShowcase } from './spu-showcase.vue'; export { default as SpuTableSelect } from './spu-table-select.vue'; +export * from './type'; diff --git a/apps/web-ele/src/views/mall/product/spu/components/property-util.ts b/apps/web-ele/src/views/mall/product/spu/components/property-util.ts new file mode 100644 index 000000000..7f6524969 --- /dev/null +++ b/apps/web-ele/src/views/mall/product/spu/components/property-util.ts @@ -0,0 +1,36 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { MallSpuApi } from '#/api/mall/product/spu'; +import type { PropertyAndValues } from '#/views/mall/product/spu/components/type'; + +/** 获得商品的规格列表 - 商品相关的公共函数(被其它模块如 promotion 使用) */ +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 }; diff --git a/apps/web-ele/src/views/mall/product/spu/components/sku-list.vue b/apps/web-ele/src/views/mall/product/spu/components/sku-list.vue new file mode 100644 index 000000000..0ee803ae4 --- /dev/null +++ b/apps/web-ele/src/views/mall/product/spu/components/sku-list.vue @@ -0,0 +1,641 @@ + + + diff --git a/apps/web-ele/src/views/mall/product/spu/components/spu-and-sku-list.vue b/apps/web-ele/src/views/mall/product/spu/components/spu-and-sku-list.vue new file mode 100644 index 000000000..edf1dd8cb --- /dev/null +++ b/apps/web-ele/src/views/mall/product/spu/components/spu-and-sku-list.vue @@ -0,0 +1,166 @@ + + + diff --git a/apps/web-ele/src/views/mall/product/spu/components/spu-select-data.ts b/apps/web-ele/src/views/mall/product/spu/components/spu-select-data.ts new file mode 100644 index 000000000..bb1296995 --- /dev/null +++ b/apps/web-ele/src/views/mall/product/spu/components/spu-select-data.ts @@ -0,0 +1,168 @@ +import type { Ref } from 'vue'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridProps, VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { MallCategoryApi } from '#/api/mall/product/category'; +import type { MallSpuApi } from '#/api/mall/product/spu'; + +import { computed } from 'vue'; + +import { fenToYuan } from '@vben/utils'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema( + categoryTreeList: Ref, +): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '商品名称', + component: 'Input', + componentProps: { + placeholder: '请输入商品名称', + clearable: true, + }, + }, + { + fieldName: 'categoryId', + label: '商品分类', + component: 'TreeSelect', + componentProps: { + data: computed(() => categoryTreeList.value), + props: { + label: 'name', + value: 'id', + }, + checkStrictly: true, + placeholder: '请选择商品分类', + clearable: true, + filterable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +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: 'formatAmount2', + }, + { + 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']; +} + +/** SKU 列表的字段 */ +export function useSkuGridColumns(): VxeGridProps['columns'] { + return [ + { + type: 'radio', + width: 55, + }, + { + field: 'id', + title: '商品编号', + minWidth: 100, + align: 'center', + }, + { + field: 'picUrl', + title: '图片', + width: 100, + align: 'center', + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'properties', + title: '规格', + minWidth: 120, + align: 'center', + formatter: ({ cellValue }) => { + return ( + cellValue?.map((p: MallSpuApi.Property) => p.valueName)?.join(' ') || + '-' + ); + }, + }, + { + field: 'price', + title: '销售价(元)', + width: 120, + align: 'center', + formatter: ({ cellValue }) => { + return fenToYuan(cellValue); + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/product/spu/components/spu-select.vue b/apps/web-ele/src/views/mall/product/spu/components/spu-select.vue new file mode 100644 index 000000000..e49b31cfd --- /dev/null +++ b/apps/web-ele/src/views/mall/product/spu/components/spu-select.vue @@ -0,0 +1,319 @@ + + + diff --git a/apps/web-ele/src/views/mall/product/spu/components/type.ts b/apps/web-ele/src/views/mall/product/spu/components/type.ts new file mode 100644 index 000000000..7e1155af1 --- /dev/null +++ b/apps/web-ele/src/views/mall/product/spu/components/type.ts @@ -0,0 +1,32 @@ +/** 商品属性及其值的树形结构(用于前端展示和操作) */ +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; +} + +// Re-export for use in generic constraint + +export { type MallSpuApi } from '#/api/mall/product/spu'; diff --git a/apps/web-ele/src/views/mall/product/spu/data.ts b/apps/web-ele/src/views/mall/product/spu/data.ts index f667c6948..7b60e836b 100644 --- a/apps/web-ele/src/views/mall/product/spu/data.ts +++ b/apps/web-ele/src/views/mall/product/spu/data.ts @@ -2,11 +2,17 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { MallSpuApi } from '#/api/mall/product/spu'; -import { handleTree } from '@vben/utils'; +import { fenToYuan, handleTree, treeToString } from '@vben/utils'; import { getCategoryList } from '#/api/mall/product/category'; import { getRangePickerDefaultProps } from '#/utils'; +/** 关联数据 */ +let categoryList: any[] = []; +getCategoryList({}).then((data) => { + categoryList = handleTree(data, 'id', 'parentId', 'children'); +}); + /** 列表的搜索表单 */ export function useGridFormSchema(): VbenFormSchema[] { return [ @@ -14,16 +20,19 @@ export function useGridFormSchema(): VbenFormSchema[] { fieldName: 'name', label: '商品名称', component: 'Input', + componentProps: { + placeholder: '请输入商品名称', + clearable: true, + }, }, { fieldName: 'categoryId', label: '商品分类', component: 'ApiTreeSelect', componentProps: { - api: async () => { - const res = await getCategoryList({}); - return handleTree(res, 'id', 'parentId', 'children'); - }, + placeholder: '请选择商品分类', + clearable: true, + options: categoryList, labelField: 'name', valueField: 'id', childrenField: 'children', @@ -49,16 +58,11 @@ export function useGridColumns( ) => PromiseLike, ): VxeTableGridOptions['columns'] { return [ - { - type: 'expand', - width: 80, - slots: { content: 'expand_content' }, - fixed: 'left', - }, { field: 'id', title: '商品编号', fixed: 'left', + minWidth: 100, }, { field: 'name', @@ -69,30 +73,23 @@ export function useGridColumns( { field: 'picUrl', title: '商品图片', + minWidth: 100, cellRender: { name: 'CellImage', }, }, { - field: 'price', - title: '价格', - formatter: 'formatFenToYuanAmount', - }, - { - field: 'salesCount', - title: '销量', - }, - { - field: 'stock', - title: '库存', - }, - { - field: 'sort', - title: '排序', + field: 'categoryId', + title: '商品分类', + minWidth: 150, + formatter: ({ row }) => { + return treeToString(categoryList, row.categoryId); + }, }, { field: 'status', title: '销售状态', + minWidth: 100, cellRender: { attrs: { beforeChange: onStatusChange }, name: 'CellSwitch', @@ -104,9 +101,57 @@ export function useGridColumns( }, }, }, + { + field: 'price', + title: '价格', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + field: 'marketPrice', + title: '市场价', + minWidth: 100, + formatter: ({ row }) => { + return `${fenToYuan(row.marketPrice)} 元`; + }, + }, + { + field: 'costPrice', + title: '成本价', + minWidth: 100, + formatter: ({ row }) => { + return `${fenToYuan(row.costPrice)} 元`; + }, + }, + { + field: 'salesCount', + title: '销量', + minWidth: 80, + }, + { + field: 'virtualSalesCount', + title: '虚拟销量', + minWidth: 100, + }, + { + field: 'stock', + title: '库存', + minWidth: 80, + }, + { + field: 'browseCount', + title: '浏览量', + minWidth: 100, + }, + { + field: 'sort', + title: '排序', + minWidth: 80, + }, { field: 'createTime', title: '创建时间', + minWidth: 160, formatter: 'formatDateTime', }, { diff --git a/apps/web-ele/src/views/mall/product/spu/modules/form-data.ts b/apps/web-ele/src/views/mall/product/spu/form/data.ts similarity index 73% rename from apps/web-ele/src/views/mall/product/spu/modules/form-data.ts rename to apps/web-ele/src/views/mall/product/spu/form/data.ts index b8cf52008..5cc80e16e 100644 --- a/apps/web-ele/src/views/mall/product/spu/modules/form-data.ts +++ b/apps/web-ele/src/views/mall/product/spu/form/data.ts @@ -4,7 +4,6 @@ import { DeliveryTypeEnum, DICT_TYPE } from '@vben/constants'; import { getDictOptions } from '@vben/hooks'; import { handleTree } from '@vben/utils'; -import { z } from '#/adapter/form'; import { getSimpleBrandList } from '#/api/mall/product/brand'; import { getCategoryList } from '#/api/mall/product/category'; import { getSimpleTemplateList } from '#/api/mall/trade/delivery/expressTemplate'; @@ -25,7 +24,7 @@ export function useInfoFormSchema(): VbenFormSchema[] { label: '商品名称', component: 'Input', componentProps: { - allowClear: true, + clearable: true, placeholder: '请输入商品名称', }, rules: 'required', @@ -33,14 +32,15 @@ export function useInfoFormSchema(): VbenFormSchema[] { { fieldName: 'categoryId', label: '分类名称', - // component: 'ApiCascader', component: 'ApiTreeSelect', componentProps: { api: async () => { const data = await getCategoryList({}); return handleTree(data); }, - fieldNames: { label: 'name', value: 'id', children: 'children' }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', placeholder: '请选择商品分类', }, rules: 'required', @@ -53,7 +53,7 @@ export function useInfoFormSchema(): VbenFormSchema[] { api: getSimpleBrandList, labelField: 'name', valueField: 'id', - allowClear: true, + clearable: true, placeholder: '请选择商品品牌', }, rules: 'required', @@ -73,10 +73,10 @@ export function useInfoFormSchema(): VbenFormSchema[] { component: 'Textarea', componentProps: { placeholder: '请输入商品简介', - autoSize: { minRows: 2, maxRows: 2 }, - showCount: true, + autosize: { minRows: 2, maxRows: 2 }, + showWordLimit: true, maxlength: 128, - allowClear: true, + clearable: true, }, rules: 'required', }, @@ -104,7 +104,10 @@ export function useInfoFormSchema(): VbenFormSchema[] { } /** 价格库存的表单 */ -export function useSkuFormSchema(): VbenFormSchema[] { +export function useSkuFormSchema( + propertyList: any[] = [], + isDetail: boolean = false, +): VbenFormSchema[] { return [ { fieldName: 'id', @@ -119,7 +122,7 @@ export function useSkuFormSchema(): VbenFormSchema[] { label: '分销类型', component: 'RadioGroup', componentProps: { - allowClear: true, + clearable: true, options: [ { label: '默认设置', @@ -138,7 +141,7 @@ export function useSkuFormSchema(): VbenFormSchema[] { label: '商品规格', component: 'RadioGroup', componentProps: { - allowClear: true, + clearable: true, options: [ { label: '单规格', @@ -152,7 +155,51 @@ export function useSkuFormSchema(): VbenFormSchema[] { }, rules: 'required', }, - // TODO @xingyu:待补充商品属性 + // 单规格时显示的 SkuList + { + fieldName: 'singleSkuList', + label: '', + component: 'Input', + dependencies: { + triggerFields: ['specType'], + // 当 specType 为 false(单规格)时显示 + show: (values) => values.specType === false, + }, + }, + // 多规格时显示的商品属性(占位,实际通过插槽渲染) + { + fieldName: 'productAttributes', + label: '商品属性', + component: 'Input', + dependencies: { + triggerFields: ['specType'], + // 当 specType 为 true(多规格)时显示 + show: (values) => values.specType === true, + }, + }, + // 多规格 - 批量设置 + { + fieldName: 'batchSkuList', + label: '批量设置', + component: 'Input', + dependencies: { + triggerFields: ['specType'], + // 当 specType 为 true(多规格)且 propertyList 有数据时显示,且非详情模式 + show: (values) => + values.specType === true && propertyList.length > 0 && !isDetail, + }, + }, + // 多规格 - 规格列表 + { + fieldName: 'multiSkuList', + label: '规格列表', + component: 'Input', + dependencies: { + triggerFields: ['specType'], + // 当 specType 为 true(多规格)且 propertyList 有数据时显示 + show: (values) => values.specType === true && propertyList.length > 0, + }, + }, ]; } @@ -237,10 +284,8 @@ export function useOtherFormSchema(): VbenFormSchema[] { component: 'InputNumber', componentProps: { min: 0, - controlsPosition: 'right', - class: '!w-full', }, - rules: z.number().min(0).optional().default(0), + rules: 'required', }, { fieldName: 'giveIntegral', @@ -248,10 +293,8 @@ export function useOtherFormSchema(): VbenFormSchema[] { component: 'InputNumber', componentProps: { min: 0, - controlsPosition: 'right', - class: '!w-full', }, - rules: z.number().min(0).optional().default(0), + rules: 'required', }, { fieldName: 'virtualSalesCount', @@ -259,10 +302,8 @@ export function useOtherFormSchema(): VbenFormSchema[] { component: 'InputNumber', componentProps: { min: 0, - controlsPosition: 'right', - class: '!w-full', }, - rules: z.number().min(0).optional().default(0), + rules: 'required', }, ]; } diff --git a/apps/web-ele/src/views/mall/product/spu/form/index.vue b/apps/web-ele/src/views/mall/product/spu/form/index.vue new file mode 100644 index 000000000..d90f07b08 --- /dev/null +++ b/apps/web-ele/src/views/mall/product/spu/form/index.vue @@ -0,0 +1,412 @@ + + + + + diff --git a/apps/web-ele/src/views/mall/product/spu/form/modules/product-attributes.vue b/apps/web-ele/src/views/mall/product/spu/form/modules/product-attributes.vue new file mode 100644 index 000000000..a81137fd0 --- /dev/null +++ b/apps/web-ele/src/views/mall/product/spu/form/modules/product-attributes.vue @@ -0,0 +1,235 @@ + + + + diff --git a/apps/web-ele/src/views/mall/product/spu/form/modules/product-property-add-form.vue b/apps/web-ele/src/views/mall/product/spu/form/modules/product-property-add-form.vue new file mode 100644 index 000000000..99653e17b --- /dev/null +++ b/apps/web-ele/src/views/mall/product/spu/form/modules/product-property-add-form.vue @@ -0,0 +1,147 @@ + + + + diff --git a/apps/web-ele/src/views/mall/product/spu/index.vue b/apps/web-ele/src/views/mall/product/spu/index.vue index 61ae8d8d0..f206c5f92 100644 --- a/apps/web-ele/src/views/mall/product/spu/index.vue +++ b/apps/web-ele/src/views/mall/product/spu/index.vue @@ -7,17 +7,11 @@ import { useRoute, useRouter } from 'vue-router'; import { confirm, DocAlert, Page } from '@vben/common-ui'; import { ProductSpuStatusEnum } from '@vben/constants'; -import { - downloadFileFromBlobPart, - fenToYuan, - handleTree, - treeToString, -} from '@vben/utils'; +import { downloadFileFromBlobPart } from '@vben/utils'; -import { ElDescriptions, ElLoading, ElMessage, ElTabs } from 'element-plus'; +import { ElLoading, ElMessage, ElTabs } from 'element-plus'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; -import { getCategoryList } from '#/api/mall/product/category'; import { deleteSpu, exportSpu, @@ -30,11 +24,8 @@ import { $t } from '#/locales'; import { useGridColumns, useGridFormSchema } from './data'; const { push } = useRouter(); -const tabType = ref(0); const route = useRoute(); -const categoryList = ref(); - -// tabs 数据 +const tabType = ref('0'); const tabsData = ref([ { name: '出售中', @@ -69,13 +60,19 @@ async function handleRefresh() { await getTabCount(); } +/** 导出表格 */ +async function handleExport() { + const data = await exportSpu(await gridApi.formApi.getValues()); + downloadFileFromBlobPart({ fileName: '商品.xls', source: data }); +} + /** 获得每个 Tab 的数量 */ async function getTabCount() { const res = await getTabsCount(); for (const objName in res) { const index = Number(objName); if (tabsData.value[index]) { - tabsData.value[index].count = res[objName] as number; + tabsData.value[index].count = res[objName]!; } } } @@ -85,12 +82,6 @@ function handleCreate() { push({ name: 'ProductSpuAdd' }); } -/** 导出表格 */ -async function handleExport() { - const data = await exportSpu(await gridApi.formApi.getValues()); - downloadFileFromBlobPart({ fileName: '商品.xls', source: data }); -} - /** 编辑商品 */ function handleEdit(row: MallSpuApi.Spu) { push({ name: 'ProductSpuEdit', params: { id: row.id } }); @@ -102,7 +93,7 @@ async function handleDelete(row: MallSpuApi.Spu) { text: $t('ui.actionMessage.deleting', [row.name]), }); try { - await deleteSpu(row.id as number); + await deleteSpu(row.id!); ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.name])); await handleRefresh(); } finally { @@ -110,19 +101,6 @@ async function handleDelete(row: MallSpuApi.Spu) { } } -/** 添加到仓库 / 回收站的状态 */ -async function handleStatus02Change(row: MallSpuApi.Spu, newStatus: number) { - // 二次确认 - const text = - newStatus === ProductSpuStatusEnum.RECYCLE.status - ? '加入到回收站' - : '恢复到仓库'; - await confirm(`确认要"${row.name}"${text}吗?`); - await updateStatus({ id: row.id as number, status: newStatus }); - ElMessage.success(`${text}成功`); - await handleRefresh(); -} - /** 更新状态 */ async function handleStatusChange( newStatus: number, @@ -130,14 +108,14 @@ async function handleStatusChange( ): Promise { return new Promise((resolve, reject) => { // 二次确认 - const text = row.status ? '上架' : '下架'; + const text = newStatus ? '上架' : '下架'; confirm({ content: `确认要${text + row.name}吗?`, }) .then(async () => { // 更新状态 await updateStatus({ - id: row.id as number, + id: row.id!, status: newStatus, }); // 提示并返回成功 @@ -150,6 +128,27 @@ async function handleStatusChange( }); } +/** 添加到仓库 / 回收站的状态 */ +async function handleStatus02Change(row: MallSpuApi.Spu, newStatus: number) { + const text = + newStatus === ProductSpuStatusEnum.RECYCLE.status + ? '加入到回收站' + : '恢复到仓库'; + await confirm({ + content: `确认要"${row.name}"${text}吗?`, + }); + const loadingInstance = ElLoading.service({ + text: `正在${text}中...`, + }); + try { + await updateStatus({ id: row.id!, status: newStatus }); + ElMessage.success(`${text}成功`); + await handleRefresh(); + } finally { + loadingInstance.close(); + } +} + /** 查看商品详情 */ function handleDetail(row: MallSpuApi.Spu) { push({ name: 'ProductSpuDetail', params: { id: row.id } }); @@ -162,12 +161,6 @@ const [Grid, gridApi] = useVbenVxeGrid({ gridOptions: { columns: useGridColumns(handleStatusChange), height: 'auto', - cellConfig: { - height: 80, - }, - expandConfig: { - height: 100, - }, keepSource: true, proxyConfig: { ajax: { @@ -175,7 +168,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ return await getSpuPage({ pageNo: page.currentPage, pageSize: page.pageSize, - tabType: tabType.value, + tabType: Number(tabType.value), ...formValues, }); }, @@ -183,7 +176,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ }, rowConfig: { keyField: 'id', - resizable: true, + isHover: true, }, toolbarConfig: { refresh: true, @@ -192,8 +185,8 @@ const [Grid, gridApi] = useVbenVxeGrid({ } as VxeTableGridOptions, }); -function onChangeTab(key: any) { - tabType.value = Number(key); +function onChangeTab(key: number | string) { + tabType.value = key.toString(); gridApi.query(); } @@ -204,9 +197,8 @@ onMounted(async () => { categoryId: Number(route.query.categoryId), }); } + // 获得每个 Tab 的数量 await getTabCount(); - const categoryRes = await getCategoryList({}); - categoryList.value = handleTree(categoryRes, 'id', 'parentId', 'children'); }); @@ -221,12 +213,12 @@ onMounted(async () => { @@ -250,39 +242,6 @@ onMounted(async () => { ]" /> - -