# Phase 4: 前端核心页面(W4-W5) > **目标:** 实现所有核心前端页面和组件,完成用户交互界面。 > **周期:** 第 4 ~ 5 周 > **任务数:** 4 个 > **可并行:** 4 个任务可由 1-2 名前端工程师并行开发 > **前置依赖:** Phase 1(前端骨架) > **备注:** 可与 Phase 3 后半段并行开始 --- ## 本阶段交付物 | 交付物 | 说明 | |---|---| | 报销入口页 | 对话式报销入口 + 快捷操作 | | 票据上传页 | 文件上传组件 + 票据类型选择 | | 报销草稿页 | 费用明细表格 + 可编辑字段 | | 预审结果页 | 风险展示 + 规则命中详情 | | 补件交互页 | 补件清单 + 上传/回复 | | 提交确认页 | 最终确认 + 同步状态 | | 审计日志页 | 操作时间线 | --- ## 任务清单 ### Task 4.1: 报销入口页 + 上传组件 **负责人:** 前端工程师 **预计工时:** 2 天 **前置依赖:** Phase 1(前端骨架) **Files:** - Create: `frontend/src/views/HomeView.vue` - Create: `frontend/src/views/UploadView.vue` - Create: `frontend/src/components/FileUpload.vue` - Create: `frontend/src/stores/task.ts` - Create: `frontend/src/api/task.ts` - Create: `frontend/src/api/document.ts` - [ ] **Step 1: 实现 API 调用层** `frontend/src/api/task.ts`: ```typescript import api from './index' export const createTask = (data: { userId: string companyId: string userIntent: string entryChannel?: string }) => api.post('/reimbursement/tasks', data) export const getTask = (taskId: string) => api.get(`/reimbursement/tasks/${taskId}`) export const listTasks = (params?: { userId?: string; status?: string; page?: number; size?: number }) => api.get('/reimbursement/tasks', { params }) export const runAgent = (taskId: string, startFrom = 'intake', mode = 'precheck') => api.post(`/reimbursement/tasks/${taskId}/agent/run`, { start_from: startFrom, mode }) ``` `frontend/src/api/document.ts`: ```typescript import api from './index' export const uploadDocument = (taskId: string, file: File, documentType: string) => { const formData = new FormData() formData.append('file', file) formData.append('document_type', documentType) return api.post(`/reimbursement/tasks/${taskId}/documents`, formData, { headers: { 'Content-Type': 'multipart/form-data' }, }) } export const listDocuments = (taskId: string) => api.get(`/reimbursement/tasks/${taskId}/documents`) ``` - [ ] **Step 2: 实现 Pinia Store** `frontend/src/stores/task.ts`: ```typescript import { defineStore } from 'pinia' import { ref } from 'vue' import { createTask, getTask, listTasks, runAgent } from '@/api/task' export const useTaskStore = defineStore('task', () => { const currentTask = ref(null) const taskList = ref([]) const loading = ref(false) async function create(userIntent: string) { loading.value = true try { const { data } = await createTask({ userId: 'U001', // TODO: 从登录态获取 companyId: 'C001', userIntent, }) currentTask.value = data return data } finally { loading.value = false } } async function fetchTask(taskId: string) { const { data } = await getTask(taskId) currentTask.value = data return data } async function startAgent(taskId: string, startFrom = 'intake') { loading.value = true try { const { data } = await runAgent(taskId, startFrom) return data } finally { loading.value = false } } return { currentTask, taskList, loading, create, fetchTask, startAgent } }) ``` - [ ] **Step 3: 实现报销入口页 HomeView** 按开发文档 9.2 节: - **对话输入框**:用户输入报销意图(如"我要报这次北京出差的费用") - **上传按钮**:直接跳转到上传页 - **最近任务列表**:显示用户最近的报销任务和状态 - **常用报销类型快捷按钮**: - "报差旅" - "看发票能不能报" - "帮我生成报销单" - "这张发票为什么不合规?" - **智能引导提示**:根据用户输入实时提示 交互流程:用户输入意图 → 调用 `taskStore.create()` → 跳转到 `/task/{taskId}/upload` - [ ] **Step 4: 实现文件上传组件 FileUpload** `frontend/src/components/FileUpload.vue`: Ant Design Vue 的 `a-upload-dragger` 封装: - 支持拖拽上传 - 支持多文件选择 - 文件类型校验(PDF、JPG、PNG、OFD) - 单文件大小限制(默认 10MB) - 上传进度条 - 预览缩略图 - 已上传文件列表 - 删除已上传文件 Props: ```typescript interface Props { taskId: string accept?: string // 默认 '.pdf,.jpg,.jpeg,.png,.ofd' maxFileSize?: number // MB,默认 10 maxCount?: number // 默认 10 } ``` - [ ] **Step 5: 实现票据上传页 UploadView** - 引用 FileUpload 组件 - 票据类型下拉选择(Ant Design Select): - 增值税发票 (vat_invoice) - 火车票 (train_ticket) - 机票行程单 (flight_itinerary) - 打车票据 (taxi_receipt) - 酒店流水 (hotel_bill) - 支付截图 (payment_screenshot) - 其他附件 (other_attachment) - 已上传文件列表展示 - "开始识别" 按钮 → 调用 `taskStore.startAgent()` → 跳转到草稿页 - 返回按钮 → 回到首页 - [ ] **Step 6: Commit** ```bash git add frontend/ git commit -m "feat: 实现报销入口页和票据上传页" ``` --- ### Task 4.2: 报销草稿页 **负责人:** 前端工程师 **预计工时:** 2 天 **前置依赖:** Task 4.1 **可并行于:** Task 4.3(如果两人并行) **Files:** - Create: `frontend/src/views/DraftView.vue` - Create: `frontend/src/components/ExpenseTable.vue` - Create: `frontend/src/api/precheck.ts` - [ ] **Step 1: 添加预审 API** `frontend/src/api/precheck.ts`: ```typescript import api from './index' export const getDraft = (taskId: string) => api.get(`/reimbursement/tasks/${taskId}/draft`) export const getPrecheckResult = (taskId: string) => api.get(`/reimbursement/tasks/${taskId}/precheck-result`) ``` - [ ] **Step 2: 实现 ExpenseTable 组件** `frontend/src/components/ExpenseTable.vue`: Ant Design Table 展示费用明细: | 列名 | 字段 | 可编辑 | 说明 | |---|---|---|---| | 费用类型 | expense_type | ✅ | 下拉选择 | | 金额 | amount | ✅ | 数字输入 | | 税额 | tax_amount | ✅ | 数字输入 | | 发生日期 | occurred_at | ✅ | 日期选择 | | 城市 | city | ✅ | 文本输入 | | 商户 | vendor_name | ✅ | 文本输入 | | 风险等级 | risk_level | ❌ | 彩色标签 | 风险等级标签颜色映射: - `low` → 绿色 `green` - `medium` → 橙色 `orange` - `high` → 红色 `red` - `blocked` → 深红 `#cf1322` 支持行内编辑(点击单元格进入编辑模式)。 - [ ] **Step 3: 实现报销草稿页 DraftView** 按开发文档 9.3 节布局: ``` ┌─────────────────────────────────────────────┐ │ 报销草稿 [预审按钮] │ ├─────────────────────────────────────────────┤ │ 基本信息 │ │ ┌──────────┬──────────┬──────────┐ │ │ │ 报销人 │ 部门 │ 成本中心 │ │ │ └──────────┴──────────┴──────────┘ │ │ ┌──────────┬──────────┐ │ │ │ 项目 │ 报销事由 │ │ │ └──────────┴──────────┘ │ ├─────────────────────────────────────────────┤ │ 费用明细 │ │ ┌─────────────────────────────────────────┐ │ │ │ ExpenseTable 组件 │ │ │ └─────────────────────────────────────────┘ │ │ 总金额:¥ 2,380.00 │ ├─────────────────────────────────────────────┤ │ 票据附件 │ │ ┌────┐ ┌────┐ ┌────┐ │ │ │ 📄 │ │ 📄 │ │ 📄 │ (缩略图 + 文件名) │ │ └────┘ └────┘ └────┘ │ ├─────────────────────────────────────────────┤ │ 预审状态:⏳ 待预审 │ │ [执行预审] │ └─────────────────────────────────────────────┘ ``` 交互: - 页面加载时调用 `getDraft(taskId)` 获取草稿数据 - 编辑字段后暂存到本地 state - 点击"执行预审" → 调用 `taskStore.startAgent(taskId, 'precheck')` → 跳转到预审结果页 - [ ] **Step 4: Commit** ```bash git add frontend/ git commit -m "feat: 实现报销草稿页和费用明细表格组件" ``` --- ### Task 4.3: 预审结果页 + 补件交互页 **负责人:** 前端工程师 **预计工时:** 2.5 天 **前置依赖:** Task 4.1 **可并行于:** Task 4.2(如果两人并行) **Files:** - Create: `frontend/src/views/PrecheckView.vue` - Create: `frontend/src/views/SupplementView.vue` - Create: `frontend/src/components/RuleHitCard.vue` - Create: `frontend/src/api/supplement.ts` - [ ] **Step 1: 添加补件 API** `frontend/src/api/supplement.ts`: ```typescript import api from './index' export const respondSupplement = (taskId: string, supplementRequestId: string, data: { responseText: string documentIds?: string[] }) => api.post(`/reimbursement/tasks/${taskId}/supplements`, { supplement_request_id: supplementRequestId, ...data, }) export const submitTask = (taskId: string, submitTo: string = 'expense_system') => api.post(`/reimbursement/tasks/${taskId}/submit`, { confirmed: true, submit_to: submitTo }) export const getSyncStatus = (taskId: string) => api.get(`/reimbursement/tasks/${taskId}/sync-status`) ``` - [ ] **Step 2: 实现 RuleHitCard 组件** `frontend/src/components/RuleHitCard.vue`: Ant Design Card 展示单条规则命中: ``` ┌─────────────────────────────────────────┐ │ 🔴 住宿费超标 TRAVEL_HOTEL_LIMIT │ ├─────────────────────────────────────────┤ │ 问题:住宿费超出当前城市和职级标准 │ │ 制度依据:差旅报销制度-住宿标准 │ │ 建议:请补充超标说明或发起特殊审批 │ │ [展开详情] │ └─────────────────────────────────────────┘ ``` Props: ```typescript interface Props { ruleCode: string ruleName: string severity: string // low / medium / high / blocked action: string message: string suggestion: string policyRef: string hitDetail?: object } ``` - [ ] **Step 3: 实现预审结果页 PrecheckView** 按开发文档 9.4 节: - **总体结论卡片**: - ✅ 通过 → 绿色 - ⚠️ 需补件 → 橙色 - 🚫 有阻断 → 红色 - **风险等级指示**:彩色 Badge - **通过项列表**:绿色勾选图标 + 规则名称 - **风险项列表**:使用 RuleHitCard 组件 - **缺件项列表**:橙色提示 + 补件按钮 - **操作按钮**: - "一键补件" → 跳转到补件页(仅在有缺件时显示) - "确认提交" → 跳转到确认页(仅预审通过时可用) 交互: - 页面加载时调用 `getPrecheckResult(taskId)` 获取预审结果 - 根据结果渲染不同状态 - [ ] **Step 4: 实现补件交互页 SupplementView** - **待补件清单**:从预审结果的 rule_hits 中过滤出 `require_attachment` / `require_explanation` 类型 - **每个补件项**: - 类型标签(补充附件 / 补充说明 / 修改字段) - 提示文案 - 操作区域: - 补充附件:调用 FileUpload 组件 - 补充说明:文本输入框 - **提交补件按钮** → 调用 `respondSupplement()` → 跳转回预审页重新预审 - [ ] **Step 5: Commit** ```bash git add frontend/ git commit -m "feat: 实现预审结果页和补件交互页" ``` --- ### Task 4.4: 提交确认页 + 审计日志页 **负责人:** 前端工程师 **预计工时:** 1.5 天 **前置依赖:** Task 4.3 **Files:** - Create: `frontend/src/views/ConfirmView.vue` - Create: `frontend/src/views/AuditView.vue` - Create: `frontend/src/api/audit.ts` - [ ] **Step 1: 添加审计 API** `frontend/src/api/audit.ts`: ```typescript import api from './index' export const getAuditLogs = (params?: { target_type?: string target_id?: string actor?: string page?: number size?: number }) => api.get('/audit/logs', { params }) ``` - [ ] **Step 2: 实现提交确认页 ConfirmView** ``` ┌─────────────────────────────────────────────┐ │ 提交确认 │ ├─────────────────────────────────────────────┤ │ 报销单摘要(不可编辑) │ │ ┌───────────────────────────────────────┐ │ │ │ 报销人:张三 部门:技术部 │ │ │ │ 事由:北京出差 总金额:¥2,380.00 │ │ │ └───────────────────────────────────────┘ │ │ │ │ 费用明细汇总: │ │ ┌───────────────────────────────────────┐ │ │ │ 差旅住宿费 ¥1,200.00 │ │ │ │ 差旅交通费 ¥ 553.00 │ │ │ │ 差旅餐补 ¥ 627.00 │ │ │ └───────────────────────────────────────┘ │ │ │ │ 附件清单:3 个文件 │ │ 同步目标:费控系统 │ │ │ │ [返回修改] [确认提交] │ ├─────────────────────────────────────────────┤ │ 同步状态:⏳ 提交中... │ └─────────────────────────────────────────────┘ ``` 交互: - "确认提交" → 调用 `submitTask(taskId)` - 提交后轮询 `getSyncStatus(taskId)`,展示同步进度 - 同步成功 → 显示后端单据号 - 同步失败 → 显示错误信息 + 重试按钮 - [ ] **Step 3: 实现审计日志页 AuditView** 按开发文档 9.5 节(简化版): - **Ant Design Timeline** 展示操作记录 - **筛选栏**:按任务ID、操作类型、时间范围筛选 - **每条日志**: - 时间戳 - 操作人 - 操作类型(彩色标签) - 详情(可展开) 操作类型颜色映射: - `file_upload` → 蓝色 - `ocr_recognize` → 青色 - `agent_call` → 紫色 - `rule_hit` → 橙色 - `supplement_request` / `supplement_respond` → 绿色 - `user_confirm` → 金色 - `backend_sync` → 灰色 - [ ] **Step 4: Commit** ```bash git add frontend/ git commit -m "feat: 实现提交确认页和审计日志页" ``` --- ## 本阶段完成检查 - [ ] 首页能创建任务并跳转到上传页 - [ ] 上传页能上传文件并开始识别 - [ ] 草稿页能展示费用明细并支持编辑 - [ ] 预审结果页能展示风险项和缺件项 - [ ] 补件页能上传附件和填写说明 - [ ] 确认页能展示摘要和同步状态 - [ ] 审计日志页能展示操作时间线 - [ ] 所有页面响应式布局正常 - [ ] 前端 `npm run build` 无报错