feat:【antd】mall 商品列表的优化代码

This commit is contained in:
YunaiV
2025-10-21 23:48:41 +08:00
parent 1286623863
commit a7b9292b8d
2 changed files with 110 additions and 113 deletions

View File

@@ -2,11 +2,17 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MallSpuApi } from '#/api/mall/product/spu'; 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 { getCategoryList } from '#/api/mall/product/category';
import { getRangePickerDefaultProps } from '#/utils'; import { getRangePickerDefaultProps } from '#/utils';
/** 关联数据 */
let categoryList: any[] = [];
getCategoryList({}).then((data) => {
categoryList = handleTree(data, 'id', 'parentId', 'children');
});
/** 列表的搜索表单 */ /** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] { export function useGridFormSchema(): VbenFormSchema[] {
return [ return [
@@ -14,16 +20,19 @@ export function useGridFormSchema(): VbenFormSchema[] {
fieldName: 'name', fieldName: 'name',
label: '商品名称', label: '商品名称',
component: 'Input', component: 'Input',
componentProps: {
placeholder: '请输入商品名称',
allowClear: true,
},
}, },
{ {
fieldName: 'categoryId', fieldName: 'categoryId',
label: '商品分类', label: '商品分类',
component: 'ApiTreeSelect', component: 'ApiTreeSelect',
componentProps: { componentProps: {
api: async () => { placeholder: '请选择商品分类',
const res = await getCategoryList({}); allowClear: true,
return handleTree(res, 'id', 'parentId', 'children'); options: categoryList,
},
fieldNames: { label: 'name', value: 'id', children: 'children' }, fieldNames: { label: 'name', value: 'id', children: 'children' },
}, },
}, },
@@ -47,16 +56,11 @@ export function useGridColumns(
) => PromiseLike<boolean | undefined>, ) => PromiseLike<boolean | undefined>,
): VxeTableGridOptions['columns'] { ): VxeTableGridOptions['columns'] {
return [ return [
{
type: 'expand',
width: 80,
slots: { content: 'expand_content' },
fixed: 'left',
},
{ {
field: 'id', field: 'id',
title: '商品编号', title: '商品编号',
fixed: 'left', fixed: 'left',
minWidth: 100,
}, },
{ {
field: 'name', field: 'name',
@@ -67,30 +71,23 @@ export function useGridColumns(
{ {
field: 'picUrl', field: 'picUrl',
title: '商品图片', title: '商品图片',
minWidth: 100,
cellRender: { cellRender: {
name: 'CellImage', name: 'CellImage',
}, },
}, },
{ {
field: 'price', field: 'categoryId',
title: '价格', title: '商品分类',
formatter: 'formatAmount2', minWidth: 150,
}, formatter: ({ row }) => {
{ return treeToString(categoryList, row.categoryId);
field: 'salesCount', },
title: '销量',
},
{
field: 'stock',
title: '库存',
},
{
field: 'sort',
title: '排序',
}, },
{ {
field: 'status', field: 'status',
title: '销售状态', title: '销售状态',
minWidth: 100,
cellRender: { cellRender: {
attrs: { beforeChange: onStatusChange }, attrs: { beforeChange: onStatusChange },
name: 'CellSwitch', 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', field: 'createTime',
title: '创建时间', title: '创建时间',
minWidth: 160,
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {

View File

@@ -7,17 +7,11 @@ import { useRoute, useRouter } from 'vue-router';
import { confirm, DocAlert, Page } from '@vben/common-ui'; import { confirm, DocAlert, Page } from '@vben/common-ui';
import { ProductSpuStatusEnum } from '@vben/constants'; import { ProductSpuStatusEnum } from '@vben/constants';
import { import { downloadFileFromBlobPart } from '@vben/utils';
downloadFileFromBlobPart,
fenToYuan,
handleTree,
treeToString,
} 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 { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getCategoryList } from '#/api/mall/product/category';
import { import {
deleteSpu, deleteSpu,
exportSpu, exportSpu,
@@ -32,10 +26,6 @@ import { useGridColumns, useGridFormSchema } from './data';
const { push } = useRouter(); const { push } = useRouter();
const route = useRoute(); const route = useRoute();
const tabType = ref(0); const tabType = ref(0);
// TODO @AI放到 data.ts 里;
const categoryList = ref();
const tabsData = ref([ const tabsData = ref([
{ {
name: '出售中', name: '出售中',
@@ -62,7 +52,7 @@ const tabsData = ref([
type: 4, type: 4,
count: 0, count: 0,
}, },
]); // tabs 数据 ]);
/** 刷新表格 */ /** 刷新表格 */
async function handleRefresh() { async function handleRefresh() {
@@ -70,13 +60,19 @@ async function handleRefresh() {
await getTabCount(); await getTabCount();
} }
/** 导出表格 */
async function handleExport() {
const data = await exportSpu(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '商品.xls', source: data });
}
/** 获得每个 Tab 的数量 */ /** 获得每个 Tab 的数量 */
async function getTabCount() { async function getTabCount() {
const res = await getTabsCount(); const res = await getTabsCount();
for (const objName in res) { for (const objName in res) {
const index = Number(objName); const index = Number(objName);
if (tabsData.value[index]) { 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' }); push({ name: 'ProductSpuAdd' });
} }
/** 导出表格 */
async function handleExport() {
const data = await exportSpu(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '商品.xls', source: data });
}
/** 编辑商品 */ /** 编辑商品 */
function handleEdit(row: MallSpuApi.Spu) { function handleEdit(row: MallSpuApi.Spu) {
push({ name: 'ProductSpuEdit', params: { id: row.id } }); push({ name: 'ProductSpuEdit', params: { id: row.id } });
@@ -104,34 +94,14 @@ async function handleDelete(row: MallSpuApi.Spu) {
duration: 0, duration: 0,
}); });
try { try {
await deleteSpu(row.id as number); await deleteSpu(row.id!);
message.success({ message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
content: $t('ui.actionMessage.deleteSuccess', [row.name]), await handleRefresh();
});
handleRefresh();
} finally { } finally {
hideLoading(); 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( async function handleStatusChange(
newStatus: number, newStatus: number,
@@ -139,14 +109,14 @@ async function handleStatusChange(
): Promise<boolean | undefined> { ): Promise<boolean | undefined> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 二次确认 // 二次确认
const text = row.status ? '上架' : '下架'; const text = newStatus ? '上架' : '下架';
confirm({ confirm({
content: `确认要${text + row.name}吗?`, content: `确认要${text + row.name}吗?`,
}) })
.then(async () => { .then(async () => {
// 更新状态 // 更新状态
const res = await updateStatus({ const res = await updateStatus({
id: row.id as number, id: row.id!,
status: newStatus, status: newStatus,
}); });
if (res) { 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) { function handleDetail(row: MallSpuApi.Spu) {
push({ name: 'ProductSpuDetail', params: { id: row.id } }); push({ name: 'ProductSpuDetail', params: { id: row.id } });
@@ -175,12 +167,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: { gridOptions: {
columns: useGridColumns(handleStatusChange), columns: useGridColumns(handleStatusChange),
height: 'auto', height: 'auto',
cellConfig: {
height: 80,
},
expandConfig: {
height: 100,
},
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
@@ -196,7 +182,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
rowConfig: { rowConfig: {
keyField: 'id', keyField: 'id',
resizable: true, isHover: true,
}, },
toolbarConfig: { toolbarConfig: {
refresh: true, refresh: true,
@@ -217,9 +203,8 @@ onMounted(async () => {
categoryId: Number(route.query.categoryId), categoryId: Number(route.query.categoryId),
}); });
} }
// 获得每个 Tab 的数量
await getTabCount(); await getTabCount();
const categoryRes = await getCategoryList({});
categoryList.value = handleTree(categoryRes, 'id', 'parentId', 'children');
}); });
</script> </script>
@@ -262,39 +247,6 @@ onMounted(async () => {
]" ]"
/> />
</template> </template>
<template #expand_content="{ row }">
<Descriptions
:column="4"
class="mx-32 my-4 w-1/2"
:label-style="{
width: '100px',
fontWeight: 'bold',
fontSize: '14px',
color: '#99a9bf',
}"
:content-style="{ width: '100px', fontSize: '14px' }"
>
<Descriptions.Item label="商品分类">
{{ treeToString(categoryList, row.categoryId!) }}
</Descriptions.Item>
<Descriptions.Item label="商品名称">
{{ row.name }}
</Descriptions.Item>
<Descriptions.Item label="市场价">
{{ fenToYuan(row.marketPrice as number) }}
</Descriptions.Item>
<Descriptions.Item label="成本价">
{{ fenToYuan(row.costPrice as number) }}
</Descriptions.Item>
<Descriptions.Item label="浏览量">
{{ row.browseCount }}
</Descriptions.Item>
<Descriptions.Item label="虚拟销量">
{{ row.virtualSalesCount }}
</Descriptions.Item>
</Descriptions>
</template>
<template #actions="{ row }"> <template #actions="{ row }">
<TableAction <TableAction
:actions="[ :actions="[