diff --git a/src/app.tsx b/src/app.tsx index abc29b2..fc9f857 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -15,7 +15,7 @@ import { usercontrollerGetuser } from './servers/api/user'; // 设置 dayjs 全局语言为中文 dayjs.locale('zh-cn'); -// 全局初始化数据配置,用于 Layout 用户信息和权限初始化 +// 全局初始化数据配置,用于 Layout 用户信息和权限初始化 // 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate export async function getInitialState(): Promise<{ user?: Record; @@ -102,11 +102,11 @@ export const request: RequestConfig = { export const onRouteChange = ({ location }: { location: Location }) => { const token = localStorage.getItem('token'); - // 白名单,不需要登录的页面 + // 白名单,不需要登录的页面 const whiteList = ['/login', '/track']; if (!token && !whiteList.includes(location.pathname)) { - // 没有 token 且不在白名单内,跳转到登录页 + // 没有 token 且不在白名单内,跳转到登录页 history.push('/login'); } }; diff --git a/src/components/SyncForm.tsx b/src/components/SyncForm.tsx index 997ecc8..c4b8ea3 100644 --- a/src/components/SyncForm.tsx +++ b/src/components/SyncForm.tsx @@ -27,7 +27,7 @@ const SyncForm: React.FC = ({ tableRef, onFinish }) => { return ( title="同步订单" - // 表单的触发器,一个带图标的按钮 + // 表单的触发器,一个带图标的按钮 trigger={ { try { await request(`/area/${row.id}`, { diff --git a/src/pages/Category/index.tsx b/src/pages/Category/index.tsx index 0a076b0..607aaa2 100644 --- a/src/pages/Category/index.tsx +++ b/src/pages/Category/index.tsx @@ -197,7 +197,7 @@ const CategoryPage: React.FC = () => { , { e?.stopPropagation(); handleDeleteCategory(item.id); @@ -218,7 +218,7 @@ const CategoryPage: React.FC = () => { {selectedCategory ? ( - 添加关联属性}> + 添加关联属性}> { handleDeleteAttribute(item.id)} > diff --git a/src/pages/Dict/List/index.tsx b/src/pages/Dict/List/index.tsx index 6482ab4..ca0d67c 100644 --- a/src/pages/Dict/List/index.tsx +++ b/src/pages/Dict/List/index.tsx @@ -289,7 +289,7 @@ const DictPage: React.FC = () => { size="small" onRow={(record) => ({ onClick: () => { - // 如果点击的是当前已选中的行,则取消选择 + // 如果点击的是当前已选中的行,则取消选择 if (selectedDict?.id === record.id) { setSelectedDict(null); } else { @@ -356,7 +356,7 @@ const DictPage: React.FC = () => { { - // 当没有选择字典时,不发起请求 + // 当没有选择字典时,不发起请求 if (!selectedDict?.id) { return { data: [], diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx index e557530..d8eaee7 100644 --- a/src/pages/Login/index.tsx +++ b/src/pages/Login/index.tsx @@ -96,11 +96,11 @@ const Page = () => { /> ), }} - placeholder={'请输入密码!'} + placeholder={'请输入密码!'} rules={[ { required: true, - message: '请输入密码!', + message: '请输入密码!', }, ]} /> diff --git a/src/pages/Logistics/Address/index.tsx b/src/pages/Logistics/Address/index.tsx index 85df945..dde2f26 100644 --- a/src/pages/Logistics/Address/index.tsx +++ b/src/pages/Logistics/Address/index.tsx @@ -75,7 +75,7 @@ const ListPage: React.FC = () => { { try { const { success, message: errMsg } = diff --git a/src/pages/Logistics/List/index.tsx b/src/pages/Logistics/List/index.tsx index 4def59a..9f48d4e 100644 --- a/src/pages/Logistics/List/index.tsx +++ b/src/pages/Logistics/List/index.tsx @@ -74,9 +74,9 @@ const ListPage: React.FC = () => { await navigator.clipboard.writeText( record.return_tracking_number, ); - message.success('复制成功!'); + message.success('复制成功!'); } catch (err) { - message.error('复制失败!'); + message.error('复制失败!'); } }} /> @@ -140,7 +140,7 @@ const ListPage: React.FC = () => { { try { setIsLoading(true); diff --git a/src/pages/Order/Items/index.tsx b/src/pages/Order/Items/index.tsx index 0929852..adaa383 100644 --- a/src/pages/Order/Items/index.tsx +++ b/src/pages/Order/Items/index.tsx @@ -28,7 +28,7 @@ const OrderItemsPage: React.FC = () => { const actionRef = useRef(); const { message } = App.useApp(); - // 列配置(中文标题,符合当前项目风格;显示英文默认语言可后续走国际化) + // 列配置(中文标题,符合当前项目风格;显示英文默认语言可后续走国际化) const columns: ProColumns[] = [ { title: '商品名称', diff --git a/src/pages/Order/List/index.tsx b/src/pages/Order/List/index.tsx index da46392..ed5b688 100644 --- a/src/pages/Order/List/index.tsx +++ b/src/pages/Order/List/index.tsx @@ -381,7 +381,7 @@ const ListPage: React.FC = () => { label: ( { try { if (!record.id) { @@ -513,7 +513,7 @@ const Detail: React.FC<{ orderId, }); if (!success || !data) return { data: {} }; - // 合并订单中相同的sku,只显示一次记录总数 + // 合并订单中相同的sku,只显示一次记录总数 data.sales = data.sales?.reduce( (acc: API.OrderSale[], cur: API.OrderSale) => { let idx = acc.findIndex((v: any) => v.productId === cur.productId); @@ -605,7 +605,7 @@ const Detail: React.FC<{ , { try { if (!record.id) { @@ -641,7 +641,7 @@ const Detail: React.FC<{ , { try { if (!record.id) { @@ -668,7 +668,7 @@ const Detail: React.FC<{ , { try { if (!record.id) { @@ -695,7 +695,7 @@ const Detail: React.FC<{ , { try { if (!record.id) { @@ -722,7 +722,7 @@ const Detail: React.FC<{ , { try { const { success, message: errMsg } = @@ -999,9 +999,9 @@ const Detail: React.FC<{ await navigator.clipboard.writeText( v.tracking_url, ); - message.success('复制成功!'); + message.success('复制成功!'); } catch (err) { - message.error('复制失败!'); + message.error('复制失败!'); } }} /> @@ -1013,7 +1013,7 @@ const Detail: React.FC<{ ? [ { try { const { success, message: errMsg } = @@ -1461,7 +1461,7 @@ const Shipping: React.FC<{ showSearch: true, filterOption: false, }} - debounceTime={300} // 防抖,减少请求频率 + debounceTime={300} // 防抖,减少请求频率 rules={[{ required: true, message: '请选择产品' }]} /> { hideInSearch: true, width: 800, render: (_, record) => { - return record?.numbers?.join?.('、'); + return record?.numbers?.join?.(','); }, }, ]; @@ -72,7 +72,7 @@ const ListPage: React.FC = () => { // 数据行 const rows = (data?.items || []).map((item) => { - return [item.name, item.quantity, item.numbers?.join('、')]; + return [item.name, item.quantity, item.numbers?.join(',')]; }); // 导出 diff --git a/src/pages/Organiza/User/index.tsx b/src/pages/Organiza/User/index.tsx index 3f209eb..939be46 100644 --- a/src/pages/Organiza/User/index.tsx +++ b/src/pages/Organiza/User/index.tsx @@ -70,7 +70,7 @@ const ListPage: React.FC = () => { danger={record.isActive} type="link" onClick={async () => { - // 中文注释:软删除为禁用(isActive=false),再次点击可启用 + // 软删除为禁用(isActive=false),再次点击可启用 const next = !record.isActive; const { success, message: errMsg } = await usercontrollerToggleactive({ @@ -204,7 +204,7 @@ const EditForm: React.FC<{ }} onFinish={async (values: any) => { try { - // 中文注释:更新用户,密码可选填 + // 更新用户,密码可选填 const { success, message: err } = await usercontrollerUpdateuser( { id: record.id }, values, diff --git a/src/pages/Product/Attribute/consts.ts b/src/pages/Product/Attribute/consts.ts index 5a6ddff..23d769b 100644 --- a/src/pages/Product/Attribute/consts.ts +++ b/src/pages/Product/Attribute/consts.ts @@ -1,4 +1,4 @@ -// 中文注释:限定允许管理的字典名称集合 +// 限定允许管理的字典名称集合 export const attributes = new Set([ 'brand', 'strength', diff --git a/src/pages/Product/Attribute/index.tsx b/src/pages/Product/Attribute/index.tsx index 474d554..a013076 100644 --- a/src/pages/Product/Attribute/index.tsx +++ b/src/pages/Product/Attribute/index.tsx @@ -23,16 +23,16 @@ const { Sider, Content } = Layout; import { attributes } from './consts'; const AttributePage: React.FC = () => { - // 中文注释:左侧字典列表状态 + // 左侧字典列表状态 const [dicts, setDicts] = useState([]); const [loadingDicts, setLoadingDicts] = useState(false); const [searchText, setSearchText] = useState(''); const [selectedDict, setSelectedDict] = useState(null); - // 中文注释:右侧字典项 ProTable 的引用 + // 右侧字典项 ProTable 的引用 const actionRef = useRef(); - // 中文注释:字典项新增/编辑模态框控制 + // 字典项新增/编辑模态框控制 const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false); const [editingDictItem, setEditingDictItem] = useState(null); const [dictItemForm] = Form.useForm(); @@ -41,7 +41,7 @@ const AttributePage: React.FC = () => { setLoadingDicts(true); try { const res = await request('/dict/list', { params: { title } }); - // 中文注释:条件判断,过滤只保留 allowedDictNames 中的字典 + // 条件判断,过滤只保留 allowedDictNames 中的字典 const filtered = (res || []).filter((d: any) => attributes.has(d?.name)); setDicts(filtered); } catch (error) { @@ -50,42 +50,42 @@ const AttributePage: React.FC = () => { setLoadingDicts(false); }; - // 中文注释:组件挂载时初始化数据 + // 组件挂载时初始化数据 useEffect(() => { fetchDicts(); }, []); - // 中文注释:搜索触发过滤 + // 搜索触发过滤 const handleSearch = (value: string) => { fetchDicts(value); }; - // 中文注释:打开添加字典项模态框 + // 打开添加字典项模态框 const handleAddDictItem = () => { setEditingDictItem(null); dictItemForm.resetFields(); setIsDictItemModalVisible(true); }; - // 中文注释:打开编辑字典项模态框 + // 打开编辑字典项模态框 const handleEditDictItem = (item: any) => { setEditingDictItem(item); dictItemForm.setFieldsValue(item); setIsDictItemModalVisible(true); }; - // 中文注释:字典项表单提交(新增或编辑) + // 字典项表单提交(新增或编辑) const handleDictItemFormSubmit = async (values: any) => { try { if (editingDictItem) { - // 中文注释:条件判断,存在编辑项则执行更新 + // 条件判断,存在编辑项则执行更新 await request(`/dict/item/${editingDictItem.id}`, { method: 'PUT', data: values, }); message.success('更新成功'); } else { - // 中文注释:否则执行新增,绑定到当前选择的字典 + // 否则执行新增,绑定到当前选择的字典 await request('/dict/item', { method: 'POST', data: { ...values, dictId: selectedDict.id }, @@ -93,30 +93,30 @@ const AttributePage: React.FC = () => { message.success('添加成功'); } setIsDictItemModalVisible(false); - actionRef.current?.reload(); // 中文注释:刷新 ProTable + actionRef.current?.reload(); // 刷新 ProTable } catch (error) { message.error(editingDictItem ? '更新失败' : '添加失败'); } }; - // 中文注释:删除字典项 + // 删除字典项 const handleDeleteDictItem = async (itemId: number) => { try { await request(`/dict/item/${itemId}`, { method: 'DELETE' }); message.success('删除成功'); - actionRef.current?.reload(); // 中文注释:刷新 ProTable + actionRef.current?.reload(); // 刷新 ProTable } catch (error) { message.error('删除失败'); } }; - // 中文注释:左侧字典列表列定义(紧凑样式) + // 左侧字典列表列定义(紧凑样式) const dictColumns = [ { title: '名称', dataIndex: 'name', key: 'name' }, { title: '标题', dataIndex: 'title', key: 'title' }, ]; - // 中文注释:右侧字典项列表列定义(紧凑样式) + // 右侧字典项列表列定义(紧凑样式) const dictItemColumns: any[] = [ { title: '名称', dataIndex: 'name', key: 'name', copyable: true }, { title: '标题', dataIndex: 'title', key: 'title', copyable: true }, @@ -177,7 +177,7 @@ const AttributePage: React.FC = () => { size="small" onRow={(record) => ({ onClick: () => { - // 中文注释:条件判断,重复点击同一行则取消选择 + // 条件判断,重复点击同一行则取消选择 if (selectedDict?.id === record.id) { setSelectedDict(null); } else { @@ -197,7 +197,7 @@ const AttributePage: React.FC = () => { columns={dictItemColumns} actionRef={actionRef} request={async (params) => { - // 中文注释:当没有选择字典时,不发起请求 + // 当没有选择字典时,不发起请求 if (!selectedDict?.id) { return { data: [], @@ -242,7 +242,7 @@ const AttributePage: React.FC = () => { showUploadList={false} disabled={!selectedDict} onChange={(info) => { - // 中文注释:条件判断,上传状态处理 + // 条件判断,上传状态处理 if (info.file.status === 'done') { message.success(`${info.file.name} 文件上传成功`); actionRef.current?.reload(); diff --git a/src/pages/Product/Category/index.tsx b/src/pages/Product/Category/index.tsx index b96283f..5ed9d09 100644 --- a/src/pages/Product/Category/index.tsx +++ b/src/pages/Product/Category/index.tsx @@ -202,7 +202,7 @@ const CategoryPage: React.FC = () => { , { e?.stopPropagation(); handleDeleteCategory(item.id); @@ -223,7 +223,7 @@ const CategoryPage: React.FC = () => { {selectedCategory ? ( - 添加关联属性}> + 添加关联属性}> { handleDeleteAttribute(item.id)} > diff --git a/src/pages/Product/List/index.tsx b/src/pages/Product/List/index.tsx index 9a0aac8..edef1e0 100644 --- a/src/pages/Product/List/index.tsx +++ b/src/pages/Product/List/index.tsx @@ -94,11 +94,11 @@ const ComponentsCell: React.FC<{ productId: number }> = ({ productId }) => { components.map((component: any) => ( {component.sku || `#${component.id}`} × {component.quantity} - (库存: + (库存: {component.stock ?.map((s: any) => `${s.name}:${s.quantity}`) .join(', ') || '-'} - ) + ) )) ) : ( @@ -110,22 +110,22 @@ const ComponentsCell: React.FC<{ productId: number }> = ({ productId }) => { const List: React.FC = () => { const actionRef = useRef(); - // 状态:存储当前选中的行 + // 状态:存储当前选中的行 const [selectedRows, setSelectedRows] = React.useState([]); const { message } = App.useApp(); - // 中文注释:导出产品 CSV(带认证请求) + // 导出产品 CSV(带认证请求) const handleDownloadProductsCSV = async () => { try { - // 中文注释:发起认证请求获取 CSV Blob + // 发起认证请求获取 CSV Blob const blob = await request('/product/export', { responseType: 'blob' }); - // 中文注释:构建下载文件名 + // 构建下载文件名 const d = new Date(); const pad = (n: number) => String(n).padStart(2, '0'); const filename = `products-${d.getFullYear()}${pad( d.getMonth() + 1, )}${pad(d.getDate())}.csv`; - // 中文注释:创建临时链接并触发下载 + // 创建临时链接并触发下载 const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; @@ -142,10 +142,12 @@ const List: React.FC = () => { { title: 'sku', dataIndex: 'sku', + sorter: true, }, { title: '名称', dataIndex: 'name', + sorter: true, }, { title: '中文名', @@ -156,27 +158,7 @@ const List: React.FC = () => { ); }, }, - { - title: '产品类型', - dataIndex: 'type', - valueType: 'select', - valueEnum: { - single: { text: '单品' }, - bundle: { text: '套装' }, - }, - render: (_, record) => { - // 如果类型不存在,则返回- - if (!record.type) return '-'; - // 判断是否为单品 - const isSingle = record.type === 'single'; - // 根据类型显示不同颜色的标签 - return ( - - {isSingle ? '单品' : '套装'} - - ); - }, - }, + { title: '商品类型', dataIndex: 'category', @@ -188,17 +170,40 @@ const List: React.FC = () => { title: '价格', dataIndex: 'price', hideInSearch: true, + sorter: true, }, { title: '促销价', dataIndex: 'promotionPrice', hideInSearch: true, + sorter: true, }, { title: '属性', dataIndex: 'attributes', hideInSearch: true, render: (_, record) => , + }, + { + title: '产品类型', + dataIndex: 'type', + valueType: 'select', + valueEnum: { + single: { text: '单品' }, + bundle: { text: '套装' }, + }, + render: (_, record) => { + // 如果类型不存在,则返回- + if (!record.type) return '-'; + // 判断是否为单品 + const isSingle = record.type === 'single'; + // 根据类型显示不同颜色的标签 + return ( + + {isSingle ? '单品' : '套装'} + + ); + }, }, { title: '构成', @@ -217,12 +222,14 @@ const List: React.FC = () => { dataIndex: 'updatedAt', valueType: 'dateTime', hideInSearch: true, + sorter: true, }, { title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime', hideInSearch: true, + sorter: true, }, { title: '操作', @@ -234,7 +241,7 @@ const List: React.FC = () => { { try { const { success, message: errMsg } = @@ -264,33 +271,53 @@ const List: React.FC = () => { actionRef={actionRef} rowKey="id" toolBarRender={() => [ - // 中文注释:新建按钮 + // 新建按钮 , - // 中文注释:导出 CSV(后端返回 text/csv,直接新窗口下载) + // 导出 CSV(后端返回 text/csv,直接新窗口下载) , - // 中文注释:导入 CSV(上传文件成功后刷新表格) + // 导入 CSV(使用 customRequest 以支持 request 拦截器和鉴权) { - if (file.status === 'done') { + customRequest={async (options) => { + const { file, onSuccess, onError } = options; + const formData = new FormData(); + formData.append('file', file); + try { + await request('/product/import', { + method: 'POST', + data: formData, + requestType: 'form', + }); message.success('导入完成'); + onSuccess?.('ok'); actionRef.current?.reload(); - } else if (file.status === 'error') { + } catch (error: any) { message.error('导入失败'); + onError?.(error); } }} > , ]} - request={async (params) => { - const { data, success } = await productcontrollerGetproductlist( - params, - ); + request={async (params, sort) => { + let sortField = undefined; + let sortOrder = undefined; + + if (sort && Object.keys(sort).length > 0) { + const field = Object.keys(sort)[0]; + sortField = field; + sortOrder = sort[field]; + } + + const { data, success } = await productcontrollerGetproductlist({ + ...params, + sortField, + sortOrder, + } as any); return { total: data?.total || 0, data: data?.items || [], @@ -355,10 +382,10 @@ const CreateForm: React.FC<{ const { humidityValues, brandValues, strengthValues, flavorValues } = formValues; // 检查是否所有必需的字段都已选择 - // 注意:这里仅检查标准属性,如果当前分类没有这些属性,可能需要调整逻辑 - // 暂时保持原样,假设常用属性会被配置 + // 注意:这里仅检查标准属性,如果当前分类没有这些属性,可能需要调整逻辑 + // 暂时保持原样,假设常用属性会被配置 - // 所选值(用于 SKU 模板传入 name) + // 所选值(用于 SKU 模板传入 name) const brandName: string = String(brandValues?.[0] || ''); const strengthName: string = String(strengthValues?.[0] || ''); const flavorName: string = String(flavorValues?.[0] || ''); @@ -480,14 +507,14 @@ const CreateForm: React.FC<{ formRef.current?.setFieldsValue({ type: 'bundle' }); } } else { - // 如果 sku 不存在,则重置状态 + // 如果 sku 不存在,则重置状态 setStockStatus(null); formRef.current?.setFieldsValue({ type: null }); } } }} onFinish={async (values: any) => { - // 中文注释:组装 attributes(根据 activeAttributes 动态组装) + // 组装 attributes(根据 activeAttributes 动态组装) const attributes = activeAttributes.flatMap((attr: any) => { const dictName = attr.name; const key = `${dictName}Values`; @@ -715,12 +742,12 @@ const EditForm: React.FC<{ sku: record.sku, } as any); if (stockData && stockData.items && stockData.items.length > 0) { - // 如果有库存,则为单品 + // 如果有库存,则为单品 setType('single'); setStockStatus('in-stock'); formRef.current?.setFieldsValue({ type: 'single' }); } else { - // 如果没有库存,则为套装 + // 如果没有库存,则为套装 setType('bundle'); setStockStatus('out-of-stock'); formRef.current?.setFieldsValue({ type: 'bundle' }); @@ -785,13 +812,13 @@ const EditForm: React.FC<{ formRef.current?.setFieldsValue({ type: 'bundle' }); } } else { - // 如果 sku 不存在,则重置状态 + // 如果 sku 不存在,则重置状态 formRef.current?.setFieldsValue({ type: null }); } } }} onFinish={async (values) => { - // 中文注释:组装 attributes + // 组装 attributes const attributes = activeAttributes.flatMap((attr: any) => { const dictName = attr.name; const key = `${dictName}Values`; diff --git a/src/pages/Product/Sync/index.tsx b/src/pages/Product/Sync/index.tsx index dfd97c2..7a0fa5f 100644 --- a/src/pages/Product/Sync/index.tsx +++ b/src/pages/Product/Sync/index.tsx @@ -8,6 +8,7 @@ interface Site { id: string; name: string; prefix?: string; + isDisabled?: boolean; } // 定义WordPress商品接口 @@ -80,7 +81,9 @@ const ProductSyncPage: React.FC = () => { setLoading(true); // 获取所有站点 const sitesResponse = await getSites(); - const siteList: Site[] = sitesResponse.data || []; + const rawSiteList = sitesResponse.data || []; + // 过滤掉已禁用的站点 + const siteList: Site[] = rawSiteList.filter(site => !site.isDisabled); setSites(siteList); // 获取所有 WordPress 商品 @@ -120,7 +123,7 @@ const ProductSyncPage: React.FC = () => { // 转换为数组 setProducts(Array.from(productMap.values())); } catch (error) { - message.error('获取数据失败,请重试'); + message.error('获取数据失败,请重试'); console.error('Error fetching data:', error); } finally { setLoading(false); diff --git a/src/pages/Site/List/index.tsx b/src/pages/Site/List/index.tsx index fb54160..55dd006 100644 --- a/src/pages/Site/List/index.tsx +++ b/src/pages/Site/List/index.tsx @@ -2,6 +2,7 @@ import { ActionType, DrawerForm, ProColumns, + ProFormDependency, ProFormInstance, ProFormSelect, ProFormSwitch, @@ -18,7 +19,7 @@ interface AreaItem { name: string; } -// 站点数据项类型(前端不包含密钥字段,后端列表不返回密钥) +// 站点数据项类型(前端不包含密钥字段,后端列表不返回密钥) interface SiteItem { id: number; name: string; @@ -29,7 +30,7 @@ interface SiteItem { areas?: AreaItem[]; } -// 创建/更新表单的值类型,包含可选的密钥字段 +// 创建/更新表单的值类型,包含可选的密钥字段 interface SiteFormValues { name: string; apiUrl?: string; @@ -37,6 +38,7 @@ interface SiteFormValues { isDisabled?: boolean; consumerKey?: string; // WooCommerce REST API 的 consumer key consumerSecret?: string; // WooCommerce REST API 的 consumer secret + token?: string; // Shopyy token skuPrefix?: string; areas?: string[]; } @@ -58,6 +60,7 @@ const SiteList: React.FC = () => { isDisabled: !!editing.isDisabled, consumerKey: undefined, consumerSecret: undefined, + token: undefined, areas: editing.areas?.map((area) => area.code) ?? [], }); } else { @@ -69,6 +72,7 @@ const SiteList: React.FC = () => { isDisabled: false, consumerKey: undefined, consumerSecret: undefined, + token: undefined, }); } }, [open, editing]); @@ -148,7 +152,7 @@ const SiteList: React.FC = () => { { try { @@ -198,14 +202,17 @@ const SiteList: React.FC = () => { } }; - // 提交创建/更新逻辑;编辑时未填写密钥则不提交(保持原值) + // 提交创建/更新逻辑;编辑时未填写密钥则不提交(保持原值) const handleSubmit = async (values: SiteFormValues) => { try { + const isShopyy = values.type === 'shopyy'; + const apiUrl = isShopyy ? 'https://openapi.oemapps.com' : values.apiUrl; + if (editing) { const payload: Record = { - // 仅提交存在的字段,避免覆盖为 null/空 + // 仅提交存在的字段,避免覆盖为 null/空 ...(values.name ? { name: values.name } : {}), - ...(values.apiUrl ? { apiUrl: values.apiUrl } : {}), + ...(apiUrl ? { apiUrl: apiUrl } : {}), ...(values.type ? { type: values.type } : {}), ...(typeof values.isDisabled === 'boolean' ? { isDisabled: values.isDisabled } @@ -213,30 +220,46 @@ const SiteList: React.FC = () => { ...(values.skuPrefix ? { skuPrefix: values.skuPrefix } : {}), areas: values.areas ?? [], }; - // 仅当输入了新密钥时才提交,未输入则保持原本值 - if (values.consumerKey && values.consumerKey.trim()) { - payload.consumerKey = values.consumerKey.trim(); - } - if (values.consumerSecret && values.consumerSecret.trim()) { - payload.consumerSecret = values.consumerSecret.trim(); + + if (isShopyy) { + if (values.token && values.token.trim()) { + payload.token = values.token.trim(); + } + } else { + // 仅当输入了新密钥时才提交,未输入则保持原本值 + if (values.consumerKey && values.consumerKey.trim()) { + payload.consumerKey = values.consumerKey.trim(); + } + if (values.consumerSecret && values.consumerSecret.trim()) { + payload.consumerSecret = values.consumerSecret.trim(); + } } + await request(`/site/update/${editing.id}`, { method: 'PUT', data: payload, }); } else { - // 新增站点时要求填写 consumerKey 和 consumerSecret - if (!values.consumerKey || !values.consumerSecret) { - throw new Error('Consumer Key and Secret are required'); + if (isShopyy) { + if (!values.token) { + throw new Error('Token is required for Shopyy'); + } + } else { + // 新增站点时要求填写 consumerKey 和 consumerSecret + if (!values.consumerKey || !values.consumerSecret) { + throw new Error('Consumer Key and Secret are required for WooCommerce'); + } } + await request('/site/create', { method: 'POST', data: { name: values.name, - apiUrl: values.apiUrl, + apiUrl: apiUrl, type: values.type || 'woocommerce', - consumerKey: values.consumerKey, - consumerSecret: values.consumerSecret, + consumerKey: isShopyy ? undefined : values.consumerKey, + consumerSecret: isShopyy ? undefined : values.consumerSecret, + token: isShopyy ? values.token : undefined, skuPrefix: values.skuPrefix, areas: values.areas ?? [], }, @@ -286,31 +309,14 @@ const SiteList: React.FC = () => { formRef={formRef} onFinish={handleSubmit} > - {/* 站点名称,必填 */} + {/* 站点名称,必填 */} - {/* API 地址,可选 */} - - {/* 平台类型选择 */} - - {/* 是否禁用 */} - - {/* 区域选择 */} + {/* 区域选择 */} { } }} /> + {/* 平台类型选择 */} + + + + {({ type }) => { + const isShopyy = type === 'shopyy'; + return isShopyy ? ( + <> + + + + ) : ( + <> + + {/* WooCommerce REST consumer key */} + + {/* WooCommerce REST consumer secret */} + + + ); + }} + + + {editing && } + - {/* WooCommerce REST consumer key;新增必填,编辑不填则保持原值 */} - - {/* WooCommerce REST consumer secret;新增必填,编辑不填则保持原值 */} - ); diff --git a/src/pages/Stock/List/index.tsx b/src/pages/Stock/List/index.tsx index 328cfa4..0f9b928 100644 --- a/src/pages/Stock/List/index.tsx +++ b/src/pages/Stock/List/index.tsx @@ -118,7 +118,7 @@ const ListPage: React.FC = () => { // 数据行 const rows = (data?.items || []).map((item: API.StockDTO) => { - // 处理stockPoint可能为undefined的情况,并正确定义类型 + // 处理stockPoint可能为undefined的情况,并正确定义类型 const stockMap = new Map( (item.stockPoint || []).map((sp: any) => [ sp.id || 0, diff --git a/src/pages/Stock/PurchaseOrder/index.tsx b/src/pages/Stock/PurchaseOrder/index.tsx index addd481..8d2eff3 100644 --- a/src/pages/Stock/PurchaseOrder/index.tsx +++ b/src/pages/Stock/PurchaseOrder/index.tsx @@ -94,7 +94,7 @@ const PurchaseOrderPage: React.FC = () => { { try { const { success, message: errMsg } = @@ -120,7 +120,7 @@ const PurchaseOrderPage: React.FC = () => { { try { const { success, message: errMsg } = @@ -297,7 +297,7 @@ const CreateForm: React.FC<{ transform={(value) => { return value?.value || value; }} - debounceTime={300} // 防抖,减少请求频率 + debounceTime={300} // 防抖,减少请求频率 rules={[{ required: true, message: '请选择产品' }]} onChange={(_, option) => { form.setFieldValue( @@ -478,7 +478,7 @@ const UpdateForm: React.FC<{ transform={(value) => { return value?.value || value; }} - debounceTime={300} // 防抖,减少请求频率 + debounceTime={300} // 防抖,减少请求频率 rules={[{ required: true, message: '请选择产品' }]} onChange={(_, option) => { form.setFieldValue( @@ -643,7 +643,7 @@ const DetailForm: React.FC<{ transform={(value) => { return value?.value || value; }} - debounceTime={300} // 防抖,减少请求频率 + debounceTime={300} // 防抖,减少请求频率 rules={[{ required: true, message: '请选择产品' }]} onChange={(_, option) => { form.setFieldValue( diff --git a/src/pages/Stock/Transfer/index.tsx b/src/pages/Stock/Transfer/index.tsx index 3166e61..0b1160e 100644 --- a/src/pages/Stock/Transfer/index.tsx +++ b/src/pages/Stock/Transfer/index.tsx @@ -95,7 +95,7 @@ const TransferPage: React.FC = () => { { try { const { success, message: errMsg } = @@ -116,7 +116,7 @@ const TransferPage: React.FC = () => { { try { const { success, message: errMsg } = @@ -137,7 +137,7 @@ const TransferPage: React.FC = () => { { try { const { success, message: errMsg } = @@ -354,7 +354,7 @@ const CreateForm: React.FC<{ transform={(value) => { return value?.value || value; }} - debounceTime={300} // 防抖,减少请求频率 + debounceTime={300} // 防抖,减少请求频率 rules={[{ required: true, message: '请选择产品' }]} onChange={(_, option) => { form.setFieldValue( @@ -530,7 +530,7 @@ const UpdateForm: React.FC<{ transform={(value) => { return value?.value || value; }} - debounceTime={300} // 防抖,减少请求频率 + debounceTime={300} // 防抖,减少请求频率 rules={[{ required: true, message: '请选择产品' }]} onChange={(_, option) => { form.setFieldValue( @@ -687,7 +687,7 @@ const DetailForm: React.FC<{ transform={(value) => { return value?.value || value; }} - debounceTime={300} // 防抖,减少请求频率 + debounceTime={300} // 防抖,减少请求频率 rules={[{ required: true, message: '请选择产品' }]} onChange={(_, option) => { form.setFieldValue( diff --git a/src/pages/Stock/Warehouse/index.tsx b/src/pages/Stock/Warehouse/index.tsx index 83d8b3a..4e7ea83 100644 --- a/src/pages/Stock/Warehouse/index.tsx +++ b/src/pages/Stock/Warehouse/index.tsx @@ -76,7 +76,7 @@ const ListPage: React.FC = () => { { try { const { success, message: errMsg } = diff --git a/src/pages/Subscription/List/index.tsx b/src/pages/Subscription/List/index.tsx index 184d578..be10469 100644 --- a/src/pages/Subscription/List/index.tsx +++ b/src/pages/Subscription/List/index.tsx @@ -29,7 +29,7 @@ const SUBSCRIPTION_STATUS_ENUM: Record = { }; /** - * 订阅列表页:展示、筛选、触发订阅同步 + * 订阅列表页:展示,筛选,触发订阅同步 */ const ListPage: React.FC = () => { // 表格操作引用:用于在同步后触发表格刷新 @@ -75,7 +75,7 @@ const ListPage: React.FC = () => { dataIndex: 'status', valueType: 'select', valueEnum: SUBSCRIPTION_STATUS_ENUM, - // 以 Tag 形式展示,更易辨识 + // 以 Tag 形式展示,更易辨识 render: (_, row) => row?.status ? ( {SUBSCRIPTION_STATUS_ENUM[row.status]?.text || row.status} @@ -159,7 +159,7 @@ const ListPage: React.FC = () => { const candidates: any[] = ( Array.isArray(data) ? data : [] ).filter((c: any) => String(c?.externalOrderId) === parentNumber); - // 拉取详情,补充状态、金额、时间 + // 拉取详情,补充状态,金额,时间 const details = [] as any[]; for (const c of candidates) { const d = await request(`/order/${c.id}`, { method: 'GET' }); @@ -205,7 +205,7 @@ const ListPage: React.FC = () => { rowKey="id" actionRef={actionRef} /** - * 列表数据请求;保持与后端分页参数一致 + * 列表数据请求;保持与后端分页参数一致 * 兼容后端 data.items 或 data.list 返回字段 */ request={async (params) => { @@ -220,7 +220,7 @@ const ListPage: React.FC = () => { // 工具栏:订阅同步入口 toolBarRender={() => []} /> - {/* 关联订单抽屉:展示订单号、关系、时间、状态与金额 */} + {/* 关联订单抽屉:展示订单号,关系,时间,状态与金额 */} { @@ -281,7 +281,7 @@ const SyncForm: React.FC<{ /** * 提交逻辑: * 1. 必填校验由 ProForm + rules 保证 - * 2. 调用同步接口,失败时友好提示 + * 2. 调用同步接口,失败时友好提示 * 3. 成功后刷新列表 */ onFinish={async (values) => { diff --git a/src/pages/Subscription/Orders/OrderDetailDrawer.tsx b/src/pages/Subscription/Orders/OrderDetailDrawer.tsx index 23dd234..a421747 100644 --- a/src/pages/Subscription/Orders/OrderDetailDrawer.tsx +++ b/src/pages/Subscription/Orders/OrderDetailDrawer.tsx @@ -17,8 +17,8 @@ import { ORDER_STATUS_ENUM } from '@/constants'; import { formatShipmentState, formatSource } from '@/utils/format'; import RelatedOrders from './RelatedOrders'; -// 为保持原文件结构简单,此处从 index.tsx 引入的子组件仍由原文件导出或保持原状 -// 若后续需要彻底解耦,可将 OrderNote / Shipping / SalesChange 也独立到文件 +// 为保持原文件结构简单,此处从 index.tsx 引入的子组件仍由原文件导出或保持原状 +// 若后续需要彻底解耦,可将 OrderNote / Shipping / SalesChange 也独立到文件 // 当前按你的要求仅抽离详情 Drawer type OrderRecord = API.Order; @@ -118,7 +118,7 @@ const OrderDetailDrawer: React.FC = ({ { try { const { success, message: errMsg } = @@ -145,7 +145,7 @@ const OrderDetailDrawer: React.FC = ({ { try { const { success, message: errMsg } = @@ -168,7 +168,7 @@ const OrderDetailDrawer: React.FC = ({ { try { const { success, message: errMsg } = @@ -191,7 +191,7 @@ const OrderDetailDrawer: React.FC = ({ { try { const { success, message: errMsg } = @@ -425,9 +425,9 @@ const OrderDetailDrawer: React.FC = ({ await navigator.clipboard.writeText( v.tracking_url, ); - message.success('复制成功!'); + message.success('复制成功!'); } catch { - message.error('复制失败!'); + message.error('复制失败!'); } }} /> @@ -440,7 +440,7 @@ const OrderDetailDrawer: React.FC = ({ { try { const { success, message: errMsg } = diff --git a/src/pages/Subscription/Orders/RelatedOrders.tsx b/src/pages/Subscription/Orders/RelatedOrders.tsx index 6600dd3..7b1befa 100644 --- a/src/pages/Subscription/Orders/RelatedOrders.tsx +++ b/src/pages/Subscription/Orders/RelatedOrders.tsx @@ -7,8 +7,8 @@ dayjs.extend(relativeTime); /** * RelatedOrders 表格组件 - * 用于展示订单详情中的关联数据(订阅/订单),按统一表格样式渲染 - * 本组件将订阅与订单统一归一化为五列展示,便于快速浏览 + * 用于展示订单详情中的关联数据(订阅/订单),按统一表格样式渲染 + * 本组件将订阅与订单统一归一化为五列展示,便于快速浏览 */ const RelatedOrders: React.FC<{ data?: any[] }> = ({ data = [] }) => { const rows = (Array.isArray(data) ? data : []).map((it: any) => { @@ -48,7 +48,7 @@ const RelatedOrders: React.FC<{ data?: any[] }> = ({ data = [] }) => { return (
- {/* 表头(英文文案,符合国际化默认英文的要求) */} + {/* 表头(英文文案,符合国际化默认英文的要求) */}
{ { if (!record.id) return; try { diff --git a/src/pages/Woo/Product/List/index.tsx b/src/pages/Woo/Product/List/index.tsx index 6c1bfa4..3a1cf3d 100644 --- a/src/pages/Woo/Product/List/index.tsx +++ b/src/pages/Woo/Product/List/index.tsx @@ -630,7 +630,7 @@ const SetComponent: React.FC<{ transform={(value) => { return value?.value || value; }} - debounceTime={300} // 防抖,减少请求频率 + debounceTime={300} // 防抖,减少请求频率 rules={[{ required: true, message: '请选择产品' }]} /> { }; /** - * @description 根据口味分类额外的标签(如 Fruit, Mint) + * @description 根据口味分类额外的标签(如 Fruit, Mint) */ const classifyExtraTags = ( flavorPart: string, @@ -111,7 +111,7 @@ const classifyExtraTags = ( const matchAttributes = (text: string, keys: string[]): string[] => { const matched = new Set(); for (const key of keys) { - // 使用单词边界匹配,避免部分匹配 + // 使用单词边界匹配,避免部分匹配 // 转义正则特殊字符 const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const regex = new RegExp(`\\b${escapedKey}\\b`, 'i'); @@ -129,7 +129,7 @@ const computeTags = (name: string, sku: string, config: TagConfig): string => { const [brand, flavorPart, mg, dryness] = parseName(name, config.brands); const tokens = splitFlavorTokens(flavorPart); - // 白名单模式:只保留在 flavorKeys 中的 token + // 白名单模式:只保留在 flavorKeys 中的 token // 且对比时忽略大小写 const flavorKeysLower = config.flavorKeys.map(k => k.toLowerCase()); @@ -181,7 +181,7 @@ const computeTags = (name: string, sku: string, config: TagConfig): string => { } // 保留原有的 dryness 提取逻辑 (从括号中提取) - // 如果 dict 匹配已经覆盖了,去重时会处理 + // 如果 dict 匹配已经覆盖了,去重时会处理 if (dryness) { if (/moist/i.test(dryness)) { tags.push('Moisture'); @@ -197,8 +197,8 @@ const computeTags = (name: string, sku: string, config: TagConfig): string => { // 去重并保留顺序 const seen = new Set(); const finalTags = tags.filter((t) => { - // 简单的去重,忽略大小写差异? 或者完全匹配 - // 这里使用完全匹配,因为前面已经做了一些格式化 + // 简单的去重,忽略大小写差异? 或者完全匹配 + // 这里使用完全匹配,因为前面已经做了一些格式化 if (t && !seen.has(t)) { seen.add(t); return true; @@ -210,7 +210,7 @@ const computeTags = (name: string, sku: string, config: TagConfig): string => { }; /** - * @description WordPress 产品工具页面,用于处理产品 CSV 并生成 Tags + * @description WordPress 产品工具页面,用于处理产品 CSV 并生成 Tags */ const WpToolPage: React.FC = () => { // 状态管理 @@ -285,9 +285,9 @@ const WpToolPage: React.FC = () => { * @param {File} uploadedFile - 用户上传的文件 */ const handleFileUpload = (uploadedFile: File) => { - // 检查文件类型,虽然 xlsx 库更宽容,但最好还是保留基本验证 + // 检查文件类型,虽然 xlsx 库更宽容,但最好还是保留基本验证 if (!uploadedFile.name.match(/\.(csv|xlsx|xls)$/)) { - message.error('请上传 CSV 或 Excel 格式的文件!'); + message.error('请上传 CSV 或 Excel 格式的文件!'); return false; } setFile(uploadedFile); @@ -302,7 +302,7 @@ const WpToolPage: React.FC = () => { const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); if (jsonData.length < 2) { - message.error('文件为空或缺少表头!'); + message.error('文件为空或缺少表头!'); setCsvData([]); return; } @@ -317,17 +317,17 @@ const WpToolPage: React.FC = () => { return rowData; }); - message.success(`成功解析 ${rows.length} 条数据。`); + message.success(`成功解析 ${rows.length} 条数据.`); setCsvData(rows); setProcessedData([]); // 清空旧的处理结果 } catch (error) { - message.error('文件解析失败,请检查文件格式!'); + message.error('文件解析失败,请检查文件格式!'); console.error('File Parse Error:', error); setCsvData([]); } }; reader.onerror = (error) => { - message.error('文件读取失败!'); + message.error('文件读取失败!'); console.error('File Read Error:', error); }; reader.readAsBinaryString(uploadedFile); @@ -352,16 +352,16 @@ const WpToolPage: React.FC = () => { const fileName = `products_with_tags_${Date.now()}.xlsx`; XLSX.writeFile(workbook, fileName); - message.success('下载任务已开始!'); + message.success('下载任务已开始!'); }; /** - * @description 核心逻辑:根据配置处理 CSV 数据并生成 Tags + * @description 核心逻辑:根据配置处理 CSV 数据并生成 Tags */ const handleProcessData = async () => { // 验证是否已上传并解析了数据 if (csvData.length === 0) { - message.warning('请先上传并成功解析一个 CSV 文件。'); + message.warning('请先上传并成功解析一个 CSV 文件.'); return; } @@ -400,7 +400,7 @@ const WpToolPage: React.FC = () => { setProcessedData(dataWithTags); message.success({ - content: 'Tags 生成成功!正在自动下载...', + content: 'Tags 生成成功!正在自动下载...', key: 'processing', }); @@ -409,7 +409,7 @@ const WpToolPage: React.FC = () => { } catch (error) { message.error({ - content: '处理失败,请检查配置或文件。', + content: '处理失败,请检查配置或文件.', key: 'processing', }); console.error('Processing Error:', error); @@ -421,7 +421,7 @@ const WpToolPage: React.FC = () => { return ( - {/* 左侧:配置表单 */} + {/* 左侧:配置表单 */} { name="brands" label="品牌列表" mode="tags" - placeholder="请输入品牌,按回车确认" + placeholder="请输入品牌,按回车确认" rules={[{ required: true, message: '至少需要一个品牌' }]} - tooltip="按品牌名称长度倒序匹配,请将较长的品牌(如 WHITE FOX)放在前面。" + tooltip="按品牌名称长度倒序匹配,请将较长的品牌(如 WHITE FOX)放在前面." /> - {/* 右侧:文件上传与操作 */} + {/* 右侧:文件上传与操作 */} ; }; @@ -1149,7 +1149,7 @@ declare namespace API { | 'pending-cancel'; /** 客户邮箱 */ customer_email?: string; - /** 关键字(订阅ID、邮箱等) */ + /** 关键字(订阅ID,邮箱等) */ keyword?: string; }; @@ -1383,7 +1383,7 @@ declare namespace API { sku?: string; /** 按库存点ID排序 */ sortPointId?: number; - /** 排序对象,格式如 { productName: "asc", sku: "desc" } */ + /** 排序对象,格式如 { productName: "asc", sku: "desc" } */ order?: Record; }; @@ -1548,7 +1548,7 @@ declare namespace API { billing_interval?: number; customer_id?: number; customer_email?: string; - /** 父订单/父订阅ID(如有) */ + /** 父订单/父订阅ID(如有) */ parent_id?: number; start_date?: string; trial_end?: string; @@ -1579,7 +1579,7 @@ declare namespace API { | 'pending-cancel'; /** 客户邮箱 */ customer_email?: string; - /** 关键字(订阅ID、邮箱等) */ + /** 关键字(订阅ID,邮箱等) */ keyword?: string; };