From a7b9292b8d5ab9eeacee7f4a5bf669f436439a36 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 21 Oct 2025 23:48:41 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E3=80=90antd=E3=80=91mall=20?= =?UTF-8?q?=E5=95=86=E5=93=81=E5=88=97=E8=A1=A8=E7=9A=84=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/mall/product/spu/data.ts | 97 ++++++++++---- .../src/views/mall/product/spu/index.vue | 126 ++++++------------ 2 files changed, 110 insertions(+), 113 deletions(-) diff --git a/apps/web-antd/src/views/mall/product/spu/data.ts b/apps/web-antd/src/views/mall/product/spu/data.ts index 138a2b53c..4373022bd 100644 --- a/apps/web-antd/src/views/mall/product/spu/data.ts +++ b/apps/web-antd/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: '请输入商品名称', + allowClear: true, + }, }, { fieldName: 'categoryId', label: '商品分类', component: 'ApiTreeSelect', componentProps: { - api: async () => { - const res = await getCategoryList({}); - return handleTree(res, 'id', 'parentId', 'children'); - }, + placeholder: '请选择商品分类', + allowClear: true, + options: categoryList, fieldNames: { label: 'name', value: 'id', children: 'children' }, }, }, @@ -47,16 +56,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', @@ -67,30 +71,23 @@ export function useGridColumns( { field: 'picUrl', title: '商品图片', + minWidth: 100, cellRender: { name: 'CellImage', }, }, { - field: 'price', - title: '价格', - formatter: 'formatAmount2', - }, - { - 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', @@ -102,9 +99,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-antd/src/views/mall/product/spu/index.vue b/apps/web-antd/src/views/mall/product/spu/index.vue index 5c9e3b699..43b87cbe4 100644 --- a/apps/web-antd/src/views/mall/product/spu/index.vue +++ b/apps/web-antd/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 { Descriptions, message, Tabs } from 'ant-design-vue'; +import { message, Tabs } from 'ant-design-vue'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; -import { getCategoryList } from '#/api/mall/product/category'; import { deleteSpu, exportSpu, @@ -32,10 +26,6 @@ import { useGridColumns, useGridFormSchema } from './data'; const { push } = useRouter(); const route = useRoute(); const tabType = ref(0); - -// TODO @AI:放到 data.ts 里; -const categoryList = ref(); - const tabsData = ref([ { name: '出售中', @@ -62,7 +52,7 @@ const tabsData = ref([ type: 4, count: 0, }, -]); // tabs 数据 +]); /** 刷新表格 */ async function handleRefresh() { @@ -70,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]!; } } } @@ -86,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 } }); @@ -104,34 +94,14 @@ async function handleDelete(row: MallSpuApi.Spu) { duration: 0, }); try { - await deleteSpu(row.id as number); - message.success({ - content: $t('ui.actionMessage.deleteSuccess', [row.name]), - }); - handleRefresh(); + await deleteSpu(row.id!); + message.success($t('ui.actionMessage.deleteSuccess', [row.name])); + await handleRefresh(); } finally { hideLoading(); } } -/** 添加到仓库 / 回收站的状态 */ -async function handleStatus02Change(row: MallSpuApi.Spu, newStatus: number) { - // 二次确认 - const text = - newStatus === ProductSpuStatusEnum.RECYCLE.status - ? '加入到回收站' - : '恢复到仓库'; - confirm(`确认要"${row.name}"${text}吗?`) - .then(async () => { - await updateStatus({ id: row.id as number, status: newStatus }); - message.success(`${text}成功`); - await handleRefresh(); - }) - .catch(() => { - message.error(`${text}失败`); - }); -} - /** 更新状态 */ async function handleStatusChange( newStatus: number, @@ -139,14 +109,14 @@ async function handleStatusChange( ): Promise { return new Promise((resolve, reject) => { // 二次确认 - const text = row.status ? '上架' : '下架'; + const text = newStatus ? '上架' : '下架'; confirm({ content: `确认要${text + row.name}吗?`, }) .then(async () => { // 更新状态 const res = await updateStatus({ - id: row.id as number, + id: row.id!, status: newStatus, }); if (res) { @@ -163,6 +133,28 @@ async function handleStatusChange( }); } +/** 添加到仓库 / 回收站的状态 */ +async function handleStatus02Change(row: MallSpuApi.Spu, newStatus: number) { + const text = + newStatus === ProductSpuStatusEnum.RECYCLE.status + ? '加入到回收站' + : '恢复到仓库'; + await confirm({ + content: `确认要"${row.name}"${text}吗?`, + }); + const hideLoading = message.loading({ + content: `正在${text}中...`, + duration: 0, + }); + try { + await updateStatus({ id: row.id!, status: newStatus }); + message.success(`${text}成功`); + await handleRefresh(); + } finally { + hideLoading(); + } +} + /** 查看商品详情 */ function handleDetail(row: MallSpuApi.Spu) { push({ name: 'ProductSpuDetail', params: { id: row.id } }); @@ -175,12 +167,6 @@ const [Grid, gridApi] = useVbenVxeGrid({ gridOptions: { columns: useGridColumns(handleStatusChange), height: 'auto', - cellConfig: { - height: 80, - }, - expandConfig: { - height: 100, - }, keepSource: true, proxyConfig: { ajax: { @@ -196,7 +182,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ }, rowConfig: { keyField: 'id', - resizable: true, + isHover: true, }, toolbarConfig: { refresh: true, @@ -217,9 +203,8 @@ onMounted(async () => { categoryId: Number(route.query.categoryId), }); } + // 获得每个 Tab 的数量 await getTabCount(); - const categoryRes = await getCategoryList({}); - categoryList.value = handleTree(categoryRes, 'id', 'parentId', 'children'); }); @@ -262,39 +247,6 @@ onMounted(async () => { ]" /> -