feat:添加对订阅的支持
refactor(docs): 移除旧版项目文档和优化订单状态标签 删除不再使用的项目文档中心和相关技术文档 在订单列表页面添加退款状态标签的空行以改善代码可读性 feat(api): 添加订阅模块和产品搜索功能 - 新增订阅模块相关接口和类型定义 - 添加产品搜索接口 - 更新产品类型定义,增加删除状态字段 - 添加openapi2ts依赖用于类型生成 feat(订阅): 新增订阅管理模块和列表页面 添加订阅管理路由分组并复用现有权限 实现订阅列表页面,包含查询、筛选和同步功能 feat(订单): 添加关联订单显示功能并创建订单商品和订阅订单页面 feat(订阅): 增加订阅订单关联功能及详情查看 feat(商品): 新增订单商品概览页面 style(订单): 优化订单详情显示格式 feat(订单列表): 添加关联订单组件并优化订阅显示 添加 RelatedOrders 组件用于统一展示关联订单和订阅信息 在订单列表中增加订阅标签显示 移除未使用的 request 导入 feat(路由配置): 添加订阅订单管理路由 在路由配置中添加新的订单管理页面路径,用于展示订阅订单列表 refactor(订单): 抽离订单详情抽屉为独立组件并复用 将订单列表页的详情抽屉抽离为独立组件 OrderDetailDrawer 在订阅订单页面复用该组件,替换原有实现 refactor(订单): 重构订单详情抽屉组件并移动相关文件 将订单详情抽屉组件从 Order/List 移动到 Subscription/Orders 目录 提取 RelatedOrders 组件为独立文件 重构订单详情逻辑,保持功能不变但提高可维护性 refactor(Subscription/Orders): 使用相对路径导入OrderDetailDrawer组件 style: 统一中文标点符号为英文格式
This commit is contained in:
parent
97d4540345
commit
8b7af3e6a4
20
.umirc.ts
20
.umirc.ts
|
|
@ -4,7 +4,7 @@ const isDev = process.env.NODE_ENV === 'development';
|
||||||
const UMI_APP_API_URL = isDev
|
const UMI_APP_API_URL = isDev
|
||||||
? 'http://localhost:7001'
|
? 'http://localhost:7001'
|
||||||
: 'https://api.yoone.ca';
|
: 'https://api.yoone.ca';
|
||||||
import { codeInspectorPlugin } from 'code-inspector-plugin';
|
import { codeInspectorPlugin } from 'code-inspector-plugin';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
hash: true,
|
hash: true,
|
||||||
|
|
@ -124,6 +124,24 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// 新增:订阅管理路由分组(权限复用 canSeeOrder)
|
||||||
|
{
|
||||||
|
name: '订阅管理',
|
||||||
|
path: '/subscription',
|
||||||
|
access: 'canSeeOrder',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: '订阅列表',
|
||||||
|
path: '/subscription/list',
|
||||||
|
component: './Subscription/List',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '订单管理',
|
||||||
|
path: '/subscription/orders',
|
||||||
|
component: './Subscription/Orders',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: '客户管理',
|
name: '客户管理',
|
||||||
path: '/customer',
|
path: '/customer',
|
||||||
|
|
|
||||||
228
docs/README.md
228
docs/README.md
|
|
@ -1,228 +0,0 @@
|
||||||
# YOONE WEB项目文档中心
|
|
||||||
|
|
||||||
## 文档概述
|
|
||||||
|
|
||||||
### 背景意义
|
|
||||||
本文档中心为YOONE WEB电商管理系统提供完整的技术文档和业务分析,帮助开发团队、产品团队和运维团队全面了解项目架构、业务流程和技术实现。
|
|
||||||
|
|
||||||
### 文档定义
|
|
||||||
采用结构化文档管理方式,按照技术架构、业务流程、项目分析三个维度组织文档内容,确保信息的完整性和可维护性。
|
|
||||||
|
|
||||||
### 使用流程
|
|
||||||
选择对应文档类型 → 查阅具体内容 → 理解系统设计 → 指导开发实践
|
|
||||||
|
|
||||||
## 文档目录
|
|
||||||
|
|
||||||
### 核心文档列表
|
|
||||||
|
|
||||||
| 文档名称 | 文档类型 | 主要内容 | 目标读者 |
|
|
||||||
|---------|---------|---------|---------|
|
|
||||||
| [项目分析报告](./项目分析报告.md) | 分析评估 | 项目优缺点、技术评价、改进建议 | 技术负责人、项目经理 |
|
|
||||||
| [技术架构图](./技术架构图.md) | 技术文档 | 系统架构、技术栈、组件关系 | 开发工程师、架构师 |
|
|
||||||
| [业务流程图](./业务流程图.md) | 业务文档 | 业务流程、用户交互、数据流转 | 产品经理、业务分析师 |
|
|
||||||
|
|
||||||
### 文档功能对比
|
|
||||||
|
|
||||||
| 功能特性 | 项目分析报告 | 技术架构图 | 业务流程图 |
|
|
||||||
|---------|-------------|-----------|-----------|
|
|
||||||
| 技术评估 | ✅ 详细分析 | ⚠️ 架构层面 | ❌ 不涉及 |
|
|
||||||
| 架构设计 | ⚠️ 概述性 | ✅ 详细设计 | ❌ 不涉及 |
|
|
||||||
| 业务流程 | ❌ 不涉及 | ❌ 不涉及 | ✅ 详细流程 |
|
|
||||||
| 改进建议 | ✅ 全面建议 | ✅ 架构优化 | ✅ 流程优化 |
|
|
||||||
| 实施指导 | ✅ 分阶段 | ✅ 技术选型 | ✅ 业务规范 |
|
|
||||||
|
|
||||||
## 项目概览
|
|
||||||
|
|
||||||
### 项目基本信息
|
|
||||||
|
|
||||||
- **项目名称**: YOONE WEB电商管理系统
|
|
||||||
- **技术栈**: React 18 + Umi Max + Ant Design + TypeScript
|
|
||||||
- **项目类型**: 企业级B端管理系统
|
|
||||||
- **业务领域**: 电商全流程管理
|
|
||||||
- **开发模式**: 前后端分离
|
|
||||||
|
|
||||||
### 核心功能模块
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
mindmap
|
|
||||||
root((YOONE系统))
|
|
||||||
用户管理
|
|
||||||
登录认证
|
|
||||||
权限控制
|
|
||||||
角色管理
|
|
||||||
商品管理
|
|
||||||
商品CRUD
|
|
||||||
分类管理
|
|
||||||
库存同步
|
|
||||||
价格策略
|
|
||||||
订单管理
|
|
||||||
订单处理
|
|
||||||
状态跟踪
|
|
||||||
发货管理
|
|
||||||
退换货
|
|
||||||
库存管理
|
|
||||||
库存监控
|
|
||||||
采购管理
|
|
||||||
库存调拨
|
|
||||||
预警机制
|
|
||||||
客户管理
|
|
||||||
客户信息
|
|
||||||
客户标签
|
|
||||||
服务记录
|
|
||||||
物流管理
|
|
||||||
物流服务商
|
|
||||||
运费计算
|
|
||||||
物流跟踪
|
|
||||||
地址管理
|
|
||||||
数据统计
|
|
||||||
销售统计
|
|
||||||
订单分析
|
|
||||||
客户分析
|
|
||||||
库存预测
|
|
||||||
```
|
|
||||||
|
|
||||||
### 技术架构概览
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
subgraph "前端层"
|
|
||||||
F1[React应用]
|
|
||||||
F2[Umi框架]
|
|
||||||
F3[Ant Design]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "业务层"
|
|
||||||
B1[页面组件]
|
|
||||||
B2[业务逻辑]
|
|
||||||
B3[状态管理]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "服务层"
|
|
||||||
S1[API接口]
|
|
||||||
S2[数据处理]
|
|
||||||
S3[错误处理]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "数据层"
|
|
||||||
D1[HTTP请求]
|
|
||||||
D2[数据缓存]
|
|
||||||
D3[本地存储]
|
|
||||||
end
|
|
||||||
|
|
||||||
F1 --> B1
|
|
||||||
F2 --> B2
|
|
||||||
F3 --> B3
|
|
||||||
|
|
||||||
B1 --> S1
|
|
||||||
B2 --> S2
|
|
||||||
B3 --> S3
|
|
||||||
|
|
||||||
S1 --> D1
|
|
||||||
S2 --> D2
|
|
||||||
S3 --> D3
|
|
||||||
```
|
|
||||||
|
|
||||||
## 快速导航
|
|
||||||
|
|
||||||
### 按角色查看文档
|
|
||||||
|
|
||||||
#### 技术人员
|
|
||||||
1. **开发工程师**: 重点查看 [技术架构图](./技术架构图.md) 了解系统设计
|
|
||||||
2. **前端工程师**: 查看 [项目分析报告](./项目分析报告.md) 的代码质量部分
|
|
||||||
3. **架构师**: 全面阅读所有文档,重点关注架构演进规划
|
|
||||||
|
|
||||||
#### 业务人员
|
|
||||||
1. **产品经理**: 重点查看 [业务流程图](./业务流程图.md) 了解业务逻辑
|
|
||||||
2. **项目经理**: 查看 [项目分析报告](./项目分析报告.md) 的项目评估部分
|
|
||||||
3. **业务分析师**: 关注业务流程和数据流转分析
|
|
||||||
|
|
||||||
#### 管理人员
|
|
||||||
1. **技术负责人**: 查看项目整体评价和改进建议
|
|
||||||
2. **团队Leader**: 关注技术债务和团队协作优化
|
|
||||||
3. **决策者**: 重点查看项目优势、风险和投入产出分析
|
|
||||||
|
|
||||||
### 按问题类型查看
|
|
||||||
|
|
||||||
| 问题类型 | 推荐文档 | 关键章节 |
|
|
||||||
|---------|---------|---------|
|
|
||||||
| 技术选型 | 项目分析报告 | 技术栈分析 |
|
|
||||||
| 架构设计 | 技术架构图 | 整体架构图 |
|
|
||||||
| 性能优化 | 项目分析报告 | 性能优化空间 |
|
|
||||||
| 业务理解 | 业务流程图 | 核心业务流程 |
|
|
||||||
| 代码质量 | 项目分析报告 | 代码质量问题 |
|
|
||||||
| 系统扩展 | 技术架构图 | 架构演进规划 |
|
|
||||||
|
|
||||||
## 文档维护
|
|
||||||
|
|
||||||
### 更新机制
|
|
||||||
|
|
||||||
1. **定期更新**: 每月更新一次,确保文档与代码同步
|
|
||||||
2. **版本控制**: 使用Git管理文档版本,记录变更历史
|
|
||||||
3. **协作编辑**: 团队成员可提交文档改进建议
|
|
||||||
4. **质量审核**: 技术负责人审核文档质量和准确性
|
|
||||||
|
|
||||||
### 文档规范
|
|
||||||
|
|
||||||
#### 编写标准
|
|
||||||
- 采用Markdown格式编写
|
|
||||||
- 使用Mermaid绘制流程图和架构图
|
|
||||||
- 遵循结构化文档模板
|
|
||||||
- 包含背景、定义、流程三部分
|
|
||||||
|
|
||||||
#### 命名规范
|
|
||||||
- 文件名使用中文,便于理解
|
|
||||||
- 图片资源统一存放在assets目录
|
|
||||||
- 链接使用相对路径
|
|
||||||
- 版本号采用语义化版本控制
|
|
||||||
|
|
||||||
### 贡献指南
|
|
||||||
|
|
||||||
#### 如何贡献
|
|
||||||
1. **发现问题**: 在使用过程中发现文档问题或不足
|
|
||||||
2. **提出建议**: 通过Issue或直接联系维护人员
|
|
||||||
3. **提交修改**: Fork项目,修改文档,提交Pull Request
|
|
||||||
4. **审核合并**: 维护人员审核后合并到主分支
|
|
||||||
|
|
||||||
#### 贡献类型
|
|
||||||
- **内容补充**: 添加缺失的技术细节或业务说明
|
|
||||||
- **错误修正**: 修复文档中的错误信息
|
|
||||||
- **格式优化**: 改进文档格式和可读性
|
|
||||||
- **图表更新**: 更新过时的架构图或流程图
|
|
||||||
|
|
||||||
## 相关资源
|
|
||||||
|
|
||||||
### 外部链接
|
|
||||||
|
|
||||||
| 资源类型 | 链接地址 | 说明 |
|
|
||||||
|---------|---------|------|
|
|
||||||
| React官方文档 | https://react.dev/ | React框架官方文档 |
|
|
||||||
| Umi官方文档 | https://umijs.org/ | Umi框架官方文档 |
|
|
||||||
| Ant Design | https://ant.design/ | UI组件库官方文档 |
|
|
||||||
| TypeScript | https://www.typescriptlang.org/ | TypeScript官方文档 |
|
|
||||||
|
|
||||||
### 内部资源
|
|
||||||
|
|
||||||
- **代码仓库**: 项目源代码和版本管理
|
|
||||||
- **API文档**: 后端接口文档和调用说明
|
|
||||||
- **设计规范**: UI设计规范和组件库
|
|
||||||
- **测试文档**: 测试用例和测试报告
|
|
||||||
|
|
||||||
## 联系方式
|
|
||||||
|
|
||||||
### 文档维护团队
|
|
||||||
|
|
||||||
- **技术负责人**: 负责技术文档的准确性和完整性
|
|
||||||
- **产品负责人**: 负责业务文档的准确性和实用性
|
|
||||||
- **项目经理**: 负责文档的整体规划和协调
|
|
||||||
|
|
||||||
### 反馈渠道
|
|
||||||
|
|
||||||
- **邮件反馈**: 发送邮件到团队邮箱
|
|
||||||
- **即时沟通**: 通过团队沟通工具反馈
|
|
||||||
- **定期会议**: 在项目会议中讨论文档改进
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*文档中心版本: v1.0*
|
|
||||||
*最后更新: 2024年12月*
|
|
||||||
*维护团队: YOONE开发团队*
|
|
||||||
556
docs/业务流程图.md
556
docs/业务流程图.md
|
|
@ -1,556 +0,0 @@
|
||||||
# WEB项目业务流程图
|
|
||||||
|
|
||||||
## 业务概述
|
|
||||||
|
|
||||||
### 背景意义
|
|
||||||
业务流程图用于展示YOONE电商管理系统的核心业务流程,帮助开发团队和业务人员理解系统的业务逻辑、用户操作路径和数据流转过程。
|
|
||||||
|
|
||||||
### 业务定义
|
|
||||||
YOONE系统是一个综合性电商管理平台,涵盖商品管理、订单处理、库存控制、客户服务、物流跟踪和数据分析等完整业务链条。
|
|
||||||
|
|
||||||
### 使用流程
|
|
||||||
通过流程图分析业务场景 → 识别关键节点 → 优化用户体验 → 提升业务效率
|
|
||||||
|
|
||||||
## 整体业务架构
|
|
||||||
|
|
||||||
### 业务模块关系图
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
subgraph "用户管理模块"
|
|
||||||
U1[用户登录]
|
|
||||||
U2[权限验证]
|
|
||||||
U3[角色管理]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "商品管理模块"
|
|
||||||
P1[商品录入]
|
|
||||||
P2[分类管理]
|
|
||||||
P3[库存同步]
|
|
||||||
P4[价格管理]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "订单管理模块"
|
|
||||||
O1[订单创建]
|
|
||||||
O2[订单处理]
|
|
||||||
O3[订单发货]
|
|
||||||
O4[订单完成]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "库存管理模块"
|
|
||||||
S1[库存监控]
|
|
||||||
S2[采购管理]
|
|
||||||
S3[库存调拨]
|
|
||||||
S4[库存预警]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "客户管理模块"
|
|
||||||
C1[客户信息]
|
|
||||||
C2[客户标签]
|
|
||||||
C3[客户服务]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "物流管理模块"
|
|
||||||
L1[物流服务商]
|
|
||||||
L2[运费计算]
|
|
||||||
L3[物流跟踪]
|
|
||||||
L4[地址管理]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "数据统计模块"
|
|
||||||
D1[销售统计]
|
|
||||||
D2[订单分析]
|
|
||||||
D3[客户分析]
|
|
||||||
D4[库存预测]
|
|
||||||
end
|
|
||||||
|
|
||||||
U1 --> P1
|
|
||||||
U2 --> O1
|
|
||||||
P3 --> S1
|
|
||||||
O2 --> L1
|
|
||||||
O4 --> D1
|
|
||||||
C1 --> D3
|
|
||||||
S2 --> D4
|
|
||||||
```
|
|
||||||
|
|
||||||
## 核心业务流程
|
|
||||||
|
|
||||||
### 1. 用户登录认证流程
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant U as 用户
|
|
||||||
participant L as 登录页面
|
|
||||||
participant A as 认证服务
|
|
||||||
participant D as 设备验证
|
|
||||||
participant H as 主页面
|
|
||||||
|
|
||||||
U->>L: 输入用户名密码
|
|
||||||
L->>A: 提交登录请求
|
|
||||||
A->>D: 设备指纹验证
|
|
||||||
|
|
||||||
alt 设备已认证
|
|
||||||
D-->>A: 验证通过
|
|
||||||
A-->>L: 返回Token
|
|
||||||
L-->>H: 跳转主页
|
|
||||||
H-->>U: 显示管理界面
|
|
||||||
else 设备未认证
|
|
||||||
D-->>A: 需要验证码
|
|
||||||
A-->>L: 发送验证码邮件
|
|
||||||
L-->>U: 提示输入验证码
|
|
||||||
U->>L: 输入验证码
|
|
||||||
L->>A: 验证码校验
|
|
||||||
A-->>L: 验证通过
|
|
||||||
L-->>H: 跳转主页
|
|
||||||
else 登录失败
|
|
||||||
A-->>L: 返回错误信息
|
|
||||||
L-->>U: 显示错误提示
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 商品管理业务流程
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[商品录入] --> B{商品类型}
|
|
||||||
B -->|新商品| C[创建商品信息]
|
|
||||||
B -->|WP商品| D[同步WP商品]
|
|
||||||
|
|
||||||
C --> E[设置商品分类]
|
|
||||||
D --> E
|
|
||||||
E --> F[配置商品属性]
|
|
||||||
F --> G[设置价格策略]
|
|
||||||
G --> H[库存初始化]
|
|
||||||
H --> I[商品发布]
|
|
||||||
|
|
||||||
I --> J{发布状态}
|
|
||||||
J -->|草稿| K[保存草稿]
|
|
||||||
J -->|发布| L[上架销售]
|
|
||||||
J -->|私有| M[内部使用]
|
|
||||||
|
|
||||||
L --> N[库存监控]
|
|
||||||
N --> O{库存状态}
|
|
||||||
O -->|充足| P[正常销售]
|
|
||||||
O -->|不足| Q[库存预警]
|
|
||||||
O -->|缺货| R[下架处理]
|
|
||||||
|
|
||||||
Q --> S[采购补货]
|
|
||||||
S --> T[更新库存]
|
|
||||||
T --> P
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 订单处理完整流程
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
subgraph "订单创建阶段"
|
|
||||||
A1[客户下单] --> A2[订单验证]
|
|
||||||
A2 --> A3[库存检查]
|
|
||||||
A3 --> A4[价格计算]
|
|
||||||
A4 --> A5[订单生成]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "订单处理阶段"
|
|
||||||
B1[订单确认] --> B2[支付处理]
|
|
||||||
B2 --> B3[库存扣减]
|
|
||||||
B3 --> B4[拣货准备]
|
|
||||||
B4 --> B5[包装处理]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "物流发货阶段"
|
|
||||||
C1[选择物流商] --> C2[运费计算]
|
|
||||||
C2 --> C3[生成运单]
|
|
||||||
C3 --> C4[打印标签]
|
|
||||||
C4 --> C5[货物发出]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "订单完成阶段"
|
|
||||||
D1[物流跟踪] --> D2[配送中]
|
|
||||||
D2 --> D3[签收确认]
|
|
||||||
D3 --> D4[订单完成]
|
|
||||||
D4 --> D5[数据统计]
|
|
||||||
end
|
|
||||||
|
|
||||||
A5 --> B1
|
|
||||||
B5 --> C1
|
|
||||||
C5 --> D1
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 库存管理流程
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
subgraph "库存监控"
|
|
||||||
M1[实时库存] --> M2[库存预警]
|
|
||||||
M2 --> M3[补货提醒]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "采购流程"
|
|
||||||
P1[创建采购单] --> P2[供应商确认]
|
|
||||||
P2 --> P3[货物入库]
|
|
||||||
P3 --> P4[库存更新]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "库存调拨"
|
|
||||||
T1[调拨申请] --> T2[库存检查]
|
|
||||||
T2 --> T3[调拨执行]
|
|
||||||
T3 --> T4[库存同步]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "库存盘点"
|
|
||||||
C1[盘点计划] --> C2[实地盘点]
|
|
||||||
C2 --> C3[差异分析]
|
|
||||||
C3 --> C4[库存调整]
|
|
||||||
end
|
|
||||||
|
|
||||||
M3 --> P1
|
|
||||||
P4 --> M1
|
|
||||||
T4 --> M1
|
|
||||||
C4 --> M1
|
|
||||||
```
|
|
||||||
|
|
||||||
## 用户角色业务流程
|
|
||||||
|
|
||||||
### 管理员操作流程
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[管理员登录] --> B[权限验证]
|
|
||||||
B --> C{权限级别}
|
|
||||||
|
|
||||||
C -->|超级管理员| D[全部功能权限]
|
|
||||||
C -->|普通管理员| E[部分功能权限]
|
|
||||||
|
|
||||||
D --> F[用户管理]
|
|
||||||
D --> G[系统配置]
|
|
||||||
D --> H[数据统计]
|
|
||||||
|
|
||||||
E --> I[商品管理]
|
|
||||||
E --> J[订单管理]
|
|
||||||
E --> K[库存管理]
|
|
||||||
|
|
||||||
F --> L[创建用户]
|
|
||||||
F --> M[分配权限]
|
|
||||||
G --> N[系统设置]
|
|
||||||
H --> O[生成报表]
|
|
||||||
|
|
||||||
I --> P[商品CRUD]
|
|
||||||
J --> Q[订单处理]
|
|
||||||
K --> R[库存操作]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 普通用户操作流程
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[用户登录] --> B[权限检查]
|
|
||||||
B --> C{分配权限}
|
|
||||||
|
|
||||||
C -->|商品权限| D[商品管理页面]
|
|
||||||
C -->|订单权限| E[订单管理页面]
|
|
||||||
C -->|库存权限| F[库存管理页面]
|
|
||||||
C -->|客户权限| G[客户管理页面]
|
|
||||||
C -->|物流权限| H[物流管理页面]
|
|
||||||
C -->|统计权限| I[数据统计页面]
|
|
||||||
|
|
||||||
D --> J[查看/编辑商品]
|
|
||||||
E --> K[处理订单]
|
|
||||||
F --> L[管理库存]
|
|
||||||
G --> M[维护客户信息]
|
|
||||||
H --> N[处理物流]
|
|
||||||
I --> O[查看统计数据]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 关键业务场景
|
|
||||||
|
|
||||||
### 场景1: 新订单处理
|
|
||||||
|
|
||||||
| 步骤 | 操作者 | 动作 | 系统响应 | 业务规则 |
|
|
||||||
|------|--------|------|----------|----------|
|
|
||||||
| 1 | 系统 | 接收新订单 | 订单状态:待处理 | 自动库存检查 |
|
|
||||||
| 2 | 操作员 | 确认订单 | 订单状态:已确认 | 验证商品可用性 |
|
|
||||||
| 3 | 系统 | 扣减库存 | 更新库存数量 | 防止超卖 |
|
|
||||||
| 4 | 操作员 | 拣货打包 | 订单状态:已打包 | 记录操作时间 |
|
|
||||||
| 5 | 系统 | 生成运单 | 创建物流信息 | 选择最优物流商 |
|
|
||||||
| 6 | 操作员 | 发货确认 | 订单状态:已发货 | 更新物流跟踪号 |
|
|
||||||
|
|
||||||
### 场景2: 库存补货流程
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant S as 库存系统
|
|
||||||
participant M as 管理员
|
|
||||||
participant P as 采购员
|
|
||||||
participant V as 供应商
|
|
||||||
participant W as 仓库
|
|
||||||
|
|
||||||
S->>M: 库存预警通知
|
|
||||||
M->>P: 创建采购任务
|
|
||||||
P->>V: 发送采购订单
|
|
||||||
V-->>P: 确认订单
|
|
||||||
P->>W: 通知入库准备
|
|
||||||
V->>W: 货物配送
|
|
||||||
W->>S: 更新库存数量
|
|
||||||
S-->>M: 库存补充完成
|
|
||||||
```
|
|
||||||
|
|
||||||
### 场景3: 客户服务流程
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[客户咨询] --> B{问题类型}
|
|
||||||
|
|
||||||
B -->|订单问题| C[查询订单状态]
|
|
||||||
B -->|商品问题| D[查询商品信息]
|
|
||||||
B -->|物流问题| E[查询物流状态]
|
|
||||||
B -->|退换货| F[处理退换货]
|
|
||||||
|
|
||||||
C --> G[提供订单详情]
|
|
||||||
D --> H[提供商品说明]
|
|
||||||
E --> I[提供物流跟踪]
|
|
||||||
F --> J[创建退货单]
|
|
||||||
|
|
||||||
G --> K[问题解决]
|
|
||||||
H --> K
|
|
||||||
I --> K
|
|
||||||
J --> L[退货处理流程]
|
|
||||||
L --> K
|
|
||||||
```
|
|
||||||
|
|
||||||
## 数据流转分析
|
|
||||||
|
|
||||||
### 订单数据流
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
subgraph "数据源"
|
|
||||||
D1[WP订单]
|
|
||||||
D2[手动创建]
|
|
||||||
D3[API接口]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "数据处理"
|
|
||||||
P1[订单验证]
|
|
||||||
P2[数据转换]
|
|
||||||
P3[业务处理]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "数据存储"
|
|
||||||
S1[订单表]
|
|
||||||
S2[订单项表]
|
|
||||||
S3[物流表]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "数据应用"
|
|
||||||
A1[订单管理]
|
|
||||||
A2[统计分析]
|
|
||||||
A3[报表生成]
|
|
||||||
end
|
|
||||||
|
|
||||||
D1 --> P1
|
|
||||||
D2 --> P1
|
|
||||||
D3 --> P1
|
|
||||||
|
|
||||||
P1 --> P2
|
|
||||||
P2 --> P3
|
|
||||||
|
|
||||||
P3 --> S1
|
|
||||||
P3 --> S2
|
|
||||||
P3 --> S3
|
|
||||||
|
|
||||||
S1 --> A1
|
|
||||||
S2 --> A2
|
|
||||||
S3 --> A3
|
|
||||||
```
|
|
||||||
|
|
||||||
### 库存数据流
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
subgraph "库存变动触发"
|
|
||||||
T1[商品入库]
|
|
||||||
T2[订单发货]
|
|
||||||
T3[库存调拨]
|
|
||||||
T4[库存盘点]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "库存计算"
|
|
||||||
C1[可用库存计算]
|
|
||||||
C2[预留库存计算]
|
|
||||||
C3[安全库存检查]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "库存更新"
|
|
||||||
U1[更新库存表]
|
|
||||||
U2[记录库存日志]
|
|
||||||
U3[触发预警机制]
|
|
||||||
end
|
|
||||||
|
|
||||||
T1 --> C1
|
|
||||||
T2 --> C1
|
|
||||||
T3 --> C1
|
|
||||||
T4 --> C1
|
|
||||||
|
|
||||||
C1 --> C2
|
|
||||||
C2 --> C3
|
|
||||||
|
|
||||||
C3 --> U1
|
|
||||||
U1 --> U2
|
|
||||||
U2 --> U3
|
|
||||||
```
|
|
||||||
|
|
||||||
## 异常处理流程
|
|
||||||
|
|
||||||
### 订单异常处理
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[订单处理] --> B{检查异常}
|
|
||||||
|
|
||||||
B -->|库存不足| C[库存异常处理]
|
|
||||||
B -->|支付失败| D[支付异常处理]
|
|
||||||
B -->|地址错误| E[地址异常处理]
|
|
||||||
B -->|商品下架| F[商品异常处理]
|
|
||||||
|
|
||||||
C --> G[暂停订单]
|
|
||||||
D --> H[重新支付]
|
|
||||||
E --> I[联系客户]
|
|
||||||
F --> J[商品替换]
|
|
||||||
|
|
||||||
G --> K[等待补货]
|
|
||||||
H --> L[支付确认]
|
|
||||||
I --> M[地址更新]
|
|
||||||
J --> N[订单修改]
|
|
||||||
|
|
||||||
K --> O{处理结果}
|
|
||||||
L --> O
|
|
||||||
M --> O
|
|
||||||
N --> O
|
|
||||||
|
|
||||||
O -->|成功| P[继续处理]
|
|
||||||
O -->|失败| Q[订单取消]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 系统异常处理
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant U as 用户
|
|
||||||
participant F as 前端
|
|
||||||
participant A as API
|
|
||||||
participant S as 后端服务
|
|
||||||
|
|
||||||
U->>F: 执行操作
|
|
||||||
F->>A: 发送请求
|
|
||||||
A->>S: 调用服务
|
|
||||||
|
|
||||||
alt 服务正常
|
|
||||||
S-->>A: 返回结果
|
|
||||||
A-->>F: 响应数据
|
|
||||||
F-->>U: 显示结果
|
|
||||||
else 服务异常
|
|
||||||
S-->>A: 返回错误
|
|
||||||
A-->>F: 错误响应
|
|
||||||
F-->>U: 显示错误信息
|
|
||||||
F->>F: 记录错误日志
|
|
||||||
else 网络异常
|
|
||||||
A-->>F: 请求超时
|
|
||||||
F-->>U: 显示网络错误
|
|
||||||
F->>F: 重试机制
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## 业务优化建议
|
|
||||||
|
|
||||||
### 流程优化对比
|
|
||||||
|
|
||||||
| 业务流程 | 当前状态 | 存在问题 | 优化建议 | 预期效果 |
|
|
||||||
|---------|---------|---------|---------|---------|
|
|
||||||
| 订单处理 | 手动确认 | 效率较低 | 自动化处理 | 提升50%效率 |
|
|
||||||
| 库存管理 | 定期盘点 | 实时性差 | 实时监控 | 减少缺货风险 |
|
|
||||||
| 客户服务 | 人工处理 | 响应较慢 | 智能客服 | 24小时服务 |
|
|
||||||
| 数据分析 | 定期报表 | 时效性差 | 实时分析 | 决策更及时 |
|
|
||||||
|
|
||||||
### 用户体验优化
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
subgraph "当前体验"
|
|
||||||
C1[多步操作]
|
|
||||||
C2[页面跳转多]
|
|
||||||
C3[等待时间长]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "优化方案"
|
|
||||||
O1[一键操作]
|
|
||||||
O2[模态框处理]
|
|
||||||
O3[异步处理]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "预期效果"
|
|
||||||
E1[操作简化]
|
|
||||||
E2[体验流畅]
|
|
||||||
E3[响应快速]
|
|
||||||
end
|
|
||||||
|
|
||||||
C1 --> O1
|
|
||||||
C2 --> O2
|
|
||||||
C3 --> O3
|
|
||||||
|
|
||||||
O1 --> E1
|
|
||||||
O2 --> E2
|
|
||||||
O3 --> E3
|
|
||||||
```
|
|
||||||
|
|
||||||
## 业务指标监控
|
|
||||||
|
|
||||||
### 关键业务指标
|
|
||||||
|
|
||||||
| 指标类别 | 具体指标 | 计算方式 | 目标值 |
|
|
||||||
|---------|---------|---------|--------|
|
|
||||||
| 订单效率 | 订单处理时间 | 从创建到发货的平均时间 | < 24小时 |
|
|
||||||
| 库存周转 | 库存周转率 | 销售成本/平均库存 | > 12次/年 |
|
|
||||||
| 客户满意 | 订单完成率 | 成功完成订单/总订单数 | > 98% |
|
|
||||||
| 系统性能 | 页面响应时间 | 用户操作到页面响应时间 | < 2秒 |
|
|
||||||
|
|
||||||
### 业务监控流程
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
subgraph "数据收集"
|
|
||||||
D1[订单数据]
|
|
||||||
D2[库存数据]
|
|
||||||
D3[用户行为]
|
|
||||||
D4[系统性能]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "数据处理"
|
|
||||||
P1[数据清洗]
|
|
||||||
P2[指标计算]
|
|
||||||
P3[趋势分析]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "结果展示"
|
|
||||||
R1[实时仪表板]
|
|
||||||
R2[定期报表]
|
|
||||||
R3[异常告警]
|
|
||||||
end
|
|
||||||
|
|
||||||
D1 --> P1
|
|
||||||
D2 --> P1
|
|
||||||
D3 --> P1
|
|
||||||
D4 --> P1
|
|
||||||
|
|
||||||
P1 --> P2
|
|
||||||
P2 --> P3
|
|
||||||
|
|
||||||
P3 --> R1
|
|
||||||
P3 --> R2
|
|
||||||
P3 --> R3
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*文档版本: v1.0*
|
|
||||||
*更新时间: 2024年12月*
|
|
||||||
*业务负责人: 产品团队*
|
|
||||||
527
docs/技术架构图.md
527
docs/技术架构图.md
|
|
@ -1,527 +0,0 @@
|
||||||
# WEB项目技术架构图
|
|
||||||
|
|
||||||
## 架构概述
|
|
||||||
|
|
||||||
### 背景意义
|
|
||||||
技术架构图用于展示YOONE WEB项目的整体技术结构,包括前端架构层次、数据流向、组件关系和技术栈组成,为开发团队提供清晰的技术视图。
|
|
||||||
|
|
||||||
### 架构定义
|
|
||||||
采用分层架构模式,将系统划分为表现层、业务层、数据层和基础设施层,实现关注点分离和模块化管理。
|
|
||||||
|
|
||||||
## 整体架构图
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
subgraph "用户层"
|
|
||||||
U1[管理员用户]
|
|
||||||
U2[普通用户]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "前端应用层 (React + Umi)"
|
|
||||||
subgraph "路由层"
|
|
||||||
R1[登录路由]
|
|
||||||
R2[业务路由]
|
|
||||||
R3[权限路由]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "页面组件层"
|
|
||||||
P1[首页 Home]
|
|
||||||
P2[用户管理 Organiza]
|
|
||||||
P3[商品管理 Product]
|
|
||||||
P4[订单管理 Order]
|
|
||||||
P5[库存管理 Stock]
|
|
||||||
P6[客户管理 Customer]
|
|
||||||
P7[物流管理 Logistics]
|
|
||||||
P8[数据统计 Statistics]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "业务组件层"
|
|
||||||
C1[表格组件 ProTable]
|
|
||||||
C2[表单组件 ProForm]
|
|
||||||
C3[图表组件 Charts]
|
|
||||||
C4[布局组件 Layout]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "工具层"
|
|
||||||
T1[API服务 servers/api]
|
|
||||||
T2[工具函数 utils]
|
|
||||||
T3[常量定义 constants]
|
|
||||||
T4[类型定义 typings]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "状态管理层"
|
|
||||||
S1[全局状态 @@initialState]
|
|
||||||
S2[用户状态 global model]
|
|
||||||
S3[权限状态 access]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "网络层"
|
|
||||||
N1[HTTP请求拦截器]
|
|
||||||
N2[响应处理器]
|
|
||||||
N3[错误处理器]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "后端API层"
|
|
||||||
A1[用户API /user]
|
|
||||||
A2[商品API /product]
|
|
||||||
A3[订单API /order]
|
|
||||||
A4[库存API /stock]
|
|
||||||
A5[物流API /logistics]
|
|
||||||
A6[统计API /statistics]
|
|
||||||
end
|
|
||||||
|
|
||||||
U1 --> R1
|
|
||||||
U2 --> R1
|
|
||||||
R1 --> P1
|
|
||||||
R2 --> P2
|
|
||||||
R2 --> P3
|
|
||||||
R2 --> P4
|
|
||||||
R2 --> P5
|
|
||||||
R2 --> P6
|
|
||||||
R2 --> P7
|
|
||||||
R2 --> P8
|
|
||||||
R3 --> S3
|
|
||||||
|
|
||||||
P1 --> C1
|
|
||||||
P2 --> C2
|
|
||||||
P3 --> C3
|
|
||||||
P4 --> C4
|
|
||||||
|
|
||||||
C1 --> T1
|
|
||||||
C2 --> T2
|
|
||||||
C3 --> T3
|
|
||||||
C4 --> T4
|
|
||||||
|
|
||||||
T1 --> N1
|
|
||||||
N1 --> N2
|
|
||||||
N2 --> N3
|
|
||||||
|
|
||||||
N3 --> A1
|
|
||||||
N3 --> A2
|
|
||||||
N3 --> A3
|
|
||||||
N3 --> A4
|
|
||||||
N3 --> A5
|
|
||||||
N3 --> A6
|
|
||||||
|
|
||||||
S1 --> P1
|
|
||||||
S2 --> P2
|
|
||||||
S3 --> R3
|
|
||||||
```
|
|
||||||
|
|
||||||
## 技术栈架构
|
|
||||||
|
|
||||||
### 核心技术栈层次图
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
subgraph "开发工具层"
|
|
||||||
D1[TypeScript 5.7]
|
|
||||||
D2[ESLint]
|
|
||||||
D3[Prettier]
|
|
||||||
D4[Husky]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "构建工具层"
|
|
||||||
B1[Umi Max 4.4]
|
|
||||||
B2[Webpack]
|
|
||||||
B3[Babel]
|
|
||||||
B4[PostCSS]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "框架层"
|
|
||||||
F1[React 18]
|
|
||||||
F2[Ant Design 5.4]
|
|
||||||
F3[Pro Components 2.4]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "功能库层"
|
|
||||||
L1[ECharts 5.6]
|
|
||||||
L2[dayjs 1.11]
|
|
||||||
L3[file-saver 2.0]
|
|
||||||
L4[xlsx 0.18]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "运行时层"
|
|
||||||
R1[浏览器环境]
|
|
||||||
R2[Node.js环境]
|
|
||||||
end
|
|
||||||
|
|
||||||
D1 --> B1
|
|
||||||
D2 --> B1
|
|
||||||
D3 --> B1
|
|
||||||
D4 --> B1
|
|
||||||
|
|
||||||
B1 --> F1
|
|
||||||
B2 --> F1
|
|
||||||
B3 --> F1
|
|
||||||
B4 --> F1
|
|
||||||
|
|
||||||
F1 --> L1
|
|
||||||
F2 --> L1
|
|
||||||
F3 --> L1
|
|
||||||
|
|
||||||
L1 --> R1
|
|
||||||
L2 --> R1
|
|
||||||
L3 --> R1
|
|
||||||
L4 --> R1
|
|
||||||
```
|
|
||||||
|
|
||||||
## 数据流架构
|
|
||||||
|
|
||||||
### 数据流向图
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant U as 用户界面
|
|
||||||
participant C as 组件层
|
|
||||||
participant S as 状态管理
|
|
||||||
participant A as API层
|
|
||||||
participant B as 后端服务
|
|
||||||
|
|
||||||
U->>C: 用户操作
|
|
||||||
C->>S: 更新状态
|
|
||||||
S->>A: 发起请求
|
|
||||||
A->>B: HTTP请求
|
|
||||||
B-->>A: 响应数据
|
|
||||||
A-->>S: 处理响应
|
|
||||||
S-->>C: 状态更新
|
|
||||||
C-->>U: 界面更新
|
|
||||||
```
|
|
||||||
|
|
||||||
### 权限控制流程
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[用户登录] --> B{Token验证}
|
|
||||||
B -->|有效| C[获取用户信息]
|
|
||||||
B -->|无效| D[跳转登录页]
|
|
||||||
C --> E[解析用户权限]
|
|
||||||
E --> F{路由权限检查}
|
|
||||||
F -->|有权限| G[渲染页面]
|
|
||||||
F -->|无权限| H[显示403页面]
|
|
||||||
G --> I[页面级权限控制]
|
|
||||||
I --> J[功能按钮权限]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 组件架构设计
|
|
||||||
|
|
||||||
### 组件层次结构
|
|
||||||
|
|
||||||
| 层级 | 组件类型 | 职责描述 | 示例组件 |
|
|
||||||
|------|---------|---------|---------|
|
|
||||||
| 1 | 页面组件 | 业务页面容器,路由对应 | Home、Order/List |
|
|
||||||
| 2 | 业务组件 | 特定业务逻辑封装 | OrderTable、ProductForm |
|
|
||||||
| 3 | 通用组件 | 可复用UI组件 | InternationalPhoneInput |
|
|
||||||
| 4 | 基础组件 | Ant Design组件 | Table、Form、Button |
|
|
||||||
|
|
||||||
### 组件通信模式
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
subgraph "父子组件通信"
|
|
||||||
P1[父组件] -->|Props| C1[子组件]
|
|
||||||
C1 -->|Events| P1
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "跨组件通信"
|
|
||||||
G1[全局状态] --> C2[组件A]
|
|
||||||
G1 --> C3[组件B]
|
|
||||||
C2 --> G1
|
|
||||||
C3 --> G1
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "兄弟组件通信"
|
|
||||||
S1[共同父组件] --> B1[兄弟组件1]
|
|
||||||
S1 --> B2[兄弟组件2]
|
|
||||||
B1 -.->|通过父组件| B2
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## API架构设计
|
|
||||||
|
|
||||||
### API接口分层
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
subgraph "API接口层"
|
|
||||||
A1[用户接口 user.ts]
|
|
||||||
A2[订单接口 order.ts]
|
|
||||||
A3[商品接口 product.ts]
|
|
||||||
A4[库存接口 stock.ts]
|
|
||||||
A5[物流接口 logistics.ts]
|
|
||||||
A6[统计接口 statistics.ts]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "请求处理层"
|
|
||||||
R1[请求拦截器]
|
|
||||||
R2[响应拦截器]
|
|
||||||
R3[错误处理器]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "数据传输层"
|
|
||||||
D1[HTTP Client]
|
|
||||||
D2[请求配置]
|
|
||||||
D3[类型定义]
|
|
||||||
end
|
|
||||||
|
|
||||||
A1 --> R1
|
|
||||||
A2 --> R1
|
|
||||||
A3 --> R1
|
|
||||||
A4 --> R1
|
|
||||||
A5 --> R1
|
|
||||||
A6 --> R1
|
|
||||||
|
|
||||||
R1 --> R2
|
|
||||||
R2 --> R3
|
|
||||||
|
|
||||||
R3 --> D1
|
|
||||||
D1 --> D2
|
|
||||||
D2 --> D3
|
|
||||||
```
|
|
||||||
|
|
||||||
### API调用流程
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant C as 组件
|
|
||||||
participant API as API函数
|
|
||||||
participant I as 拦截器
|
|
||||||
participant H as HTTP客户端
|
|
||||||
participant S as 后端服务
|
|
||||||
|
|
||||||
C->>API: 调用API函数
|
|
||||||
API->>I: 请求拦截
|
|
||||||
I->>I: 添加Token
|
|
||||||
I->>H: 发送请求
|
|
||||||
H->>S: HTTP请求
|
|
||||||
S-->>H: 响应数据
|
|
||||||
H-->>I: 响应拦截
|
|
||||||
I-->>I: 错误处理
|
|
||||||
I-->>API: 返回结果
|
|
||||||
API-->>C: 返回数据
|
|
||||||
```
|
|
||||||
|
|
||||||
## 构建部署架构
|
|
||||||
|
|
||||||
### 构建流程图
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
subgraph "开发环境"
|
|
||||||
D1[源代码]
|
|
||||||
D2[TypeScript编译]
|
|
||||||
D3[热重载]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "构建流程"
|
|
||||||
B1[代码检查]
|
|
||||||
B2[类型检查]
|
|
||||||
B3[打包构建]
|
|
||||||
B4[资源优化]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "部署环境"
|
|
||||||
P1[静态资源]
|
|
||||||
P2[CDN分发]
|
|
||||||
P3[Web服务器]
|
|
||||||
end
|
|
||||||
|
|
||||||
D1 --> D2
|
|
||||||
D2 --> D3
|
|
||||||
D3 --> B1
|
|
||||||
B1 --> B2
|
|
||||||
B2 --> B3
|
|
||||||
B3 --> B4
|
|
||||||
B4 --> P1
|
|
||||||
P1 --> P2
|
|
||||||
P2 --> P3
|
|
||||||
```
|
|
||||||
|
|
||||||
### 环境配置对比
|
|
||||||
|
|
||||||
| 环境 | 构建模式 | API地址 | 特性 |
|
|
||||||
|------|---------|---------|------|
|
|
||||||
| 开发环境 | development | http://localhost:7001 | 热重载、Source Map |
|
|
||||||
| 生产环境 | production | https://api.yoone.ca | 代码压缩、资源优化 |
|
|
||||||
|
|
||||||
## 性能优化架构
|
|
||||||
|
|
||||||
### 性能优化策略
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
subgraph "加载优化"
|
|
||||||
L1[代码分割]
|
|
||||||
L2[懒加载]
|
|
||||||
L3[预加载]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "渲染优化"
|
|
||||||
R1[虚拟滚动]
|
|
||||||
R2[组件缓存]
|
|
||||||
R3[防抖节流]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "资源优化"
|
|
||||||
A1[图片压缩]
|
|
||||||
A2[资源合并]
|
|
||||||
A3[CDN加速]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "缓存策略"
|
|
||||||
C1[浏览器缓存]
|
|
||||||
C2[HTTP缓存]
|
|
||||||
C3[应用缓存]
|
|
||||||
end
|
|
||||||
|
|
||||||
L1 --> R1
|
|
||||||
L2 --> R2
|
|
||||||
L3 --> R3
|
|
||||||
|
|
||||||
R1 --> A1
|
|
||||||
R2 --> A2
|
|
||||||
R3 --> A3
|
|
||||||
|
|
||||||
A1 --> C1
|
|
||||||
A2 --> C2
|
|
||||||
A3 --> C3
|
|
||||||
```
|
|
||||||
|
|
||||||
## 安全架构设计
|
|
||||||
|
|
||||||
### 安全防护层次
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
subgraph "认证授权层"
|
|
||||||
S1[JWT Token认证]
|
|
||||||
S2[权限验证]
|
|
||||||
S3[设备指纹识别]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "数据安全层"
|
|
||||||
D1[HTTPS传输]
|
|
||||||
D2[数据加密]
|
|
||||||
D3[敏感信息脱敏]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "应用安全层"
|
|
||||||
A1[XSS防护]
|
|
||||||
A2[CSRF防护]
|
|
||||||
A3[输入验证]
|
|
||||||
end
|
|
||||||
|
|
||||||
S1 --> D1
|
|
||||||
S2 --> D2
|
|
||||||
S3 --> D3
|
|
||||||
|
|
||||||
D1 --> A1
|
|
||||||
D2 --> A2
|
|
||||||
D3 --> A3
|
|
||||||
```
|
|
||||||
|
|
||||||
## 监控架构设计
|
|
||||||
|
|
||||||
### 监控体系结构
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
subgraph "前端监控"
|
|
||||||
F1[性能监控]
|
|
||||||
F2[错误监控]
|
|
||||||
F3[用户行为]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "数据收集"
|
|
||||||
C1[埋点数据]
|
|
||||||
C2[日志收集]
|
|
||||||
C3[性能指标]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "数据分析"
|
|
||||||
A1[实时分析]
|
|
||||||
A2[报表生成]
|
|
||||||
A3[告警通知]
|
|
||||||
end
|
|
||||||
|
|
||||||
F1 --> C1
|
|
||||||
F2 --> C2
|
|
||||||
F3 --> C3
|
|
||||||
|
|
||||||
C1 --> A1
|
|
||||||
C2 --> A2
|
|
||||||
C3 --> A3
|
|
||||||
```
|
|
||||||
|
|
||||||
## 架构演进规划
|
|
||||||
|
|
||||||
### 短期优化(1-3个月)
|
|
||||||
|
|
||||||
1. **组件库建设**
|
|
||||||
- 提取公共业务组件
|
|
||||||
- 建立组件文档系统
|
|
||||||
- 实现组件单元测试
|
|
||||||
|
|
||||||
2. **性能优化**
|
|
||||||
- 实现路由级代码分割
|
|
||||||
- 添加组件懒加载
|
|
||||||
- 优化打包体积
|
|
||||||
|
|
||||||
### 中期升级(3-6个月)
|
|
||||||
|
|
||||||
1. **状态管理升级**
|
|
||||||
- 引入Redux Toolkit
|
|
||||||
- 实现状态持久化
|
|
||||||
- 优化数据流管理
|
|
||||||
|
|
||||||
2. **工程化完善**
|
|
||||||
- 建立自动化测试
|
|
||||||
- 完善CI/CD流程
|
|
||||||
- 添加代码质量检查
|
|
||||||
|
|
||||||
### 长期规划(6个月以上)
|
|
||||||
|
|
||||||
1. **微前端架构**
|
|
||||||
- 模块联邦改造
|
|
||||||
- 独立部署能力
|
|
||||||
- 团队协作优化
|
|
||||||
|
|
||||||
2. **技术栈升级**
|
|
||||||
- React 19升级
|
|
||||||
- 新特性应用
|
|
||||||
- 性能进一步优化
|
|
||||||
|
|
||||||
## 架构最佳实践
|
|
||||||
|
|
||||||
### 设计原则
|
|
||||||
|
|
||||||
| 原则 | 描述 | 实现方式 |
|
|
||||||
|------|------|---------|
|
|
||||||
| 单一职责 | 每个组件只负责一个功能 | 组件功能拆分 |
|
|
||||||
| 开闭原则 | 对扩展开放,对修改关闭 | 插件化设计 |
|
|
||||||
| 依赖倒置 | 依赖抽象而非具体实现 | 接口定义 |
|
|
||||||
| 关注点分离 | 不同关注点分层处理 | 分层架构 |
|
|
||||||
|
|
||||||
### 代码组织规范
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 推荐的文件组织结构
|
|
||||||
src/
|
|
||||||
├── components/ // 公共组件
|
|
||||||
│ ├── Business/ // 业务组件
|
|
||||||
│ └── Common/ // 通用组件
|
|
||||||
├── pages/ // 页面组件
|
|
||||||
│ └── [Module]/ // 按模块组织
|
|
||||||
├── services/ // API服务
|
|
||||||
├── utils/ // 工具函数
|
|
||||||
├── hooks/ // 自定义Hook
|
|
||||||
├── constants/ // 常量定义
|
|
||||||
└── types/ // 类型定义
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*文档版本: v1.0*
|
|
||||||
*更新时间: 2024年12月*
|
|
||||||
*维护团队: 前端开发组*
|
|
||||||
288
docs/项目分析报告.md
288
docs/项目分析报告.md
|
|
@ -1,288 +0,0 @@
|
||||||
# WEB项目分析报告
|
|
||||||
|
|
||||||
## 项目概述
|
|
||||||
|
|
||||||
### 背景意义
|
|
||||||
YOONE WEB项目是一个基于React和Umi框架的企业级管理系统,主要用于电商业务的全流程管理,包括商品管理、订单处理、库存控制、客户管理、物流跟踪和数据统计等核心功能。
|
|
||||||
|
|
||||||
### 项目定位
|
|
||||||
- **项目类型**: 企业级B端管理系统
|
|
||||||
- **业务领域**: 电商管理平台
|
|
||||||
- **技术架构**: 前后端分离的单页面应用(SPA)
|
|
||||||
- **目标用户**: 企业内部管理人员
|
|
||||||
|
|
||||||
## 技术栈分析
|
|
||||||
|
|
||||||
### 核心技术框架对比
|
|
||||||
|
|
||||||
| 技术分类 | 选用技术 | 版本 | 优势 | 适用性评价 |
|
|
||||||
|---------|---------|------|------|-----------|
|
|
||||||
| 前端框架 | React | ^18.0.33 | 生态成熟、组件化开发 | ✅ 适合 |
|
|
||||||
| 应用框架 | Umi Max | ^4.4.4 | 企业级、开箱即用 | ✅ 适合 |
|
|
||||||
| UI组件库 | Ant Design | ^5.4.0 | 企业级设计语言 | ✅ 适合 |
|
|
||||||
| Pro组件 | @ant-design/pro-components | ^2.4.4 | 高级业务组件 | ✅ 适合 |
|
|
||||||
| 图表库 | @ant-design/charts + ECharts | ^2.2.6 + ^5.6.0 | 功能强大、可视化丰富 | ✅ 适合 |
|
|
||||||
| 状态管理 | Umi内置Model | - | 轻量级、易使用 | ⚠️ 中等 |
|
|
||||||
| 类型检查 | TypeScript | ^5.7.3 | 类型安全、开发体验好 | ✅ 适合 |
|
|
||||||
| 包管理器 | yarn | - | 性能优秀、节省空间 | ✅ 适合 |
|
|
||||||
|
|
||||||
### 依赖分析
|
|
||||||
|
|
||||||
#### 生产依赖评价
|
|
||||||
- **核心依赖合理**: React生态 + Ant Design + Umi框架组合成熟稳定
|
|
||||||
- **功能依赖完善**: 涵盖图表、文件处理、国际化电话输入等业务需求
|
|
||||||
- **版本管理良好**: 主要依赖版本较新,安全性和功能性有保障
|
|
||||||
|
|
||||||
#### 开发依赖评价
|
|
||||||
- **代码质量工具**: ESLint、Prettier、Husky配置完整
|
|
||||||
- **类型支持**: TypeScript配置完善
|
|
||||||
- **构建工具**: 基于Umi的现代化构建流程
|
|
||||||
|
|
||||||
## 项目结构分析
|
|
||||||
|
|
||||||
### 目录结构评价
|
|
||||||
|
|
||||||
| 目录 | 功能 | 组织方式 | 评价 |
|
|
||||||
|------|------|---------|------|
|
|
||||||
| `/src/pages` | 页面组件 | 按业务模块分组 | ✅ 清晰合理 |
|
|
||||||
| `/src/servers/api` | API接口 | 按业务领域分文件 | ✅ 结构清晰 |
|
|
||||||
| `/src/components` | 公共组件 | 功能导向 | ⚠️ 组件较少 |
|
|
||||||
| `/src/utils` | 工具函数 | 功能分类 | ✅ 组织良好 |
|
|
||||||
| `/src/constants` | 常量定义 | 集中管理 | ✅ 便于维护 |
|
|
||||||
|
|
||||||
### 代码组织特点
|
|
||||||
1. **模块化程度高**: 按业务功能清晰划分
|
|
||||||
2. **文件命名规范**: 遵循统一的命名约定
|
|
||||||
3. **类型定义完整**: TypeScript使用充分
|
|
||||||
4. **配置集中化**: 路由、权限、API等配置统一管理
|
|
||||||
|
|
||||||
## 功能模块分析
|
|
||||||
|
|
||||||
### 核心业务模块
|
|
||||||
|
|
||||||
| 模块名称 | 功能描述 | 实现质量 | 复杂度 |
|
|
||||||
|---------|---------|---------|--------|
|
|
||||||
| 用户管理 | 登录认证、权限控制 | ✅ 良好 | 中等 |
|
|
||||||
| 商品管理 | 商品CRUD、分类管理 | ✅ 完善 | 中等 |
|
|
||||||
| 订单管理 | 订单处理、状态跟踪 | ✅ 复杂但完整 | 高 |
|
|
||||||
| 库存管理 | 库存监控、采购管理 | ✅ 功能完整 | 中高 |
|
|
||||||
| 客户管理 | 客户信息维护 | ✅ 基础功能 | 低 |
|
|
||||||
| 物流管理 | 物流跟踪、地址管理 | ✅ 功能丰富 | 中高 |
|
|
||||||
| 数据统计 | 多维度数据分析 | ✅ 图表丰富 | 中等 |
|
|
||||||
|
|
||||||
### 技术特性分析
|
|
||||||
|
|
||||||
#### 权限控制系统
|
|
||||||
- **实现方式**: 基于角色的访问控制(RBAC)
|
|
||||||
- **权限粒度**: 页面级权限控制
|
|
||||||
- **优点**: 配置灵活、易于扩展
|
|
||||||
- **改进空间**: 可考虑按钮级权限控制
|
|
||||||
|
|
||||||
#### 路由管理
|
|
||||||
- **配置方式**: 集中式路由配置
|
|
||||||
- **嵌套路由**: 支持多级嵌套
|
|
||||||
- **权限集成**: 路由与权限系统深度集成
|
|
||||||
- **评价**: 结构清晰、维护方便
|
|
||||||
|
|
||||||
## 项目优势分析
|
|
||||||
|
|
||||||
### 技术优势
|
|
||||||
|
|
||||||
1. **现代化技术栈**
|
|
||||||
- 使用最新版本的React 18和TypeScript 5.7
|
|
||||||
- Umi Max框架提供企业级开发体验
|
|
||||||
- Ant Design 5.x提供现代化UI设计
|
|
||||||
|
|
||||||
2. **开发体验优秀**
|
|
||||||
- TypeScript提供完整类型支持
|
|
||||||
- 热重载和快速构建
|
|
||||||
- 代码格式化和质量检查自动化
|
|
||||||
|
|
||||||
3. **架构设计合理**
|
|
||||||
- 前后端分离架构清晰
|
|
||||||
- 模块化程度高,易于维护
|
|
||||||
- API接口设计规范
|
|
||||||
|
|
||||||
### 业务优势
|
|
||||||
|
|
||||||
1. **功能覆盖全面**
|
|
||||||
- 涵盖电商业务全流程
|
|
||||||
- 多维度数据统计分析
|
|
||||||
- 完整的权限管理体系
|
|
||||||
|
|
||||||
2. **用户体验良好**
|
|
||||||
- 响应式设计适配多设备
|
|
||||||
- 丰富的交互组件
|
|
||||||
- 直观的数据可视化
|
|
||||||
|
|
||||||
3. **扩展性强**
|
|
||||||
- 模块化设计便于功能扩展
|
|
||||||
- 配置化路由和权限
|
|
||||||
- 标准化的API接口
|
|
||||||
|
|
||||||
## 项目不足分析
|
|
||||||
|
|
||||||
### 技术层面问题
|
|
||||||
|
|
||||||
1. **状态管理简单**
|
|
||||||
- 仅使用Umi内置Model,对于复杂状态管理可能不足
|
|
||||||
- 缺乏全局状态的统一管理方案
|
|
||||||
- **影响**: 随着业务复杂度增加,状态管理可能成为瓶颈
|
|
||||||
|
|
||||||
2. **组件复用度低**
|
|
||||||
- 公共组件数量较少
|
|
||||||
- 业务组件耦合度较高
|
|
||||||
- **影响**: 代码重复,维护成本高
|
|
||||||
|
|
||||||
3. **错误处理不完善**
|
|
||||||
- 缺乏统一的错误处理机制
|
|
||||||
- 用户友好的错误提示不足
|
|
||||||
- **影响**: 用户体验和系统稳定性
|
|
||||||
|
|
||||||
### 代码质量问题
|
|
||||||
|
|
||||||
1. **文档缺失**
|
|
||||||
- API接口缺乏详细注释
|
|
||||||
- 业务逻辑文档不完整
|
|
||||||
- **影响**: 新人上手困难,维护成本高
|
|
||||||
|
|
||||||
2. **测试覆盖不足**
|
|
||||||
- 缺乏单元测试和集成测试
|
|
||||||
- 没有自动化测试流程
|
|
||||||
- **影响**: 代码质量难以保证
|
|
||||||
|
|
||||||
3. **性能优化空间**
|
|
||||||
- 大型页面组件未做懒加载
|
|
||||||
- 图片和资源优化不足
|
|
||||||
- **影响**: 首屏加载时间较长
|
|
||||||
|
|
||||||
### 架构设计问题
|
|
||||||
|
|
||||||
1. **微前端架构缺失**
|
|
||||||
- 单体应用架构,难以支持大团队协作
|
|
||||||
- **影响**: 随着团队扩大,开发效率可能下降
|
|
||||||
|
|
||||||
2. **国际化支持不足**
|
|
||||||
- 硬编码中文文本较多
|
|
||||||
- **影响**: 国际化扩展困难
|
|
||||||
|
|
||||||
## 改进建议
|
|
||||||
|
|
||||||
### 短期改进(1-3个月)
|
|
||||||
|
|
||||||
1. **完善错误处理**
|
|
||||||
```typescript
|
|
||||||
// 建议实现统一错误处理中间件
|
|
||||||
export const errorHandler = (error: any) => {
|
|
||||||
// 统一错误处理逻辑
|
|
||||||
console.error('API Error:', error);
|
|
||||||
message.error(getErrorMessage(error));
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **增加代码注释**
|
|
||||||
- 为所有API接口添加详细注释
|
|
||||||
- 为复杂业务逻辑添加说明文档
|
|
||||||
- 建立代码注释规范
|
|
||||||
|
|
||||||
3. **性能优化**
|
|
||||||
- 实现路由级代码分割
|
|
||||||
- 添加图片懒加载
|
|
||||||
- 优化大数据列表渲染
|
|
||||||
|
|
||||||
### 中期改进(3-6个月)
|
|
||||||
|
|
||||||
1. **重构状态管理**
|
|
||||||
```typescript
|
|
||||||
// 建议引入Redux Toolkit或Zustand
|
|
||||||
import { configureStore } from '@reduxjs/toolkit';
|
|
||||||
|
|
||||||
export const store = configureStore({
|
|
||||||
reducer: {
|
|
||||||
user: userSlice.reducer,
|
|
||||||
order: orderSlice.reducer,
|
|
||||||
// 其他业务模块
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **建立组件库**
|
|
||||||
- 提取公共业务组件
|
|
||||||
- 建立设计系统
|
|
||||||
- 实现组件文档化
|
|
||||||
|
|
||||||
3. **完善测试体系**
|
|
||||||
- 添加单元测试覆盖核心业务逻辑
|
|
||||||
- 建立E2E测试流程
|
|
||||||
- 集成CI/CD自动化测试
|
|
||||||
|
|
||||||
### 长期改进(6个月以上)
|
|
||||||
|
|
||||||
1. **微前端架构升级**
|
|
||||||
- 考虑使用qiankun或Module Federation
|
|
||||||
- 按业务域拆分独立应用
|
|
||||||
- 建立统一的基础设施
|
|
||||||
|
|
||||||
2. **国际化改造**
|
|
||||||
- 使用react-intl实现国际化
|
|
||||||
- 建立多语言资源管理
|
|
||||||
- 支持RTL布局
|
|
||||||
|
|
||||||
3. **监控和分析**
|
|
||||||
- 集成前端监控系统
|
|
||||||
- 建立性能分析体系
|
|
||||||
- 用户行为数据收集
|
|
||||||
|
|
||||||
## 技术债务评估
|
|
||||||
|
|
||||||
### 债务等级分类
|
|
||||||
|
|
||||||
| 等级 | 问题描述 | 影响范围 | 修复优先级 |
|
|
||||||
|------|---------|---------|-----------|
|
|
||||||
| 高 | 缺乏测试覆盖 | 整个项目 | 🔴 高 |
|
|
||||||
| 高 | 错误处理不统一 | 用户体验 | 🔴 高 |
|
|
||||||
| 中 | 状态管理简单 | 开发效率 | 🟡 中 |
|
|
||||||
| 中 | 组件复用度低 | 维护成本 | 🟡 中 |
|
|
||||||
| 低 | 文档不完善 | 团队协作 | 🟢 低 |
|
|
||||||
|
|
||||||
### 修复成本估算
|
|
||||||
|
|
||||||
- **高优先级问题**: 需要2-3个开发周期
|
|
||||||
- **中优先级问题**: 需要4-6个开发周期
|
|
||||||
- **低优先级问题**: 可在日常开发中逐步完善
|
|
||||||
|
|
||||||
## 总结评价
|
|
||||||
|
|
||||||
### 整体评分
|
|
||||||
|
|
||||||
| 评价维度 | 得分 | 说明 |
|
|
||||||
|---------|------|------|
|
|
||||||
| 技术选型 | 8.5/10 | 技术栈现代化,选择合理 |
|
|
||||||
| 代码质量 | 7.0/10 | 结构清晰但缺乏测试 |
|
|
||||||
| 功能完整性 | 8.0/10 | 业务功能覆盖全面 |
|
|
||||||
| 用户体验 | 7.5/10 | 界面友好但性能有待优化 |
|
|
||||||
| 可维护性 | 7.0/10 | 模块化良好但文档不足 |
|
|
||||||
| 可扩展性 | 7.5/10 | 架构支持扩展但有局限 |
|
|
||||||
|
|
||||||
### 综合评价
|
|
||||||
|
|
||||||
YOONE WEB项目是一个**技术栈现代化、功能相对完整**的企业级管理系统。项目在技术选型、架构设计和业务功能实现方面表现良好,能够满足当前的业务需求。
|
|
||||||
|
|
||||||
**主要优势**:
|
|
||||||
- 现代化的技术栈和开发体验
|
|
||||||
- 清晰的模块化架构
|
|
||||||
- 完整的业务功能覆盖
|
|
||||||
- 良好的用户界面设计
|
|
||||||
|
|
||||||
**主要挑战**:
|
|
||||||
- 代码质量保障体系不完善
|
|
||||||
- 性能优化和错误处理需要加强
|
|
||||||
- 随着业务增长,当前架构可能面临扩展性挑战
|
|
||||||
|
|
||||||
**建议**: 在保持现有优势的基础上,重点关注代码质量、性能优化和长期架构演进,确保项目能够支撑业务的持续发展。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*报告生成时间: 2024年12月*
|
|
||||||
*分析范围: WEB前端项目完整代码库*
|
|
||||||
*评估标准: 企业级项目开发最佳实践*
|
|
||||||
|
|
@ -35,6 +35,7 @@
|
||||||
"code-inspector-plugin": "^1.2.10",
|
"code-inspector-plugin": "^1.2.10",
|
||||||
"husky": "^9",
|
"husky": "^9",
|
||||||
"lint-staged": "^13.2.0",
|
"lint-staged": "^13.2.0",
|
||||||
|
"openapi2ts": "^1.1.14",
|
||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.7",
|
||||||
"prettier-plugin-organize-imports": "^3.2.2",
|
"prettier-plugin-organize-imports": "^3.2.2",
|
||||||
"prettier-plugin-packagejson": "^2.4.3",
|
"prettier-plugin-packagejson": "^2.4.3",
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { usercontrollerGetuser } from './servers/api/user';
|
||||||
dayjs.locale('zh-cn');
|
dayjs.locale('zh-cn');
|
||||||
|
|
||||||
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
||||||
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
|
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
|
||||||
export async function getInitialState(): Promise<{
|
export async function getInitialState(): Promise<{
|
||||||
user?: Record<string, any>;
|
user?: Record<string, any>;
|
||||||
categoryList?: ProSchemaValueEnumObj;
|
categoryList?: ProSchemaValueEnumObj;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
||||||
import FingerprintJS from '@fingerprintjs/fingerprintjs';
|
import FingerprintJS from '@fingerprintjs/fingerprintjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook: 获取设备指纹(visitorId)
|
* Hook: 获取设备指纹(visitorId)
|
||||||
*/
|
*/
|
||||||
export function useDeviceFingerprint() {
|
export function useDeviceFingerprint() {
|
||||||
const [fingerprint, setFingerprint] = useState<string | null>(null);
|
const [fingerprint, setFingerprint] = useState<string | null>(null);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
|
import type { ProColumns, ActionType, ProTableProps } from '@ant-design/pro-components';
|
||||||
|
import { ProTable } from '@ant-design/pro-components';
|
||||||
|
import { App } from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { ordercontrollerGetordersales } from '@/servers/api/order';
|
||||||
|
import { sitecontrollerAll } from '@/servers/api/site';
|
||||||
|
|
||||||
|
// 列表行数据结构(订单商品聚合)
|
||||||
|
interface OrderItemAggRow {
|
||||||
|
externalProductId: number; // 商品ID(来自 WooCommerce 产品ID)
|
||||||
|
externalVariationId: number; // 变体ID(来自 WooCommerce 变体ID)
|
||||||
|
name: string; // 商品名称
|
||||||
|
totalQuantity: number; // 总售出数量(时间范围内)
|
||||||
|
totalOrders: number; // 涉及订单数(去重)
|
||||||
|
firstOrderCount: number; // 客户首单次数(该商品)
|
||||||
|
secondOrderCount: number; // 客户第二次购买次数(该商品)
|
||||||
|
thirdOrderCount: number; // 客户第三次购买次数(该商品)
|
||||||
|
moreThirdOrderCount: number; // 客户超过三次购买次数(该商品)
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderItemsPage: React.FC = () => {
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
const { message } = App.useApp();
|
||||||
|
|
||||||
|
// 列配置(中文标题,符合当前项目风格;显示英文默认语言可后续走国际化)
|
||||||
|
const columns: ProColumns<OrderItemAggRow>[] = [
|
||||||
|
{
|
||||||
|
title: '商品名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
width: 220,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '商品ID',
|
||||||
|
dataIndex: 'externalProductId',
|
||||||
|
width: 120,
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '变体ID',
|
||||||
|
dataIndex: 'externalVariationId',
|
||||||
|
width: 120,
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '总售出数量',
|
||||||
|
dataIndex: 'totalQuantity',
|
||||||
|
width: 130,
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '订单数',
|
||||||
|
dataIndex: 'totalOrders',
|
||||||
|
width: 110,
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '首单次数',
|
||||||
|
dataIndex: 'firstOrderCount',
|
||||||
|
width: 120,
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '第二次购买',
|
||||||
|
dataIndex: 'secondOrderCount',
|
||||||
|
width: 120,
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '第三次购买',
|
||||||
|
dataIndex: 'thirdOrderCount',
|
||||||
|
width: 120,
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '超过三次购买',
|
||||||
|
dataIndex: 'moreThirdOrderCount',
|
||||||
|
width: 140,
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
// 搜索区域字段
|
||||||
|
{
|
||||||
|
title: '站点',
|
||||||
|
dataIndex: 'siteId',
|
||||||
|
valueType: 'select',
|
||||||
|
request: async () => {
|
||||||
|
// 拉取站点列表(后台 /site/all)
|
||||||
|
const { data = [] } = await sitecontrollerAll();
|
||||||
|
return (data || []).map((item: any) => ({ label: item.siteName, value: item.id }));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '时间范围',
|
||||||
|
dataIndex: 'dateRange',
|
||||||
|
valueType: 'dateRange',
|
||||||
|
hideInTable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '商品关键字',
|
||||||
|
dataIndex: 'name',
|
||||||
|
hideInTable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 表格请求方法:调用 /order/getOrderSales 接口并设置 isSource=true 获取订单项聚合
|
||||||
|
const request: ProTableProps<OrderItemAggRow>['request'] = async (params:any) => {
|
||||||
|
try {
|
||||||
|
const { current = 1, pageSize = 10, siteId, name } = params as any;
|
||||||
|
const [startDate, endDate] = (params as any).dateRange || [];
|
||||||
|
// 调用后端接口(isSource=true 表示按订单项聚合)
|
||||||
|
const resp = await ordercontrollerGetordersales({
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
siteId,
|
||||||
|
name,
|
||||||
|
isSource: true as any,
|
||||||
|
startDate: startDate ? (dayjs(startDate).toISOString() as any) : undefined,
|
||||||
|
endDate: endDate ? (dayjs(endDate).toISOString() as any) : undefined,
|
||||||
|
} as any);
|
||||||
|
const { success, data, message: errMsg } = resp as any;
|
||||||
|
if (!success) throw new Error(errMsg || '获取失败');
|
||||||
|
return {
|
||||||
|
data: (data?.items ?? []) as OrderItemAggRow[],
|
||||||
|
total: data?.total ?? 0,
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e?.message || '获取失败');
|
||||||
|
return { data: [], total: 0, success: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer title='订单商品概览'>
|
||||||
|
<ProTable<OrderItemAggRow>
|
||||||
|
actionRef={actionRef}
|
||||||
|
rowKey={(r) => `${r.externalProductId}-${r.externalVariationId}-${r.name}`}
|
||||||
|
columns={columns}
|
||||||
|
request={request}
|
||||||
|
pagination={{ showSizeChanger: true }}
|
||||||
|
search={{ labelWidth: 90, span: 6 }}
|
||||||
|
toolBarRender={false}
|
||||||
|
/>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrderItemsPage;
|
||||||
|
|
@ -77,9 +77,10 @@ import {
|
||||||
Space,
|
Space,
|
||||||
Tabs,
|
Tabs,
|
||||||
TabsProps,
|
TabsProps,
|
||||||
|
Tag,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import Item from 'antd/es/list/Item';
|
import Item from 'antd/es/list/Item';
|
||||||
import dayjs from 'dayjs';
|
import RelatedOrders from '../../Subscription/Orders/RelatedOrders';
|
||||||
import React, { useMemo, useRef, useState } from 'react';
|
import React, { useMemo, useRef, useState } from 'react';
|
||||||
import { printPDF } from '@/utils/util';
|
import { printPDF } from '@/utils/util';
|
||||||
|
|
||||||
|
|
@ -172,9 +173,16 @@ const ListPage: React.FC = () => {
|
||||||
hideInTable: true,
|
hideInTable: true,
|
||||||
valueType: 'dateRange',
|
valueType: 'dateRange',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: '订单号',
|
title: '订阅',
|
||||||
dataIndex: 'externalOrderId',
|
dataIndex: 'isSubscription',
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, record) => {
|
||||||
|
const related = Array.isArray((record as any)?.related) ? (record as any).related : [];
|
||||||
|
const isSub = related.some((it) => it?.externalSubscriptionId || it?.billing_period || it?.line_items);
|
||||||
|
return <Tag color={isSub ? 'green' : 'default'}>{isSub ? '是' : '否'}</Tag>;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '站点',
|
title: '站点',
|
||||||
|
|
@ -816,7 +824,7 @@ const Detail: React.FC<{
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
company:
|
company:
|
||||||
<span>
|
<span>
|
||||||
{record?.shipping?.company ||
|
{record?.shipping?.company ||
|
||||||
record?.billing?.company ||
|
record?.billing?.company ||
|
||||||
|
|
@ -824,7 +832,7 @@ const Detail: React.FC<{
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
first_name:
|
first_name:
|
||||||
<span>
|
<span>
|
||||||
{record?.shipping?.first_name ||
|
{record?.shipping?.first_name ||
|
||||||
record?.billing?.first_name ||
|
record?.billing?.first_name ||
|
||||||
|
|
@ -832,7 +840,7 @@ const Detail: React.FC<{
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
last_name:
|
last_name:
|
||||||
<span>
|
<span>
|
||||||
{record?.shipping?.last_name ||
|
{record?.shipping?.last_name ||
|
||||||
record?.billing?.last_name ||
|
record?.billing?.last_name ||
|
||||||
|
|
@ -840,7 +848,7 @@ const Detail: React.FC<{
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
country:
|
country:
|
||||||
<span>
|
<span>
|
||||||
{record?.shipping?.country ||
|
{record?.shipping?.country ||
|
||||||
record?.billing?.country ||
|
record?.billing?.country ||
|
||||||
|
|
@ -848,19 +856,19 @@ const Detail: React.FC<{
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
state:
|
state:
|
||||||
<span>
|
<span>
|
||||||
{record?.shipping?.state || record?.billing?.state || '-'}
|
{record?.shipping?.state || record?.billing?.state || '-'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
city:
|
city:
|
||||||
<span>
|
<span>
|
||||||
{record?.shipping?.city || record?.billing?.city || '-'}
|
{record?.shipping?.city || record?.billing?.city || '-'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
postcode:
|
postcode:
|
||||||
<span>
|
<span>
|
||||||
{record?.shipping?.postcode ||
|
{record?.shipping?.postcode ||
|
||||||
record?.billing?.postcode ||
|
record?.billing?.postcode ||
|
||||||
|
|
@ -868,13 +876,13 @@ const Detail: React.FC<{
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
phone:
|
phone:
|
||||||
<span>
|
<span>
|
||||||
{record?.shipping?.phone || record?.billing?.phone || '-'}
|
{record?.shipping?.phone || record?.billing?.phone || '-'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
address_1:
|
address_1:
|
||||||
<span>
|
<span>
|
||||||
{record?.shipping?.address_1 ||
|
{record?.shipping?.address_1 ||
|
||||||
record?.billing?.address_1 ||
|
record?.billing?.address_1 ||
|
||||||
|
|
@ -885,6 +893,7 @@ const Detail: React.FC<{
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{/* 原始订单 */}
|
||||||
<ProDescriptions.Item
|
<ProDescriptions.Item
|
||||||
label="原始订单"
|
label="原始订单"
|
||||||
span={3}
|
span={3}
|
||||||
|
|
@ -893,13 +902,22 @@ const Detail: React.FC<{
|
||||||
<ul>
|
<ul>
|
||||||
{record?.items?.map((item: any) => (
|
{record?.items?.map((item: any) => (
|
||||||
<li key={item.id}>
|
<li key={item.id}>
|
||||||
{item.name}:{item.quantity}
|
{item.name}:{item.quantity}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{/* 显示 related order */}
|
||||||
|
<ProDescriptions.Item
|
||||||
|
label="关联"
|
||||||
|
span={3}
|
||||||
|
render={(_, record) => {
|
||||||
|
return <RelatedOrders data={record?.related} />;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* 订单内容 */}
|
||||||
<ProDescriptions.Item
|
<ProDescriptions.Item
|
||||||
label="订单内容"
|
label="订单内容"
|
||||||
span={3}
|
span={3}
|
||||||
|
|
@ -908,7 +926,7 @@ const Detail: React.FC<{
|
||||||
<ul>
|
<ul>
|
||||||
{record?.sales?.map((item: any) => (
|
{record?.sales?.map((item: any) => (
|
||||||
<li key={item.id}>
|
<li key={item.id}>
|
||||||
{item.name}:{item.quantity}
|
{item.name}:{item.quantity}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -1021,7 +1039,7 @@ const Detail: React.FC<{
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div>订单号: {v?.orderIds?.join(',')}</div>
|
<div>订单号: {v?.orderIds?.join(',')}</div>
|
||||||
{v?.items?.map((item) => (
|
{v?.items?.map((item) => (
|
||||||
<div>
|
<div>
|
||||||
{item.name}: {item.quantity}
|
{item.name}: {item.quantity}
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ const ListPage: React.FC = () => {
|
||||||
<div>
|
<div>
|
||||||
{record?.first_hot_purchase?.map((v) => (
|
{record?.first_hot_purchase?.map((v) => (
|
||||||
<div>
|
<div>
|
||||||
产品名称:{v.name} 用户数:{v.user_count}
|
产品名称:{v.name} 用户数:{v.user_count}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -206,7 +206,7 @@ const ListPage: React.FC = () => {
|
||||||
<div>
|
<div>
|
||||||
{record?.second_hot_purchase?.map((v) => (
|
{record?.second_hot_purchase?.map((v) => (
|
||||||
<div>
|
<div>
|
||||||
产品名称:{v.name} 用户数:{v.user_count}
|
产品名称:{v.name} 用户数:{v.user_count}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -220,7 +220,7 @@ const ListPage: React.FC = () => {
|
||||||
<div>
|
<div>
|
||||||
{record?.third_hot_purchase?.map((v) => (
|
{record?.third_hot_purchase?.map((v) => (
|
||||||
<div>
|
<div>
|
||||||
产品名称:{v.name} 用户数:{v.user_count}
|
产品名称:{v.name} 用户数:{v.user_count}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ const ListPage: React.FC = () => {
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dateFormatter="number"
|
dateFormatter="number"
|
||||||
footer={() => `总计: ${total}`}
|
footer={() => `总计: ${total}`}
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ const CreateForm: React.FC<{
|
||||||
/>
|
/>
|
||||||
<ProFormDependency name={['items']}>
|
<ProFormDependency name={['items']}>
|
||||||
{({ items }) => {
|
{({ items }) => {
|
||||||
return '数量:' + items?.reduce((acc, cur) => acc + cur.quantity, 0);
|
return '数量:' + items?.reduce((acc, cur) => acc + cur.quantity, 0);
|
||||||
}}
|
}}
|
||||||
</ProFormDependency>
|
</ProFormDependency>
|
||||||
<ProFormList<API.PurchaseOrderItem>
|
<ProFormList<API.PurchaseOrderItem>
|
||||||
|
|
@ -428,7 +428,7 @@ const UpdateForm: React.FC<{
|
||||||
<ProFormDependency name={['items']}>
|
<ProFormDependency name={['items']}>
|
||||||
{({ items }) => {
|
{({ items }) => {
|
||||||
return (
|
return (
|
||||||
'数量:' + items?.reduce((acc, cur) => acc + cur.quantity, 0)
|
'数量:' + items?.reduce((acc, cur) => acc + cur.quantity, 0)
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</ProFormDependency>
|
</ProFormDependency>
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@ const CreateForm: React.FC<{
|
||||||
}}>引用</Button>} />
|
}}>引用</Button>} />
|
||||||
<ProFormDependency name={['items']}>
|
<ProFormDependency name={['items']}>
|
||||||
{({ items }) => {
|
{({ items }) => {
|
||||||
return '数量:' + (items?.reduce?.((acc, cur) => acc + cur.quantity, 0)||0);
|
return '数量:' + (items?.reduce?.((acc, cur) => acc + cur.quantity, 0)||0);
|
||||||
}}
|
}}
|
||||||
</ProFormDependency>
|
</ProFormDependency>
|
||||||
<ProFormList
|
<ProFormList
|
||||||
|
|
@ -465,7 +465,7 @@ const UpdateForm: React.FC<{
|
||||||
<ProFormTextArea name="note" label="备注" />
|
<ProFormTextArea name="note" label="备注" />
|
||||||
<ProFormDependency name={['items']}>
|
<ProFormDependency name={['items']}>
|
||||||
{({ items }) => {
|
{({ items }) => {
|
||||||
return '数量:' + items?.reduce?.((acc, cur) => acc + cur.quantity, 0);
|
return '数量:' + items?.reduce?.((acc, cur) => acc + cur.quantity, 0);
|
||||||
}}
|
}}
|
||||||
</ProFormDependency>
|
</ProFormDependency>
|
||||||
<ProFormList
|
<ProFormList
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,308 @@
|
||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
import {
|
||||||
|
ActionType,
|
||||||
|
DrawerForm,
|
||||||
|
PageContainer,
|
||||||
|
ProColumns,
|
||||||
|
ProFormSelect,
|
||||||
|
ProTable,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import { App, Button, Tag, Drawer, List } from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { request } from 'umi';
|
||||||
|
import {
|
||||||
|
subscriptioncontrollerList,
|
||||||
|
subscriptioncontrollerSync,
|
||||||
|
} from '@/servers/api/subscription';
|
||||||
|
import { sitecontrollerAll } from '@/servers/api/site';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅状态枚举(用于筛选与展示)
|
||||||
|
* 保持与后端同步的原始状态值
|
||||||
|
*/
|
||||||
|
const SUBSCRIPTION_STATUS_ENUM: Record<string, { text: string }> = {
|
||||||
|
active: { text: '激活' },
|
||||||
|
cancelled: { text: '已取消' },
|
||||||
|
expired: { text: '已过期' },
|
||||||
|
pending: { text: '待处理' },
|
||||||
|
'on-hold': { text: '暂停' },
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅列表页:展示、筛选、触发订阅同步
|
||||||
|
*/
|
||||||
|
const ListPage: React.FC = () => {
|
||||||
|
// 表格操作引用:用于在同步后触发表格刷新
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
const { message } = App.useApp();
|
||||||
|
|
||||||
|
// 关联订单抽屉状态
|
||||||
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
const [drawerTitle, setDrawerTitle] = useState('详情');
|
||||||
|
const [relatedOrders, setRelatedOrders] = useState<any[]>([]);
|
||||||
|
|
||||||
|
// 表格列定义(尽量与项目风格保持一致)
|
||||||
|
const columns: ProColumns<API.Subscription>[] = [
|
||||||
|
{
|
||||||
|
title: '站点',
|
||||||
|
dataIndex: 'siteId',
|
||||||
|
valueType: 'select',
|
||||||
|
// 动态加载站点选项
|
||||||
|
request: async () => {
|
||||||
|
const { data = [] } = await sitecontrollerAll();
|
||||||
|
return data.map((item) => ({
|
||||||
|
label: item.siteName,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
render: (_, row) => row?.siteId ?? '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '订阅ID',
|
||||||
|
dataIndex: 'externalSubscriptionId',
|
||||||
|
hideInSearch: true,
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '父订单号',
|
||||||
|
dataIndex: 'parent_id',
|
||||||
|
hideInSearch: true,
|
||||||
|
width: 120,
|
||||||
|
render: (_, row) => (row?.parent_id ? <Tag>{row.parent_id}</Tag> : '-'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
valueType: 'select',
|
||||||
|
valueEnum: SUBSCRIPTION_STATUS_ENUM,
|
||||||
|
// 以 Tag 形式展示,更易辨识
|
||||||
|
render: (_, row) =>
|
||||||
|
row?.status ? <Tag>{SUBSCRIPTION_STATUS_ENUM[row.status]?.text || row.status}</Tag> : '-',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '客户邮箱',
|
||||||
|
dataIndex: 'customer_email',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '金额',
|
||||||
|
dataIndex: 'total',
|
||||||
|
hideInSearch: true,
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '币种',
|
||||||
|
dataIndex: 'currency',
|
||||||
|
hideInSearch: true,
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '开始时间',
|
||||||
|
dataIndex: 'start_date',
|
||||||
|
hideInSearch: true,
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '下次支付',
|
||||||
|
dataIndex: 'next_payment_date',
|
||||||
|
hideInSearch: true,
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '结束时间',
|
||||||
|
dataIndex: 'end_date',
|
||||||
|
hideInSearch: true,
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'createdAt',
|
||||||
|
valueType: 'dateTime',
|
||||||
|
hideInSearch: true,
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '更新时间',
|
||||||
|
dataIndex: 'updatedAt',
|
||||||
|
valueType: 'dateTime',
|
||||||
|
hideInSearch: true,
|
||||||
|
width: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'actions',
|
||||||
|
hideInSearch: true,
|
||||||
|
width: 120,
|
||||||
|
render: (_, row) => (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const parentNumber = String(row?.parent_id || '');
|
||||||
|
if (!parentNumber) {
|
||||||
|
message.warning('该订阅缺少父订单号');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 通过父订单号查询关联订单(模糊匹配)
|
||||||
|
const resp = await request('/order/getOrderByNumber', {
|
||||||
|
method: 'POST',
|
||||||
|
data: { number: parentNumber },
|
||||||
|
});
|
||||||
|
const { success, data, message: errMsg } = resp as any;
|
||||||
|
if (!success) throw new Error(errMsg || '获取失败');
|
||||||
|
// 仅保留与父订单号完全一致的订单(避免模糊匹配误入)
|
||||||
|
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' });
|
||||||
|
if ((d as any)?.success) {
|
||||||
|
const od = (d as any)?.data || {};
|
||||||
|
details.push({
|
||||||
|
id: c.id,
|
||||||
|
externalOrderId: c.externalOrderId,
|
||||||
|
siteName: c.siteName,
|
||||||
|
status: od?.status,
|
||||||
|
total: od?.total,
|
||||||
|
currency_symbol: od?.currency_symbol,
|
||||||
|
date_created: od?.date_created,
|
||||||
|
relationship: 'Parent Order',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
details.push({
|
||||||
|
id: c.id,
|
||||||
|
externalOrderId: c.externalOrderId,
|
||||||
|
siteName: c.siteName,
|
||||||
|
relationship: 'Parent Order',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setRelatedOrders(details);
|
||||||
|
setDrawerTitle(`详情`);
|
||||||
|
setDrawerOpen(true);
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e?.message || '获取失败');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
查看详情
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer header={{ title: '订阅列表' }}>
|
||||||
|
<ProTable<API.Subscription>
|
||||||
|
headerTitle="查询表格"
|
||||||
|
rowKey="id"
|
||||||
|
actionRef={actionRef}
|
||||||
|
/**
|
||||||
|
* 列表数据请求;保持与后端分页参数一致
|
||||||
|
* 兼容后端 data.items 或 data.list 返回字段
|
||||||
|
*/
|
||||||
|
request={async (params) => {
|
||||||
|
const { data, success } = await subscriptioncontrollerList(params);
|
||||||
|
return {
|
||||||
|
total: data?.total || 0,
|
||||||
|
data: data?.items || data?.list || [],
|
||||||
|
success,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
columns={columns}
|
||||||
|
// 工具栏:订阅同步入口
|
||||||
|
toolBarRender={() => [<SyncForm key="sync" tableRef={actionRef} />]}
|
||||||
|
/>
|
||||||
|
{/* 关联订单抽屉:展示订单号、关系、时间、状态与金额 */}
|
||||||
|
<Drawer
|
||||||
|
open={drawerOpen}
|
||||||
|
title={drawerTitle}
|
||||||
|
width={720}
|
||||||
|
onClose={() => setDrawerOpen(false)}
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
header={<div>关联订单</div>}
|
||||||
|
dataSource={relatedOrders}
|
||||||
|
renderItem={(item: any) => (
|
||||||
|
<List.Item>
|
||||||
|
<List.Item.Meta
|
||||||
|
title={`#${item?.externalOrderId || '-'}`}
|
||||||
|
description={`关系:${item?.relationship || '-'},站点:${item?.siteName || '-'}`}
|
||||||
|
/>
|
||||||
|
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
|
||||||
|
<span>{item?.date_created ? dayjs(item.date_created).format('YYYY-MM-DD HH:mm') : '-'}</span>
|
||||||
|
<Tag>{item?.status || '-'}</Tag>
|
||||||
|
<span>
|
||||||
|
{item?.currency_symbol || ''}
|
||||||
|
{typeof item?.total === 'number' ? item.total.toFixed(2) : item?.total ?? '-'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步订阅抽屉表单:选择站点后触发同步
|
||||||
|
*/
|
||||||
|
const SyncForm: React.FC<{
|
||||||
|
tableRef: React.MutableRefObject<ActionType | undefined>;
|
||||||
|
}> = ({ tableRef }) => {
|
||||||
|
const { message } = App.useApp();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DrawerForm<API.subscriptioncontrollerSyncParams>
|
||||||
|
title="同步订阅"
|
||||||
|
trigger={
|
||||||
|
<Button key="syncSite" type="primary">
|
||||||
|
同步订阅
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
autoFocusFirstInput
|
||||||
|
drawerProps={{ destroyOnHidden: true }}
|
||||||
|
/**
|
||||||
|
* 提交逻辑:
|
||||||
|
* 1. 必填校验由 ProForm + rules 保证
|
||||||
|
* 2. 调用同步接口,失败时友好提示
|
||||||
|
* 3. 成功后刷新列表
|
||||||
|
*/
|
||||||
|
onFinish={async (values) => {
|
||||||
|
try {
|
||||||
|
const { success, message: errMsg } = await subscriptioncontrollerSync(values);
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
message.success('同步成功');
|
||||||
|
tableRef.current?.reload();
|
||||||
|
return true;
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message || '同步失败');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProFormSelect
|
||||||
|
name="siteId"
|
||||||
|
width="lg"
|
||||||
|
label="站点"
|
||||||
|
placeholder="请选择站点"
|
||||||
|
// 动态加载站点选项
|
||||||
|
request={async () => {
|
||||||
|
const { data = [] } = await sitecontrollerAll();
|
||||||
|
return data.map((item) => ({
|
||||||
|
label: item.siteName,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
rules={[{ required: true, message: '请选择站点' }]}
|
||||||
|
/>
|
||||||
|
</DrawerForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListPage;
|
||||||
|
|
@ -0,0 +1,327 @@
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import {
|
||||||
|
App,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Divider,
|
||||||
|
Drawer,
|
||||||
|
Empty,
|
||||||
|
Popconfirm,
|
||||||
|
Space,
|
||||||
|
Tag,
|
||||||
|
} from 'antd';
|
||||||
|
import { ActionType, ProDescriptions } from '@ant-design/pro-components';
|
||||||
|
import { CopyOutlined, DeleteFilled } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// 服务器 API 引用(保持与原 index.tsx 一致)
|
||||||
|
import {
|
||||||
|
ordercontrollerChangestatus,
|
||||||
|
ordercontrollerGetorderdetail,
|
||||||
|
ordercontrollerSyncorderbyid,
|
||||||
|
} from '@/servers/api/order';
|
||||||
|
import { logisticscontrollerDelshipment } from '@/servers/api/logistics';
|
||||||
|
import { sitecontrollerAll } from '@/servers/api/site';
|
||||||
|
|
||||||
|
// 工具与子组件
|
||||||
|
import { formatShipmentState, formatSource } from '@/utils/format';
|
||||||
|
import RelatedOrders from './RelatedOrders';
|
||||||
|
import { ORDER_STATUS_ENUM } from '@/constants';
|
||||||
|
|
||||||
|
// 中文注释:为保持原文件结构简单,此处从 index.tsx 引入的子组件仍由原文件导出或保持原状
|
||||||
|
// 若后续需要彻底解耦,可将 OrderNote / Shipping / SalesChange 也独立到文件
|
||||||
|
// 当前按你的要求仅抽离详情 Drawer
|
||||||
|
|
||||||
|
type OrderRecord = API.Order;
|
||||||
|
|
||||||
|
interface OrderDetailDrawerProps {
|
||||||
|
tableRef: React.MutableRefObject<ActionType | undefined>; // 中文注释:列表刷新引用
|
||||||
|
orderId: number; // 中文注释:订单主键 ID
|
||||||
|
record: OrderRecord; // 中文注释:订单行记录
|
||||||
|
open: boolean; // 中文注释:是否打开抽屉
|
||||||
|
onClose: () => void; // 中文注释:关闭抽屉回调
|
||||||
|
setActiveLine: (id: number) => void; // 中文注释:高亮当前行
|
||||||
|
OrderNoteComponent: React.ComponentType<any>; // 中文注释:备注组件(从外部注入)
|
||||||
|
SalesChangeComponent: React.ComponentType<any>; // 中文注释:换货组件(从外部注入)
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrderDetailDrawer: React.FC<OrderDetailDrawerProps> = ({
|
||||||
|
tableRef,
|
||||||
|
orderId,
|
||||||
|
record,
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
setActiveLine,
|
||||||
|
OrderNoteComponent,
|
||||||
|
SalesChangeComponent,
|
||||||
|
}) => {
|
||||||
|
const { message } = App.useApp();
|
||||||
|
const ref = useRef<ActionType>();
|
||||||
|
|
||||||
|
// 中文注释:加载详情数据(与 index.tsx 中完全保持一致)
|
||||||
|
const initRequest = async () => {
|
||||||
|
const { data, success }: API.OrderDetailRes = await ordercontrollerGetorderdetail({ orderId });
|
||||||
|
if (!success || !data) return { data: {} } as any;
|
||||||
|
data.sales = data.sales?.reduce((acc: API.OrderSale[], cur: API.OrderSale) => {
|
||||||
|
const idx = acc.findIndex((v: any) => v.productId === cur.productId);
|
||||||
|
if (idx === -1) acc.push(cur);
|
||||||
|
else acc[idx].quantity += cur.quantity;
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
return { data } as any;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && record?.id) {
|
||||||
|
setActiveLine(record.id as number);
|
||||||
|
}
|
||||||
|
}, [open, record?.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
title="订单详情"
|
||||||
|
open={open}
|
||||||
|
destroyOnHidden
|
||||||
|
size="large"
|
||||||
|
onClose={onClose}
|
||||||
|
footer={[
|
||||||
|
// 中文注释:备注组件(外部传入以避免循环依赖)
|
||||||
|
<OrderNoteComponent key="order-note" id={orderId} descRef={ref} />,
|
||||||
|
...(['after_sale_pending', 'pending_reshipment'].includes(
|
||||||
|
record.orderStatus,
|
||||||
|
)
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
<Divider key="divider-sync" type="vertical" />,
|
||||||
|
<Button
|
||||||
|
key="btn-sync"
|
||||||
|
type="primary"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const { success, message: errMsg } =
|
||||||
|
await ordercontrollerSyncorderbyid({
|
||||||
|
siteId: record.siteId as string,
|
||||||
|
orderId: record.externalOrderId as string,
|
||||||
|
});
|
||||||
|
if (!success) throw new Error(errMsg);
|
||||||
|
message.success('同步成功');
|
||||||
|
tableRef.current?.reload();
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error?.message || '同步失败');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
同步订单
|
||||||
|
</Button>,
|
||||||
|
]),
|
||||||
|
...([
|
||||||
|
'processing',
|
||||||
|
'pending_reshipment',
|
||||||
|
'completed',
|
||||||
|
'pending_refund',
|
||||||
|
].includes(record.orderStatus)
|
||||||
|
? [
|
||||||
|
<Divider key="divider-after-sale" type="vertical" />,
|
||||||
|
<Popconfirm
|
||||||
|
key="btn-after-sale"
|
||||||
|
title="转至售后"
|
||||||
|
description="确认转至售后?"
|
||||||
|
onConfirm={async () => {
|
||||||
|
try {
|
||||||
|
const { success, message: errMsg } =
|
||||||
|
await ordercontrollerChangestatus(
|
||||||
|
{ id: record.id },
|
||||||
|
{ status: 'after_sale_pending' },
|
||||||
|
);
|
||||||
|
if (!success) throw new Error(errMsg);
|
||||||
|
tableRef.current?.reload();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" ghost>
|
||||||
|
转至售后
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(record.orderStatus === 'after_sale_pending'
|
||||||
|
? [
|
||||||
|
<Divider key="divider-cancel" type="vertical" />,
|
||||||
|
<Popconfirm
|
||||||
|
key="btn-cancel"
|
||||||
|
title="转至取消"
|
||||||
|
description="确认转至取消?"
|
||||||
|
onConfirm={async () => {
|
||||||
|
try {
|
||||||
|
const { success, message: errMsg } =
|
||||||
|
await ordercontrollerChangestatus(
|
||||||
|
{ id: record.id },
|
||||||
|
{ status: 'cancelled' },
|
||||||
|
);
|
||||||
|
if (!success) throw new Error(errMsg);
|
||||||
|
tableRef.current?.reload();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" ghost>
|
||||||
|
转至取消
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>,
|
||||||
|
<Divider key="divider-refund" type="vertical" />,
|
||||||
|
<Popconfirm
|
||||||
|
key="btn-refund"
|
||||||
|
title="转至退款"
|
||||||
|
description="确认转至退款?"
|
||||||
|
onConfirm={async () => {
|
||||||
|
try {
|
||||||
|
const { success, message: errMsg } =
|
||||||
|
await ordercontrollerChangestatus(
|
||||||
|
{ id: record.id },
|
||||||
|
{ status: 'refund_requested' },
|
||||||
|
);
|
||||||
|
if (!success) throw new Error(errMsg);
|
||||||
|
tableRef.current?.reload();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" ghost>
|
||||||
|
转至退款
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>,
|
||||||
|
<Divider key="divider-completed" type="vertical" />,
|
||||||
|
<Popconfirm
|
||||||
|
key="btn-completed"
|
||||||
|
title="转至完成"
|
||||||
|
description="确认转至完成?"
|
||||||
|
onConfirm={async () => {
|
||||||
|
try {
|
||||||
|
const { success, message: errMsg } =
|
||||||
|
await ordercontrollerChangestatus(
|
||||||
|
{ id: record.id },
|
||||||
|
{ status: 'completed' },
|
||||||
|
);
|
||||||
|
if (!success) throw new Error(errMsg);
|
||||||
|
tableRef.current?.reload();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" ghost>
|
||||||
|
转至完成
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<ProDescriptions labelStyle={{ width: '100px' }} actionRef={ref} request={initRequest}>
|
||||||
|
<ProDescriptions.Item label="站点" dataIndex="siteId" valueType="select" request={async () => {
|
||||||
|
const { data = [] } = await sitecontrollerAll();
|
||||||
|
return data.map((item) => ({ label: item.siteName, value: item.id }));
|
||||||
|
}} />
|
||||||
|
<ProDescriptions.Item label="订单日期" dataIndex="date_created" valueType="dateTime" />
|
||||||
|
<ProDescriptions.Item label="订单状态" dataIndex="orderStatus" valueType="select" valueEnum={ORDER_STATUS_ENUM as any} />
|
||||||
|
<ProDescriptions.Item label="金额" dataIndex="total" />
|
||||||
|
<ProDescriptions.Item label="客户邮箱" dataIndex="customer_email" />
|
||||||
|
<ProDescriptions.Item label="联系电话" span={3} render={(_, r: any) => (
|
||||||
|
<div><span>{r?.shipping?.phone || r?.billing?.phone || '-'}</span></div>
|
||||||
|
)} />
|
||||||
|
<ProDescriptions.Item label="交易Id" dataIndex="transaction_id" />
|
||||||
|
<ProDescriptions.Item label="IP" dataIndex="customer_id_address" />
|
||||||
|
<ProDescriptions.Item label="设备" dataIndex="device_type" />
|
||||||
|
<ProDescriptions.Item label="来源" render={(_, r: any) => formatSource(r.source_type, r.utm_source)} />
|
||||||
|
<ProDescriptions.Item label="原订单状态" dataIndex="status" valueType="select" valueEnum={ORDER_STATUS_ENUM as any} />
|
||||||
|
<ProDescriptions.Item label="支付链接" dataIndex="payment_url" span={3} copyable />
|
||||||
|
<ProDescriptions.Item label="客户备注" dataIndex="customer_note" span={3} />
|
||||||
|
<ProDescriptions.Item label="发货信息" span={3} render={(_, r: any) => (
|
||||||
|
<div>
|
||||||
|
<div>company:<span>{r?.shipping?.company || r?.billing?.company || '-'}</span></div>
|
||||||
|
<div>first_name:<span>{r?.shipping?.first_name || r?.billing?.first_name || '-'}</span></div>
|
||||||
|
<div>last_name:<span>{r?.shipping?.last_name || r?.billing?.last_name || '-'}</span></div>
|
||||||
|
<div>country:<span>{r?.shipping?.country || r?.billing?.country || '-'}</span></div>
|
||||||
|
<div>state:<span>{r?.shipping?.state || r?.billing?.state || '-'}</span></div>
|
||||||
|
<div>city:<span>{r?.shipping?.city || r?.billing?.city || '-'}</span></div>
|
||||||
|
<div>postcode:<span>{r?.shipping?.postcode || r?.billing?.postcode || '-'}</span></div>
|
||||||
|
<div>phone:<span>{r?.shipping?.phone || r?.billing?.phone || '-'}</span></div>
|
||||||
|
<div>address_1:<span>{r?.shipping?.address_1 || r?.billing?.address_1 || '-'}</span></div>
|
||||||
|
</div>
|
||||||
|
)} />
|
||||||
|
<ProDescriptions.Item label="原始订单" span={3} render={(_, r: any) => (
|
||||||
|
<ul>
|
||||||
|
{(r?.items || []).map((item: any) => (
|
||||||
|
<li key={item.id}>{item.name}:{item.quantity}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)} />
|
||||||
|
<ProDescriptions.Item label="关联" span={3} render={(_, r: any) => (
|
||||||
|
<RelatedOrders data={r?.related} />
|
||||||
|
)} />
|
||||||
|
<ProDescriptions.Item label="订单内容" span={3} render={(_, r: any) => (
|
||||||
|
<ul>
|
||||||
|
{(r?.sales || []).map((item: any) => (
|
||||||
|
<li key={item.id}>{item.name}:{item.quantity}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)} />
|
||||||
|
<ProDescriptions.Item label="换货" span={3} render={(_, r: any) => (
|
||||||
|
<SalesChangeComponent detailRef={ref} id={r.id as number} />
|
||||||
|
)} />
|
||||||
|
<ProDescriptions.Item label="备注" span={3} render={(_, r: any) => {
|
||||||
|
if (!r.notes || r.notes.length === 0) return (<Empty description="暂无备注" />);
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
{r.notes.map((note: any) => (
|
||||||
|
<div style={{ marginBottom: 10 }} key={note.id}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<span>{note.username}</span>
|
||||||
|
<span>{note.createdAt}</span>
|
||||||
|
</div>
|
||||||
|
<div>{note.content}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}} />
|
||||||
|
<ProDescriptions.Item label="物流信息" span={3} render={(_, r: any) => {
|
||||||
|
if (!r.shipment || r.shipment.length === 0) return (<Empty description="暂无物流信息" />);
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
|
||||||
|
{r.shipment.map((v: any) => (
|
||||||
|
<Card key={v.id} style={{ marginBottom: '10px' }} extra={formatShipmentState(v.state)} title={<>
|
||||||
|
{v.tracking_provider}
|
||||||
|
{v.primary_tracking_number}
|
||||||
|
<CopyOutlined onClick={async () => {
|
||||||
|
try { await navigator.clipboard.writeText(v.tracking_url); message.success('复制成功!'); }
|
||||||
|
catch { message.error('复制失败!'); }
|
||||||
|
}} />
|
||||||
|
</>}
|
||||||
|
actions={ (v.state === 'waiting-for-scheduling' || v.state === 'waiting-for-transit') ? [
|
||||||
|
<Popconfirm key="action-cancel" title="取消运单" description="确认取消运单?" onConfirm={async () => {
|
||||||
|
try { const { success, message: errMsg } = await logisticscontrollerDelshipment({ id: v.id }); if (!success) throw new Error(errMsg); tableRef.current?.reload(); ref.current?.reload?.(); }
|
||||||
|
catch (error: any) { message.error(error.message); }
|
||||||
|
}}>
|
||||||
|
<DeleteFilled />取消运单
|
||||||
|
</Popconfirm>
|
||||||
|
] : [] }
|
||||||
|
>
|
||||||
|
<div>订单号: {Array.isArray(v?.orderIds) ? v.orderIds.join(',') : '-'}</div>
|
||||||
|
{Array.isArray(v?.items) && v.items.map((item: any) => (
|
||||||
|
<div key={item.id}>{item.name}: {item.quantity}</div>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}} />
|
||||||
|
</ProDescriptions>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrderDetailDrawer;
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Empty, Tag } from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RelatedOrders 表格组件
|
||||||
|
* 用于展示订单详情中的关联数据(订阅/订单),按统一表格样式渲染
|
||||||
|
* 中文注释:本组件将订阅与订单统一归一化为五列展示,便于快速浏览
|
||||||
|
*/
|
||||||
|
const RelatedOrders: React.FC<{ data?: any[] }> = ({ data = [] }) => {
|
||||||
|
const rows = (Array.isArray(data) ? data : []).map((it: any) => {
|
||||||
|
const isSubscription = !!it?.externalSubscriptionId || !!it?.billing_period || !!it?.line_items;
|
||||||
|
const number = isSubscription ? `#${it?.externalSubscriptionId || it?.id}` : `#${it?.externalOrderId || it?.id}`;
|
||||||
|
const relationship = isSubscription ? 'Subscription' : 'Order';
|
||||||
|
const dateRaw = it?.start_date || it?.date_created || it?.createdAt || it?.updatedAt;
|
||||||
|
const dateText = dateRaw ? dayjs(dateRaw).fromNow() : '-';
|
||||||
|
const status = (isSubscription ? it?.status : it?.orderStatus) || '-';
|
||||||
|
const statusLower = String(status).toLowerCase();
|
||||||
|
const color = statusLower === 'active' ? 'green' : statusLower === 'cancelled' ? 'red' : 'default';
|
||||||
|
const totalNum = Number(it?.total || 0);
|
||||||
|
const totalText = isSubscription ? `$${totalNum.toFixed(2)} / ${it?.billing_period || 'period'}` : `$${totalNum.toFixed(2)}`;
|
||||||
|
return { key: `${isSubscription ? 'sub' : 'order'}-${it?.id}`, number, relationship, dateText, status, color, totalText };
|
||||||
|
});
|
||||||
|
|
||||||
|
if (rows.length === 0) return <Empty description="暂无关联" />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
{/* 表头(英文文案,符合国际化默认英文的要求) */}
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr 1fr 1fr 1fr', padding: '8px 0', fontWeight: 600 }}>
|
||||||
|
<div>订单编号</div>
|
||||||
|
<div>关系</div>
|
||||||
|
<div>日期</div>
|
||||||
|
<div>状态</div>
|
||||||
|
<div>金额</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{rows.map((r) => (
|
||||||
|
<div key={r.key} style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr 1fr 1fr 1fr', padding: '6px 0', borderTop: '1px solid #f0f0f0' }}>
|
||||||
|
<div><a>{r.number}</a></div>
|
||||||
|
<div>{r.relationship}</div>
|
||||||
|
<div style={{ color: '#1677ff' }}>{r.dateText}</div>
|
||||||
|
<div><Tag color={r.color}>{r.status}</Tag></div>
|
||||||
|
<div>{r.totalText}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RelatedOrders;
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
|
import type { ProColumns, ActionType, ProTableProps } from '@ant-design/pro-components';
|
||||||
|
import { ProTable } from '@ant-design/pro-components';
|
||||||
|
import { App, Tag, Button } from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { ordercontrollerGetorders } from '@/servers/api/order';
|
||||||
|
import OrderDetailDrawer from './OrderDetailDrawer';
|
||||||
|
import { sitecontrollerAll } from '@/servers/api/site';
|
||||||
|
|
||||||
|
interface OrderItemRow {
|
||||||
|
id: number;
|
||||||
|
externalOrderId: string;
|
||||||
|
siteId: string;
|
||||||
|
date_created: string;
|
||||||
|
customer_email: string;
|
||||||
|
payment_method: string;
|
||||||
|
total: number;
|
||||||
|
orderStatus: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrdersPage: React.FC = () => {
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
const { message } = App.useApp();
|
||||||
|
// 抽屉状态:改为复用订单详情抽屉组件
|
||||||
|
const [detailOpen, setDetailOpen] = useState(false);
|
||||||
|
const [detailRecord, setDetailRecord] = useState<any | null>(null);
|
||||||
|
const [detailOrderId, setDetailOrderId] = useState<number | null>(null);
|
||||||
|
const Noop: React.FC<any> = () => null;
|
||||||
|
|
||||||
|
const columns: ProColumns<OrderItemRow>[] = [
|
||||||
|
{
|
||||||
|
title: '订单ID',
|
||||||
|
dataIndex: 'externalOrderId',
|
||||||
|
width: 120,
|
||||||
|
ellipsis: true,
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '站点',
|
||||||
|
dataIndex: 'siteId',
|
||||||
|
width: 120,
|
||||||
|
valueType: 'select',
|
||||||
|
request: async () => {
|
||||||
|
const { data = [] } = await sitecontrollerAll();
|
||||||
|
return (data || []).map((item: any) => ({ label: item.siteName, value: item.id }));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '下单时间',
|
||||||
|
dataIndex: 'date_created',
|
||||||
|
width: 180,
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, row) => (row?.date_created ? dayjs(row.date_created).format('YYYY-MM-DD HH:mm') : '-'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '邮箱',
|
||||||
|
dataIndex: 'customer_email',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '支付方式',
|
||||||
|
dataIndex: 'payment_method',
|
||||||
|
width: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '金额',
|
||||||
|
dataIndex: 'total',
|
||||||
|
width: 100,
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ERP状态',
|
||||||
|
dataIndex: 'orderStatus',
|
||||||
|
width: 120,
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, row) => <Tag>{row.orderStatus}</Tag>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '订阅关联',
|
||||||
|
dataIndex: 'subscription_related',
|
||||||
|
width: 120,
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, row) => (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setDetailRecord(row as any);
|
||||||
|
setDetailOrderId(row.id as number);
|
||||||
|
setDetailOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
查看
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '时间范围',
|
||||||
|
dataIndex: 'dateRange',
|
||||||
|
valueType: 'dateRange',
|
||||||
|
hideInTable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '商品关键字',
|
||||||
|
dataIndex: 'keyword',
|
||||||
|
hideInTable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const request: ProTableProps<OrderItemRow>['request'] = async (params) => {
|
||||||
|
try {
|
||||||
|
const { current = 1, pageSize = 10, siteId, keyword, customer_email, payment_method } = params as any;
|
||||||
|
const [startDate, endDate] = (params as any).dateRange || [];
|
||||||
|
const resp = await ordercontrollerGetorders({
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
siteId,
|
||||||
|
keyword,
|
||||||
|
customer_email,
|
||||||
|
payment_method,
|
||||||
|
isSubscriptionOnly: true as any,
|
||||||
|
startDate: startDate ? (dayjs(startDate).toISOString() as any) : undefined,
|
||||||
|
endDate: endDate ? (dayjs(endDate).toISOString() as any) : undefined,
|
||||||
|
} as any);
|
||||||
|
const { success, data, message: errMsg } = resp as any;
|
||||||
|
if (!success) throw new Error(errMsg || '获取失败');
|
||||||
|
return {
|
||||||
|
data: (data?.items ?? []) as OrderItemRow[],
|
||||||
|
total: data?.total ?? 0,
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e?.message || '获取失败');
|
||||||
|
return { data: [], total: 0, success: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer title='订阅订单'>
|
||||||
|
<ProTable<OrderItemRow>
|
||||||
|
actionRef={actionRef}
|
||||||
|
rowKey='id'
|
||||||
|
columns={columns}
|
||||||
|
request={request}
|
||||||
|
pagination={{ showSizeChanger: true }}
|
||||||
|
search={{
|
||||||
|
labelWidth: 90,
|
||||||
|
span: 6,
|
||||||
|
}}
|
||||||
|
toolBarRender={false}
|
||||||
|
/>
|
||||||
|
{/* 订阅关联:直接使用订单详情抽屉组件 */}
|
||||||
|
{detailRecord && detailOrderId !== null && (
|
||||||
|
<OrderDetailDrawer
|
||||||
|
open={detailOpen}
|
||||||
|
onClose={() => setDetailOpen(false)}
|
||||||
|
tableRef={actionRef}
|
||||||
|
orderId={detailOrderId as number}
|
||||||
|
record={detailRecord as any}
|
||||||
|
setActiveLine={() => {}}
|
||||||
|
OrderNoteComponent={Noop}
|
||||||
|
SalesChangeComponent={Noop}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrdersPage;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// API 更新时间:
|
// API 更新时间:
|
||||||
// API 唯一标识:
|
// API 唯一标识:
|
||||||
import * as customer from './customer';
|
import * as customer from './customer';
|
||||||
import * as logistics from './logistics';
|
import * as logistics from './logistics';
|
||||||
import * as order from './order';
|
import * as order from './order';
|
||||||
|
|
@ -9,6 +9,7 @@ import * as product from './product';
|
||||||
import * as site from './site';
|
import * as site from './site';
|
||||||
import * as statistics from './statistics';
|
import * as statistics from './statistics';
|
||||||
import * as stock from './stock';
|
import * as stock from './stock';
|
||||||
|
import * as subscription from './subscription';
|
||||||
import * as user from './user';
|
import * as user from './user';
|
||||||
import * as webhook from './webhook';
|
import * as webhook from './webhook';
|
||||||
import * as wpProduct from './wpProduct';
|
import * as wpProduct from './wpProduct';
|
||||||
|
|
@ -20,6 +21,7 @@ export default {
|
||||||
site,
|
site,
|
||||||
statistics,
|
statistics,
|
||||||
stock,
|
stock,
|
||||||
|
subscription,
|
||||||
user,
|
user,
|
||||||
webhook,
|
webhook,
|
||||||
wpProduct,
|
wpProduct,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// @ts-ignore
|
||||||
|
/* eslint-disable */
|
||||||
|
import { request } from 'umi';
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 GET /subscription/list */
|
||||||
|
export async function subscriptioncontrollerList(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.subscriptioncontrollerListParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
return request<API.SubscriptionListRes>('/subscription/list', {
|
||||||
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 此处后端没有提供注释 POST /subscription/sync/${param0} */
|
||||||
|
export async function subscriptioncontrollerSync(
|
||||||
|
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
|
||||||
|
params: API.subscriptioncontrollerSyncParams,
|
||||||
|
options?: { [key: string]: any },
|
||||||
|
) {
|
||||||
|
const { siteId: param0, ...queryParams } = params;
|
||||||
|
return request<API.BooleanRes>(`/subscription/sync/${param0}`, {
|
||||||
|
method: 'POST',
|
||||||
|
params: { ...queryParams },
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -334,9 +334,9 @@ declare namespace API {
|
||||||
| 'after_sale_pending'
|
| 'after_sale_pending'
|
||||||
| 'pending_reshipment'
|
| 'pending_reshipment'
|
||||||
| 'pending_refund'
|
| 'pending_refund'
|
||||||
| 'refund_requested'
|
| 'return-requested'
|
||||||
| 'refund_approved'
|
| 'return-approved'
|
||||||
| 'refund_cancelled';
|
| 'return-cancelled';
|
||||||
payment_method?: string;
|
payment_method?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -863,9 +863,9 @@ declare namespace API {
|
||||||
| 'after_sale_pending'
|
| 'after_sale_pending'
|
||||||
| 'pending_reshipment'
|
| 'pending_reshipment'
|
||||||
| 'pending_refund'
|
| 'pending_refund'
|
||||||
| 'refund_requested'
|
| 'return-requested'
|
||||||
| 'refund_approved'
|
| 'return-approved'
|
||||||
| 'refund_cancelled';
|
| 'return-cancelled';
|
||||||
payment_method?: string;
|
payment_method?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -948,6 +948,27 @@ declare namespace API {
|
||||||
name?: string;
|
name?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type QuerySubscriptionDTO = {
|
||||||
|
/** 页码 */
|
||||||
|
current?: number;
|
||||||
|
/** 每页大小 */
|
||||||
|
pageSize?: number;
|
||||||
|
/** 站点ID */
|
||||||
|
siteId?: string;
|
||||||
|
/** 订阅状态 */
|
||||||
|
status?:
|
||||||
|
| 'active'
|
||||||
|
| 'pending'
|
||||||
|
| 'on-hold'
|
||||||
|
| 'cancelled'
|
||||||
|
| 'expired'
|
||||||
|
| 'pending-cancel';
|
||||||
|
/** 客户邮箱 */
|
||||||
|
customer_email?: string;
|
||||||
|
/** 关键字(订阅ID、邮箱等) */
|
||||||
|
keyword?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type QueryWpProductDTO = {
|
type QueryWpProductDTO = {
|
||||||
/** 页码 */
|
/** 页码 */
|
||||||
current?: number;
|
current?: number;
|
||||||
|
|
@ -1309,6 +1330,82 @@ declare namespace API {
|
||||||
items?: StockRecordDTO[];
|
items?: StockRecordDTO[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Subscription = {
|
||||||
|
id?: number;
|
||||||
|
/** 来源站点唯一标识 */
|
||||||
|
siteId?: string;
|
||||||
|
/** WooCommerce 订阅 ID */
|
||||||
|
externalSubscriptionId?: string;
|
||||||
|
status?: any;
|
||||||
|
currency?: string;
|
||||||
|
total?: number;
|
||||||
|
/** 计费周期 e.g. day/week/month/year */
|
||||||
|
billing_period?: string;
|
||||||
|
/** 计费周期间隔 e.g. 1/3/12 */
|
||||||
|
billing_interval?: number;
|
||||||
|
customer_id?: number;
|
||||||
|
customer_email?: string;
|
||||||
|
/** 父订单/父订阅ID(如有) */
|
||||||
|
parent_id?: number;
|
||||||
|
start_date?: string;
|
||||||
|
trial_end?: string;
|
||||||
|
next_payment_date?: string;
|
||||||
|
end_date?: string;
|
||||||
|
line_items?: any;
|
||||||
|
meta_data?: any;
|
||||||
|
/** 创建时间 */
|
||||||
|
createdAt: string;
|
||||||
|
/** 更新时间 */
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type subscriptioncontrollerListParams = {
|
||||||
|
/** 页码 */
|
||||||
|
current?: number;
|
||||||
|
/** 每页大小 */
|
||||||
|
pageSize?: number;
|
||||||
|
/** 站点ID */
|
||||||
|
siteId?: string;
|
||||||
|
/** 订阅状态 */
|
||||||
|
status?:
|
||||||
|
| 'active'
|
||||||
|
| 'pending'
|
||||||
|
| 'on-hold'
|
||||||
|
| 'cancelled'
|
||||||
|
| 'expired'
|
||||||
|
| 'pending-cancel';
|
||||||
|
/** 客户邮箱 */
|
||||||
|
customer_email?: string;
|
||||||
|
/** 关键字(订阅ID、邮箱等) */
|
||||||
|
keyword?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type subscriptioncontrollerSyncParams = {
|
||||||
|
siteId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SubscriptionListRes = {
|
||||||
|
/** 状态码 */
|
||||||
|
code?: number;
|
||||||
|
/** 是否成功 */
|
||||||
|
success?: boolean;
|
||||||
|
/** 消息内容 */
|
||||||
|
message?: string;
|
||||||
|
/** 响应数据 */
|
||||||
|
data?: SubscriptionPaginatedResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SubscriptionPaginatedResponse = {
|
||||||
|
/** 当前页码 */
|
||||||
|
page?: number;
|
||||||
|
/** 每页大小 */
|
||||||
|
pageSize?: number;
|
||||||
|
/** 总记录数 */
|
||||||
|
total?: number;
|
||||||
|
/** 数据列表 */
|
||||||
|
items?: Subscription[];
|
||||||
|
};
|
||||||
|
|
||||||
type Surcharges = {
|
type Surcharges = {
|
||||||
type?: string;
|
type?: string;
|
||||||
amount?: Money;
|
amount?: Money;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ declare namespace BaseType {
|
||||||
type EnumTransformOptions {
|
type EnumTransformOptions {
|
||||||
value: string; // 用于作为 value 的字段名
|
value: string; // 用于作为 value 的字段名
|
||||||
label: string; // 用于作为 text 的字段名
|
label: string; // 用于作为 text 的字段名
|
||||||
status?: string | undefined; // 可选:用于设置状态的字段名
|
status?: string | undefined; // 可选:用于设置状态的字段名
|
||||||
color?: string | undefined; // 可选:用于设置颜色的字段名
|
color?: string | undefined; // 可选:用于设置颜色的字段名
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11702
yarn.lock1
11702
yarn.lock1
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue