From bd3d9a00c558abc4c3dd4fd19b73d4f7a4fcf216 Mon Sep 17 00:00:00 2001 From: tikkhun Date: Tue, 25 Nov 2025 11:38:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=AB=99=E7=82=B9Sit?= =?UTF-8?q?e=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .umirc.ts | 12 + src/access.ts | 2 + src/pages/Site/List/index.tsx | 254 ++++++++++++++++++ .../Subscription/Orders/RelatedOrders.tsx | 2 +- 5 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 src/pages/Site/List/index.tsx diff --git a/.gitignore b/.gitignore index d779301..34eb7a6 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ *.yaml # /docs +.DS_Store diff --git a/.umirc.ts b/.umirc.ts index 86cce20..0e17571 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -43,6 +43,18 @@ export default defineConfig({ }, ], }, + { + name: '站点管理', + path: '/site', + access: 'canSeeSite', + routes: [ + { + name: '站点列表', + path: '/site/list', + component: './Site/List', + }, + ], + }, { name: '商品管理', path: '/product', diff --git a/src/access.ts b/src/access.ts index 35a8b86..c6d252a 100644 --- a/src/access.ts +++ b/src/access.ts @@ -9,6 +9,7 @@ export default (initialState: any) => { 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); return { canSeeOrganiza, @@ -18,5 +19,6 @@ export default (initialState: any) => { canSeeCustomer, canSeeLogistics, canSeeStatistics, + canSeeSite, }; }; diff --git a/src/pages/Site/List/index.tsx b/src/pages/Site/List/index.tsx new file mode 100644 index 0000000..40ad842 --- /dev/null +++ b/src/pages/Site/List/index.tsx @@ -0,0 +1,254 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { ActionType, ProColumns, ProTable, ProFormInstance } from '@ant-design/pro-components'; +import { DrawerForm, ProFormText, ProFormSelect, ProFormSwitch } from '@ant-design/pro-components'; +import { Button, message, Popconfirm, Space, Tag } from 'antd'; +import { request } from '@umijs/max'; + +// 站点数据项类型(前端不包含密钥字段,后端列表不返回密钥) +interface SiteItem { + id: number; + siteName: string; + apiUrl?: string; + type?: 'woocommerce' | 'shopyy'; + skuPrefix?: string; + isDisabled: number; +} + +// 创建/更新表单的值类型,包含可选的密钥字段 +interface SiteFormValues { + siteName: string; + apiUrl?: string; + type?: 'woocommerce' | 'shopyy'; + isDisabled?: boolean; + consumerKey?: string; // WooCommerce REST API 的 consumer key + consumerSecret?: string; // WooCommerce REST API 的 consumer secret + skuPrefix?: string; +} + +const SiteList: React.FC = () => { + const actionRef = useRef(); + const formRef = useRef(); + const [open, setOpen] = useState(false); + const [editing, setEditing] = useState(null); + + useEffect(() => { + if (!open) return; + if (editing) { + formRef.current?.setFieldsValue({ + siteName: editing.siteName, + apiUrl: editing.apiUrl, + type: editing.type, + skuPrefix: editing.skuPrefix, + isDisabled: !!editing.isDisabled, + consumerKey: undefined, + consumerSecret: undefined, + }); + } else { + formRef.current?.setFieldsValue({ + siteName: undefined, + apiUrl: undefined, + type: 'woocommerce', + skuPrefix: undefined, + isDisabled: false, + consumerKey: undefined, + consumerSecret: undefined, + }); + } + }, [open, editing]); + + // 表格列定义 + const columns: ProColumns[] = [ + { title: 'ID', dataIndex: 'id', width: 80, sorter: true, hideInSearch: true }, + { title: '站点名称', dataIndex: 'siteName', width: 220 }, + { title: 'API 地址', dataIndex: 'apiUrl', width: 280, hideInSearch: true }, + { title: 'SKU 前缀', dataIndex: 'skuPrefix', width: 160, hideInSearch: true }, + { + title: '平台', + dataIndex: 'type', + width: 140, + valueType: 'select', + request: async () => [ + { label: 'WooCommerce', value: 'woocommerce' }, + { label: 'Shopyy', value: 'shopyy' }, + ], + }, + { + title: '状态', + dataIndex: 'isDisabled', + width: 120, + hideInSearch: true, + render: (_, row) => ( + + {row.isDisabled ? '已禁用' : '启用中'} + + ), + }, + { + title: '操作', + dataIndex: 'actions', + width: 240, + hideInSearch: true, + render: (_, row) => ( + + + { + try { + await request(`/site/disable/${row.id}`, { + method: 'PUT', + data: { disabled: !row.isDisabled }, + }); + message.success('更新成功'); + actionRef.current?.reload(); + } catch (e: any) { + message.error(e?.message || '更新失败'); + } + }} + > + + + + ), + }, + ]; + + // 表格数据请求 + const tableRequest = async (params: Record) => { + try { + const { current = 1, pageSize = 10, siteName, type } = params; + const resp = await request('/site/list', { + method: 'GET', + params: { + current, + pageSize, + keyword: siteName || undefined, + type: type || undefined, + }, + }); + const { success, data, message: errMsg } = resp as any; + if (!success) throw new Error(errMsg || '获取失败'); + return { + data: (data?.items ?? []) as SiteItem[], + total: data?.total ?? 0, + success: true, + }; + } catch (e: any) { + message.error(e?.message || '获取失败'); + return { data: [], total: 0, success: false }; + } + }; + + // 提交创建/更新逻辑;编辑时未填写密钥则不提交(保持原值) + const handleSubmit = async (values: SiteFormValues) => { + try { + if (editing) { + const payload: Record = { + // 仅提交存在的字段,避免覆盖为 null/空 + ...(values.siteName ? { siteName: values.siteName } : {}), + ...(values.apiUrl ? { apiUrl: values.apiUrl } : {}), + ...(values.type ? { type: values.type } : {}), + ...(typeof values.isDisabled === 'boolean' ? { isDisabled: values.isDisabled } : {}), + ...(values.skuPrefix ? { skuPrefix: values.skuPrefix } : {}), + }; + // 仅当输入了新密钥时才提交,未输入则保持原本值 + 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'); + } + await request('/site/create', { + method: 'POST', + data: { + siteName: values.siteName, + apiUrl: values.apiUrl, + type: values.type || 'woocommerce', + consumerKey: values.consumerKey, + consumerSecret: values.consumerSecret, + skuPrefix: values.skuPrefix, + }, + }); + } + message.success('提交成功'); + setOpen(false); + setEditing(null); + actionRef.current?.reload(); + return true; + } catch (e: any) { + message.error(e?.message || '提交失败'); + return false; + } + }; + + return ( + <> + + actionRef={actionRef} + rowKey="id" + columns={columns} + request={tableRequest} + toolBarRender={() => [ + , + ]} + /> + + + title={editing ? '编辑站点' : '新增站点'} + open={open} + onOpenChange={setOpen} + formRef={formRef} + onFinish={handleSubmit} + > + {/* 站点名称,必填 */} + + {/* API 地址,可选 */} + + {/* 平台类型选择 */} + + {/* 是否禁用 */} + + + {/* WooCommerce REST consumer key;新增必填,编辑不填则保持原值 */} + + {/* WooCommerce REST consumer secret;新增必填,编辑不填则保持原值 */} + + + + ); +}; + +export default SiteList; \ No newline at end of file diff --git a/src/pages/Subscription/Orders/RelatedOrders.tsx b/src/pages/Subscription/Orders/RelatedOrders.tsx index 57470ab..5b06530 100644 --- a/src/pages/Subscription/Orders/RelatedOrders.tsx +++ b/src/pages/Subscription/Orders/RelatedOrders.tsx @@ -8,7 +8,7 @@ dayjs.extend(relativeTime); /** * RelatedOrders 表格组件 * 用于展示订单详情中的关联数据(订阅/订单),按统一表格样式渲染 - * 中文注释:本组件将订阅与订单统一归一化为五列展示,便于快速浏览 + * 本组件将订阅与订单统一归一化为五列展示,便于快速浏览 */ const RelatedOrders: React.FC<{ data?: any[] }> = ({ data = [] }) => { const rows = (Array.isArray(data) ? data : []).map((it: any) => {