feat: 添加产品工具, 重构产品 #31

Closed
zksu wants to merge 37 commits from (deleted):main into main
6 changed files with 102 additions and 56 deletions
Showing only changes of commit 6244438f26 - Show all commits

View File

@ -93,19 +93,9 @@ export default defineConfig({
component: './Product/List',
},
{
name: '品牌',
path: '/product/brand',
component: './Product/Brand',
},
{
name: '强度',
path: '/product/strength',
component: './Product/Strength',
},
{
name: '口味',
path: '/product/flavors',
component: './Product/Flavors',
name: '产品属性',
path: '/product/attribute',
component: './Product/Attribute',
},
{

View File

@ -245,7 +245,7 @@ const DictPage: React.FC = () => {
title: '操作',
key: 'action',
render: (_: any, record: any) => (
<Space>
<Space size="small">
<Button
size='small'
type="link"
@ -313,15 +313,15 @@ const DictPage: React.FC = () => {
<PageContainer>
<Layout style={{ background: '#fff' }}>
<Sider
width={300}
width={240}
style={{
background: '#fff',
padding: '16px',
padding: '8px',
borderRight: '1px solid #f0f0f0',
}}
>
<Space direction="vertical" style={{ width: '100%' }}>
<Input.Search
<Input.Search size="small" size="small"
placeholder="搜索字典"
onSearch={handleSearch}
onChange={(e) => setSearchText(e.target.value)}
@ -331,11 +331,11 @@ const DictPage: React.FC = () => {
<Button
type="primary"
onClick={() => setIsAddDictModalVisible(true)}
block
size="small"
>
</Button>
<Space>
<Space size="small">
<Upload
name="file"
action="/dict/import"
@ -349,9 +349,9 @@ const DictPage: React.FC = () => {
}
}}
>
<Button icon={<UploadOutlined />}></Button>
<Button size="small" icon={<UploadOutlined />}></Button>
</Upload>
<Button onClick={handleDownloadDictTemplate}>
<Button size="small" onClick={handleDownloadDictTemplate}>
</Button>
</Space>
@ -360,6 +360,7 @@ const DictPage: React.FC = () => {
columns={dictColumns}
rowKey="id"
loading={loadingDicts}
size="small"
onRow={(record) => ({
onClick: () => {
// 如果点击的是当前已选中的行,则取消选择
@ -377,13 +378,14 @@ const DictPage: React.FC = () => {
/>
</Space>
</Sider>
<Content style={{ padding: '16px' }}>
<Content style={{ padding: '8px' }}>
<Space direction="vertical" style={{ width: '100%' }}>
<div style={{ width: '100%', display: 'flex', flexDirection: 'row', gap: '2px' }}>
<Button
type="primary"
onClick={handleAddDictItem}
disabled={!selectedDict}
size="small"
>
</Button>
@ -402,13 +404,14 @@ const DictPage: React.FC = () => {
}
}}
>
<Button icon={<UploadOutlined />} disabled={!selectedDict}>
<Button size="small" icon={<UploadOutlined />} disabled={!selectedDict}>
</Button>
</Upload>
<Button
onClick={handleDownloadDictItemTemplate}
disabled={!selectedDict}
size="small"
>
</Button>
@ -418,6 +421,7 @@ const DictPage: React.FC = () => {
columns={dictItemColumns}
rowKey="id"
loading={loadingDictItems}
size="small"
/>
</Space>
</Content>

View File

@ -0,0 +1,2 @@
// 中文注释:限定允许管理的字典名称集合
export const allowedDictNames = new Set(['brand', 'strength', 'flavor', 'size', 'humidity']);

View File

@ -6,8 +6,7 @@ import React, { useEffect, useState } from 'react';
const { Sider, Content } = Layout;
// 中文注释:限定允许管理的字典名称集合
const allowedDictNames = new Set(['brand', 'strength', 'flavor', 'size', 'humidity']);
import { allowedDictNames } from './consts';
const AttributePage: React.FC = () => {
// 中文注释:左侧字典列表状态

View File

@ -1,5 +1,6 @@
import {
productcontrollerCreateproduct,
productcontrollerCompatsizeall,
productcontrollerDeleteproduct,
productcontrollerCompatbrandall,
productcontrollerCompatflavorsall,
@ -27,7 +28,8 @@ import {
ProFormTextArea,
ProTable,
} from '@ant-design/pro-components';
import { App, Button, Popconfirm } from 'antd';
import { App, Button, Popconfirm, Tag } from 'antd';
import { allowedDictNames } from '@/pages/Product/Attribute/consts';
import React, { useRef, useState } from 'react';
const capitalize = (s: string) => s.charAt(0).toLocaleUpperCase() + s.slice(1);
// TODO
@ -67,6 +69,50 @@ const NameCn: React.FC<{
/>
);
};
const AttributesCell: React.FC<{ record: any }> = ({ record }) => {
const items: { key: string; value: string }[] = [];
// 中文注释:按允许的属性集合收集展示值
if (allowedDictNames.has('brand') && record?.brand?.name) items.push({ key: '品牌', value: record.brand.name });
if (allowedDictNames.has('strength') && record?.strength?.name) items.push({ key: '强度', value: record.strength.name });
if (allowedDictNames.has('flavor') && record?.flavors?.name) items.push({ key: '口味', value: record.flavors.name });
if (allowedDictNames.has('size') && record?.size?.name) items.push({ key: '规格', value: record.size.name });
if (allowedDictNames.has('humidity') && record?.humidity) items.push({ key: '湿度', value: record.humidity });
return (
<div>
{items.length ? items.map((it, idx) => (
<Tag key={idx} color="purple" style={{ marginBottom: 4 }}>
{it.key}: {it.value}
</Tag>
)) : <span>-</span>}
</div>
);
};
const ComponentsCell: React.FC<{ productId: number }> = ({ productId }) => {
const [items, setItems] = React.useState<any[]>([]);
React.useEffect(() => {
(async () => {
const { data = [] } = await productcontrollerGetproductcomponents({ id: productId });
setItems(data || []);
})();
}, [productId]);
return (
<div>
{items && items.length ? (
items.map((c: any) => (
<Tag key={c.id} color="blue" style={{ marginBottom: 4 }}>
{(c.stock && c.stock.productSku) || `#${c.stockId}`} × {c.quantity}{c.stock ? c.stock.quantity : '-'}
</Tag>
))
) : (
<span>-</span>
)}
</div>
);
};
const List: React.FC = () => {
const actionRef = useRef<ActionType>();
// 状态:存储当前选中的行
@ -102,32 +148,23 @@ const List: React.FC = () => {
hideInSearch: true,
},
{
title: '库存',
dataIndex: 'stock',
title: '属性',
dataIndex: 'attributes',
hideInSearch: true,
render: (_, record) => <AttributesCell record={record} />,
},
{
title: '构成',
dataIndex: 'components',
hideInSearch: true,
render: (_, record) => <ComponentsCell productId={(record as any).id} />,
},
{
title: '描述',
dataIndex: 'description',
hideInSearch: true,
},
{
title: '品牌',
dataIndex: 'brand.name',
},
{
title: '强度',
dataIndex: 'strength.name',
},
{
title: '口味',
dataIndex: 'flavors.name',
},
{
title: '湿度',
dataIndex: 'humidity',
},
{
title: '更新时间',
dataIndex: 'updatedAt',
@ -163,7 +200,7 @@ const List: React.FC = () => {
}
}}
>
<Button type="primary" danger>
<Button type="link" danger>
</Button>
</Popconfirm>
@ -322,7 +359,8 @@ const CreateForm: React.FC<{
values.brandId ? { id: values.brandId } : null,
values.strengthId ? { id: values.strengthId } : null,
values.flavorsId ? { id: values.flavorsId } : null,
values.humidity ? { dictName: 'humidity', name: values.humidity } : null,
values.sizeId ? { id: values.sizeId } : null,
values.humidity ? { id: values.humidityId } : null,
].filter(Boolean),
};
const { success, message: errMsg } =
@ -381,6 +419,17 @@ const CreateForm: React.FC<{
}}
rules={[{ required: true, message: '请选择口味' }]}
/>
<ProFormSelect
name="sizeId"
width="lg"
label="规格"
placeholder="请选择规格"
request={async () => {
const { data = [] } = await productcontrollerCompatsizeall();
return (data || []).map((item: any) => ({ label: item.name, value: item.id }));
}}
rules={[{ required: false }]}
/>
<ProFormSelect
name="humidity"
width="lg"
@ -455,9 +504,9 @@ const EditForm: React.FC<{
const [components, setComponents] = useState<{ stockId: number; quantity: number }[]>([]);
const setInitialIds = () => {
const brand = brandOptions.find((item) => item.title === record.brand.name);
const strength = strengthOptions.find((item) => item.title === record.strength.name);
const flavor = flavorOptions.find((item) => item.title === record.flavors.name);
const brand = brandOptions.find((item) => item.title === (record.brand?.name));
const strength = strengthOptions.find((item) => item.title === (record.strength?.name));
const flavor = flavorOptions.find((item) => item.title === (record.flavors?.name));
formRef.current?.setFieldsValue({
brandId: brand?.id,
strengthId: strength?.id,
@ -495,10 +544,10 @@ const EditForm: React.FC<{
name: record.name,
sku: record.sku,
description: record.description,
humidity: record.humidity,
price: (record as any).price,
promotionPrice: (record as any).promotionPrice,
price: record.price,
promotionPrice: record.promotionPrice,
components,
attributes: record.attributes || [],
}}
onFinish={async (values) => {
// 中文注释:组装 attributes若选择了则发送
@ -506,6 +555,7 @@ const EditForm: React.FC<{
values.brandId ? { id: values.brandId } : null,
values.strengthId ? { id: values.strengthId } : null,
values.flavorsId ? { id: values.flavorsId } : null,
values.sizeId ? { id: values.sizeId } : null,
values.humidity ? { dictName: 'humidity', name: values.humidity } : null,
].filter(Boolean);
const updatePayload: any = {
@ -629,7 +679,7 @@ const EditForm: React.FC<{
formRef.current?.setFieldsValue({ components: items });
}}
>
SKU
SKU
</Button>
</ProForm.Group>
<ProFormList

View File

@ -688,6 +688,7 @@ declare namespace API {
};
type Product = {
attributes: never[];
/** ID */
id: number;
/** 产品名称 */