Files
X-Financial/docs/plans/phase-4-frontend-pages/README.md

501 lines
17 KiB
Markdown
Raw Normal View History

# 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<any>(null)
const taskList = ref<any[]>([])
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` 无报错