# 海洋项目管理系统 - 测试用例文档

## 目录

1. [单元测试用例](#1-单元测试用例)
2. [集成测试用例](#2-集成测试用例)
3. [E2E测试用例](#3-e2e测试用例)

---

## 1. 单元测试用例

### 1.1 工具函数测试

#### TC-001: formatMoney 函数测试

| 用例ID | TC-001-01 |
|--------|-----------|
| 测试模块 | utils/format.js |
| 测试函数 | formatMoney |
| 优先级 | 高 |
| 前置条件 | 无 |

**测试场景**

| 场景 | 输入 | 预期输出 |
|------|------|---------|
| 正整数 | 1234 | "1,234.00" |
| 小数 | 1234.56 | "1,234.56" |
| 千分位 | 10000 | "10,000.00" |
| 零 | 0 | "0.00" |
| 负数 | -1234.56 | "-1,234.56" |
| 四舍五入 | 100.123 | "100.12" |
| 大数字 | 9999999999 | "9,999,999,999.00" |

---

#### TC-002: formatPercentage 函数测试

| 用例ID | TC-002-01 |
|--------|-----------|
| 测试模块 | utils/format.js |
| 测试函数 | formatPercentage |
| 优先级 | 高 |
| 前置条件 | 无 |

**测试场景**

| 场景 | 输入 | 预期输出 |
|------|------|---------|
| 50% | 0.5 | "50.00%" |
| 100% | 1 | "100.00%" |
| 0% | 0 | "0.00%" |
| 小数 | 0.6666 | "66.66%" |
| 负数 | -0.5 | "-50.00%" |

---

#### TC-003: formatDate 函数测试

| 用例ID | TC-003-01 |
|--------|-----------|
| 测试模块 | utils/format.js |
| 测试函数 | formatDate |
| 优先级 | 高 |
| 前置条件 | 无 |

**测试场景**

| 场景 | 输入 | 预期输出 |
|------|------|---------|
| 日期字符串 | "2026-01-25" | "2026-01-25" |
| Date对象 | new Date("2026-01-25") | "2026-01-25" |
| 时间戳 | 1706150400000 | "2026-01-25" |

---

#### TC-004: validateEmail 函数测试

| 用例ID | TC-004-01 |
|--------|-----------|
| 测试模块 | utils/validate.js |
| 测试函数 | validateEmail |
| 优先级 | 高 |
| 前置条件 | 无 |

**测试场景**

| 场景 | 输入 | 预期输出 |
|------|------|---------|
| 有效邮箱 | "test@example.com" | true |
| 有效邮箱（带点） | "test.name@example.com" | true |
| 无效邮箱（无@） | "testexample.com" | false |
| 无效邮箱（无域名） | "test@" | false |
| 无效邮箱（无用户名） | "@example.com" | false |
| 空字符串 | "" | false |

---

#### TC-005: validatePhone 函数测试

| 用例ID | TC-005-01 |
|--------|-----------|
| 测试模块 | utils/validate.js |
| 测试函数 | validatePhone |
| 优先级 | 高 |
| 前置条件 | 无 |

**测试场景**

| 场景 | 输入 | 预期输出 |
|------|------|---------|
| 有效手机号 | "13800000001" | true |
| 有效手机号（其他运营商） | "15900139000" | true |
| 无效手机号（太短） | "1380000" | false |
| 无效手机号（太长） | "138000000001" | false |
| 无效手机号（非数字） | "138a0000001" | false |
| 空字符串 | "" | false |

---

### 1.2 Hooks测试

#### TC-006: useAuth Hook 测试

| 用例ID | TC-006-01 |
|--------|-----------|
| 测试模块 | hooks/useAuth.js |
| 测试Hook | useAuth |
| 优先级 | 高 |
| 前置条件 | AuthProvider包裹 |

**测试场景**

| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 初始状态 | 渲染组件 | isAuthenticated=false, user=null, token=null |
| 登录成功 | 调用login('admin', 'password123') | isAuthenticated=true, user有值, token有值 |
| 登录失败 | 调用login('admin', 'wrong') | isAuthenticated=false, 显示错误提示 |
| 登出 | 调用logout() | isAuthenticated=false, user=null, token=null, localStorage清空 |
| hasRole | 调用hasRole(['admin']) | 返回true或false |
| canEdit | 调用canEdit(projectId) | 根据角色和创建人返回true或false |

---

#### TC-007: useTable Hook 测试

| 用例ID | TC-007-01 |
|--------|-----------|
| 测试模块 | hooks/useTable.js |
| 测试Hook | useTable |
| 优先级 | 中 |
| 前置条件 | 无 |

**测试场景**

| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 初始状态 | 初始化Hook | pagination={current:1, pageSize:10}, filters={}, sorter={} |
| 页码变化 | 调用handleTableChange({current:2}) | pagination.current=2 |
| 筛选变化 | 调用handleTableChange({current:1}, {engineering_type:'基建'}) | filters.engineering_type='基建' |
| 排序变化 | 调用handleTableChange({current:1}, {}, {field:'signing_date', order:'desc'}) | sorter.field='signing_date', sorter.order='desc' |
| 重置 | 调用resetTable() | 恢复初始状态 |
| 选择行 | 调用setSelectedRowKeys([1,2,3]) | selectedRowKeys=[1,2,3] |

---

#### TC-008: useDebounce Hook 测试

| 用例ID | TC-008-01 |
|--------|-----------|
| 测试模块 | hooks/useDebounce.js |
| 测试Hook | useDebounce |
| 优先级 | 低 |
| 前置条件 | 无 |

**测试场景**

| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 防抖生效 | 快速改变值3次 | 只返回最后一次的值，延迟300ms |
| 默认延迟 | 不传delay参数 | 使用默认延迟300ms |
| 自定义延迟 | 传入delay=500 | 使用自定义延迟500ms |

---

### 1.3 组件测试

#### TC-009: Button 组件测试

| 用例ID | TC-009-01 |
|--------|-----------|
| 测试模块 | components/Common/Button |
| 测试组件 | Button |
| 优先级 | 高 |
| 前置条件 | 无 |

**测试场景**

| 场景 | Props | 预期结果 |
|------|-------|---------|
| 渲染按钮 | children="点击我" | 显示"点击我"文字 |
| 不同类型 | type="primary" | 蓝色按钮 |
| 不同类型 | type="default" | 白色按钮 |
| 不同类型 | type="danger" | 红色按钮 |
| 加载状态 | loading=true | 显示加载图标，禁用按钮 |
| 禁用状态 | disabled=true | 按钮不可点击 |
| 点击事件 | onClick=mockFn | 点击时调用mockFn |

---

#### TC-010: Tag 组件测试

| 用例ID | TC-010-01 |
|--------|-----------|
| 测试模块 | components/Common/Tag |
| 测试组件 | Tag |
| 优先级 | 中 |
| 前置条件 | 无 |

**测试场景**

| 场景 | Props | 预期结果 |
|------|-------|---------|
| 渲染标签 | children="基建" | 显示"基建"文字 |
| 不同类型 | type="new" | 蓝色背景和文字 |
| 不同类型 | type="inProgress" | 绿色背景和文字 |
| 不同类型 | type="completed" | 灰色背景和文字 |
| 不同类型 | type="cancelled" | 红色背景和文字 |
| 自定义颜色 | color="#ff0000" | 使用自定义颜色 |

---

#### TC-011: StatCard 组件测试

| 用例ID | TC-011-01 |
|--------|-----------|
| 测试模块 | components/Business/StatCard |
| 测试组件 | StatCard |
| 优先级 | 中 |
| 前置条件 | 无 |

**测试场景**

| 场景 | Props | 预期结果 |
|------|-------|---------|
| 渲染卡片 | title="项目总数", value=100 | 显示标题和数值 |
| 显示图标 | icon="📊" | 显示图标 |
| 前缀 | prefix="¥" | 数值前显示"¥" |
| 后缀 | suffix="万元" | 数值后显示"万元" |
| 金额格式化 | value=1234.56 | 显示"1,234.56万元" |

---

#### TC-012: Modal 组件测试

| 用例ID | TC-012-01 |
|--------|-----------|
| 测试模块 | components/Common/Modal |
| 测试组件 | Modal |
| 优先级 | 高 |
| 前置条件 | 无 |

**测试场景**

| 场景 | Props | 预期结果 |
|------|-------|---------|
| 显示模态框 | visible=true | 显示模态框 |
| 隐藏模态框 | visible=false | 不显示模态框 |
| 标题 | title="新建项目" | 显示标题 |
| 确定按钮 | onOk=mockFn, okText="保存" | 显示"保存"按钮，点击调用mockFn |
| 取消按钮 | onCancel=mockFn, cancelText="取消" | 显示"取消"按钮，点击调用mockFn |
| 加载状态 | confirmLoading=true | 确定按钮显示加载状态 |
| 自定义宽度 | width={800} | 宽度为800px |
| 点击遮罩关闭 | 点击遮罩层 | 调用onCancel |

---

### 1.4 API服务测试

#### TC-013: projectAPI 测试

| 用例ID | TC-013-01 |
|--------|-----------|
| 测试模块 | services/project.js |
| 测试API | projectAPI |
| 优先级 | 高 |
| 前置条件 | 模拟api.get/post/put/delete |

**测试场景**

| 场景 | 调用 | 预期结果 |
|------|------|---------|
| 获取列表 | getList({page:1, pageSize:10}) | api.get('/projects', {params:{page:1, pageSize:10}}) |
| 获取详情 | getDetail(1) | api.get('/projects/1') |
| 创建项目 | create({name:'新项目'}) | api.post('/projects', {name:'新项目'}) |
| 更新项目 | update(1, {name:'更新项目'}) | api.put('/projects/1', {name:'更新项目'}) |
| 删除项目 | delete(1) | api.delete('/projects/1') |
| 批量删除 | batchDelete([1,2,3]) | api.post('/projects/batch-delete', {ids:[1,2,3]}) |
| 获取统计 | getStatistics() | api.get('/projects/statistics') |
| 获取分组统计 | getGroupStatistics({groupBy:'engineering_type'}) | api.get('/projects/statistics/group', {params:{groupBy:'engineering_type'}}) |
| 获取时间统计 | getTimelineStatistics({timeField:'signing_date'}) | api.get('/projects/statistics/timeline', {params:{timeField:'signing_date'}}) |
| 导出项目 | export() | api.get('/projects/export', {responseType:'blob'}) |

---

#### TC-014: userAPI 测试

| 用例ID | TC-014-01 |
|--------|-----------|
| 测试模块 | services/user.js |
| 测试API | userAPI |
| 优先级 | 中 |
| 前置条件 | 模拟api.get/post/put/delete |

**测试场景**

| 场景 | 调用 | 预期结果 |
|------|------|---------|
| 获取列表 | getList({page:1, pageSize:10}) | api.get('/users', {params:{page:1, pageSize:10}}) |
| 获取详情 | getDetail(1) | api.get('/users/1') |
| 创建用户 | create({username:'test'}) | api.post('/users', {username:'test'}) |
| 更新用户 | update(1, {realName:'新姓名'}) | api.put('/users/1', {realName:'新姓名'}) |
| 删除用户 | delete(1) | api.delete('/users/1') |
| 重置密码 | resetPassword(1, 'newPassword') | api.post('/users/1/reset-password', {new_password:'newPassword'}) |

---

## 2. 集成测试用例

### 2.1 页面组件测试

#### TC-015: Login 页面测试

| 用例ID | TC-015-01 |
|--------|-----------|
| 测试模块 | pages/Login |
| 测试页面 | Login |
| 优先级 | 高 |
| 前置条件 | 无 |

**测试场景**

| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 渲染登录页 | 访问/login | 显示登录表单、Logo、用户名输入框、密码输入框、登录按钮 |
| 必填字段验证 | 不输入直接点击登录 | 显示"请输入用户名"和"请输入密码"错误 |
| 用户名太短 | 输入2个字符 | 显示"用户名至少3个字符"错误 |
| 密码太短 | 输入5个字符 | 显示"密码至少6个字符"错误 |
| 登录成功 | 输入正确用户名和密码，点击登录 | 调用login API，保存Token到localStorage，跳转到/dashboard |
| 登录失败 | 输入错误密码，点击登录 | 显示"用户名或密码错误"提示 |
| 记住密码 | 勾选"记住密码"，点击登录 | localStorage保存用户名和密码（加密） |
| 加载状态 | 点击登录按钮 | 按钮显示加载图标，禁用状态 |
| Enter键提交 | 输入后按Enter键 | 触发登录 |

---

#### TC-016: Dashboard 页面测试

| 用例ID | TC-016-01 |
|--------|-----------|
| 测试模块 | pages/Dashboard |
| 测试页面 | Dashboard |
| 优先级 | 高 |
| 前置条件 | 已登录 |

**测试场景**

| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 渲染仪表盘 | 访问/dashboard | 显示统计卡片、图表、最近项目列表 |
| 加载统计卡片 | 页面加载 | 调用getBasicStatistics API，显示4个统计卡片 |
| 加载图表 | 页面加载 | 调用getTimelineStatistics API，显示项目趋势图 |
| 加载最近项目 | 页面加载 | 调用getProjects API，显示最近5个项目 |
| 点击项目卡片 | 点击统计卡片 | 跳转到对应功能页面 |
| 点击最近项目 | 点击项目项 | 跳转到项目详情页 |
| 刷新数据 | 点击刷新按钮 | 重新获取所有数据 |
| 自动刷新 | 等待5分钟 | 自动刷新统计数据 |

---

#### TC-017: ProjectList 页面测试

| 用例ID | TC-017-01 |
|--------|-----------|
| 测试模块 | pages/ProjectList |
| 测试页面 | ProjectList |
| 优先级 | 高 |
| 前置条件 | 已登录 |

**测试场景**

| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 渲染列表页 | 访问/projects | 显示工具栏、项目表格、分页控件 |
| 加载项目列表 | 页面加载 | 调用getList API，默认参数page=1, pageSize=10 |
| 搜索项目 | 输入关键词，按Enter | 调用getList API，参数包含keyword |
| 按工程类别筛选 | 选择"基建" | 调用getList API，参数engineering_type='基建' |
| 按日期筛选 | 选择日期范围 | 调用getList API，参数包含signing_date_start和signing_date_end |
| 按金额筛选 | 输入金额范围 | 调用getList API，参数包含contract_amount_min和contract_amount_max |
| 组合筛选 | 同时设置多个条件 | 调用getList API，所有条件AND关系 |
| 清除筛选 | 点击"清除"按钮 | 重置所有筛选条件，刷新列表 |
| 排序 | 点击表头"签订日期" | 切换升序/降序，调用getList API，参数sort_by和sort_order |
| 分页 | 点击页码 | 调用getList API，参数page更新 |
| 查看项目 | 点击"查看"按钮 | 跳转到/projects/:id |
| 新建项目 | 点击"新建项目"按钮 | 打开新建项目模态框 |
| 编辑项目 | 点击"编辑"按钮 | 调用getDetail API，填充表单，打开编辑模态框 |
| 删除项目 | 点击"删除"按钮 | 显示确认对话框，确认后调用delete API |
| 权限控制 | 市场部用户查看其他部门项目 | "编辑"和"删除"按钮不显示 |
| 表格行点击 | 点击表格行 | 跳转到项目详情页 |

---

#### TC-018: ProjectDetail 页面测试

| 用例ID | TC-018-01 |
|--------|-----------|
| 测试模块 | pages/ProjectDetail |
| 测试页面 | ProjectDetail |
| 优先级 | 高 |
| 前置条件 | 已登录 |

**测试场景**

| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 渲染详情页 | 访问/projects/1 | 调用getDetail API，显示项目信息卡片 |
| 信息分组显示 | 页面加载 | 显示多个信息分组（基本信息、项目时间、成本管理等） |
| 格式化金额 | 显示金额字段 | 千分位分隔，保留两位小数，单位"万元" |
| 格式化日期 | 显示日期字段 | 格式为YYYY-MM-DD |
| 编辑项目 | 点击"编辑"按钮 | 打开编辑模态框，填充当前数据 |
| 删除项目 | 点击"删除"按钮 | 显示确认对话框，确认后调用delete API，跳转到列表页 |
| 返回列表 | 点击面包屑"项目管理" | 跳转到/projects |
| 字段级权限 | 其他部门用户编辑 | 基础信息字段只读，其他字段可编辑 |
| 金额计算 | 修改成本字段 | 自动计算总成本控制金额 |
| 进度计算 | 修改进度字段 | 自动更新进度百分比 |

---

#### TC-019: UserList 页面测试

| 用例ID | TC-019-01 |
|--------|-----------|
| 测试模块 | pages/UserList |
| 测试页面 | UserList |
| 优先级 | 高 |
| 前置条件 | 已登录，管理员角色 |

**测试场景**

| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 渲染用户列表 | 访问/users | 显示工具栏、用户表格、分页控件 |
| 加载用户列表 | 页面加载 | 调用getUsers API |
| 搜索用户 | 输入关键词，按Enter | 调用getUsers API，参数包含keyword |
| 按部门筛选 | 选择"市场部" | 调用getUsers API，参数department='市场部' |
| 按角色筛选 | 选择"admin" | 调用getUsers API，参数role='admin' |
| 新建用户 | 点击"新建用户"按钮 | 打开新建用户模态框 |
| 编辑用户 | 点击"编辑"按钮 | 调用getUserDetail API，填充表单，打开编辑模态框 |
| 用户名只读 | 编辑模式 | 用户名字段禁用，不可修改 |
| 删除用户 | 点击"删除"按钮 | 显示确认对话框，确认后调用delete API |
| 重置密码 | 点击"重置密码"按钮 | 显示重置密码对话框，输入新密码后调用resetPassword API |
| 权限控制 | 非管理员用户访问 | 重定向到403页或显示权限错误 |

---

### 2.2 交互流程测试

#### TC-020: 完整的项目创建流程测试

| 用例ID | TC-020-01 |
|--------|-----------|
| 测试模块 | 交互流程 |
| 测试场景 | 项目创建流程 |
| 优先级 | 高 |
| 前置条件 | 已登录，有创建项目权限 |

**测试步骤**

| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 访问/projects | 显示项目列表页 |
| 2 | 点击"新建项目"按钮 | 打开新建项目模态框 |
| 3 | 输入合同编号"PRJ001" | 输入框显示"PRJ001" |
| 4 | 输入项目名称"测试项目" | 输入框显示"测试项目" |
| 5 | 选择工程类别"基建" | 下拉框显示"基建" |
| 6 | 输入合同金额"100" | 输入框显示"100" |
| 7 | 选择签订日期"2026-01-25" | 日期选择器显示"2026-01-25" |
| 8 | 不输入必填字段，点击保存 | 显示必填字段验证错误 |
| 9 | 填写所有必填字段，点击保存 | 调用create API，loading=true |
| 10 | API返回成功 | 模态框关闭，列表刷新，显示成功提示"项目创建成功" |
| 11 | API返回失败（合同编号重复） | 显示错误提示"合同编号已存在" |
| 12 | 新项目出现在列表中 | 列表第一行显示新项目 |

---

#### TC-021: 完整的项目编辑流程测试

| 用例ID | TC-021-01 |
|--------|-----------|
| 测试模块 | 交互流程 |
| 测试场景 | 项目编辑流程 |
| 优先级 | 高 |
| 前置条件 | 已登录，有编辑权限 |

**测试步骤**

| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 访问/projects | 显示项目列表页 |
| 2 | 找到项目PRJ001 | 列表显示该项目 |
| 3 | 点击"编辑"按钮 | 调用getDetail API，打开编辑模态框 |
| 4 | 表单填充当前数据 | 输入框显示项目当前值 |
| 5 | 修改项目名称为"更新项目" | 输入框显示"更新项目" |
| 6 | 修改合同金额为"200" | 输入框显示"200" |
| 7 | 点击保存 | 调用update API，loading=true |
| 8 | API返回成功 | 模态框关闭，列表刷新，显示成功提示"项目更新成功" |
| 9 | API返回失败 | 显示错误提示 |
| 10 | 列表显示更新后的数据 | 项目名称和金额更新 |

---

#### TC-022: 完整的项目删除流程测试

| 用例ID | TC-022-01 |
|--------|-----------|
| 测试模块 | 交互流程 |
| 测试场景 | 项目删除流程 |
| 优先级 | 中 |
| 前置条件 | 已登录，有删除权限 |

**测试步骤**

| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 访问/projects | 显示项目列表页 |
| 2 | 找到项目PRJ001 | 列表显示该项目 |
| 3 | 点击"删除"按钮 | 显示确认对话框，内容"确定要删除该项目吗？" |
| 4 | 点击"取消"按钮 | 对话框关闭，项目不被删除 |
| 5 | 再次点击"删除"按钮 | 显示确认对话框 |
| 6 | 点击"确定"按钮 | 调用delete API |
| 7 | API返回成功 | 列表刷新，显示成功提示"项目删除成功"，项目从列表消失 |
| 8 | API返回失败 | 显示错误提示 |
| 9 | 项目不存在 | 显示404错误提示 |

---

## 3. E2E测试用例

### 3.1 认证流程E2E测试

#### TC-E2E-001: 登录流程E2E测试

| 用例ID | TC-E2E-001 |
|--------|-----------|
| 测试模块 | 登录流程 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 后端服务运行，测试账号存在 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 正常登录 | 1. 访问http://localhost:3000<br>2. 输入用户名"admin"<br>3. 输入密码"password123"<br>4. 点击登录按钮 | 1. 显示登录页<br>2. 自动跳转到/dashboard<br>3. 顶部导航显示用户信息"系统管理员(管理部)" |
| 登录失败 | 1. 访问登录页<br>2. 输入正确用户名<br>3. 输入错误密码<br>4. 点击登录按钮 | 1. 显示错误提示"用户名或密码错误"<br>2. 停留在登录页<br>3. 输入框保留输入值 |
| 字段验证 | 1. 访问登录页<br>2. 不输入任何内容<br>3. 点击登录按钮 | 1. 用户名和密码输入框显示红色边框<br>2. 显示"请输入用户名"和"请输入密码"提示<br>3. 登录按钮无法提交 |
| 记住密码 | 1. 勾选"记住密码"<br>2. 输入用户名和密码<br>3. 点击登录<br>4. 刷新页面 | 1. 用户名和密码自动填充<br>2. 复选框保持选中 |

---

#### TC-E2E-002: 登出流程E2E测试

| 用例ID | TC-E2E-002 |
|--------|-----------|
| 测试模块 | 登出流程 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 登出 | 1. 点击顶部导航"登出"按钮<br>2. 点击确认对话框"确定"按钮 | 1. 显示确认对话框"确定要退出登录吗？"<br>2. 跳转到/login<br>3. localStorage清空 |
| 取消登出 | 1. 点击"登出"按钮<br>2. 点击"取消"按钮 | 1. 对话框关闭<br>2. 保持在当前页面，保持登录状态 |

---

### 3.2 项目管理E2E测试

#### TC-E2E-003: 项目列表E2E测试

| 用例ID | TC-E2E-003 |
|--------|-----------|
| 测试模块 | 项目列表 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 浏览列表 | 1. 点击侧边栏"项目管理"<br>2. 等待页面加载<br>3. 查看项目表格 | 1. 跳转到/projects<br>2. 显示项目表格<br>3. 表格显示项目数据，每页10条 |
| 分页浏览 | 1. 点击"下一页"按钮<br>2. 点击第3页<br>3. 点击"上一页" | 1. 显示第2页数据<br>2. 显示第3页数据<br>3. 显示第2页数据 |
| 搜索项目 | 1. 在搜索框输入"基建"<br>2. 按Enter键 | 1. 表格只显示包含"基建"的项目<br>2. 显示搜索结果数量 |
| 筛选项目 | 1. 点击"工程类别"下拉框<br>2. 选择"基建"<br>3. 点击"签订日期"日期选择器<br>4. 选择日期范围<br>5. 点击"确定" | 1. 表格只显示基建项目<br>2. 签订日期在范围内的项目 |
| 清除筛选 | 1. 设置多个筛选条件<br>2. 点击"清除"按钮 | 1. 所有筛选条件清空<br>2. 列表显示所有数据 |

---

#### TC-E2E-004: 项目创建E2E测试

| 用例ID | TC-E2E-004 |
|--------|-----------|
| 测试模块 | 项目创建 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录，有创建项目权限 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 创建项目 | 1. 访问/projects<br>2. 点击"新建项目"按钮<br>3. 输入合同编号"PRJ999"<br>4. 输入项目名称"E2E测试项目"<br>5. 选择工程类别"基建"<br>6. 输入合同金额"100"<br>7. 选择签订日期"2026-01-25"<br>8. 点击"保存"按钮<br>9. 等待API响应 | 1. 打开新建项目模态框<br>2. 模态框标题"新建项目"<br>3-7. 所有输入框显示输入值<br>8. 按钮显示loading状态<br>9. 模态框关闭<br>10. 显示成功提示"项目创建成功"<br>11. 列表第一行显示新项目 |
| 验证失败创建 | 1. 点击"新建项目"<br>2. 不输入必填字段<br>3. 点击"保存" | 1. 必填字段显示红色边框<br>2. 显示错误提示<br>3. 模态框不关闭 |

---

#### TC-E2E-005: 项目编辑E2E测试

| 用例ID | TC-E2E-005 |
|--------|-----------|
| 测试模块 | 项目编辑 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录，有编辑权限 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 编辑项目 | 1. 访问/projects<br>2. 找到项目PRJ001<br>3. 点击"编辑"按钮<br>4. 修改项目名称为"E2E更新项目"<br>5. 修改合同金额为"200"<br>6. 点击"保存"按钮<br>7. 等待API响应 | 1. 打开编辑项目模态框<br>2. 模态框标题"编辑项目"<br>3. 表单填充当前数据<br>4-5. 输入框显示新值<br>6. 按钮显示loading状态<br>7. 模态框关闭<br>8. 显示成功提示"项目更新成功"<br>9. 列表显示更新后的数据 |
| 权限限制编辑 | 1. 其他部门用户登录<br>2. 编辑项目PRJ001<br>3. 查看表单字段 | 1. 基础信息字段（合同编号、项目名称等）只读<br>2. 成本、财务等字段可编辑 |

---

#### TC-E2E-006: 项目删除E2E测试

| 用例ID | TC-E2E-006 |
|--------|-----------|
| 测试模块 | 项目删除 |
| 测试类型 | E2E |
| 优先级 | 中 |
| 前置条件 | 已登录，有删除权限 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 删除项目 | 1. 访问/projects<br>2. 找到项目PRJ999<br>3. 点击"删除"按钮<br>4. 查看确认对话框内容<br>5. 点击"确定"按钮<br>6. 等待API响应 | 1. 显示确认对话框<br>2. 对话框内容"确定要删除该项目吗？"<br>3. 显示项目名称和合同编号<br>4. 点击"确定"后<br>5. 显示成功提示"项目删除成功"<br>6. 列表刷新，项目消失 |
| 取消删除 | 1. 点击"删除"按钮<br>2. 点击"取消"按钮 | 1. 对话框关闭<br>2. 项目不被删除，仍在列表中 |

---

### 3.3 用户管理E2E测试

#### TC-E2E-007: 用户列表E2E测试

| 用例ID | TC-E2E-007 |
|--------|-----------|
| 测试模块 | 用户列表 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录，管理员角色 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 浏览用户列表 | 1. 点击侧边栏"用户管理"<br>2. 等待页面加载 | 1. 跳转到/users<br>2. 显示用户表格<br>3. 表格显示所有用户 |
| 搜索用户 | 1. 在搜索框输入"admin"<br>2. 按Enter键 | 1. 表格只显示匹配的用户 |
| 按部门筛选 | 1. 点击"部门"下拉框<br>2. 选择"市场部" | 1. 表格只显示市场部用户 |
| 按角色筛选 | 1. 点击"角色"下拉框<br>2. 选择"admin" | 1. 表格只显示管理员用户 |

---

#### TC-E2E-008: 用户创建E2E测试

| 用例ID | TC-E2E-008 |
|--------|-----------|
| 测试模块 | 用户创建 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录，管理员角色 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 创建用户 | 1. 访问/users<br>2. 点击"新建用户"按钮<br>3. 输入用户名"testuser"<br>4. 输入密码"password123"<br>5. 输入真实姓名"测试用户"<br>6. 选择部门"管理部"<br>7. 选择角色"admin"<br>8. 点击"保存"按钮<br>9. 等待API响应 | 1. 打开新建用户模态框<br>2. 模态框标题"新建用户"<br>3-7. 所有输入框显示输入值<br>8. 按钮显示loading状态<br>9. 模态框关闭<br>10. 显示成功提示"用户创建成功"<br>11. 列表显示新用户 |
| 用户名重复 | 1. 创建用户，用户名"admin"<br>2. 点击"保存" | 1. 显示错误提示"用户名已存在"<br>2. 模态框不关闭 |

---

#### TC-E2E-009: 用户编辑E2E测试

| 用例ID | TC-E2E-009 |
|--------|-----------|
| 测试模块 | 用户编辑 |
| 测试类型 | E2E |
| 优先级 | 中 |
| 前置条件 | 已登录，管理员角色 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 编辑用户 | 1. 访问/users<br>2. 找到用户testuser<br>3. 点击"编辑"按钮<br>4. 修改真实姓名为"测试用户更新"<br>5. 修改邮箱为新邮箱<br>6. 点击"保存"按钮<br>7. 等待API响应 | 1. 打开编辑用户模态框<br>2. 用户名字段只读<br>3. 其他字段可编辑<br>4-5. 输入框显示新值<br>6. 按钮显示loading状态<br>7. 模态框关闭<br>8. 显示成功提示<br>9. 列表显示更新后的数据 |

---

#### TC-E2E-010: 用户删除E2E测试

| 用例ID | TC-E2E-010 |
|--------|-----------|
| 测试模块 | 用户删除 |
| 测试类型 | E2E |
| 优先级 | 中 |
| 前置条件 | 已登录，管理员角色 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 删除用户 | 1. 访问/users<br>2. 找到用户testuser<br>3. 点击"删除"按钮<br>4. 点击"确定"按钮<br>5. 等待API响应 | 1. 显示确认对话框<br>2. 点击"确定"后<br>3. 显示成功提示"用户删除成功"<br>4. 列表刷新，用户消失 |

---

#### TC-E2E-011: 重置密码E2E测试

| 用例ID | TC-E2E-011 |
|--------|-----------|
| 测试模块 | 重置密码 |
| 测试类型 | E2E |
| 优先级 | 中 |
| 前置条件 | 已登录，管理员角色 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 重置密码 | 1. 访问/users<br>2. 找到用户testuser<br>3. 点击"重置密码"按钮<br>4. 输入新密码"newpassword123"<br>5. 点击"确定"按钮<br>6. 等待API响应 | 1. 显示重置密码对话框<br>2. 输入框显示新密码<br>3. 点击"确定"后<br>4. 显示成功提示"密码重置成功" |

---

### 3.4 统计分析E2E测试

#### TC-E2E-012: 仪表盘E2E测试

| 用例ID | TC-E2E-012 |
|--------|-----------|
| 测试模块 | 仪表盘 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 查看仪表盘 | 1. 访问/dashboard<br>2. 等待页面加载 | 1. 显示4个统计卡片<br>2. 显示项目趋势图<br>3. 显示最近项目列表 |
| 查看统计卡片 | 1. 查看统计卡片数据 | 1. 项目总数、进行中、已完成、总合同金额<br>2. 数值格式化（千分位、保留两位小数） |
| 查看图表 | 1. 鼠标悬停在图表上 | 1. 显示Tooltip，显示详细数据 |
| 点击最近项目 | 1. 点击最近项目列表项 | 1. 跳转到项目详情页 |
| 刷新数据 | 1. 点击刷新按钮 | 1. 重新获取所有数据 |

---

#### TC-E2E-013: 项目统计E2E测试

| 用例ID | TC-E2E-013 |
|--------|-----------|
| 测试模块 | 项目统计 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录 |

**测试场景**

| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 查看统计页 | 1. 点击侧边栏"项目统计"<br>2. 等待页面加载 | 1. 显示筛选工具栏<br>2. 显示统计图表<br>3. 显示分组统计表格 |
| 基础统计 | 1. 查看基础统计卡片 | 1. 显示8个统计卡片<br>2. 数值格式化 |
| 分组统计 | 1. 查看按工程类别分组 | 1. 显示饼图和柱状图<br>2. 显示分组统计表格 |
| 时间维度统计 | 1. 选择时间维度"按月"<br>2. 查看折线图 | 1. 显示每月项目数量和金额趋势 |
| 筛选统计 | 1. 选择工程类别"基建"<br>2. 选择日期范围 | 1. 所有统计只显示符合条件的数据 |
| 导出报表 | 1. 点击"导出报表"按钮<br>2. 等待下载完成 | 1. 下载Excel文件<br>2. 文件名"项目统计_YYYY-MM-DD.xlsx" |

---

## 4. 测试数据准备

### 4.1 测试用户数据

```javascript
const testUsers = [
  {
    id: 1,
    username: 'admin',
    password: 'password123',
    real_name: '系统管理员',
    department: '管理部',
    role: 'admin',
    email: 'admin@example.com',
    is_active: true,
  },
  {
    id: 2,
    username: 'market',
    password: 'password123',
    real_name: '市场部用户',
    department: '市场部',
    role: 'market',
    email: 'market@example.com',
    is_active: true,
  },
  {
    id: 3,
    username: 'other',
    password: 'password123',
    real_name: '其他部门用户',
    department: '技术部',
    role: 'other',
    email: 'other@example.com',
    is_active: true,
  },
];
```

### 4.2 测试项目数据

```javascript
const testProjects = [
  {
    id: 1,
    project_no: 'PRJ001',
    name: '某电力基建工程项目',
    engineering_type: '基建',
    contract_amount: 950.00,
    signing_date: '2026-01-01',
    start_date: '2026-01-15',
    planned_end_date: '2026-12-31',
    project_department: '项目部一',
    project_leader: '李四',
    created_by: 2,
  },
  {
    id: 2,
    project_no: 'PRJ002',
    name: '业扩工程项目',
    engineering_type: '业扩',
    contract_amount: 500.00,
    signing_date: '2026-01-10',
    start_date: '2026-01-20',
    planned_end_date: '2026-06-30',
    project_department: '项目部二',
    project_leader: '王五',
    created_by: 2,
  },
];
```

---

## 5. 测试覆盖率目标

| 类型 | 目标覆盖率 |
|------|----------|
| 语句覆盖率 | 80% |
| 分支覆盖率 | 75% |
| 函数覆盖率 | 80% |
| 行覆盖率 | 80% |

---

**文档维护**: 前端程序员
**文档类型**: 测试用例文档
**最后更新**: 2026-01-25
