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