Compare commits
No commits in common. "ecc22cec01aceef349688cf9e37984ca5199f8b8" and "34f331d8d42b7df43099f4d1502dd093985daa4d" have entirely different histories.
ecc22cec01
...
34f331d8d4
12
.umirc.ts
12
.umirc.ts
|
|
@ -43,18 +43,6 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: '站点管理',
|
|
||||||
path: '/site',
|
|
||||||
access: 'canSeeSite',
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
name: '站点列表',
|
|
||||||
path: '/site/list',
|
|
||||||
component: './Site/List',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: '商品管理',
|
name: '商品管理',
|
||||||
path: '/product',
|
path: '/product',
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ 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,
|
||||||
|
|
@ -19,6 +18,5 @@ export default (initialState: any) => {
|
||||||
canSeeCustomer,
|
canSeeCustomer,
|
||||||
canSeeLogistics,
|
canSeeLogistics,
|
||||||
canSeeStatistics,
|
canSeeStatistics,
|
||||||
canSeeSite,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,254 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue