Compare commits

..

21 Commits

Author SHA1 Message Date
tikkhun ecc22cec01 Merge branch 'dev_szk' of https://git.yoone.ca/yoone/WEB into dev_szk 2025-11-24 12:05:10 +08:00
tikkhun 3614efef1e docs: 移除代码中的中文注释标记 2025-11-24 10:32:31 +08:00
tikkhun 1d0cddf901 feat(站点管理): 新增站点管理功能模块
添加站点管理路由配置和权限控制
实现站点列表页面,包含站点增删改查功能
2025-11-22 10:28:19 +08:00
tikkhun 429a844563 style: 统一中文标点符号为英文格式 2025-11-21 17:16:30 +08:00
tikkhun 7234c4ae55 refactor(Subscription/Orders): 使用相对路径导入OrderDetailDrawer组件 2025-11-21 15:22:42 +08:00
tikkhun 34fd506e31 refactor(订单): 重构订单详情抽屉组件并移动相关文件
将订单详情抽屉组件从 Order/List 移动到 Subscription/Orders 目录
提取 RelatedOrders 组件为独立文件
重构订单详情逻辑,保持功能不变但提高可维护性
2025-11-21 15:21:42 +08:00
tikkhun 96426a64b1 refactor(订单): 抽离订单详情抽屉为独立组件并复用
将订单列表页的详情抽屉抽离为独立组件 OrderDetailDrawer
在订阅订单页面复用该组件,替换原有实现
2025-11-21 14:59:49 +08:00
tikkhun 9de6b8b8d1 feat(路由配置): 添加订阅订单管理路由
在路由配置中添加新的订单管理页面路径,用于展示订阅订单列表
2025-11-21 11:58:49 +08:00
tikkhun 22ecfba0e5 feat(订单列表): 添加关联订单组件并优化订阅显示
添加 RelatedOrders 组件用于统一展示关联订单和订阅信息
在订单列表中增加订阅标签显示
移除未使用的 request 导入
2025-11-21 11:27:07 +08:00
tikkhun a1e8e81407 feat(订单): 添加关联订单显示功能并创建订单商品和订阅订单页面
feat(订阅): 增加订阅订单关联功能及详情查看
feat(商品): 新增订单商品概览页面
style(订单): 优化订单详情显示格式
2025-11-19 15:55:40 +08:00
tikkhun 737cfef733 feat(订阅): 新增订阅管理模块和列表页面
添加订阅管理路由分组并复用现有权限
实现订阅列表页面,包含查询、筛选和同步功能
2025-11-17 17:54:05 +08:00
tikkhun 721ccac46f feat(api): 添加订阅模块和产品搜索功能
- 新增订阅模块相关接口和类型定义
- 添加产品搜索接口
- 更新产品类型定义,增加删除状态字段
- 添加openapi2ts依赖用于类型生成
2025-11-15 11:21:05 +08:00
tikkhun 50bbf309c9 Merge branch 'main' of https://git.yoone.ca/yoone/WEB 2025-10-14 14:54:45 +08:00
tikkhun 19f8fa6fa5 Merge branch 'main' of https://git.yoone.ca/zksu/WEB 2025-10-14 14:34:01 +08:00
tikkhun 9e3fcff3dd refactor(docs): 移除旧版项目文档和优化订单状态标签
删除不再使用的项目文档中心和相关技术文档
在订单列表页面添加退款状态标签的空行以改善代码可读性
2025-10-14 14:27:21 +08:00
tikkhun e6ec5b6887 Merge branch 'main' of https://git.yoone.ca/zksu/WEB 2025-10-14 14:21:33 +08:00
tikkhun c555269030 refactor(Order/List): 更新退款相关状态标签为更简洁的表达
refund_approved 已退款
refund_cancelled 已完成
2025-10-14 14:20:30 +08:00
tikkhun edb854f777 build: 添加 code-inspector-plugin 依赖及配置
添加 code-inspector-plugin 作为开发依赖,并在 webpack 配置中启用该插件以增强代码检查能力
2025-10-14 14:18:45 +08:00
tikkhun 36c470a5b3 Merge branch 'main' of https://git.yoone.ca/yoone/WEB
# Conflicts:
#	src/pages/Order/List/index.tsx
2025-10-14 14:12:56 +08:00
tikkhun 87534c4d22 refactor(Order/List): 更新退款相关状态标签为更简洁的表达
refund_approved 已退款
refund_cancelled 已完成
2025-10-14 11:13:06 +08:00
tikkhun 37c8518289 build: 添加 code-inspector-plugin 依赖及配置
添加 code-inspector-plugin 作为开发依赖,并在 webpack 配置中启用该插件以增强代码检查能力
2025-10-14 11:08:54 +08:00
5 changed files with 280 additions and 12 deletions

View File

@ -43,6 +43,18 @@ export default defineConfig({
}, },
], ],
}, },
{
name: '站点管理',
path: '/site',
access: 'canSeeSite',
routes: [
{
name: '站点列表',
path: '/site/list',
component: './Site/List',
},
],
},
{ {
name: '商品管理', name: '商品管理',
path: '/product', path: '/product',

View File

@ -9,6 +9,7 @@ export default (initialState: any) => {
const canSeeCustomer = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('customer') ?? false); const canSeeCustomer = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('customer') ?? false);
const canSeeLogistics = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('logistics') ?? false); const canSeeLogistics = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('logistics') ?? false);
const canSeeStatistics = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('statistics') ?? false); const canSeeStatistics = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('statistics') ?? false);
const canSeeSite = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('site') ?? false);
return { return {
canSeeOrganiza, canSeeOrganiza,
@ -18,5 +19,6 @@ export default (initialState: any) => {
canSeeCustomer, canSeeCustomer,
canSeeLogistics, canSeeLogistics,
canSeeStatistics, canSeeStatistics,
canSeeSite,
}; };
}; };

View File

@ -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<ActionType>();
const formRef = useRef<ProFormInstance>();
const [open, setOpen] = useState(false);
const [editing, setEditing] = useState<SiteItem | null>(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<SiteItem>[] = [
{ 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) => (
<Tag color={row.isDisabled ? 'red' : 'green'}>
{row.isDisabled ? '已禁用' : '启用中'}
</Tag>
),
},
{
title: '操作',
dataIndex: 'actions',
width: 240,
hideInSearch: true,
render: (_, row) => (
<Space>
<Button
size="small"
onClick={() => {
setEditing(row);
setOpen(true);
}}
>
</Button>
<Popconfirm
title={row.isDisabled ? '启用站点' : '禁用站点'}
description={row.isDisabled ? '确认启用该站点?' : '确认禁用该站点?'}
onConfirm={async () => {
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 || '更新失败');
}
}}
>
<Button size="small" type="primary" danger={!row.isDisabled}>
{row.isDisabled ? '启用' : '禁用'}
</Button>
</Popconfirm>
</Space>
),
},
];
// 表格数据请求
const tableRequest = async (params: Record<string, any>) => {
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<string, any> = {
// 仅提交存在的字段,避免覆盖为 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 (
<>
<ProTable<SiteItem>
actionRef={actionRef}
rowKey="id"
columns={columns}
request={tableRequest}
toolBarRender={() => [
<Button
key="new"
type="primary"
onClick={() => {
setEditing(null);
setOpen(true);
}}
>
</Button>,
]}
/>
<DrawerForm<SiteFormValues>
title={editing ? '编辑站点' : '新增站点'}
open={open}
onOpenChange={setOpen}
formRef={formRef}
onFinish={handleSubmit}
>
{/* 站点名称,必填 */}
<ProFormText name="siteName" label="站点名称" placeholder="例如:本地商店" rules={[{ required: true, message: '站点名称为必填项' }]} />
{/* API 地址,可选 */}
<ProFormText name="apiUrl" label="API 地址" placeholder="例如https://shop.example.com" />
{/* 平台类型选择 */}
<ProFormSelect
name="type"
label="平台"
options={[
{ label: 'WooCommerce', value: 'woocommerce' },
{ label: 'Shopyy', value: 'shopyy' },
]}
/>
{/* 是否禁用 */}
<ProFormSwitch name="isDisabled" label="禁用" />
<ProFormText name="skuPrefix" label="SKU 前缀" placeholder={editing ? '留空表示不修改' : '可选'} />
{/* WooCommerce REST consumer key新增必填编辑不填则保持原值 */}
<ProFormText name="consumerKey" label="Key" placeholder={editing ? '留空表示不修改' : '必填'} rules={editing ? [] : [{ required: true, message: 'Key 为必填项' }]} />
{/* WooCommerce REST consumer secret新增必填编辑不填则保持原值 */}
<ProFormText name="consumerSecret" label="Secret" placeholder={editing ? '留空表示不修改' : '必填'} rules={editing ? [] : [{ required: true, message: 'Secret 为必填项' }]} />
</DrawerForm>
</>
);
};
export default SiteList;

View File

@ -27,21 +27,21 @@ import { formatShipmentState, formatSource } from '@/utils/format';
import RelatedOrders from './RelatedOrders'; import RelatedOrders from './RelatedOrders';
import { ORDER_STATUS_ENUM } from '@/constants'; import { ORDER_STATUS_ENUM } from '@/constants';
// 中文注释:为保持原文件结构简单,此处从 index.tsx 引入的子组件仍由原文件导出或保持原状 // 为保持原文件结构简单,此处从 index.tsx 引入的子组件仍由原文件导出或保持原状
// 若后续需要彻底解耦,可将 OrderNote / Shipping / SalesChange 也独立到文件 // 若后续需要彻底解耦,可将 OrderNote / Shipping / SalesChange 也独立到文件
// 当前按你的要求仅抽离详情 Drawer // 当前按你的要求仅抽离详情 Drawer
type OrderRecord = API.Order; type OrderRecord = API.Order;
interface OrderDetailDrawerProps { interface OrderDetailDrawerProps {
tableRef: React.MutableRefObject<ActionType | undefined>; // 中文注释:列表刷新引用 tableRef: React.MutableRefObject<ActionType | undefined>; // 列表刷新引用
orderId: number; // 中文注释:订单主键 ID orderId: number; // 订单主键 ID
record: OrderRecord; // 中文注释:订单行记录 record: OrderRecord; // 订单行记录
open: boolean; // 中文注释:是否打开抽屉 open: boolean; // 是否打开抽屉
onClose: () => void; // 中文注释:关闭抽屉回调 onClose: () => void; // 关闭抽屉回调
setActiveLine: (id: number) => void; // 中文注释:高亮当前行 setActiveLine: (id: number) => void; // 高亮当前行
OrderNoteComponent: React.ComponentType<any>; // 中文注释:备注组件(从外部注入) OrderNoteComponent: React.ComponentType<any>; // 备注组件(从外部注入)
SalesChangeComponent: React.ComponentType<any>; // 中文注释:换货组件(从外部注入) SalesChangeComponent: React.ComponentType<any>; // 换货组件(从外部注入)
} }
const OrderDetailDrawer: React.FC<OrderDetailDrawerProps> = ({ const OrderDetailDrawer: React.FC<OrderDetailDrawerProps> = ({
@ -57,7 +57,7 @@ const OrderDetailDrawer: React.FC<OrderDetailDrawerProps> = ({
const { message } = App.useApp(); const { message } = App.useApp();
const ref = useRef<ActionType>(); const ref = useRef<ActionType>();
// 中文注释:加载详情数据(与 index.tsx 中完全保持一致) // 加载详情数据(与 index.tsx 中完全保持一致)
const initRequest = async () => { const initRequest = async () => {
const { data, success }: API.OrderDetailRes = await ordercontrollerGetorderdetail({ orderId }); const { data, success }: API.OrderDetailRes = await ordercontrollerGetorderdetail({ orderId });
if (!success || !data) return { data: {} } as any; if (!success || !data) return { data: {} } as any;
@ -84,7 +84,7 @@ const OrderDetailDrawer: React.FC<OrderDetailDrawerProps> = ({
size="large" size="large"
onClose={onClose} onClose={onClose}
footer={[ footer={[
// 中文注释:备注组件(外部传入以避免循环依赖) // 备注组件(外部传入以避免循环依赖)
<OrderNoteComponent key="order-note" id={orderId} descRef={ref} />, <OrderNoteComponent key="order-note" id={orderId} descRef={ref} />,
...(['after_sale_pending', 'pending_reshipment'].includes( ...(['after_sale_pending', 'pending_reshipment'].includes(
record.orderStatus, record.orderStatus,

View File

@ -8,7 +8,7 @@ dayjs.extend(relativeTime);
/** /**
* RelatedOrders * RelatedOrders
* (/) * (/)
* 中文注释:本组件将订阅与订单统一归一化为五列展便 * 便
*/ */
const RelatedOrders: React.FC<{ data?: any[] }> = ({ data = [] }) => { const RelatedOrders: React.FC<{ data?: any[] }> = ({ data = [] }) => {
const rows = (Array.isArray(data) ? data : []).map((it: any) => { const rows = (Array.isArray(data) ? data : []).map((it: any) => {