refactor(Product): 优化产品列表和编辑表单的代码结构和逻辑

fix(Product): 修正产品类型和库存状态的关联逻辑
style: 统一代码格式和缩进
fix(Product): 修复产品组成项编辑时的类型错误
feat(Product): 添加产品类型选择功能
refactor(Product): 重构属性表单组件
fix(Product): 修正WP产品列表的站点显示问题
This commit is contained in:
tikkhun 2025-12-01 09:30:51 +08:00
parent b2575a11fd
commit 0f264c15a5
16 changed files with 725 additions and 286 deletions

View File

@ -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"

View File

@ -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,

View File

@ -116,5 +116,5 @@ export const ORDER_STATUS_ENUM: ProSchemaValueEnumObj = {
refund_cancelled: { refund_cancelled: {
text: '已取消退款', text: '已取消退款',
status: 'refund_cancelled', status: 'refund_cancelled',
} },
}; };

View File

@ -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)}
@ -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>

View File

@ -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} />]}
/> />
@ -163,7 +176,12 @@ const CreateForm: 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>
); );
@ -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>
); );

View File

@ -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 (

View File

@ -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',
]);

View File

@ -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;

View File

@ -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,30 +422,61 @@ 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,
@ -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 (record.sku) {
const { data: stockData } = await stockcontrollerGetstocks({ productSku: record.sku } as any);
if (stockData && stockData.items && stockData.items.length > 0) { if (stockData && stockData.items && stockData.items.length > 0) {
setProductType('single'); setProductType('single');
formRef.current?.setFieldsValue({ productType: 'single' });
} else { } else {
setProductType('bundle'); setProductType('bundle');
formRef.current?.setFieldsValue({ productType: 'bundle' });
} }
} const { data: componentsData } =
await productcontrollerGetproductcomponents({ id: record.id });
setComponents(componentsData || []);
})(); })();
}, [record]); }, [record]);
const initialValues = useMemo(() => { const initialValues = useMemo(() => {
if (!record) return {}; 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 = {
...record, ...record,
brandValues: attributesGroupedByName.brand || [], ...((record as any).attributes || []).reduce((acc: any, cur: any) => {
strengthValues: attributesGroupedByName.strength || [], const dictName = cur.dict?.name;
flavorValues: attributesGroupedByName.flavor || [], if (dictName) {
humidityValues: attributesGroupedByName.humidity || [], const key = `${dictName}Values`;
sizeValues: attributesGroupedByName.size || [], if (!acc[key]) {
category: attributesGroupedByName.category?.[0] || '', acc[key] = [];
}
acc[key].push(cur.name);
}
return acc;
}, {} as any),
components: components,
productType: productType,
}; };
return values; }, [record, components, productType]);
}, [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 (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) { if (!success2) {
// 中文注释:同步更新组成(覆盖式) message.error(errMsg2);
const items = (values as any)?.components || []; return false;
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 });
} }
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'}> {() => {
const productType = formRef.current?.getFieldValue('productType');
return (
productType && (
<Tag
style={{ marginTop: '32px' }}
color={productType === 'single' ? 'green' : 'orange'}
>
{productType === 'single' ? '单品' : '套装'} {productType === 'single' ? '单品' : '套装'}
</Tag> </Tag>
)} )
);
}}
</ProForm.Item>
</ProForm.Group>
<ProForm.Group>
<ProFormText <ProFormText
name="name" name="name"
label="名称" label="名称"
@ -629,58 +816,97 @@ 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: '套装' },
]}
rules={[{ required: true, message: '请选择产品类型' }]}
/>
<ProFormTextArea
name="description"
label="描述"
placeholder="请输入描述"
/>
<ProForm.Group>
<ProFormText <ProFormText
name="price" name="price"
label="价格" label="价格"
width="md" width="md"
placeholder="请输入价格" placeholder="请输入价格"
rules={[{ required: false }]} rules={[{ required: true, message: '请输入价格' }]}
/> />
<ProFormText <ProFormText
name="promotionPrice" name="promotionPrice"
label="促销价" label="促销价"
width="md" width="md"
placeholder="请输入促销价" placeholder="请输入促销价"
rules={[{ required: false }]}
/> />
<ProFormTextArea </ProForm.Group>
name="description" <AttributeFormItem
width="lg" dictName="brand"
label="产品描述" name="brandValues"
placeholder="请输入产品描述" label="产品品牌"
isTag
/> />
<AttributeFormItem
{/* 中文注释编辑产品组成库存ID + 数量) */} dictName="strength"
<ProForm.Group> name="strengthValues"
<ProFormText label="强度"
name={['__helper']} isTag
hidden
/> />
<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
shouldUpdate={(prevValues: any, curValues: any) =>
prevValues.productType !== curValues.productType
}
noStyle
>
{({ getFieldValue }: { getFieldValue: (name: string) => any }) =>
getFieldValue('productType') === 'bundle' ? (
<ProFormList <ProFormList
name="components" name="components"
label="组成项" label="组成项"
creatorButtonProps={{ position: 'bottom', creatorButtonText: '新增组成项' }} creatorButtonProps={{
position: 'bottom',
creatorButtonText: '新增组成项',
}}
itemRender={({ listDom, action }) => ( itemRender={({ listDom, action }) => (
<div style={{ marginBottom: 8, display: 'flex', flexDirection: 'row', alignItems: 'end' }}> <div
style={{
marginBottom: 8,
display: 'flex',
flexDirection: 'row',
alignItems: 'end',
}}
>
{listDom} {listDom}
{action} {action}
</div> </div>
@ -688,7 +914,7 @@ const EditForm: React.FC<{
> >
<ProForm.Group> <ProForm.Group>
<ProFormText <ProFormText
name="productSku" name="sku"
label="库存SKU" label="库存SKU"
width="md" width="md"
placeholder="请输入库存SKU" placeholder="请输入库存SKU"
@ -703,7 +929,9 @@ const EditForm: React.FC<{
/> />
</ProForm.Group> </ProForm.Group>
</ProFormList> </ProFormList>
) : null
}
</ProForm.Item> </ProForm.Item>
</DrawerForm > </DrawerForm>
); );
}; };

View File

@ -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>

View File

@ -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,
];
}); });
// 导出 // 导出

View File

@ -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;

View File

@ -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 */

View File

@ -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 {