v3.1 + 批次0: 智能回复重构基线 - ApprovalMatcher + 关键词降级 + 文档速修 + v4.0任务书面化
This commit is contained in:
+247
-476
@@ -1,550 +1,322 @@
|
||||
# 系统设计文档 — 代办事项真实数据源集成
|
||||
# 系统架构设计 — 历史会话开关功能
|
||||
|
||||
> 项目:企微IT智能服务台 — 代办面板去mock改造
|
||||
> 版本:v1.0
|
||||
> 日期:2026-07-11
|
||||
> 架构师:高见远(Bob)
|
||||
> 日期:2026-07-01
|
||||
> 基于 PRD v1.0 + 现有代码结构分析
|
||||
|
||||
---
|
||||
|
||||
## Part A: 系统设计
|
||||
|
||||
### 1. 实现方案
|
||||
### 1. 实现方案 + 框架选型
|
||||
|
||||
#### 1.1 核心技术挑战
|
||||
|
||||
| 挑战 | 方案 |
|
||||
|------|------|
|
||||
| 两个异构数据源(企微审批 + ITSM 工单)聚合为统一列表 | 引入 `TodoAggregatorService`,通过抽象接口 `TodoSourceService` 统一两个数据源,`asyncio.gather` 并行查询,`return_exceptions=True` 容错 |
|
||||
| 企微审批需 getapprovaldata → getapprovaldetail 二次过滤当前审批人,性能开销大 | 先用 `getapprovaldata` 按 sp_status=1 + 18 个模板批量获取 sp_no_list,再 `asyncio.gather` 并发调用 `getapprovaldetail`,最后用 `_extract_current_approver` 过滤 |
|
||||
| ITSM 列表 API 尚未获取,签名认证方式特殊 | 设计 `ITSMService` 为可插拔实现,签名计算抽为独立工具 `itsm_signer.py`,列表方法暂返回空列表 + 日志告警,待 API 到位后填充 |
|
||||
| Redis 缓存需区分不同坐席 | 缓存 key 设计:`todo:cache:{agent_userid}:{type_filter}`,TTL 45 秒 |
|
||||
| 彻底去 mock(后端 MOCK_TODO_ITEMS + 前端 mockTodoListData + device 类型) | 全量删除 mock 数据,schema 中 `VALID_TODO_TYPES` 移除 device,前端移除 DeviceDetail 引用 |
|
||||
| 挑战 | 说明 | 方案 |
|
||||
|------|------|------|
|
||||
| 跨会话消息聚合 | 需要将同一员工的所有会话消息合并为一条时间线,按时间排序 | 后端新增按 `employee_id` 聚合查询的接口,JOIN conversations + messages 表,按 `created_at` 全局排序 |
|
||||
| 分隔条主题提取 | 分隔条显示该会话中员工首条消息摘要(前20字),不新增数据库字段 | 后端在聚合查询时,对每个会话查找 `sender_type='employee'` 的最早一条消息,截取前20字作为 `conversation_summaries` 返回 |
|
||||
| 游标分页(跨会话) | 向上滚动加载更多历史消息,需跨会话游标分页 | 使用 `before` 参数(消息ID),后端根据该消息的 `created_at` 查询更早的消息,全局时间线分页 |
|
||||
| 模式切换无闪烁 | 开关切换时正常模式↔历史模式,消息列表无缝切换 | Store 新增 `displayMessages` computed,根据 `historyMode` 返回不同数据源;前端 `v-if` 切换加载态 |
|
||||
| 历史模式只读 | 历史模式下隐藏输入框、回复建议区 | ChatArea 中用 `historyMode` 控制 `ReplyBox` / `ReplySuggestArea` 的 `v-if` |
|
||||
| 会话切换自动重置 | 切换会话时关闭历史模式 | Store 的 `selectConversation()` 中调用 `resetHistoryState()` |
|
||||
|
||||
#### 1.2 框架与库选择
|
||||
#### 1.2 框架与库选型
|
||||
|
||||
| 库 | 用途 | 说明 |
|
||||
| 层 | 技术 | 说明 |
|
||||
|----|------|------|
|
||||
| `httpx` | 异步 HTTP 客户端 | 已用于 approval.py,复用 |
|
||||
| `redis.asyncio` | Redis 异步缓存 | 已用于 token 管理,复用 |
|
||||
| `asyncio` | 并发查询两个数据源 | Python 标准库,`asyncio.gather(return_exceptions=True)` |
|
||||
| `hashlib` | ITSM SHA1 签名 | Python 标准库 |
|
||||
| `urllib.parse.quote_plus` | ITSM 签名 URL 编码 | Python 标准库 |
|
||||
| 后端 | FastAPI + SQLAlchemy 2.0 (async) | 沿用现有技术栈,新增一个 GET 接口 |
|
||||
| 前端 | Vue 3 + Pinia + Element Plus | 沿用现有技术栈,新增一个组件 + Store 扩展 |
|
||||
| 分页 | 游标分页(`before` 参数) | 与现有 `getMessages()` 的分页方式一致,前端向上滚动触发 |
|
||||
|
||||
**结论:无需新增任何第三方依赖。**
|
||||
#### 1.3 后端新接口设计
|
||||
|
||||
#### 1.3 架构模式
|
||||
**`GET /api/employees/{employee_id}/history-messages`**
|
||||
|
||||
采用 **Service Layer + 策略模式**:
|
||||
| 参数 | 类型 | 默认 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `employee_id` | path (str) | — | 员工企微 UserID |
|
||||
| `limit` | query (int) | 50 | 每页消息数量(1~100) |
|
||||
| `before` | query (str?) | null | 游标:加载此消息ID之前的消息(向上翻页) |
|
||||
| `current_conversation_id` | query (str?) | null | 当前会话ID(用于标记当前会话的分隔条) |
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ API Layer (todo_items.py) │
|
||||
│ GET /api/todo-items │
|
||||
│ GET /api/todo-items/{id} │
|
||||
└──────────────────────┬──────────────────────────────────┘
|
||||
│ 调用
|
||||
┌──────────────────────▼──────────────────────────────────┐
|
||||
│ TodoAggregatorService │
|
||||
│ ┌─ Redis 缓存检查 ─────────────────────────────────┐ │
|
||||
│ │ 命中 → 直接返回 │ │
|
||||
│ │ 未命中 → asyncio.gather 并行查询 │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ ┌──────────────┬──────────────┐ │
|
||||
│ │ │ │ │
|
||||
│ ┌──────▼──────┐ ┌────▼─────────┐ │ │
|
||||
│ │ApprovalTodo │ │ ITSMService │ │ │
|
||||
│ │ Service │ │ (abstract) │ │ │
|
||||
│ └──────┬──────┘ └────┬─────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ ┌──────▼──────┐ ┌────▼─────────┐ │ │
|
||||
│ │ 企微审批API │ │ ITSM API │ │ │
|
||||
│ │ getapproval │ │ (待实现) │ │ │
|
||||
│ │ data/detail│ │ │ │ │
|
||||
│ └─────────────┘ └──────────────┘ │ │
|
||||
└──────────────────────────────────────┘
|
||||
**响应体:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"items": [ /* Message[] — 按时间倒序(最新在前),前端 reverse 后展示 */ ],
|
||||
"has_more": true,
|
||||
"conversation_summaries": {
|
||||
"conv-uuid-1": "VPN连接不上怎么办急",
|
||||
"conv-uuid-2": "邮箱登录失败提示密码"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.4 后端改造方案
|
||||
**后端查询逻辑:**
|
||||
1. 查询 `conversations` 表中 `employee_id = ?` 的所有会话,获取会话ID列表
|
||||
2. 查询 `messages` 表中 `conversation_id IN (会话ID列表)` 的消息
|
||||
3. 如有 `before` 参数,获取该消息的 `created_at`,只查更早的消息
|
||||
4. 按 `created_at DESC` 排序,取 `limit + 1` 条(多取1条判断 `has_more`)
|
||||
5. 对涉及的每个会话,查询其 `sender_type='employee'` 的最早一条消息,取前20字作为摘要
|
||||
6. 返回消息列表 + `has_more` + `conversation_summaries`
|
||||
|
||||
1. **新增 Service 层**(3 个文件):
|
||||
- `TodoSourceService`(抽象基类)+ `ApprovalTodoService`(企微审批实现)
|
||||
- `ITSMService`(ITSM 工单实现,列表方法待 API 到位)
|
||||
- `TodoAggregatorService`(聚合服务,缓存 + 并行查询 + 容错)
|
||||
#### 1.4 前端组件改动方案
|
||||
|
||||
2. **新增 ITSM 签名工具**:`itsm_signer.py`,独立封装 SHA1 签名计算
|
||||
| 文件 | 改动类型 | 改动概述 |
|
||||
|------|----------|----------|
|
||||
| `UserInfoBar.vue` | 修改 | chips 区末尾(L86 备注chip之后)新增"历史会话"开关按钮,三态视觉(关闭/打开/加载中),新增 `toggle-history` emit |
|
||||
| `ChatArea.vue` | 修改 | 消息列表使用 `displayMessages` 替代 `messages`;渲染时插入 `ConversationSeparator`;历史模式隐藏 `ReplyBox`/`ReplySuggestArea`;监听向上滚动触发分页 |
|
||||
| `ConversationSeparator.vue` | **新增** | 会话分隔条组件,props: `summary`(首条消息摘要前20字)、`isCurrent`(是否当前会话) |
|
||||
| `conversation.ts` (Store) | 修改 | 新增历史模式状态(6个 ref + 2个 computed + 4个 action) |
|
||||
| `message.ts` (API) | 修改 | 新增 `getHistoryMessages()` 函数 + `HistoryMessageListData` 类型 |
|
||||
| `data.ts` (Mock) | 修改 | 新增 mock 历史消息数据(开发环境 fallback) |
|
||||
|
||||
3. **重写 `todo_items.py`**:删除 `MOCK_TODO_ITEMS`,调用 `TodoAggregatorService` 获取真实数据
|
||||
#### 1.5 状态管理方案(Store 新增)
|
||||
|
||||
4. **补充 `approval.py`**:新增 `get_approval_data()` 函数调用企微 `getapprovaldata` API
|
||||
**新增 State(6个 ref):**
|
||||
```typescript
|
||||
historyMode: ref<boolean>(false) // 历史模式开关
|
||||
historyMessages: ref<Message[]>([]) // 历史合并时间线消息
|
||||
historyLoading: ref<boolean>(false) // 加载中状态
|
||||
historyHasMore: ref<boolean>(false) // 是否还有更多历史消息
|
||||
historyConversationSummaries: ref<Record<string, string>>({}) // 会话ID→首条消息摘要
|
||||
historyCursor: ref<string | null>(null) // 分页游标(最后加载的消息ID)
|
||||
```
|
||||
|
||||
5. **更新 `config.py`**:新增 `itsm_app_id`、`itsm_app_secret`、`itsm_base_url` 配置项
|
||||
**新增 Getters(2个 computed):**
|
||||
```typescript
|
||||
displayMessages // historyMode ? historyMessages : messages
|
||||
isHistoryReadonly // historyMode(历史模式只读)
|
||||
```
|
||||
|
||||
6. **更新 `schemas/todo_item.py`**:`VALID_TODO_TYPES` 移除 `device`
|
||||
|
||||
#### 1.5 前端改造方案
|
||||
|
||||
1. **`api/todo.ts`**:类型定义移除 `device`,新增 `type` 查询参数
|
||||
2. **`stores/todo.ts`**:删除 `mockTodoListData` import 和 catch 块 mock fallback
|
||||
3. **`mock/data.ts`**:删除 `mockTodoListData` 导出
|
||||
4. **`TodoPanel.vue`**:新增类型筛选 Tab(全部/审批/工单)+ 手动刷新按钮 + 移除 device 类型样式
|
||||
5. **`TaskDetailView.vue`**:移除 DeviceDetail 引用
|
||||
6. **`DeviceDetail.vue`**:标记为废弃(保留文件但不再引用)
|
||||
**新增 Actions(4个):**
|
||||
```typescript
|
||||
enableHistoryMode() // 打开历史模式,加载初始消息
|
||||
disableHistoryMode() // 关闭历史模式,清空历史状态
|
||||
loadMoreHistory() // 向上滚动加载更多(分页)
|
||||
resetHistoryState() // 重置所有历史状态(切换会话时调用)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 文件列表
|
||||
### 2. 文件列表及相对路径
|
||||
|
||||
#### 后端
|
||||
|
||||
| 文件路径 | 操作 | 说明 |
|
||||
|----------|------|------|
|
||||
| `backend/app/config.py` | 修改 | 新增 ITSM 配置项 |
|
||||
| `backend/app/schemas/todo_item.py` | 修改 | VALID_TODO_TYPES 移除 device |
|
||||
| `backend/app/models/todo_item.py` | 修改 | type 注释移除 device |
|
||||
| `backend/app/services/todo_source_service.py` | 新建 | 抽象基类 + ApprovalTodoService |
|
||||
| `backend/app/services/itsm_service.py` | 新建 | ITSM Service(签名认证 + 可插拔列表) |
|
||||
| `backend/app/services/todo_aggregator_service.py` | 新建 | 聚合服务(缓存 + 并行 + 容错) |
|
||||
| `backend/app/utils/itsm_signer.py` | 新建 | ITSM SHA1 签名工具 |
|
||||
| `backend/app/api/todo_items.py` | 修改 | 删除 mock,重写为真实数据聚合 |
|
||||
| `backend/app/api/approval.py` | 修改 | 新增 get_approval_data() |
|
||||
|
||||
#### 前端
|
||||
|
||||
| 文件路径 | 操作 | 说明 |
|
||||
|----------|------|------|
|
||||
| `frontend-agent/src/api/todo.ts` | 修改 | 移除 device 类型,新增 type 参数 |
|
||||
| `frontend-agent/src/stores/todo.ts` | 修改 | 移除 mock fallback |
|
||||
| `frontend-agent/src/mock/data.ts` | 修改 | 删除 mockTodoListData |
|
||||
| `frontend-agent/src/components/conversation/TodoPanel.vue` | 修改 | 新增 Tab + 刷新 + 移除 device |
|
||||
| `frontend-agent/src/components/chat/TaskDetailView.vue` | 修改 | 移除 DeviceDetail 引用 |
|
||||
| `frontend-agent/src/components/chat/task/DeviceDetail.vue` | 修改 | 废弃标记 |
|
||||
| `frontend-agent/src/components/chat/task/TicketDetail.vue` | 修改 | 适配真实 ITSM 数据结构 |
|
||||
| `frontend-agent/src/components/chat/task/ApprovalDetail.vue` | 修改 | 适配真实企微审批数据结构 |
|
||||
| # | 文件路径 | 改动类型 | 改动概述 |
|
||||
|---|----------|----------|----------|
|
||||
| 1 | `backend/app/api/messages.py` | 修改 | 新增 `GET /employees/{employee_id}/history-messages` 路由处理函数 |
|
||||
| 2 | `backend/app/services/conversation/session_query_service.py` | 修改 | 新增 `get_employee_history_messages()` 方法 |
|
||||
| 3 | `backend/app/schemas/message.py` | 修改 | 新增 `HistoryMessageListResponse` Pydantic Schema |
|
||||
| 4 | `frontend-agent/src/api/message.ts` | 修改 | 新增 `getHistoryMessages()` API 函数 + `HistoryMessageListData` 接口 |
|
||||
| 5 | `frontend-agent/src/stores/conversation.ts` | 修改 | 新增历史模式 state/getters/actions,修改 `selectConversation` 加入重置逻辑 |
|
||||
| 6 | `frontend-agent/src/mock/data.ts` | 修改 | 新增 `mockHistoryMessageData` mock 数据(开发 fallback) |
|
||||
| 7 | `frontend-agent/src/components/chat/UserInfoBar.vue` | 修改 | chips 区末尾新增历史开关按钮 + `toggle-history` emit + 三态样式 |
|
||||
| 8 | `frontend-agent/src/components/chat/ConversationSeparator.vue` | **新增** | 会话分隔条组件 |
|
||||
| 9 | `frontend-agent/src/components/chat/ChatArea.vue` | 修改 | 消息列表切换、分隔条插入、只读模式、滚动分页、空状态提示 |
|
||||
|
||||
---
|
||||
|
||||
### 3. 数据结构和接口(类图)
|
||||
### 3. 数据结构和接口
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class TodoSourceService {
|
||||
<<abstract>>
|
||||
+agent_userid: str
|
||||
+redis: aioredis.Redis
|
||||
+get_todo_list() List~TodoItemData~*
|
||||
+get_todo_detail(item_id: str) TodoItemData*
|
||||
}
|
||||
#### 3.1 类图
|
||||
|
||||
class ApprovalTodoService {
|
||||
-redis: aioredis.Redis
|
||||
-agent_userid: str
|
||||
+get_todo_list() List~TodoItemData~
|
||||
+get_todo_detail(sp_no: str) TodoItemData
|
||||
-_fetch_approval_sp_no_list() List~str~
|
||||
-_fetch_approval_details(sp_no_list: List~str~) List~dict~
|
||||
-_filter_by_current_approver(details: List~dict~) List~dict~
|
||||
-_map_to_todo_item(detail: dict) TodoItemData
|
||||
}
|
||||
> 详见 `docs/class-diagram.mermaid`
|
||||
|
||||
class ITSMService {
|
||||
-base_url: str
|
||||
-app_id: str
|
||||
-app_secret: str
|
||||
-redis: aioredis.Redis
|
||||
-agent_userid: str
|
||||
+get_todo_list() List~TodoItemData~
|
||||
+get_todo_detail(workitem_id: str) TodoItemData
|
||||
-_do_post(url: str, body: dict) dict
|
||||
-_get_workitem_detail(process_instance_id: int, executor: str) dict
|
||||
}
|
||||
#### 3.2 后端 Schema(Pydantic)
|
||||
|
||||
class ITSMSigner {
|
||||
+compute_signature(app_id: str, timestamp: str, app_secret: str, biz_data: dict) str
|
||||
-_get_signature_compatible(params: dict) str
|
||||
}
|
||||
```python
|
||||
# backend/app/schemas/message.py — 新增
|
||||
|
||||
class TodoAggregatorService {
|
||||
-redis: aioredis.Redis
|
||||
-cache_ttl: int
|
||||
+get_todo_list(agent_userid: str, todo_type: Optional~str~) dict
|
||||
+get_todo_detail(agent_userid: str, item_id: str, todo_type: str) dict
|
||||
-_get_from_cache(agent_userid: str, todo_type: Optional~str~) Optional~dict~
|
||||
-_set_to_cache(agent_userid: str, todo_type: Optional~str~, data: dict) void
|
||||
-_invalidate_cache(agent_userid: str) void
|
||||
}
|
||||
class ConversationSummary(BaseModel):
|
||||
"""会话分隔条摘要信息"""
|
||||
conversation_id: str
|
||||
summary: str # 员工首条消息前20字
|
||||
status: str # 会话状态
|
||||
created_at: datetime # 会话创建时间
|
||||
|
||||
class TodoItemData {
|
||||
+id: str
|
||||
+type: str
|
||||
+title: str
|
||||
+priority: str
|
||||
+description: dict
|
||||
+status: str
|
||||
+assigned_agent_id: Optional~str~
|
||||
+corp_id: str
|
||||
+created_at: str
|
||||
+updated_at: str
|
||||
}
|
||||
class HistoryMessageListResponse(BaseModel):
|
||||
"""历史消息列表响应(跨会话聚合)"""
|
||||
items: List[MessageResponse] # 消息列表(按时间倒序)
|
||||
has_more: bool # 是否还有更多
|
||||
conversation_summaries: Dict[str, str] # {conversation_id: "前20字摘要"}
|
||||
```
|
||||
|
||||
TodoSourceService <|-- ApprovalTodoService
|
||||
TodoSourceService <|-- ITSMService
|
||||
TodoAggregatorService o-- ApprovalTodoService : creates
|
||||
TodoAggregatorService o-- ITSMService : creates
|
||||
ITSMService --> ITSMSigner : uses
|
||||
TodoSourceService ..> TodoItemData : returns
|
||||
#### 3.3 前端 TypeScript 类型定义
|
||||
|
||||
```typescript
|
||||
// frontend-agent/src/api/message.ts — 新增
|
||||
|
||||
/** 历史消息列表响应(跨会话聚合) */
|
||||
export interface HistoryMessageListData {
|
||||
/** 消息列表(按时间倒序,最新在前) */
|
||||
items: Message[]
|
||||
/** 是否还有更多历史消息 */
|
||||
has_more: boolean
|
||||
/** 会话ID → 首条消息摘要(前20字) */
|
||||
conversation_summaries: Record<string, string>
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// frontend-agent/src/components/chat/ConversationSeparator.vue — Props
|
||||
|
||||
interface ConversationSeparatorProps {
|
||||
/** 分隔条显示文本(首条消息摘要前20字) */
|
||||
summary: string
|
||||
/** 是否为当前会话(当前会话高亮显示) */
|
||||
isCurrent: boolean
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 程序调用流程(时序图)
|
||||
|
||||
#### 4.1 代办列表查询流程
|
||||
> 详见 `docs/sequence-diagram.mermaid`
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant FE as 前端 TodoPanel
|
||||
participant API as GET /api/todo-items
|
||||
participant Agg as TodoAggregatorService
|
||||
participant Cache as Redis Cache
|
||||
participant Appr as ApprovalTodoService
|
||||
participant Wecom as 企微审批API
|
||||
participant ITSM as ITSMService
|
||||
participant ITSM_API as ITSM OpenAPI
|
||||
**核心流程:**
|
||||
|
||||
FE->>API: GET /api/todo-items?type=approval
|
||||
API->>Agg: get_todo_list(agent_userid, type="approval")
|
||||
Agg->>Cache: GET todo:cache:{userid}:approval
|
||||
alt 缓存命中
|
||||
Cache-->>Agg: cached_data
|
||||
Agg-->>API: {items, total, cached:true}
|
||||
else 缓存未命中
|
||||
Agg->>Agg: asyncio.gather(approval_svc, itsm_svc, return_exceptions=True)
|
||||
par 并行查询审批
|
||||
Agg->>Appr: get_todo_list()
|
||||
Appr->>Wecom: getapprovaldata(sp_status=1, templates=18)
|
||||
Wecom-->>Appr: sp_no_list
|
||||
Appr->>Appr: asyncio.gather(getapprovaldetail × N)
|
||||
loop 每个sp_no并发获取详情
|
||||
Appr->>Wecom: getapprovaldetail(sp_no)
|
||||
Wecom-->>Appr: approval_detail
|
||||
end
|
||||
Appr->>Appr: _filter_by_current_approver(details)
|
||||
Appr->>Appr: _map_to_todo_item(detail)
|
||||
Appr-->>Agg: List[TodoItemData]
|
||||
and 并行查询工单
|
||||
Agg->>ITSM: get_todo_list()
|
||||
Note over ITSM: ITSM列表API待实现<br/>当前返回空列表+日志告警
|
||||
ITSM-->>Agg: List[TodoItemData] (空)
|
||||
end
|
||||
Agg->>Agg: merge + sort by priority
|
||||
Agg->>Cache: SET todo:cache:{userid}:approval TTL=45s
|
||||
Agg-->>API: {items, total, cached:false}
|
||||
end
|
||||
API-->>FE: {code:0, data:{items, total}}
|
||||
```
|
||||
|
||||
#### 4.2 代办详情查询流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant FE as 前端 TaskDetailView
|
||||
participant API as GET /api/todo-items/{id}
|
||||
participant Agg as TodoAggregatorService
|
||||
participant Appr as ApprovalTodoService
|
||||
participant ITSM as ITSMService
|
||||
participant Wecom as 企微审批API
|
||||
participant ITSM_API as ITSM OpenAPI
|
||||
|
||||
FE->>API: GET /api/todo-items/approval:{sp_no}
|
||||
API->>Agg: get_todo_detail(agent_userid, item_id, todo_type="approval")
|
||||
|
||||
alt type == "approval"
|
||||
Agg->>Appr: get_todo_detail(sp_no)
|
||||
Appr->>Wecom: getapprovaldetail(sp_no)
|
||||
Wecom-->>Appr: approval_detail
|
||||
Appr->>Appr: _map_to_todo_item(detail)
|
||||
Appr-->>Agg: TodoItemData
|
||||
else type == "ticket"
|
||||
Agg->>ITSM: get_todo_detail(workitem_id)
|
||||
ITSM->>ITSM_API: POST /openapi/v1/process/workitem/detail
|
||||
ITSM_API-->>ITSM: {code:20000, data:{workitem_detail}}
|
||||
ITSM->>ITSM: _map_to_todo_item(detail)
|
||||
ITSM-->>Agg: TodoItemData
|
||||
end
|
||||
|
||||
Agg-->>API: TodoItemData
|
||||
API-->>FE: {code:0, data:{item}}
|
||||
```
|
||||
|
||||
#### 4.3 ITSM 签名认证流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Svc as ITSMService
|
||||
participant Signer as ITSMSigner
|
||||
participant API as ITSM OpenAPI
|
||||
|
||||
Svc->>Svc: timestamp = str(int(time.time()*1000))
|
||||
Svc->>Signer: compute_signature(app_id, timestamp, app_secret, biz_data)
|
||||
Signer->>Signer: sign_params = {appId, timestamp, appSecret, bizData}
|
||||
Signer->>Signer: sort by key ASC
|
||||
Signer->>Signer: concat all values → canonicalized_str
|
||||
Signer->>Signer: quote_plus(canonicalized_str)
|
||||
Signer->>Signer: sha1(q2).hexdigest().upper()
|
||||
Signer-->>Svc: sign_str
|
||||
Svc->>API: POST url, headers={appId, timestamp, sign}, json=body
|
||||
API-->>Svc: {code:20000, data:{...}}
|
||||
```
|
||||
1. **打开历史模式**:点击开关 → Store.enableHistoryMode() → API 请求 → 渲染合并时间线
|
||||
2. **向上滚动加载更多**:检测滚动到顶部 → Store.loadMoreHistory() → API 请求(before游标)→ 前插消息
|
||||
3. **关闭历史模式**:点击开关 → Store.disableHistoryMode() → 恢复正常消息列表
|
||||
4. **切换会话重置**:selectConversation() → resetHistoryState() → historyMode=false
|
||||
|
||||
---
|
||||
|
||||
### 5. 待明确事项
|
||||
### 5. 任务列表
|
||||
|
||||
| # | 问题 | 当前假设 | 影响 |
|
||||
|---|------|---------|------|
|
||||
| 1 | ITSM 代办列表 API 端点和请求/响应格式 | 设计为抽象接口,列表方法暂返回空列表 | 待用户抓包获取后实现,不影响架构 |
|
||||
| 2 | ITSM app_id 和 app_secret | 需在 config.py 中新增配置项占位 | 部署时通过环境变量注入 |
|
||||
| 3 | 企微 getapprovaldata 单次查询上限(size 参数) | 假设每页100条,循环 cursor 分页 | 如上限更小需增加分页逻辑 |
|
||||
| 4 | 审批详情并发查询数量较多时的限流 | 使用 asyncio.Semaphore 限制并发数(默认10) | 防止企微 API 限流 |
|
||||
| 5 | 坐席身份标识(agent_userid)从哪里获取 | 假设从请求 header 或 JWT token 中提取 | 需确认认证中间件传递方式 |
|
||||
| 6 | ITSM 工单的优先级映射规则 | 假设 ITSM 有自己的优先级字段,需映射到 urgent/high/normal | 待 API 确认后调整映射逻辑 |
|
||||
| 任务ID | 任务名称 | 涉及文件 | 依赖 | 优先级 |
|
||||
|--------|----------|----------|------|--------|
|
||||
| T01 | 后端 — 历史消息聚合接口 | `backend/app/api/messages.py`、`backend/app/services/conversation/session_query_service.py`、`backend/app/schemas/message.py` | 无 | P0 |
|
||||
| T02 | 前端数据层 — API + Store + Mock | `frontend-agent/src/api/message.ts`、`frontend-agent/src/stores/conversation.ts`、`frontend-agent/src/mock/data.ts` | T01 | P0 |
|
||||
| T03 | 前端组件层 — 开关 + 分隔条 + 消息列表改造 | `frontend-agent/src/components/chat/UserInfoBar.vue`、`frontend-agent/src/components/chat/ConversationSeparator.vue`(新增)、`frontend-agent/src/components/chat/ChatArea.vue` | T02 | P0 |
|
||||
|
||||
---
|
||||
|
||||
## Part B: 任务分解
|
||||
|
||||
### 6. 依赖包列表
|
||||
|
||||
**无新增第三方依赖。** 所有所需库已在项目中使用:
|
||||
- `httpx` — 已用于 approval.py 的企微 API 调用
|
||||
- `redis.asyncio` — 已用于 token 缓存管理
|
||||
- `hashlib` — Python 标准库(ITSM SHA1 签名)
|
||||
- `urllib.parse.quote_plus` — Python 标准库(ITSM 签名 URL 编码)
|
||||
- `asyncio` — Python 标准库(并行查询)
|
||||
**无需新增任何第三方依赖。**
|
||||
|
||||
- 后端:复用现有 FastAPI + SQLAlchemy 2.0 async
|
||||
- 前端:复用现有 Vue 3 + Pinia + Element Plus + Axios
|
||||
|
||||
---
|
||||
|
||||
### 7. 任务列表
|
||||
### 7. 共享知识(跨文件约定)
|
||||
|
||||
#### T01: 后端基础设施与数据模型
|
||||
**依赖**:无
|
||||
**优先级**:P0
|
||||
**文件**:
|
||||
- `backend/app/config.py`(修改)
|
||||
- `backend/app/schemas/todo_item.py`(修改)
|
||||
- `backend/app/models/todo_item.py`(修改)
|
||||
#### 7.1 消息合并时间线数据结构约定
|
||||
|
||||
**描述**:
|
||||
1. config.py 新增 ITSM 配置项:
|
||||
- `itsm_app_id: str = ""`
|
||||
- `itsm_app_secret: str = ""`
|
||||
- `itsm_base_url: str = "https://devops.dc.servyou-it.com/itsm"`(生产)
|
||||
- `itsm_test_base_url: str = "https://test-devops.dc.servyou-it.com/itsm"`(测试)
|
||||
2. schemas/todo_item.py:
|
||||
- `VALID_TODO_TYPES` 从 `{"ticket", "approval", "device"}` 改为 `{"ticket", "approval"}`
|
||||
- 更新所有字段描述中的类型注释
|
||||
3. models/todo_item.py:
|
||||
- type 字段注释从 `ticket/approval/device` 改为 `ticket/approval`
|
||||
```
|
||||
历史模式 displayMessages 返回的是一维 Message[] 数组(与正常模式相同的类型),
|
||||
按 created_at 升序排列(最旧在前,最新在后),与正常聊天列表一致。
|
||||
|
||||
---
|
||||
前端在渲染时遍历 displayMessages,当检测到相邻两条消息的 conversation_id 不同时,
|
||||
在它们之间插入一个 ConversationSeparator 组件。
|
||||
|
||||
#### T02: 后端 Service 层 + ITSM 签名工具
|
||||
**依赖**:T01
|
||||
**优先级**:P0
|
||||
**文件**:
|
||||
- `backend/app/services/todo_source_service.py`(新建)
|
||||
- `backend/app/services/itsm_service.py`(新建)
|
||||
- `backend/app/services/todo_aggregator_service.py`(新建)
|
||||
- `backend/app/utils/itsm_signer.py`(新建)
|
||||
conversation_summaries 是一个 Record<string, string> 映射:
|
||||
key = conversation_id
|
||||
value = 该会话中员工首条消息的前20字摘要
|
||||
|
||||
**描述**:
|
||||
1. `todo_source_service.py`:
|
||||
- 定义抽象基类 `TodoSourceService`,含 `get_todo_list()` 和 `get_todo_detail()` 抽象方法
|
||||
- 实现 `ApprovalTodoService`:
|
||||
- `get_todo_list()`:调 `getapprovaldata`(sp_status=1, 18模板)→ 并发 `getapprovaldetail` → `_extract_current_approver` 过滤 → `_map_to_todo_item` 映射
|
||||
- `get_todo_detail(sp_no)`:调 `getapprovaldetail` → `_map_to_todo_item`
|
||||
- `_map_to_todo_item()`:企微审批详情 → TodoItemData 格式映射
|
||||
- 使用 `asyncio.Semaphore(10)` 限制并发详情查询
|
||||
2. `itsm_signer.py`:
|
||||
- `ITSMSigner.compute_signature(app_id, timestamp, app_secret, biz_data)` 静态方法
|
||||
- 实现 PRD 中的签名算法:sort → concat → quote_plus → sha1 → upper
|
||||
3. `itsm_service.py`:
|
||||
- 继承 `TodoSourceService`
|
||||
- `_do_post(url, body)`:签名 + 发送请求(复用 ITSMSigner)
|
||||
- `get_workitem_detail(process_instance_id, executor)`:调已知详情 API
|
||||
- `get_todo_list()`:**待 API 到位**,当前返回空列表 + `logger.warning`
|
||||
- `get_todo_detail(workitem_id)`:调 `get_workitem_detail` → 映射
|
||||
4. `todo_aggregator_service.py`:
|
||||
- `get_todo_list(agent_userid, todo_type)`:Redis 缓存 → `asyncio.gather(return_exceptions=True)` → 合并排序 → 写缓存
|
||||
- `get_todo_detail(agent_userid, item_id, todo_type)`:按类型路由到对应 Service
|
||||
- 缓存 key:`todo:cache:{agent_userid}:{todo_type or "all"}`,TTL 45s
|
||||
分隔条的 summary 从 conversation_summaries[message.conversation_id] 获取。
|
||||
```
|
||||
|
||||
---
|
||||
#### 7.2 分隔条组件 Props 约定
|
||||
|
||||
#### T03: 后端 API 改造
|
||||
**依赖**:T02
|
||||
**优先级**:P0
|
||||
**文件**:
|
||||
- `backend/app/api/todo_items.py`(修改)
|
||||
- `backend/app/api/approval.py`(修改)
|
||||
- `backend/app/api/router.py`(确认,无需修改路由注册)
|
||||
|
||||
**描述**:
|
||||
1. `todo_items.py`:
|
||||
- 删除 `MOCK_TODO_ITEMS`(全部20条硬编码数据)
|
||||
- 删除 `TodoItemResponse`、`TodoItemListResponse`(移至 schemas,复用已有)
|
||||
- `list_todo_items()`:新增 `type` 查询参数,调用 `TodoAggregatorService.get_todo_list()`
|
||||
- `get_todo_item()`:从 `item_id` 中解析类型前缀(如 `approval:{sp_no}` / `ticket:{workitem_id}`),调用 `TodoAggregatorService.get_todo_detail()`
|
||||
- `update_todo_item_status()`:保留但标记为"仅展示,不支持在服务台内操作"(按用户决策,交互方式为跳转原系统)
|
||||
- 从请求中获取 `agent_userid`(header 或 JWT)
|
||||
2. `approval.py`:
|
||||
- 新增 `get_approval_data(access_token, starttime, endtime, filters)` 异步函数
|
||||
- 调用企微 `POST /cgi-bin/oa/getapprovaldata` API
|
||||
- 支持 cursor 分页循环
|
||||
|
||||
---
|
||||
|
||||
#### T04: 前端数据层与 API 改造
|
||||
**依赖**:T03
|
||||
**优先级**:P0
|
||||
**文件**:
|
||||
- `frontend-agent/src/api/todo.ts`(修改)
|
||||
- `frontend-agent/src/stores/todo.ts`(修改)
|
||||
- `frontend-agent/src/mock/data.ts`(修改)
|
||||
|
||||
**描述**:
|
||||
1. `api/todo.ts`:
|
||||
- `TodoItemData.type` 注释移除 device
|
||||
- `getTodoItems()` 新增 `type` 参数:`type?: 'ticket' | 'approval'`
|
||||
- 新增 `refreshTodoItems()` 函数(带 `_force=1` 参数跳过缓存)
|
||||
2. `stores/todo.ts`:
|
||||
- 删除 `import { mockTodoListData } from '@/mock/data'`
|
||||
- `fetchTodoList()` catch 块:删除 mock fallback,改为 `todoList.value = []` + 错误日志
|
||||
- 新增 `activeType` ref:`'all' | 'approval' | 'ticket'`,传入 API type 参数
|
||||
- 新增 `refreshList()` 方法:强制刷新(调 `refreshTodoItems`)
|
||||
3. `mock/data.ts`:
|
||||
- 删除 `mockTodoListData` 常量和导出
|
||||
- 删除 `mockTodos` 数组(5条前端 mock 数据)
|
||||
|
||||
---
|
||||
|
||||
#### T05: 前端组件层改造
|
||||
**依赖**:T04
|
||||
**优先级**:P0(移除 device)/ P1(Tab+详情+刷新)/ P2(定时刷新+跳转)
|
||||
**文件**:
|
||||
- `frontend-agent/src/components/conversation/TodoPanel.vue`(修改)
|
||||
- `frontend-agent/src/components/chat/TaskDetailView.vue`(修改)
|
||||
- `frontend-agent/src/components/chat/task/DeviceDetail.vue`(修改/废弃)
|
||||
- `frontend-agent/src/components/chat/task/TicketDetail.vue`(修改)
|
||||
- `frontend-agent/src/components/chat/task/ApprovalDetail.vue`(修改)
|
||||
|
||||
**描述**:
|
||||
1. `TodoPanel.vue`(P0+P1+P2):
|
||||
- `typeLabel` 移除 `device: '设备'`
|
||||
- 新增类型筛选 Tab(全部/审批/工单),切换时更新 `todoStore.activeType` + 刷新列表
|
||||
- 新增刷新按钮(🔄 图标),点击调 `todoStore.refreshList()`
|
||||
- 新增定时刷新(P2):`setInterval(fetchTodoList, 60000)`,组件 `onUnmounted` 时清除
|
||||
- 新增跳转按钮(P2):每条待办条目增加"在原系统中打开"链接(审批跳企微审批URL / 工单跳ITSM URL)
|
||||
- 移除 `.todo-type-tag.type-device` CSS 样式
|
||||
2. `TaskDetailView.vue`(P0):
|
||||
- 移除 `import DeviceDetail from './task/DeviceDetail.vue'`
|
||||
- 移除 `v-else-if="todoItem.type === 'device'"` 条件渲染块
|
||||
- `typeLabelMap` 移除 `device: '🖥 设备异常'`
|
||||
- 移除 `.tdv-type-device` CSS 样式
|
||||
3. `DeviceDetail.vue`(P0):
|
||||
- 文件头注释标记为"已废弃 — v1.0 移除 device 类型"
|
||||
- 保留文件但不再被任何组件引用
|
||||
4. `TicketDetail.vue`(P1):
|
||||
- 适配真实 ITSM 工单数据结构(description 字段映射调整)
|
||||
- 底部操作按钮改为"在 ITSM 中打开"跳转链接(P2)
|
||||
5. `ApprovalDetail.vue`(P1):
|
||||
- 适配真实企微审批数据结构(description 字段映射调整)
|
||||
- 底部操作按钮改为"在企微审批中打开"跳转链接(P2)
|
||||
|
||||
---
|
||||
|
||||
### 8. 共享知识(跨文件约定)
|
||||
|
||||
#### 8.1 统一数据映射规则
|
||||
|
||||
**企微审批 → TodoItemData 映射**:
|
||||
```python
|
||||
{
|
||||
"id": f"approval:{sp_no}", # 前缀类型 + 原始ID
|
||||
"type": "approval",
|
||||
"title": sp_name, # 审批单名称
|
||||
"priority": "high", # 审批默认 high(企微无优先级概念)
|
||||
"description": {
|
||||
"sp_no": sp_no,
|
||||
"template_name": template_name,
|
||||
"applicant": applyer_userid,
|
||||
"apply_time": apply_time,
|
||||
"sp_status": sp_status,
|
||||
"current_approver": current_approver,
|
||||
"template_id": template_id,
|
||||
},
|
||||
"status": "pending", # 审批中统一映射为 pending
|
||||
"assigned_agent_id": current_approver,
|
||||
"corp_id": corp_id,
|
||||
"created_at": apply_time_iso, # apply_time 时间戳转 ISO
|
||||
"updated_at": apply_time_iso,
|
||||
```typescript
|
||||
// ConversationSeparator.vue
|
||||
interface Props {
|
||||
summary: string // 首条消息摘要(前20字),已由后端截取
|
||||
isCurrent: boolean // 是否为当前会话(当前会话的分隔条高亮/加粗)
|
||||
}
|
||||
// 无 emit,纯展示组件(P1 搁置跳转功能)
|
||||
```
|
||||
|
||||
**ITSM 工单 → TodoItemData 映射**:
|
||||
```python
|
||||
#### 7.3 Store 状态切换约定
|
||||
|
||||
```
|
||||
正常模式 → 历史模式:
|
||||
1. historyMode = true
|
||||
2. historyLoading = true(触发 UI loading 态)
|
||||
3. 调用 API 加载初始50条
|
||||
4. 成功后:historyMessages = data.items.reverse(),historyHasMore = data.has_more
|
||||
5. historyCursor = historyMessages[0]?.id(最旧消息ID,用于下次分页)
|
||||
6. historyLoading = false
|
||||
|
||||
历史模式 → 正常模式:
|
||||
1. historyMode = false
|
||||
2. 清空 historyMessages、historyConversationSummaries、historyCursor
|
||||
3. messages ref 不受影响(正常模式数据源未变)
|
||||
|
||||
切换会话时:
|
||||
1. resetHistoryState() — 强制 historyMode = false,清空所有历史状态
|
||||
2. 然后执行正常的 fetchMessages()
|
||||
```
|
||||
|
||||
#### 7.4 API 响应格式约定
|
||||
|
||||
```
|
||||
所有后端 API 响应统一使用 success_response() 包装:
|
||||
{
|
||||
"id": f"ticket:{process_instance_id}", # 前缀类型 + 原始ID
|
||||
"type": "ticket",
|
||||
"title": title,
|
||||
"priority": itsm_priority_to_todo(priority), # ITSM 优先级 → urgent/high/normal
|
||||
"description": {
|
||||
"process_instance_id": process_instance_id,
|
||||
"executor": executor,
|
||||
"status": itsm_status,
|
||||
"creator": creator,
|
||||
# ... 其他 ITSM 字段
|
||||
},
|
||||
"status": "pending",
|
||||
"assigned_agent_id": agent_userid,
|
||||
"corp_id": "",
|
||||
"created_at": created_at_iso,
|
||||
"updated_at": updated_at_iso,
|
||||
"code": 200,
|
||||
"data": { ... },
|
||||
"message": "success"
|
||||
}
|
||||
|
||||
前端 apiClient 拦截器已自动解包,返回 response.data.data。
|
||||
因此 getHistoryMessages() 返回的是 data 字段内容(HistoryMessageListData)。
|
||||
```
|
||||
|
||||
#### 8.2 ID 格式约定
|
||||
|
||||
所有 TodoItem 的 `id` 字段使用 `{type}:{原始ID}` 格式:
|
||||
- 审批:`approval:{sp_no}`(如 `approval:202607110001`)
|
||||
- 工单:`ticket:{process_instance_id}`(如 `ticket:12345`)
|
||||
|
||||
解析规则:`item_id.split(":", 1)` → `[type, original_id]`
|
||||
|
||||
#### 8.3 缓存 Key 设计
|
||||
#### 7.5 消息排序约定
|
||||
|
||||
```
|
||||
todo:cache:{agent_userid}:{todo_type}
|
||||
后端返回:按 created_at DESC(最新在前)
|
||||
前端 Store:reverse() 后存储为 ASC(最旧在前,最新在后)
|
||||
前端渲染:从上到下 = 从旧到新(与正常聊天一致)
|
||||
分页游标:historyCursor = 最旧消息的ID(数组第一个元素)
|
||||
向上滚动:用 before=historyCursor 请求更旧的消息,prepend 到数组头部
|
||||
```
|
||||
- `agent_userid`:坐席企微 userid
|
||||
- `todo_type`:`all` / `approval` / `ticket`
|
||||
- TTL:45 秒
|
||||
- 强制刷新:删除 key 后重新查询
|
||||
|
||||
#### 8.4 API 响应格式
|
||||
#### 7.6 分隔条插入逻辑约定
|
||||
|
||||
统一使用 `{code: 0, data: {...}, message: "success"}` 格式(复用 `success_response`)。
|
||||
```typescript
|
||||
// ChatArea.vue 渲染逻辑伪代码
|
||||
const renderedItems = computed(() => {
|
||||
const msgs = conversationStore.displayMessages
|
||||
const result: Array<{ type: 'separator'; data: SeparatorData } | { type: 'message'; data: Message }> = []
|
||||
let lastConvId = ''
|
||||
|
||||
#### 8.5 并发控制
|
||||
for (const msg of msgs) {
|
||||
if (msg.conversation_id !== lastConvId) {
|
||||
// 会话切换,插入分隔条
|
||||
result.push({
|
||||
type: 'separator',
|
||||
data: {
|
||||
summary: conversationStore.historyConversationSummaries[msg.conversation_id] || '未知会话',
|
||||
isCurrent: msg.conversation_id === conversationStore.currentConversationId,
|
||||
}
|
||||
})
|
||||
lastConvId = msg.conversation_id
|
||||
}
|
||||
result.push({ type: 'message', data: msg })
|
||||
}
|
||||
return result
|
||||
})
|
||||
```
|
||||
|
||||
- 企微审批详情并发查询:`asyncio.Semaphore(10)` 限制
|
||||
- 两个数据源并行查询:`asyncio.gather(return_exceptions=True)`
|
||||
- 任一数据源失败不影响另一个:`isinstance(result, Exception)` 检查后跳过
|
||||
---
|
||||
|
||||
#### 8.6 前端类型约定
|
||||
### 8. 待明确事项
|
||||
|
||||
- `type` 字段:仅 `'ticket'` | `'approval'`(移除 `'device'`)
|
||||
- 前缀格式:前端通过 `id.includes('approval:')` 或 `id.includes('ticket:')` 判断类型
|
||||
| # | 问题 | 当前假设 | 建议确认方 |
|
||||
|---|------|----------|------------|
|
||||
| 1 | "当前会话排在最上方"的视觉含义 | 假设为:消息按时间正序排列(旧→新,与正常聊天一致),当前会话因最新而位于列表底部(用户初始可视区域)。分隔条中当前会话高亮标记。 | 产品经理 |
|
||||
| 2 | 历史模式下是否暂停消息轮询 | 假设:历史模式下暂停当前会话的消息轮询(`stopMessagePoll`),避免新消息混入历史时间线。关闭历史模式后恢复轮询。 | 产品经理 |
|
||||
| 3 | 历史消息是否需要标记已读 | 假设:历史消息不触发标记已读逻辑(只读查看,不修改 is_read 状态) | 产品经理 |
|
||||
| 4 | 员工无任何历史会话(仅当前会话)时的展示 | 假设:正常展示当前会话消息 + 一条当前会话的分隔条,不显示"暂无历史会话"提示 | 产品经理 |
|
||||
| 5 | 分隔条中是否显示会话状态(如"已结单") | 假设:P0 仅显示首条消息摘要,不显示状态标签。P1 可扩展。 | 产品经理 |
|
||||
| 6 | 历史模式下 WebSocket 新消息推送的处理 | 假设:历史模式下收到新消息仍更新 `messages` ref(正常数据源),但不混入 `historyMessages`。关闭历史模式后即可看到。 | 架构师 |
|
||||
|
||||
---
|
||||
|
||||
@@ -552,20 +324,19 @@ todo:cache:{agent_userid}:{todo_type}
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
T01[T01: 后端基础设施与数据模型<br/>config.py + schemas + models]
|
||||
T02[T02: 后端Service层 + ITSM签名<br/>4个新文件]
|
||||
T03[T03: 后端API改造<br/>todo_items.py + approval.py]
|
||||
T04[T04: 前端数据层与API<br/>api/todo.ts + store + mock]
|
||||
T05[T05: 前端组件层<br/>TodoPanel + TaskDetailView + 3个子视图]
|
||||
T01[T01: 后端历史消息聚合接口]
|
||||
T02[T02: 前端数据层 API+Store+Mock]
|
||||
T03[T03: 前端组件层 开关+分隔条+消息列表]
|
||||
|
||||
T01 --> T02
|
||||
T02 --> T03
|
||||
T03 --> T04
|
||||
T04 --> T05
|
||||
|
||||
style T01 fill:#4CAF50,color:#fff
|
||||
style T02 fill:#2196F3,color:#fff
|
||||
style T03 fill:#2196F3,color:#fff
|
||||
style T04 fill:#FF9800,color:#fff
|
||||
style T05 fill:#FF9800,color:#fff
|
||||
style T03 fill:#FF9800,color:#fff
|
||||
```
|
||||
|
||||
**说明:**
|
||||
- T01(后端)无依赖,可最先开始
|
||||
- T02(前端数据层)依赖 T01 的接口契约(URL、参数、响应格式),但可基于接口契约先行开发 mock
|
||||
- T03(前端组件层)依赖 T02 的 Store API,是最终集成层
|
||||
|
||||
Reference in New Issue
Block a user