refactor(Product): 优化产品列表和编辑表单的代码结构和逻辑
fix(Product): 修正产品类型和库存状态的关联逻辑 style: 统一代码格式和缩进 fix(Product): 修复产品组成项编辑时的类型错误 feat(Product): 添加产品类型选择功能 refactor(Product): 重构属性表单组件 fix(Product): 修正WP产品列表的站点显示问题
This commit is contained in:
parent
b2575a11fd
commit
0f264c15a5
|
|
@ -4,10 +4,10 @@
|
|||
"scripts": {
|
||||
"build": "max build",
|
||||
"dev": "max dev",
|
||||
"fix:openapi2ts": "sed -i '' 's/\r$//' /Users/zksu/Developer/work/workcode/web/node_modules/@umijs/openapi/dist/cli.js",
|
||||
"format": "prettier --cache --write .",
|
||||
"postinstall": "max setup",
|
||||
"openapi2ts": "openapi2ts",
|
||||
"fix:openapi2ts": "sed -i '' 's/\r$//' /Users/zksu/Developer/work/workcode/web/node_modules/@umijs/openapi/dist/cli.js",
|
||||
"prepare": "husky",
|
||||
"setup": "max setup",
|
||||
"start": "npm run dev"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,47 @@
|
|||
export default (initialState: any) => {
|
||||
const isSuper = initialState?.user?.isSuper ?? false;
|
||||
const isAdmin = initialState?.user?.Admin ?? false;
|
||||
const canSeeOrganiza = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('organiza') ?? false);
|
||||
const canSeeProduct = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('product') ?? false);
|
||||
const canSeeStock = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('stock') ?? false);
|
||||
const canSeeOrder = (isSuper || isAdmin) ||
|
||||
((initialState?.user?.permissions?.includes('order') ?? false) || (initialState?.user?.permissions?.includes('order-10-days') ?? false));
|
||||
const canSeeCustomer = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('customer') ?? false);
|
||||
const canSeeLogistics = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('logistics') ?? false);
|
||||
const canSeeStatistics = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('statistics') ?? false);
|
||||
const canSeeSite = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('site') ?? false);
|
||||
const canSeeDict = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('dict') ?? false);
|
||||
const canSeeTemplate = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('template') ?? false);
|
||||
const canSeeOrganiza =
|
||||
isSuper ||
|
||||
isAdmin ||
|
||||
(initialState?.user?.permissions?.includes('organiza') ?? false);
|
||||
const canSeeProduct =
|
||||
isSuper ||
|
||||
isAdmin ||
|
||||
(initialState?.user?.permissions?.includes('product') ?? false);
|
||||
const canSeeStock =
|
||||
isSuper ||
|
||||
isAdmin ||
|
||||
(initialState?.user?.permissions?.includes('stock') ?? false);
|
||||
const canSeeOrder =
|
||||
isSuper ||
|
||||
isAdmin ||
|
||||
(initialState?.user?.permissions?.includes('order') ?? false) ||
|
||||
(initialState?.user?.permissions?.includes('order-10-days') ?? false);
|
||||
const canSeeCustomer =
|
||||
isSuper ||
|
||||
isAdmin ||
|
||||
(initialState?.user?.permissions?.includes('customer') ?? false);
|
||||
const canSeeLogistics =
|
||||
isSuper ||
|
||||
isAdmin ||
|
||||
(initialState?.user?.permissions?.includes('logistics') ?? false);
|
||||
const canSeeStatistics =
|
||||
isSuper ||
|
||||
isAdmin ||
|
||||
(initialState?.user?.permissions?.includes('statistics') ?? false);
|
||||
const canSeeSite =
|
||||
isSuper ||
|
||||
isAdmin ||
|
||||
(initialState?.user?.permissions?.includes('site') ?? false);
|
||||
const canSeeDict =
|
||||
isSuper ||
|
||||
isAdmin ||
|
||||
(initialState?.user?.permissions?.includes('dict') ?? false);
|
||||
const canSeeTemplate =
|
||||
isSuper ||
|
||||
isAdmin ||
|
||||
(initialState?.user?.permissions?.includes('template') ?? false);
|
||||
return {
|
||||
canSeeOrganiza,
|
||||
canSeeProduct,
|
||||
|
|
|
|||
|
|
@ -116,5 +116,5 @@ export const ORDER_STATUS_ENUM: ProSchemaValueEnumObj = {
|
|||
refund_cancelled: {
|
||||
text: '已取消退款',
|
||||
status: 'refund_cancelled',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -213,7 +213,9 @@ const DictPage: React.FC = () => {
|
|||
const handleDownloadDictItemTemplate = async () => {
|
||||
try {
|
||||
// 使用带有认证拦截的 request 发起下载请求(后端鉴权通过)
|
||||
const blob = await request('/dict/item/template', { responseType: 'blob' });
|
||||
const blob = await request('/dict/item/template', {
|
||||
responseType: 'blob',
|
||||
});
|
||||
// 创建临时链接并触发下载
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
|
|
@ -247,7 +249,7 @@ const DictPage: React.FC = () => {
|
|||
render: (_: any, record: any) => (
|
||||
<Space size="small">
|
||||
<Button
|
||||
size='small'
|
||||
size="small"
|
||||
type="link"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -257,7 +259,7 @@ const DictPage: React.FC = () => {
|
|||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
size='small'
|
||||
size="small"
|
||||
type="link"
|
||||
danger
|
||||
onClick={(e) => {
|
||||
|
|
@ -321,7 +323,9 @@ const DictPage: React.FC = () => {
|
|||
}}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Input.Search size="small" size="small"
|
||||
<Input.Search
|
||||
size="small"
|
||||
size="small"
|
||||
placeholder="搜索字典"
|
||||
onSearch={handleSearch}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
|
|
@ -349,7 +353,9 @@ const DictPage: React.FC = () => {
|
|||
}
|
||||
}}
|
||||
>
|
||||
<Button size="small" icon={<UploadOutlined />}>导入字典</Button>
|
||||
<Button size="small" icon={<UploadOutlined />}>
|
||||
导入字典
|
||||
</Button>
|
||||
</Upload>
|
||||
<Button size="small" onClick={handleDownloadDictTemplate}>
|
||||
下载模板
|
||||
|
|
@ -380,7 +386,14 @@ const DictPage: React.FC = () => {
|
|||
</Sider>
|
||||
<Content style={{ padding: '8px' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<div style={{ width: '100%', display: 'flex', flexDirection: 'row', gap: '2px' }}>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: '2px',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleAddDictItem}
|
||||
|
|
@ -404,7 +417,11 @@ const DictPage: React.FC = () => {
|
|||
}
|
||||
}}
|
||||
>
|
||||
<Button size="small" icon={<UploadOutlined />} disabled={!selectedDict}>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<UploadOutlined />}
|
||||
disabled={!selectedDict}
|
||||
>
|
||||
导入字典项
|
||||
</Button>
|
||||
</Upload>
|
||||
|
|
|
|||
|
|
@ -72,7 +72,11 @@ const ListPage: React.FC = () => {
|
|||
onClick={async () => {
|
||||
// 中文注释:软删除为禁用(isActive=false),再次点击可启用
|
||||
const next = !record.isActive;
|
||||
const { success, message: errMsg } = await usercontrollerToggleactive({ userId: record.id, isActive: next });
|
||||
const { success, message: errMsg } =
|
||||
await usercontrollerToggleactive({
|
||||
userId: record.id,
|
||||
isActive: next,
|
||||
});
|
||||
if (!success) return message.error(errMsg);
|
||||
actionRef.current?.reload();
|
||||
}}
|
||||
|
|
@ -89,23 +93,32 @@ const ListPage: React.FC = () => {
|
|||
headerTitle="查询表格"
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
|
||||
request={async (params) => {
|
||||
const { current = 1, pageSize = 10, username, isActive, isSuper, remark } = params as any;
|
||||
console.log(`params`,params)
|
||||
const {
|
||||
current = 1,
|
||||
pageSize = 10,
|
||||
username,
|
||||
isActive,
|
||||
isSuper,
|
||||
remark,
|
||||
} = params as any;
|
||||
console.log(`params`, params);
|
||||
const qp: any = { current, pageSize };
|
||||
if (username) qp.username = username;
|
||||
if (typeof isActive !== 'undefined' && isActive !== '') qp.isActive = String(isActive);
|
||||
if (typeof isSuper !== 'undefined' && isSuper !== '') qp.isSuper = String(isSuper);
|
||||
if (typeof isActive !== 'undefined' && isActive !== '')
|
||||
qp.isActive = String(isActive);
|
||||
if (typeof isSuper !== 'undefined' && isSuper !== '')
|
||||
qp.isSuper = String(isSuper);
|
||||
if (remark) qp.remark = remark;
|
||||
const { data, success } = await usercontrollerListusers({ params: qp });
|
||||
const { data, success } = await usercontrollerListusers({
|
||||
params: qp,
|
||||
});
|
||||
return {
|
||||
total: data?.total || 0,
|
||||
data: data?.items || [],
|
||||
success,
|
||||
};
|
||||
}}
|
||||
|
||||
columns={columns}
|
||||
toolBarRender={() => [<CreateForm tableRef={actionRef} />]}
|
||||
/>
|
||||
|
|
@ -163,7 +176,12 @@ const CreateForm: React.FC<{
|
|||
/>
|
||||
<ProFormSwitch name="isSuper" label="超管" />
|
||||
<ProFormSwitch name="isAdmin" label="管理员" />
|
||||
<ProFormTextArea name="remark" label="备注" placeholder="请输入备注" fieldProps={{ autoSize: { minRows: 2, maxRows: 4 } }} />
|
||||
<ProFormTextArea
|
||||
name="remark"
|
||||
label="备注"
|
||||
placeholder="请输入备注"
|
||||
fieldProps={{ autoSize: { minRows: 2, maxRows: 4 } }}
|
||||
/>
|
||||
</ProForm.Group>
|
||||
</DrawerForm>
|
||||
);
|
||||
|
|
@ -187,7 +205,10 @@ const EditForm: React.FC<{
|
|||
onFinish={async (values: any) => {
|
||||
try {
|
||||
// 中文注释:更新用户,密码可选填
|
||||
const { success, message: err } = await usercontrollerUpdateuser({ id: record.id }, values);
|
||||
const { success, message: err } = await usercontrollerUpdateuser(
|
||||
{ id: record.id },
|
||||
values,
|
||||
);
|
||||
if (!success) throw new Error(err);
|
||||
tableRef.current?.reload();
|
||||
message.success('更新成功');
|
||||
|
|
@ -214,7 +235,12 @@ const EditForm: React.FC<{
|
|||
/>
|
||||
<ProFormSwitch name="isSuper" label="超管" />
|
||||
<ProFormSwitch name="isAdmin" label="管理员" />
|
||||
<ProFormTextArea name="remark" label="备注" placeholder="请输入备注" fieldProps={{ autoSize: { minRows: 2, maxRows: 4 } }} />
|
||||
<ProFormTextArea
|
||||
name="remark"
|
||||
label="备注"
|
||||
placeholder="请输入备注"
|
||||
fieldProps={{ autoSize: { minRows: 2, maxRows: 4 } }}
|
||||
/>
|
||||
</ProForm.Group>
|
||||
</DrawerForm>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
import { productcontrollerGetattributelist } from '@/servers/api/product';
|
||||
import { ProFormSelect } from '@ant-design/pro-components';
|
||||
import { useState } from 'react';
|
||||
import { productcontrollerGetattributeall, productcontrollerGetattributelist } from '@/servers/api/product';
|
||||
|
||||
interface AttributeFormItemProps {
|
||||
dictName: string;
|
||||
|
|
@ -11,12 +10,25 @@ interface AttributeFormItemProps {
|
|||
}
|
||||
|
||||
const fetchDictOptions = async (dictName: string, keyword?: string) => {
|
||||
const { data } = await productcontrollerGetattributelist({ dictName, name: keyword });
|
||||
return (data?.items || []).map((item: any) => ({ label: item.name, value: item.name }));
|
||||
const { data } = await productcontrollerGetattributelist({
|
||||
dictName,
|
||||
name: keyword,
|
||||
});
|
||||
return (data?.items || []).map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.name,
|
||||
}));
|
||||
};
|
||||
|
||||
const AttributeFormItem: React.FC<AttributeFormItemProps> = ({ dictName, name, label, isTag = false }) => {
|
||||
const [options, setOptions] = useState<{ label: string; value: string }[]>([]);
|
||||
const AttributeFormItem: React.FC<AttributeFormItemProps> = ({
|
||||
dictName,
|
||||
name,
|
||||
label,
|
||||
isTag = false,
|
||||
}) => {
|
||||
const [options, setOptions] = useState<{ label: string; value: string }[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
if (isTag) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,2 +1,9 @@
|
|||
// 中文注释:限定允许管理的字典名称集合
|
||||
export const attributes = new Set(['brand', 'strength', 'flavor', 'size', 'humidity','category']);
|
||||
export const attributes = new Set([
|
||||
'brand',
|
||||
'strength',
|
||||
'flavor',
|
||||
'size',
|
||||
'humidity',
|
||||
'category',
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
import { UploadOutlined } from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { request } from '@umijs/max';
|
||||
import { Button, Form, Input, Layout, Modal, Space, Table, Upload, message } from 'antd';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
Input,
|
||||
Layout,
|
||||
Modal,
|
||||
Space,
|
||||
Table,
|
||||
Upload,
|
||||
message,
|
||||
} from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
const { Sider, Content } = Layout;
|
||||
|
|
@ -132,8 +142,21 @@ const AttributePage: React.FC = () => {
|
|||
key: 'action',
|
||||
render: (_: any, record: any) => (
|
||||
<Space size="small">
|
||||
<Button size="small" type="link" onClick={() => handleEditDictItem(record)}>编辑</Button>
|
||||
<Button size="small" type="link" danger onClick={() => handleDeleteDictItem(record.id)}>删除</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
onClick={() => handleEditDictItem(record)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
danger
|
||||
onClick={() => handleDeleteDictItem(record.id)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
|
|
@ -144,7 +167,11 @@ const AttributePage: React.FC = () => {
|
|||
<Layout style={{ background: '#fff' }}>
|
||||
<Sider
|
||||
width={240}
|
||||
style={{ background: '#fff', padding: '8px', borderRight: '1px solid #f0f0f0' }}
|
||||
style={{
|
||||
background: '#fff',
|
||||
padding: '8px',
|
||||
borderRight: '1px solid #f0f0f0',
|
||||
}}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="small">
|
||||
<Input.Search
|
||||
|
|
@ -171,15 +198,31 @@ const AttributePage: React.FC = () => {
|
|||
}
|
||||
},
|
||||
})}
|
||||
rowClassName={(record) => (selectedDict?.id === record.id ? 'ant-table-row-selected' : '')}
|
||||
rowClassName={(record) =>
|
||||
selectedDict?.id === record.id ? 'ant-table-row-selected' : ''
|
||||
}
|
||||
pagination={false}
|
||||
/>
|
||||
</Space>
|
||||
</Sider>
|
||||
<Content style={{ padding: '8px' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="small">
|
||||
<div style={{ width: '100%', display: 'flex', flexDirection: 'row', gap: '4px' }}>
|
||||
<Button type="primary" size="small" onClick={handleAddDictItem} disabled={!selectedDict}>添加字典项</Button>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: '4px',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={handleAddDictItem}
|
||||
disabled={!selectedDict}
|
||||
>
|
||||
添加字典项
|
||||
</Button>
|
||||
<Upload
|
||||
name="file"
|
||||
action={`/dict/item/import`}
|
||||
|
|
@ -196,7 +239,11 @@ const AttributePage: React.FC = () => {
|
|||
}
|
||||
}}
|
||||
>
|
||||
<Button size="small" icon={<UploadOutlined />} disabled={!selectedDict}>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<UploadOutlined />}
|
||||
disabled={!selectedDict}
|
||||
>
|
||||
导入字典项
|
||||
</Button>
|
||||
</Upload>
|
||||
|
|
@ -219,11 +266,23 @@ const AttributePage: React.FC = () => {
|
|||
onCancel={() => setIsDictItemModalVisible(false)}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={dictItemForm} layout="vertical" onFinish={handleDictItemFormSubmit}>
|
||||
<Form.Item label="名称" name="name" rules={[{ required: true, message: '请输入名称' }]}>
|
||||
<Form
|
||||
form={dictItemForm}
|
||||
layout="vertical"
|
||||
onFinish={handleDictItemFormSubmit}
|
||||
>
|
||||
<Form.Item
|
||||
label="名称"
|
||||
name="name"
|
||||
rules={[{ required: true, message: '请输入名称' }]}
|
||||
>
|
||||
<Input size="small" placeholder="名称 (e.g., zyn)" />
|
||||
</Form.Item>
|
||||
<Form.Item label="标题" name="title" rules={[{ required: true, message: '请输入标题' }]}>
|
||||
<Form.Item
|
||||
label="标题"
|
||||
name="title"
|
||||
rules={[{ required: true, message: '请输入标题' }]}
|
||||
>
|
||||
<Input size="small" placeholder="标题 (e.g., ZYN)" />
|
||||
</Form.Item>
|
||||
<Form.Item label="中文标题" name="titleCN">
|
||||
|
|
@ -239,4 +298,3 @@ const AttributePage: React.FC = () => {
|
|||
};
|
||||
|
||||
export default AttributePage;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
import {
|
||||
productcontrollerAutobindcomponents,
|
||||
productcontrollerCreateproduct,
|
||||
productcontrollerDeleteproduct,
|
||||
productcontrollerGetproductlist,
|
||||
productcontrollerUpdateproductnamecn,
|
||||
productcontrollerUpdateproduct,
|
||||
productcontrollerGetproductcomponents,
|
||||
productcontrollerGetproductlist,
|
||||
productcontrollerSetproductcomponents,
|
||||
productcontrollerAutobindcomponents,
|
||||
productcontrollerGetattributelist,
|
||||
productcontrollerUpdateproduct,
|
||||
productcontrollerUpdateproductnamecn,
|
||||
} from '@/servers/api/product';
|
||||
import { stockcontrollerGetstocks } from '@/servers/api/stock';
|
||||
import { request } from '@umijs/max';
|
||||
import { templatecontrollerRendertemplate } from '@/servers/api/template';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
|
|
@ -19,6 +17,7 @@ import {
|
|||
PageContainer,
|
||||
ProColumns,
|
||||
ProForm,
|
||||
ProFormDigit,
|
||||
ProFormInstance,
|
||||
ProFormList,
|
||||
ProFormSelect,
|
||||
|
|
@ -26,6 +25,7 @@ import {
|
|||
ProFormTextArea,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import { request } from '@umijs/max';
|
||||
import { App, Button, Popconfirm, Tag, Upload } from 'antd';
|
||||
|
||||
import AttributeFormItem from '@/pages/Product/Attribute/components/AttributeFormItem';
|
||||
|
|
@ -82,12 +82,13 @@ const AttributesCell: React.FC<{ record: any }> = ({ record }) => {
|
|||
);
|
||||
};
|
||||
|
||||
|
||||
const ComponentsCell: React.FC<{ productId: number }> = ({ productId }) => {
|
||||
const [components, setComponents] = React.useState<any[]>([]);
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
const { data = [] } = await productcontrollerGetproductcomponents({ id: productId });
|
||||
const { data = [] } = await productcontrollerGetproductcomponents({
|
||||
id: productId,
|
||||
});
|
||||
setComponents(data || []);
|
||||
})();
|
||||
}, [productId]);
|
||||
|
|
@ -96,8 +97,11 @@ const ComponentsCell: React.FC<{ productId: number }> = ({ productId }) => {
|
|||
{components && components.length ? (
|
||||
components.map((component: any) => (
|
||||
<Tag key={component.id} color="blue" style={{ marginBottom: 4 }}>
|
||||
{(component.productSku) || `#${component.id}`} × {component.quantity}(库存:
|
||||
{component.stock?.map((s: any) => `${s.name}:${s.quantity}`).join(', ') || '-'}
|
||||
{component.productSku || `#${component.id}`} × {component.quantity}
|
||||
(库存:
|
||||
{component.stock
|
||||
?.map((s: any) => `${s.name}:${s.quantity}`)
|
||||
.join(', ') || '-'}
|
||||
)
|
||||
</Tag>
|
||||
))
|
||||
|
|
@ -122,7 +126,9 @@ const List: React.FC = () => {
|
|||
// 中文注释:构建下载文件名
|
||||
const d = new Date();
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
const filename = `products-${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}.csv`;
|
||||
const filename = `products-${d.getFullYear()}${pad(
|
||||
d.getMonth() + 1,
|
||||
)}${pad(d.getDate())}.csv`;
|
||||
// 中文注释:创建临时链接并触发下载
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
|
|
@ -288,8 +294,9 @@ const CreateForm: React.FC<{
|
|||
const { message } = App.useApp();
|
||||
// 表单引用
|
||||
const formRef = useRef<ProFormInstance>();
|
||||
const [productType, setProductType] = useState<'single' | 'bundle' | null>(null);
|
||||
|
||||
const [stockStatus, setStockStatus] = useState<
|
||||
'in-stock' | 'out-of-stock' | null
|
||||
>(null);
|
||||
|
||||
/**
|
||||
* @description 生成 SKU
|
||||
|
|
@ -298,9 +305,15 @@ const CreateForm: React.FC<{
|
|||
try {
|
||||
// 从表单引用中获取当前表单的值
|
||||
const formValues = formRef.current?.getFieldsValue();
|
||||
const { humidityValues, brandValues, strengthValues, flavorValues } = formValues;
|
||||
const { humidityValues, brandValues, strengthValues, flavorValues } =
|
||||
formValues;
|
||||
// 检查是否所有必需的字段都已选择
|
||||
if (!brandValues?.length || !strengthValues?.length || !flavorValues?.length || !humidityValues?.length) {
|
||||
if (
|
||||
!brandValues?.length ||
|
||||
!strengthValues?.length ||
|
||||
!flavorValues?.length ||
|
||||
!humidityValues?.length
|
||||
) {
|
||||
message.warning('请先选择品牌、强度、口味和干湿');
|
||||
return;
|
||||
}
|
||||
|
|
@ -312,10 +325,14 @@ const CreateForm: React.FC<{
|
|||
const humidityName: string = String(humidityValues[0]);
|
||||
|
||||
// 调用模板渲染API来生成SKU
|
||||
const { data: rendered, message: msg, success } = await templatecontrollerRendertemplate(
|
||||
const {
|
||||
data: rendered,
|
||||
message: msg,
|
||||
success,
|
||||
} = await templatecontrollerRendertemplate(
|
||||
{ name: 'product.sku' },
|
||||
{
|
||||
brand: brandName || "",
|
||||
brand: brandName || '',
|
||||
strength: strengthName || '',
|
||||
flavor: flavorName || '',
|
||||
humidity: humidityName ? capitalize(humidityName) : '',
|
||||
|
|
@ -339,9 +356,15 @@ const CreateForm: React.FC<{
|
|||
try {
|
||||
// 从表单引用中获取当前表单的值
|
||||
const formValues = formRef.current?.getFieldsValue();
|
||||
const { humidityValues, brandValues, strengthValues, flavorValues } = formValues;
|
||||
const { humidityValues, brandValues, strengthValues, flavorValues } =
|
||||
formValues;
|
||||
// 检查是否所有必需的字段都已选择
|
||||
if (!brandValues?.length || !strengthValues?.length || !flavorValues?.length || !humidityValues?.length) {
|
||||
if (
|
||||
!brandValues?.length ||
|
||||
!strengthValues?.length ||
|
||||
!flavorValues?.length ||
|
||||
!humidityValues?.length
|
||||
) {
|
||||
message.warning('请先选择品牌、强度、口味和干湿');
|
||||
return;
|
||||
}
|
||||
|
|
@ -355,14 +378,23 @@ const CreateForm: React.FC<{
|
|||
const flavorTitle = flavorName;
|
||||
|
||||
// 调用模板渲染API来生成产品名称
|
||||
const { message: msg, data: rendered, success } = await templatecontrollerRendertemplate(
|
||||
const {
|
||||
message: msg,
|
||||
data: rendered,
|
||||
success,
|
||||
} = await templatecontrollerRendertemplate(
|
||||
{ name: 'product.title' },
|
||||
{
|
||||
brand: brandTitle,
|
||||
strength: strengthTitle,
|
||||
flavor: flavorTitle,
|
||||
model: '',
|
||||
humidity: humidityName === 'dry' ? 'Dry' : humidityName === 'moisture' ? 'Moisture' : capitalize(humidityName),
|
||||
humidity:
|
||||
humidityName === 'dry'
|
||||
? 'Dry'
|
||||
: humidityName === 'moisture'
|
||||
? 'Moisture'
|
||||
: capitalize(humidityName),
|
||||
},
|
||||
);
|
||||
if (!success) {
|
||||
|
|
@ -390,30 +422,61 @@ const CreateForm: React.FC<{
|
|||
destroyOnHidden: true,
|
||||
}}
|
||||
onValuesChange={async (changedValues) => {
|
||||
// 当 SKU 发生变化时
|
||||
if ('sku' in changedValues) {
|
||||
const sku = changedValues.sku;
|
||||
// 如果 sku 存在
|
||||
if (sku) {
|
||||
const { data } = await stockcontrollerGetstocks({ productSku: sku } as any);
|
||||
// 获取库存信息
|
||||
const { data } = await stockcontrollerGetstocks({
|
||||
productSku: sku,
|
||||
} as any);
|
||||
// 如果库存信息存在且不为空
|
||||
if (data && data.items && data.items.length > 0) {
|
||||
setProductType('single');
|
||||
// 设置在库状态
|
||||
setStockStatus('in-stock');
|
||||
// 设置产品类型为单品
|
||||
formRef.current?.setFieldsValue({ productType: 'single' });
|
||||
} else {
|
||||
setProductType('bundle');
|
||||
// 设置未在库状态
|
||||
setStockStatus('out-of-stock');
|
||||
// 设置产品类型为套装
|
||||
formRef.current?.setFieldsValue({ productType: 'bundle' });
|
||||
}
|
||||
} else {
|
||||
setProductType(null);
|
||||
// 如果 sku 不存在,则重置状态
|
||||
setStockStatus(null);
|
||||
formRef.current?.setFieldsValue({ productType: null });
|
||||
}
|
||||
}
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
onFinish={async (values: any) => {
|
||||
// 中文注释:组装 attributes(支持输入新值,按标题传入创建/绑定)
|
||||
|
||||
const attributes = [
|
||||
...(values.brandValues || []).map((v: string) => ({ dictName: 'brand', name: v })),
|
||||
...(values.strengthValues || []).map((v: string) => ({ dictName: 'strength', name: v })),
|
||||
...(values.flavorValues || []).map((v: string) => ({ dictName: 'flavor', name: v })),
|
||||
...(values.humidityValues || []).map((v: string) => ({ dictName: 'humidity', name: v })),
|
||||
...(values.sizeValues || []).map((v: string) => ({ dictName: 'size', name: v })),
|
||||
...(values.category ? [{ dictName: 'category', name: values.category }] : []),
|
||||
...(values.brandValues || []).map((v: string) => ({
|
||||
dictName: 'brand',
|
||||
name: v,
|
||||
})),
|
||||
...(values.strengthValues || []).map((v: string) => ({
|
||||
dictName: 'strength',
|
||||
name: v,
|
||||
})),
|
||||
...(values.flavorValues || []).map((v: string) => ({
|
||||
dictName: 'flavor',
|
||||
name: v,
|
||||
})),
|
||||
...(values.humidityValues || []).map((v: string) => ({
|
||||
dictName: 'humidity',
|
||||
name: v,
|
||||
})),
|
||||
...(values.sizeValues || []).map((v: string) => ({
|
||||
dictName: 'size',
|
||||
name: v,
|
||||
})),
|
||||
...(values.category
|
||||
? [{ dictName: 'category', name: values.category }]
|
||||
: []),
|
||||
];
|
||||
const payload: any = {
|
||||
name: (values as any).name,
|
||||
|
|
@ -422,6 +485,8 @@ const CreateForm: React.FC<{
|
|||
price: (values as any).price,
|
||||
promotionPrice: (values as any).promotionPrice,
|
||||
attributes,
|
||||
components:
|
||||
values.productType === 'bundle' ? values.components : undefined,
|
||||
};
|
||||
const { success, message: errMsg } =
|
||||
await productcontrollerCreateproduct(payload);
|
||||
|
|
@ -434,7 +499,6 @@ const CreateForm: React.FC<{
|
|||
return false;
|
||||
}}
|
||||
>
|
||||
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name="sku"
|
||||
|
|
@ -446,9 +510,12 @@ const CreateForm: React.FC<{
|
|||
<Button style={{ marginTop: '32px' }} onClick={handleGenerateSku}>
|
||||
自动生成
|
||||
</Button>
|
||||
{productType && (
|
||||
<Tag style={{ marginTop: '32px' }} color={productType === 'single' ? 'green' : 'orange'}>
|
||||
{productType === 'single' ? '单品' : '套装'}
|
||||
{stockStatus && (
|
||||
<Tag
|
||||
style={{ marginTop: '32px' }}
|
||||
color={stockStatus === 'in-stock' ? 'green' : 'orange'}
|
||||
>
|
||||
{stockStatus === 'in-stock' ? '在库' : '未在库'}
|
||||
</Tag>
|
||||
)}
|
||||
</ProForm.Group>
|
||||
|
|
@ -464,12 +531,83 @@ const CreateForm: React.FC<{
|
|||
自动生成
|
||||
</Button>
|
||||
</ProForm.Group>
|
||||
<AttributeFormItem dictName="brand" name="brandValues" label="产品品牌" isTag />
|
||||
<AttributeFormItem dictName="strength" name="strengthValues" label="强度" isTag />
|
||||
<AttributeFormItem dictName="flavor" name="flavorValues" label="口味" isTag />
|
||||
<AttributeFormItem dictName="humidity" name="humidityValues" label="干湿" isTag />
|
||||
<ProFormSelect
|
||||
name="productType"
|
||||
label="产品类型"
|
||||
options={[
|
||||
{ value: 'single', label: '单品' },
|
||||
{ value: 'bundle', label: '套装' },
|
||||
]}
|
||||
rules={[{ required: true, message: '请选择产品类型' }]}
|
||||
/>
|
||||
<ProForm.Item
|
||||
shouldUpdate={(prevValues: any, curValues: any) =>
|
||||
prevValues.productType !== curValues.productType
|
||||
}
|
||||
noStyle
|
||||
>
|
||||
{({ getFieldValue }: { getFieldValue: (name: string) => any }) =>
|
||||
getFieldValue('productType') === 'bundle' ? (
|
||||
<ProFormList
|
||||
name="components"
|
||||
label="产品组成"
|
||||
initialValue={[{ sku: '', quantity: 1 }]}
|
||||
creatorButtonProps={{
|
||||
creatorButtonText: '添加子产品',
|
||||
}}
|
||||
>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name="sku"
|
||||
label="子产品SKU"
|
||||
width="md"
|
||||
placeholder="请输入子产品SKU"
|
||||
rules={[{ required: true, message: '请输入子产品SKU' }]}
|
||||
/>
|
||||
<ProFormDigit
|
||||
name="quantity"
|
||||
label="数量"
|
||||
width="xs"
|
||||
min={1}
|
||||
initialValue={1}
|
||||
rules={[{ required: true, message: '请输入数量' }]}
|
||||
/>
|
||||
</ProForm.Group>
|
||||
</ProFormList>
|
||||
) : null
|
||||
}
|
||||
</ProForm.Item>
|
||||
<AttributeFormItem
|
||||
dictName="brand"
|
||||
name="brandValues"
|
||||
label="产品品牌"
|
||||
isTag
|
||||
/>
|
||||
<AttributeFormItem
|
||||
dictName="strength"
|
||||
name="strengthValues"
|
||||
label="强度"
|
||||
isTag
|
||||
/>
|
||||
<AttributeFormItem
|
||||
dictName="flavor"
|
||||
name="flavorValues"
|
||||
label="口味"
|
||||
isTag
|
||||
/>
|
||||
<AttributeFormItem
|
||||
dictName="humidity"
|
||||
name="humidityValues"
|
||||
label="干湿"
|
||||
isTag
|
||||
/>
|
||||
<AttributeFormItem dictName="size" name="sizeValues" label="大小" isTag />
|
||||
<AttributeFormItem dictName="category" name="category" label="分类" isTag />
|
||||
<AttributeFormItem
|
||||
dictName="category"
|
||||
name="category"
|
||||
label="分类"
|
||||
isTag
|
||||
/>
|
||||
<ProFormText
|
||||
name="price"
|
||||
label="价格"
|
||||
|
|
@ -497,109 +635,147 @@ const CreateForm: React.FC<{
|
|||
export default List;
|
||||
|
||||
const EditForm: React.FC<{
|
||||
tableRef: React.MutableRefObject<ActionType | undefined>;
|
||||
record: API.Product;
|
||||
}> = ({ tableRef, record }) => {
|
||||
tableRef: React.MutableRefObject<ActionType | undefined>;
|
||||
}> = ({ record, tableRef }) => {
|
||||
const { message } = App.useApp();
|
||||
const formRef = useRef<ProFormInstance>();
|
||||
const [components, setComponents] = useState<{ productSku: string; quantity: number }[]>([]);
|
||||
const [productType, setProductType] = useState<'single' | 'bundle' | null>(null);
|
||||
const [components, setComponents] = useState<
|
||||
{ productSku: string; quantity: number }[]
|
||||
>([]);
|
||||
const [productType, setProductType] = useState<'single' | 'bundle' | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
// 中文注释:加载当前产品的组成
|
||||
(async () => {
|
||||
const { data = [] } = await productcontrollerGetproductcomponents({ id: (record as any).id });
|
||||
const items = (data || []).map((c: any) => ({ productSku: c.productSku, quantity: c.quantity }));
|
||||
setComponents(items as any);
|
||||
formRef.current?.setFieldsValue({ components: items });
|
||||
|
||||
// 检查产品类型
|
||||
if (record.sku) {
|
||||
const { data: stockData } = await stockcontrollerGetstocks({ productSku: record.sku } as any);
|
||||
const { data: stockData } = await stockcontrollerGetstocks({
|
||||
productSku: record.sku,
|
||||
} as any);
|
||||
if (stockData && stockData.items && stockData.items.length > 0) {
|
||||
setProductType('single');
|
||||
formRef.current?.setFieldsValue({ productType: 'single' });
|
||||
} else {
|
||||
setProductType('bundle');
|
||||
formRef.current?.setFieldsValue({ productType: 'bundle' });
|
||||
}
|
||||
}
|
||||
const { data: componentsData } =
|
||||
await productcontrollerGetproductcomponents({ id: record.id });
|
||||
setComponents(componentsData || []);
|
||||
})();
|
||||
}, [record]);
|
||||
|
||||
const initialValues = useMemo(() => {
|
||||
if (!record) return {};
|
||||
const attributes = record.attributes || [];
|
||||
|
||||
const attributesGroupedByName = (attributes as any[]).reduce((group, attr) => {
|
||||
const dictName = attr.dict.name;
|
||||
if (!group[dictName]) {
|
||||
group[dictName] = [];
|
||||
}
|
||||
group[dictName].push(attr.name);
|
||||
return group;
|
||||
}, {} as Record<string, string[]>);
|
||||
|
||||
const values: any = {
|
||||
return {
|
||||
...record,
|
||||
brandValues: attributesGroupedByName.brand || [],
|
||||
strengthValues: attributesGroupedByName.strength || [],
|
||||
flavorValues: attributesGroupedByName.flavor || [],
|
||||
humidityValues: attributesGroupedByName.humidity || [],
|
||||
sizeValues: attributesGroupedByName.size || [],
|
||||
category: attributesGroupedByName.category?.[0] || '',
|
||||
...((record as any).attributes || []).reduce((acc: any, cur: any) => {
|
||||
const dictName = cur.dict?.name;
|
||||
if (dictName) {
|
||||
const key = `${dictName}Values`;
|
||||
if (!acc[key]) {
|
||||
acc[key] = [];
|
||||
}
|
||||
acc[key].push(cur.name);
|
||||
}
|
||||
return acc;
|
||||
}, {} as any),
|
||||
components: components,
|
||||
productType: productType,
|
||||
};
|
||||
return values;
|
||||
}, [record]);
|
||||
}, [record, components, productType]);
|
||||
|
||||
return (
|
||||
<DrawerForm<any>
|
||||
formRef={formRef}
|
||||
title="编辑"
|
||||
formRef={formRef}
|
||||
trigger={<Button type="link">编辑</Button>}
|
||||
autoFocusFirstInput
|
||||
drawerProps={{
|
||||
destroyOnHidden: true,
|
||||
}}
|
||||
initialValues={initialValues}
|
||||
onValuesChange={async (changedValues) => {
|
||||
// 当 SKU 发生变化时
|
||||
if ('sku' in changedValues) {
|
||||
const sku = changedValues.sku;
|
||||
// 如果 sku 存在
|
||||
if (sku) {
|
||||
const { data } = await stockcontrollerGetstocks({ productSku: sku } as any);
|
||||
// 获取库存信息
|
||||
const { data } = await stockcontrollerGetstocks({
|
||||
productSku: sku,
|
||||
} as any);
|
||||
// 如果库存信息存在且不为空
|
||||
if (data && data.items && data.items.length > 0) {
|
||||
setProductType('single');
|
||||
// 设置产品类型为单品
|
||||
formRef.current?.setFieldsValue({ productType: 'single' });
|
||||
} else {
|
||||
setProductType('bundle');
|
||||
// 设置产品类型为套装
|
||||
formRef.current?.setFieldsValue({ productType: 'bundle' });
|
||||
}
|
||||
} else {
|
||||
setProductType(null);
|
||||
// 如果 sku 不存在,则重置状态
|
||||
formRef.current?.setFieldsValue({ productType: null });
|
||||
}
|
||||
}
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
const attributes = [
|
||||
...(values.brandValues || []).map((v: string) => ({ dictName: 'brand', name: v })),
|
||||
...(values.strengthValues || []).map((v: string) => ({ dictName: 'strength', name: v })),
|
||||
...(values.flavorValues || []).map((v: string) => ({ dictName: 'flavor', name: v })),
|
||||
...(values.humidityValues || []).map((v: string) => ({ dictName: 'humidity', name: v })),
|
||||
...(values.sizeValues || []).map((v: string) => ({ dictName: 'size', name: v })),
|
||||
...(values.category ? [{ dictName: 'category', name: values.category }] : []),
|
||||
...(values.brandValues || []).map((v: string) => ({
|
||||
dictName: 'brand',
|
||||
name: v,
|
||||
})),
|
||||
...(values.strengthValues || []).map((v: string) => ({
|
||||
dictName: 'strength',
|
||||
name: v,
|
||||
})),
|
||||
...(values.flavorValues || []).map((v: string) => ({
|
||||
dictName: 'flavor',
|
||||
name: v,
|
||||
})),
|
||||
...(values.humidityValues || []).map((v: string) => ({
|
||||
dictName: 'humidity',
|
||||
name: v,
|
||||
})),
|
||||
...(values.sizeValues || []).map((v: string) => ({
|
||||
dictName: 'size',
|
||||
name: v,
|
||||
})),
|
||||
...(values.category
|
||||
? [{ dictName: 'category', name: values.category }]
|
||||
: []),
|
||||
];
|
||||
const updatePayload: any = {
|
||||
name: values.name,
|
||||
sku: values.sku,
|
||||
description: values.description,
|
||||
price: values.price,
|
||||
promotionPrice: values.promotionPrice,
|
||||
|
||||
const payload: any = {
|
||||
name: (values as any).name,
|
||||
description: (values as any).description,
|
||||
sku: (values as any).sku,
|
||||
price: (values as any).price,
|
||||
promotionPrice: (values as any).promotionPrice,
|
||||
attributes,
|
||||
};
|
||||
const { success, message: errMsg } = await productcontrollerUpdateproduct(
|
||||
{ id: (record as any).id },
|
||||
updatePayload,
|
||||
|
||||
const { success, message: errMsg } =
|
||||
await productcontrollerUpdateproduct({ id: record.id }, payload);
|
||||
|
||||
if (values.productType === 'bundle') {
|
||||
const { success: success2, message: errMsg2 } =
|
||||
await productcontrollerSetproductcomponents(
|
||||
{ id: record.id },
|
||||
{
|
||||
items: (values.components || []).map((c: any) => ({
|
||||
sku: c.sku,
|
||||
quantity: Number(c.quantity),
|
||||
})),
|
||||
},
|
||||
);
|
||||
if (success) {
|
||||
// 中文注释:同步更新组成(覆盖式)
|
||||
const items = (values as any)?.components || [];
|
||||
if (Array.isArray(items)) {
|
||||
const payloadItems = items
|
||||
.filter((i: any) => i && i.productSku && i.quantity && i.quantity > 0)
|
||||
.map((i: any) => ({ productSku: i.productSku, quantity: Number(i.quantity) }));
|
||||
await productcontrollerSetproductcomponents({ id: (record as any).id }, { items: payloadItems as any });
|
||||
if (!success2) {
|
||||
message.error(errMsg2);
|
||||
return false;
|
||||
}
|
||||
message.success('更新成功');
|
||||
}
|
||||
|
||||
if (success) {
|
||||
message.success('提交成功');
|
||||
tableRef.current?.reloadAndRest?.();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -607,7 +783,6 @@ const EditForm: React.FC<{
|
|||
return false;
|
||||
}}
|
||||
>
|
||||
{/* 在这里列举attribute字段 */}
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name="sku"
|
||||
|
|
@ -616,11 +791,23 @@ const EditForm: React.FC<{
|
|||
placeholder="请输入SKU"
|
||||
rules={[{ required: true, message: '请输入SKU' }]}
|
||||
/>
|
||||
{productType && (
|
||||
<Tag style={{ marginTop: '32px' }} color={productType === 'single' ? 'green' : 'orange'}>
|
||||
<ProForm.Item noStyle shouldUpdate>
|
||||
{() => {
|
||||
const productType = formRef.current?.getFieldValue('productType');
|
||||
return (
|
||||
productType && (
|
||||
<Tag
|
||||
style={{ marginTop: '32px' }}
|
||||
color={productType === 'single' ? 'green' : 'orange'}
|
||||
>
|
||||
{productType === 'single' ? '单品' : '套装'}
|
||||
</Tag>
|
||||
)}
|
||||
)
|
||||
);
|
||||
}}
|
||||
</ProForm.Item>
|
||||
</ProForm.Group>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name="name"
|
||||
label="名称"
|
||||
|
|
@ -629,58 +816,97 @@ const EditForm: React.FC<{
|
|||
rules={[{ required: true, message: '请输入名称' }]}
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<AttributeFormItem dictName="brand" name="brandValues" label="产品品牌" isTag />
|
||||
<AttributeFormItem dictName="strength" name="strengthValues" label="强度" isTag />
|
||||
<AttributeFormItem dictName="flavor" name="flavorValues" label="口味" isTag />
|
||||
<AttributeFormItem dictName="humidity" name="humidityValues" label="干湿" isTag />
|
||||
<AttributeFormItem dictName="size" name="sizeValues" label="大小" isTag />
|
||||
<AttributeFormItem dictName="category" name="category" label="分类" />
|
||||
<ProFormSelect
|
||||
name="productType"
|
||||
label="产品类型"
|
||||
options={[
|
||||
{ value: 'single', label: '单品' },
|
||||
{ value: 'bundle', label: '套装' },
|
||||
]}
|
||||
rules={[{ required: true, message: '请选择产品类型' }]}
|
||||
/>
|
||||
<ProFormTextArea
|
||||
name="description"
|
||||
label="描述"
|
||||
placeholder="请输入描述"
|
||||
/>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name="price"
|
||||
label="价格"
|
||||
width="md"
|
||||
placeholder="请输入价格"
|
||||
rules={[{ required: false }]}
|
||||
rules={[{ required: true, message: '请输入价格' }]}
|
||||
/>
|
||||
<ProFormText
|
||||
name="promotionPrice"
|
||||
label="促销价"
|
||||
width="md"
|
||||
placeholder="请输入促销价"
|
||||
rules={[{ required: false }]}
|
||||
/>
|
||||
<ProFormTextArea
|
||||
name="description"
|
||||
width="lg"
|
||||
label="产品描述"
|
||||
placeholder="请输入产品描述"
|
||||
</ProForm.Group>
|
||||
<AttributeFormItem
|
||||
dictName="brand"
|
||||
name="brandValues"
|
||||
label="产品品牌"
|
||||
isTag
|
||||
/>
|
||||
|
||||
{/* 中文注释:编辑产品组成(库存ID + 数量) */}
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name={['__helper']}
|
||||
hidden
|
||||
<AttributeFormItem
|
||||
dictName="strength"
|
||||
name="strengthValues"
|
||||
label="强度"
|
||||
isTag
|
||||
/>
|
||||
<AttributeFormItem
|
||||
dictName="flavor"
|
||||
name="flavorValues"
|
||||
label="口味"
|
||||
isTag
|
||||
/>
|
||||
<AttributeFormItem
|
||||
dictName="humidity"
|
||||
name="humidityValues"
|
||||
label="干湿度"
|
||||
isTag
|
||||
/>
|
||||
<AttributeFormItem dictName="size" name="sizeValues" label="尺寸" isTag />
|
||||
<AttributeFormItem dictName="category" name="category" label="分类" />
|
||||
<ProForm.Group title="自动绑定">
|
||||
<Button
|
||||
disabled={productType === 'single'}
|
||||
onClick={async () => {
|
||||
await productcontrollerAutobindcomponents({ id: (record as any).id });
|
||||
const { data = [] } = await productcontrollerGetproductcomponents({ id: (record as any).id });
|
||||
const items = (data || []).map((c: any) => ({ stockId: c.stockId, quantity: c.quantity }));
|
||||
formRef.current?.setFieldsValue({ components: items });
|
||||
await productcontrollerAutobindcomponents({ id: record.id });
|
||||
const { data: componentsData } =
|
||||
await productcontrollerGetproductcomponents({ id: record.id });
|
||||
formRef.current?.setFieldsValue({ components: componentsData });
|
||||
}}
|
||||
>
|
||||
自动绑定组成(同 SKU)
|
||||
Run
|
||||
</Button>
|
||||
</ProForm.Group>
|
||||
<ProForm.Item hidden={productType === 'single'}>
|
||||
<ProForm.Item
|
||||
shouldUpdate={(prevValues: any, curValues: any) =>
|
||||
prevValues.productType !== curValues.productType
|
||||
}
|
||||
noStyle
|
||||
>
|
||||
{({ getFieldValue }: { getFieldValue: (name: string) => any }) =>
|
||||
getFieldValue('productType') === 'bundle' ? (
|
||||
<ProFormList
|
||||
name="components"
|
||||
label="组成项"
|
||||
creatorButtonProps={{ position: 'bottom', creatorButtonText: '新增组成项' }}
|
||||
creatorButtonProps={{
|
||||
position: 'bottom',
|
||||
creatorButtonText: '新增组成项',
|
||||
}}
|
||||
itemRender={({ listDom, action }) => (
|
||||
<div style={{ marginBottom: 8, display: 'flex', flexDirection: 'row', alignItems: 'end' }}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 8,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'end',
|
||||
}}
|
||||
>
|
||||
{listDom}
|
||||
{action}
|
||||
</div>
|
||||
|
|
@ -688,7 +914,7 @@ const EditForm: React.FC<{
|
|||
>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name="productSku"
|
||||
name="sku"
|
||||
label="库存SKU"
|
||||
width="md"
|
||||
placeholder="请输入库存SKU"
|
||||
|
|
@ -703,6 +929,8 @@ const EditForm: React.FC<{
|
|||
/>
|
||||
</ProForm.Group>
|
||||
</ProFormList>
|
||||
) : null
|
||||
}
|
||||
</ProForm.Item>
|
||||
</DrawerForm>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@ import { useRef } from 'react';
|
|||
const List: React.FC = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
const columns: ProColumns<API.WpProductDTO>[] = [
|
||||
{
|
||||
title: 'sku',
|
||||
dataIndex: 'sku',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
|
|
@ -42,22 +46,67 @@ const List: React.FC = () => {
|
|||
request: async () => {
|
||||
const { data = [] } = await sitecontrollerAll();
|
||||
return data.map((item) => ({
|
||||
label: item.siteName,
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
},
|
||||
render: (_, record) => {
|
||||
return record.site?.name;
|
||||
},
|
||||
{
|
||||
title: 'sku',
|
||||
dataIndex: 'sku',
|
||||
hideInSearch: true,
|
||||
},
|
||||
|
||||
{
|
||||
title: '产品状态',
|
||||
dataIndex: 'status',
|
||||
valueType: 'select',
|
||||
valueEnum: PRODUCT_STATUS_ENUM,
|
||||
},
|
||||
{
|
||||
title: '产品类型',
|
||||
dataIndex: 'type',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '总销量',
|
||||
dataIndex: 'total_sales',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '库存',
|
||||
dataIndex: 'stock_quantity',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '图片',
|
||||
dataIndex: 'images',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => {
|
||||
if (record.images && record.images.length > 0) {
|
||||
return <img src={record.images[0].src} width="50" />;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
dataIndex: 'categories',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => {
|
||||
return record.categories
|
||||
?.map((item: { name: string }) => item.name)
|
||||
.join(', ');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
dataIndex: 'tags',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => {
|
||||
return record.tags
|
||||
?.map((item: { name: string }) => item.name)
|
||||
.join(', ');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '常规价格',
|
||||
dataIndex: 'regular_price',
|
||||
|
|
@ -224,7 +273,7 @@ const SyncForm: React.FC<{
|
|||
request={async () => {
|
||||
const { data = [] } = await sitecontrollerAll();
|
||||
return data.map((item) => ({
|
||||
label: item.siteName,
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
}}
|
||||
|
|
@ -240,7 +289,10 @@ const UpdateStatus: React.FC<{
|
|||
}> = ({ tableRef, values: initialValues }) => {
|
||||
const { message } = App.useApp();
|
||||
return (
|
||||
<DrawerForm<API.UpdateProductDTO>
|
||||
<DrawerForm<{
|
||||
status: API.WpProductDTO['status'];
|
||||
stock_status: API.WpProductDTO['status'];
|
||||
}>
|
||||
title="修改产品上下架状态"
|
||||
initialValues={initialValues}
|
||||
trigger={
|
||||
|
|
@ -316,15 +368,14 @@ const UpdateForm: React.FC<{
|
|||
destroyOnHidden: true,
|
||||
}}
|
||||
onFinish={async (values) => {
|
||||
const { siteId, ...params } = values;
|
||||
try {
|
||||
const { success, message: errMsg } =
|
||||
await wpproductcontrollerUpdateproduct(
|
||||
{
|
||||
productId: initialValues.externalProductId,
|
||||
siteId,
|
||||
siteId: initialValues.siteId as number,
|
||||
},
|
||||
params,
|
||||
values,
|
||||
);
|
||||
if (!success) {
|
||||
throw new Error(errMsg);
|
||||
|
|
@ -345,7 +396,7 @@ const UpdateForm: React.FC<{
|
|||
request={async () => {
|
||||
const { data = [] } = await sitecontrollerAll();
|
||||
return data.map((item) => ({
|
||||
label: item.siteName,
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
}}
|
||||
|
|
@ -411,7 +462,7 @@ const UpdateVaritation: React.FC<{
|
|||
const { success, message: errMsg } =
|
||||
await wpproductcontrollerUpdatevariation(
|
||||
{
|
||||
siteId: initialValues.siteId,
|
||||
siteId: initialValues.siteId as number,
|
||||
productId: initialValues.externalProductId,
|
||||
variationId: initialValues.externalVariationId,
|
||||
},
|
||||
|
|
@ -545,7 +596,7 @@ const SetComponent: React.FC<{
|
|||
creatorButtonProps={{ children: '新增' }}
|
||||
>
|
||||
{(fields, idx, { remove }) => (
|
||||
<div key={idx}>
|
||||
<div key={fields.key}>
|
||||
<ProFormSelect
|
||||
request={async ({ keyWords }) => {
|
||||
if (keyWords.length < 3) return [];
|
||||
|
|
@ -566,7 +617,7 @@ const SetComponent: React.FC<{
|
|||
return [];
|
||||
}
|
||||
}}
|
||||
name="sku"
|
||||
name={[fields.name, 'sku']}
|
||||
label="产品"
|
||||
width="lg"
|
||||
placeholder="请选择产品"
|
||||
|
|
@ -582,7 +633,7 @@ const SetComponent: React.FC<{
|
|||
rules={[{ required: true, message: '请选择产品' }]}
|
||||
/>
|
||||
<ProFormDigit
|
||||
name="quantity"
|
||||
name={[fields.name, 'quantity']}
|
||||
label="数量"
|
||||
placeholder="请输入数量"
|
||||
rules={[{ required: true, message: '请输入数量' }]}
|
||||
|
|
@ -590,7 +641,7 @@ const SetComponent: React.FC<{
|
|||
precision: 0,
|
||||
}}
|
||||
/>
|
||||
<Button type="link" danger onClick={() => remove(fields.key)}>
|
||||
<Button type="link" danger onClick={() => remove(idx)}>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -120,10 +120,19 @@ const ListPage: React.FC = () => {
|
|||
const rows = (data?.items || []).map((item: API.StockDTO) => {
|
||||
// 处理stockPoint可能为undefined的情况,并正确定义类型
|
||||
const stockMap = new Map<number, number>(
|
||||
(item.stockPoint || []).map((sp: any) => [sp.id || 0, sp.quantity || 0]),
|
||||
(item.stockPoint || []).map((sp: any) => [
|
||||
sp.id || 0,
|
||||
sp.quantity || 0,
|
||||
]),
|
||||
);
|
||||
const stockRow = points.map((p) => stockMap.get(p.id || 0) || 0);
|
||||
return [item.productName || '', item.productSku || '', ...stockRow];
|
||||
const stockRow = points.map(
|
||||
(p) => stockMap.get(p.id || 0) || 0,
|
||||
);
|
||||
return [
|
||||
item.productName || '',
|
||||
item.productSku || '',
|
||||
...stockRow,
|
||||
];
|
||||
});
|
||||
|
||||
// 导出
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import OrderDetailDrawer from './OrderDetailDrawer';
|
|||
interface OrderItemRow {
|
||||
id: number;
|
||||
externalOrderId: string;
|
||||
siteId: string;
|
||||
siteId: number;
|
||||
date_created: string;
|
||||
customer_email: string;
|
||||
payment_method: string;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ declare namespace API {
|
|||
createdAt: string;
|
||||
/** 更新时间 */
|
||||
updatedAt: string;
|
||||
attributes?: any[];
|
||||
};
|
||||
|
||||
type areacontrollerDeleteareaParams = {
|
||||
|
|
@ -431,11 +432,11 @@ declare namespace API {
|
|||
|
||||
type ordercontrollerSyncorderbyidParams = {
|
||||
orderId: string;
|
||||
siteId: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type ordercontrollerSyncorderParams = {
|
||||
siteId: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type ordercontrollerUpdateorderitemsParams = {
|
||||
|
|
@ -1543,7 +1544,7 @@ declare namespace API {
|
|||
};
|
||||
|
||||
type subscriptioncontrollerSyncParams = {
|
||||
siteId: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type SubscriptionListRes = {
|
||||
|
|
@ -1704,7 +1705,7 @@ declare namespace API {
|
|||
/** ID */
|
||||
id: number;
|
||||
/** wp网站ID */
|
||||
siteId: string;
|
||||
siteId: number;
|
||||
/** wp产品ID */
|
||||
externalProductId: string;
|
||||
/** wp变体ID */
|
||||
|
|
@ -1765,18 +1766,18 @@ declare namespace API {
|
|||
};
|
||||
|
||||
type wpproductcontrollerSyncproductsParams = {
|
||||
siteId: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type wpproductcontrollerUpdateproductParams = {
|
||||
productId: string;
|
||||
siteId: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type wpproductcontrollerUpdatevariationParams = {
|
||||
variationId: string;
|
||||
productId: string;
|
||||
siteId: string;
|
||||
siteId: number;
|
||||
};
|
||||
|
||||
type wpproductcontrollerUpdatewpproductstateParams = {
|
||||
|
|
@ -1787,7 +1788,7 @@ declare namespace API {
|
|||
/** ID */
|
||||
id: number;
|
||||
/** wp网站ID */
|
||||
siteId: string;
|
||||
siteId: number;
|
||||
/** wp产品ID */
|
||||
externalProductId: string;
|
||||
/** 商店sku */
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@ export function formatUniuniShipmentState(state: string) {
|
|||
'230': 'RETURN TO SENDER WAREHOUSE',
|
||||
'231': 'FAILED_DELIVERY_RETRY1',
|
||||
'232': 'FAILED_DELIVERY_RETRY2',
|
||||
'255': 'Gateway_To_Gateway_Transit'
|
||||
}
|
||||
'255': 'Gateway_To_Gateway_Transit',
|
||||
};
|
||||
if (state in UNIUNI_STATUS_ENUM) {
|
||||
return UNIUNI_STATUS_ENUM[state];
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in New Issue