# PRD: 代办事项系统集成真实数据源 > **实现状态(2026-07-11 更新)**: > - ✅ P0 REQ-001 企微审批待处理列表 — 已实现并验证(`getapprovalinfo` API + `TokenManager`) > - ✅ P0 REQ-003 统一代办列表 API — 已实现(`todo_aggregator_service.py`,Redis 缓存 45s) > - ✅ P0 REQ-004 前端移除 mock fallback — 已实现 > - ✅ P0 REQ-005 移除 device 类型 — 已实现 > - ✅ P1 REQ-006 按类型筛选 — 已实现 > - ✅ P1 REQ-008 手动刷新 — 已实现(`_force=1` 跳过缓存) > - ⏳ P0 REQ-002 ITSM 代办工单列表 — 待 ITSM API 抓包 + app_id/app_secret 申请 > - ⏳ P1 REQ-007 点击代办条目展示详情 — 待 ITSM 部分完成 > - ⏳ P2 REQ-009/010 自动刷新/跳转 — 待后续迭代 ## 项目信息 - **Language**: 中文 - **Programming Language**: 后端 FastAPI + SQLAlchemy + PostgreSQL + Redis | 前端 Vue3 + Element Plus + Pinia - **Project Name**: `todo_integration_real_data` - **原始需求复述**: 将坐席端"代办事项"面板从 mock 数据切换为真实数据源。数据来源包括企微审批待处理审批单(`getapprovalinfo` API)和 ITSM 运维平台"我的代办"工单(ITSM API + 企微 SSO 免登录)。移除所有 mock 数据和 device 类型,不保留 mock fallback。 - **API 变更说明**: 原始需求引用的 `getapprovaldata`(`/cgi-bin/oa/getapprovaldata`)已于 2019 年废弃返回 404,实际使用新接口 `getapprovalinfo`(`/cgi-bin/oa/getapprovalinfo`),分页参数从 `cursor`(int) 变为 `new_cursor`(string),返回从 `data` 数组变为 `sp_no_list` 字符串数组。 - **Token 变更说明**: 原始设计使用 `ApprovalTokenManager`(审批应用 Secret),但服务器出口 IP `218.75.34.87` 未在审批应用可信 IP 白名单中(errcode=60020)。实际改用 `TokenManager`(IT 支持应用 Secret,IP 已在白名单)。 --- ## 1. 产品目标 1. **数据真实性**:将坐席端代办面板从 20 条硬编码 mock 数据切换为两个真实数据源(企微审批 + ITSM 工单),确保坐席看到的是真实待处理工作。 2. **统一聚合**:后端聚合企微审批和 ITSM 工单两个异构数据源为统一代办列表 API,保持前端交互体验不变。 3. **彻底去 mock**:移除所有 mock 数据(后端 `MOCK_TODO_ITEMS`、前端 `mockTodoListData`)和 device 类型相关代码,确保系统数据真实性,不留 fallback。 --- ## 2. 用户故事 1. **作为坐席**,我希望在代办面板看到我真实的待处理企微审批单和 ITSM 工单,而不是 mock 数据,以便我了解真实的工作待办。 2. **作为坐席**,我希望点击代办条目能查看对应的审批单/工单详情,以便快速了解任务内容并决定处理优先级。 3. **作为坐席**,我希望代办列表能按类型(全部/审批/工单)筛选,以便高效分类处理不同来源的待办。 4. **作为坐席**,我希望代办列表能反映最新的待处理状态(支持手动刷新),以便不遗漏新分配的审批和工单。 5. **作为坐席**,我希望代办条目中能清晰区分审批单和工单的类型标签和优先级,以便快速识别紧急事项。 --- ## 3. 需求池 ### P0 — Must Have | ID | 需求描述 | 验收标准 | |---|---|---| | REQ-001 ✅ | **后端实现企微审批待处理列表查询**:集成企微 `getapprovalinfo` API(POST `https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovalinfo`),查询当前登录坐席的待处理审批单列表。使用 `TokenManager`(IT 支持应用 Secret)获取 access_token,通过 `filters` 参数按审批状态(sp_status=1 审批中)过滤,返回 sp_no_list 后调用 `getapprovaldetail` 补充详情,在代码层按 18 个模板 ID 过滤,再通过 `_extract_current_approver` 按当前审批人过滤。 | ① 调用 `getapprovalinfo` 成功返回审批单号列表;② 仅返回 sp_status=1(审批中)的审批单;③ 支持分页(new_cursor + new_next_cursor);④ token 获取复用 Redis 缓存机制(`wecom:access_token`);⑤ 异常时记日志并返回空列表(不阻塞另一个数据源) | | REQ-002 ⏳ | **后端实现 ITSM 代办工单列表查询**:集成 ITSM 平台 API(`https://devops.dc.servyou-it.com/ITSM/workflow/allTickets/umiAllticketsTodo`),通过企微 SSO 免登录认证查询当前坐席的代办工单列表。**待办:需抓包获取 API 接口规范 + 申请 app_id/app_secret**。 | ① 成功调用 ITSM API 返回代办工单列表;② SSO 认证无需坐席手动登录;③ 返回工单包含标题、优先级、状态、创建时间等关键字段;④ 网络异常时记日志并返回空列表(不阻塞另一个数据源) | | REQ-003 ✅ | **后端聚合两个数据源为统一代办列表 API**:重写 `GET /api/todo-items` 端点,并行查询企微审批和 ITSM 工单两个数据源,将结果映射为统一的 `TodoItemResponse` 格式返回。移除 `MOCK_TODO_ITEMS` 硬编码数据。 | ① API 返回的 items 仅包含 type=approval 和 type=ticket 两种类型;② 两个数据源并行查询(asyncio.gather),任一失败不影响另一个返回;③ 返回数据按优先级排序(urgent→high→normal);④ 响应时间 < 3 秒(两个外部 API 并行);⑤ 不再返回任何 mock 数据 | | REQ-004 ✅ | **前端移除 mock fallback 逻辑**:修改 `stores/todo.ts`,移除 `import { mockTodoListData } from '@/mock/data'` 和 catch 块中的 mock fallback 逻辑。API 调用失败时显示空列表 + 错误提示,不回退 mock。 | ① 代码中不再 import mockTodoListData;② API 失败时 todoList 为空数组;③ 控制台输出错误日志;④ 开发环境和生产环境行为一致(无 DEV 特殊分支) | | REQ-005 ✅ | **移除 device 类型相关代码**:后端从 `VALID_TODO_TYPES` 移除 "device"(schema/model);前端从 `typeLabel`、`typeLabelMap`、`TaskDetailView` 的 DeviceDetail 分支、TodoPanel 的 type-device 样式中移除 device 相关代码。`DeviceDetail.vue` 组件可保留文件但不再被引用。 | ① 后端 `VALID_TODO_TYPES` 不再包含 "device";② 前端 `typeLabel` 不再包含 device 映射;③ `TaskDetailView.vue` 不再渲染 DeviceDetail 分支;④ TodoPanel 中无 type-device 样式或该样式不生效;⑤ API 返回数据中无 type=device 的条目 | ### P1 — Should Have | ID | 需求描述 | 验收标准 | |---|---|---| | REQ-006 ✅ | **代办列表支持按类型筛选**:在 `TodoPanel.vue` 标题行增加"全部 / 审批 / 工单"三个筛选 Tab,点击后过滤展示对应类型的代办条目。后端 API 增加 `type` 查询参数支持服务端过滤。 | ① 默认显示"全部";② 点击"审批"仅显示 type=approval 的条目;③ 点击"工单"仅显示 type=ticket 的条目;④ 筛选状态有视觉高亮 | | REQ-007 ⏳ | **点击代办条目展示对应详情**:复用现有 `TaskDetailView` → `TicketDetail` / `ApprovalDetail` 子视图。点击审批条目展示审批详情,点击工单条目展示工单详情。详情数据来自后端 `GET /api/todo-items/{id}` 端点(需支持按 ID 查询单条代办详情,可能需要缓存或重新查询外部 API)。 | ① 点击审批条目 → 中间栏切换为 task 视图,展示 ApprovalDetail;② 点击工单条目 → 展示 TicketDetail;③ 详情数据正确展示标题、优先级、描述等字段;④ 返回按钮正常回到会话视图 | | REQ-008 ✅ | **代办列表支持手动刷新**:在 TodoPanel 标题行增加刷新按钮,点击后重新调用 `fetchTodoList()` 获取最新数据。 | ① 刷新按钮可见且有刷新图标;② 点击后触发 API 重新查询;③ 刷新过程中按钮显示 loading 状态;④ 刷新完成后列表更新 | ### P2 — Nice to Have | ID | 需求描述 | 验收标准 | |---|---|---| | REQ-009 ⏳ | **代办列表自动定时刷新**:TodoPanel 挂载后每 60 秒自动刷新一次代办列表,组件卸载时清除定时器。 | ① 每 60 秒自动调用 fetchTodoList;② 组件卸载时 clearInterval;③ 不与手动刷新冲突 | | REQ-010 ⏳ | **代办条目支持跳转到原系统操作**:在详情页底部增加"在企微审批中打开" / "在 ITSM 中打开"按钮,点击后在新窗口打开对应的审批单/工单原始页面。 | ① 审批详情页有"在企微审批中打开"按钮,跳转到企微审批 URL;② 工单详情页有"在 ITSM 中打开"按钮,跳转到 ITSM 工单 URL | --- ## 4. UI 设计稿描述 ### 4.1 TodoPanel(左栏待办面板)变化 **标题行变化**: - 现有:`📋 待办事项` + 紧急数量徽章 - 变更后:`📋 待办事项` + 紧急数量徽章 + **类型筛选 Tab**(全部/审批/工单)+ **刷新按钮**(🔄 图标) - 布局:标题文字左对齐,筛选 Tab 紧跟其后,刷新按钮最右侧 **待办条目变化**: - 每条代办仍保持现有布局:优先级圆点 + 标题文本 + 类型标签 + 时间 + 上报人头像 - 类型标签仅保留两种:`工单`(蓝色,type=ticket)、`审批`(紫色,type=approval) - **移除** `设备`(橙色,type=device)标签 - 数据来源从 mock 变为真实 API 返回 **空状态变化**: - 现有:`暂无待办` - 变更后:API 加载中显示 loading 动画;加载完成无数据时显示`暂无待办`;API 失败时显示`加载失败,点击重试` **底部坐席统计**: - 保持不变(仍使用 `getAgentStats()` mock 数据,不在本次改造范围内) ### 4.2 TaskDetailView(中间栏任务详情)变化 **顶部标题栏**: - 类型标签映射移除 device:`📋 运维工单` / `📝 审批单`(移除 `🖥 设备异常`) - 优先级标签保持不变 **内容区**: - type=ticket → 渲染 `TicketDetail` 子视图(保持不变,但数据来自 ITSM 真实工单) - type=approval → 渲染 `ApprovalDetail` 子视图(保持不变,但数据来自企微真实审批单) - **移除** type=device → `DeviceDetail` 分支 - 未知类型 fallback 保持不变 ### 4.3 ApprovalDetail / TicketDetail 子视图 - 整体布局保持不变(卡片式信息展示 + 操作按钮区) - 数据字段需适配真实 API 返回结构: - 审批详情:`description` 字段需映射企微审批返回的申请人、审批类型、表单内容等 - 工单详情:`description` 字段需映射 ITSM 返回的工单标题、类型、上报人、描述等 - 操作按钮(审批通过/拒绝、接单/结单等)的具体行为取决于待确认问题 #4 的决策 --- ## 5. 待确认问题 ### 5.1 ITSM API 相关(阻塞 REQ-002)— ⏳ 待申请授权 | # | 问题 | 说明 | 状态 | |---|------|------|------| | Q1 | **ITSM API 的完整接口规范** | 需抓包获取请求方法/参数/返回格式 | ⏳ 待 agent-browser 抓包 | | Q2 | **ITSM SSO 认证的具体方式** | 后端调用 ITSM API 的认证链路待确认 | ⏳ 待抓包确认 | | Q2.1 | **ITSM app_id/app_secret 申请** | openapi 签名认证需要 app_id 和 app_secret(签名:params 按 key 升序 → 拼接 value → quote_plus → SHA1 → 大写 hex) | ⏳ 待向 ITSM 平台申请 | ### 5.2 企微审批相关(影响 REQ-001)— ✅ 已确认 | # | 问题 | 确认结果 | |---|------|---------| | Q3 | 过滤条件与时间范围 | ✅ API 层只传 `sp_status=1`(每个 filter key 只能一次),代码层按 18 个模板 ID 过滤。时间范围 7 天(企微 API 最大 31 天,建议改为 30 天) | | Q4 | "待处理"定义 | ✅ `sp_status=1`=审批中,需二次过滤当前审批人。企微 API 实际字段:`sp_record[].sp_status`(非 `status`)、审批人在 `sp_record[].details[].approver.userid`(非 `approver[].userid`) | ### 5.3 交互与数据策略相关(影响 REQ-007、REQ-003) | # | 问题 | 说明 | 状态 | |---|------|------|------| | Q5 | **代办状态更新的交互方式** | 现有 `ApprovalDetail` 有"审批通过/拒绝/转交"按钮,`TicketDetail` 有"接单/处理/结单/转派"按钮。如需在服务台内操作,需对接企微审批 API 和 ITSM 工单 API 的状态变更接口 | ✅ 已确认:仅展示+跳转,不在服务台内操作 | | Q6 | **代办详情查询策略** | 列表查询时是否缓存每条代办的详情数据(Redis)?还是点击时实时查询外部 API? | ✅ 已确认:Redis 缓存 45s,手动刷新跳过缓存 | | Q7 | **数据刷新策略** | 每次打开/刷新代办面板都实时查询两个外部 API?还是后端缓存? | ✅ 已确认:初始+60s自动=用缓存,手动刷新=`_force=1`跳过缓存 | | Q8 | **ITSM 工单优先级映射** | ITSM 返回的优先级字段如何映射到 urgent/high/normal? | ⏳ 待 ITSM API 返回结构确认 | --- ## 附录:现有代码资产清单 ### 可复用资产 | 资产 | 位置 | 复用方式 | |---|---|---| | `TokenManager` | `backend/app/utils/token_manager.py` | ✅ 已复用,获取企微 access_token(IT 支持应用 Secret) | | `get_approval_detail()` | `backend/app/api/approval.py` | ✅ 已复用,获取审批单详情 | | `_extract_current_approver()` | `backend/app/api/approval.py` | ✅ 已复用,过滤当前审批人(字段名已修正) | | `_APPROVAL_TEMPLATE_IDS` | `backend/app/services/todo_source_service.py` | ✅ 已实现,18 个模板 ID 代码层过滤 | | `TodoItem` Model | `backend/app/models/todo_item.py` | 可选复用(如需持久化缓存代办数据) | | `TodoItemResponse` Schema | `backend/app/schemas/todo_item.py` | ✅ 已复用,已移除 device 类型 | | 前端 `TodoPanel.vue` | `frontend-agent/src/components/conversation/TodoPanel.vue` | ✅ 已改造 | | 前端 `TaskDetailView.vue` | `frontend-agent/src/components/chat/TaskDetailView.vue` | ✅ 已改造 | | 前端 `TicketDetail.vue` / `ApprovalDetail.vue` | `frontend-agent/src/components/chat/task/` | ✅ 已改造 | ### 需删除/废弃资产 | 资产 | 位置 | 处理方式 | |---|---|---| | `MOCK_TODO_ITEMS` | `backend/app/api/todo_items.py` | ✅ 已删除 | | `mockTodoListData` | `frontend-agent/src/mock/data.ts` | ✅ 已删除 | | mock fallback 逻辑 | `frontend-agent/src/stores/todo.ts` L66-72 | ✅ 已删除 | | `DeviceDetail.vue` | `frontend-agent/src/components/chat/task/DeviceDetail.vue` | 保留文件但不再被引用 | | device 类型样式 | `TodoPanel.vue` L299-304 | ✅ 已删除 | | device 类型映射 | `TodoPanel.vue` L110, `TaskDetailView.vue` L105 | ✅ 已删除 |