文件夹整理
This commit is contained in:
223
openspec/changes/add-employee-info-ui/design.md
Normal file
223
openspec/changes/add-employee-info-ui/design.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# Design: 员工信息管理前端设计
|
||||
|
||||
## 页面布局
|
||||
|
||||
### 主页面布局
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 信息维护 > 员工信息管理 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ 搜索区: [姓名] [柜员号] [所属部门] [身份证号] [状态▼] │ │
|
||||
│ │ [搜索] [重置] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ [新增] [导入] [搜索框展开] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ ┌───┬─────┬───────┬───────┬─────────┬─────┬─────────┐ │ │
|
||||
│ │ │□ │姓名 │柜员号 │身份证号│所属部门 │状态 │ 操作 │ │ │
|
||||
│ │ ├───┼─────┼───────┼───────┼─────────┼─────┼─────────┤ │ │
|
||||
│ │ │□ │张三 │001 │110... │总部 │在职 │详情|编辑│ │ │
|
||||
│ │ │ │ │ │ │ │ │删除 │ │ │
|
||||
│ │ └───┴─────┴───────┴───────┴─────────┴─────┴─────────┘ │ │
|
||||
│ │ < 1 2 3 4 5 ... 10 > │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 新增/编辑弹窗布局
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 新增员工 / 编辑员工 [X]│
|
||||
├─────────────────────────────────────────────┤
|
||||
│ ┌───────────────────────────────────────┐ │
|
||||
│ │ 姓名: [_____________] │ │
|
||||
│ │ 柜员号: [_____________] │ │
|
||||
│ │ 所属部门: [请选择部门 ▼] │ │
|
||||
│ │ 身份证号: [__________________] │ │
|
||||
│ │ 电话: [_____________] │ │
|
||||
│ │ 入职时间: [_______________] │ │
|
||||
│ │ 状态: ( ) 在职 ( ) 离职 │ │
|
||||
│ └───────────────────────────────────────┘ │
|
||||
│ ┌───────────────────────────────────────┐ │
|
||||
│ │ 亲属信息 [+ 添加亲属] │ │
|
||||
│ │ ┌───────────────────────────────────┐ │ │
|
||||
│ │ │ 亲属姓名 | 身份证号 | 电话 | 关系 │ │ │
|
||||
│ │ │ 李四 | 110... | 138..| 配偶 │ │ │
|
||||
│ │ │ | [删除] | | │ │ │
|
||||
│ │ └───────────────────────────────────┘ │ │
|
||||
│ └───────────────────────────────────────┘ │
|
||||
│ [确定] [取消] │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 详情弹窗布局
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 员工详情 [X]│
|
||||
├─────────────────────────────────────────────┤
|
||||
│ ┌───────────────────────────────────────┐ │
|
||||
│ │ 姓名: 张三 │ │
|
||||
│ │ 柜员号: 001 │ │
|
||||
│ │ 所属部门: 研发部门 │ │
|
||||
│ │ 身份证号: 110101199001011234 │ │
|
||||
│ │ 电话: 13800138000 │ │
|
||||
│ │ 入职时间: 2020-01-01 │ │
|
||||
│ │ 状态: 在职 │ │
|
||||
│ │ 创建时间: 2026-01-28 10:00:00 │ │
|
||||
│ └───────────────────────────────────────┘ │
|
||||
│ ┌───────────────────────────────────────┐ │
|
||||
│ │ 亲属信息 │ │
|
||||
│ │ ┌───────────────────────────────────┐ │ │
|
||||
│ │ │ 亲属姓名 │ 身份证号 │ 电话 │关系│ │ │
|
||||
│ │ │ 李四 │ 110101... │ 138..│配偶│ │ │
|
||||
│ │ │ │ │ │ │ │ │
|
||||
│ │ └───────────────────────────────────┘ │ │
|
||||
│ └───────────────────────────────────────┘ │
|
||||
│ [关闭] │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 组件结构
|
||||
|
||||
### 文件结构
|
||||
```
|
||||
ruoyi-ui/src/
|
||||
├── api/
|
||||
│ └── dpcEmployee.js # API 接口定义
|
||||
└── views/
|
||||
└── dpcEmployee/
|
||||
└── index.vue # 主页面组件
|
||||
```
|
||||
|
||||
### 数据模型
|
||||
|
||||
#### 员工列表项
|
||||
```javascript
|
||||
{
|
||||
employeeId: Number,
|
||||
name: String,
|
||||
tellerNo: String,
|
||||
deptId: Number, // 所属部门ID
|
||||
deptName: String, // 所属部门名称
|
||||
idCard: String,
|
||||
phone: String,
|
||||
hireDate: String,
|
||||
status: String,
|
||||
statusDesc: String,
|
||||
createTime: String
|
||||
}
|
||||
```
|
||||
|
||||
#### 员工表单
|
||||
```javascript
|
||||
{
|
||||
employeeId: Number | null,
|
||||
name: String,
|
||||
tellerNo: String,
|
||||
deptId: Number, // 所属部门ID
|
||||
idCard: String,
|
||||
phone: String,
|
||||
hireDate: String,
|
||||
status: String,
|
||||
relatives: Array<{
|
||||
relativeId: Number | null,
|
||||
relativeName: String,
|
||||
relativeIdCard: String,
|
||||
relativePhone: String,
|
||||
relationship: String
|
||||
}>
|
||||
}
|
||||
```
|
||||
|
||||
### API 接口
|
||||
|
||||
| 接口名 | 方法 | 路径 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| listEmployee | GET | /dpc/employee/list | 查询员工列表 |
|
||||
| getEmployee | GET | /dpc/employee/{employeeId} | 获取员工详情 |
|
||||
| addEmployee | POST | /dpc/employee | 新增员工 |
|
||||
| updateEmployee | PUT | /dpc/employee | 编辑员工 |
|
||||
| delEmployee | DELETE | /dpc/employee/{employeeIds} | 删除员工 |
|
||||
| importTemplate | POST | /dpc/employee/importTemplate | 下载导入模板 |
|
||||
| importData | POST | /dpc/employee/importData | 导入员工信息 |
|
||||
|
||||
## 交互设计
|
||||
|
||||
### 1. 列表页面
|
||||
- **默认状态**: 显示所有员工列表,默认分页大小 10
|
||||
- **搜索**: 支持按姓名(模糊)、柜员号(精确)、所属部门、身份证号(精确)、状态筛选
|
||||
- **所属部门筛选**: 使用部门树选择器
|
||||
- **新增**: 点击"新增"按钮打开新增弹窗
|
||||
- **编辑**: 点击"编辑"按钮打开编辑弹窗
|
||||
- **详情**: 点击"详情"按钮打开详情弹窗
|
||||
- **删除**: 点击"删除"按钮,弹出确认对话框后删除
|
||||
- **导入**: 点击"导入"按钮,打开导入弹窗
|
||||
|
||||
### 2. 新增/编辑弹窗
|
||||
- **所属部门**: 使用部门树选择器(`el-tree-select`),存储 `dept_id`
|
||||
- **亲属管理**:
|
||||
- 点击"+ 添加亲属"添加空行
|
||||
- 点击"删除"移除对应亲属行
|
||||
- 亲属姓名、关系为必填
|
||||
- **表单验证**:
|
||||
- 姓名: 必填,最大100字符
|
||||
- 柜员号: 必填,最大50字符
|
||||
- 所属部门: 选填
|
||||
- 身份证号: 必填,18位格式校验
|
||||
- 电话: 选填,11位手机号格式
|
||||
- 亲属姓名: 必填
|
||||
- 亲属关系: 必填
|
||||
|
||||
### 3. 详情弹窗
|
||||
- 只读展示员工信息和亲属列表
|
||||
- 无亲属时显示"暂无亲属信息"
|
||||
|
||||
### 4. 导入弹窗
|
||||
- 支持拖拽上传
|
||||
- 支持"更新已存在数据"选项
|
||||
- 显示导入结果消息
|
||||
|
||||
## 表单验证规则
|
||||
|
||||
### 员工基本信息
|
||||
| 字段 | 验证规则 |
|
||||
|------|----------|
|
||||
| 姓名 | 必填,最大100字符 |
|
||||
| 柜员号 | 必填,最大50字符 |
|
||||
| 身份证号 | 必填,18位,符合国标 |
|
||||
| 电话 | 选填,11位手机号 |
|
||||
| 入职时间 | 选填,日期格式 |
|
||||
| 状态 | 必填,0或1 |
|
||||
|
||||
### 亲属信息
|
||||
| 字段 | 验证规则 |
|
||||
|------|----------|
|
||||
| 亲属姓名 | 必填 |
|
||||
| 亲属身份证号 | 选填 |
|
||||
| 亲属电话 | 选填,11位手机号 |
|
||||
| 与员工关系 | 必填 |
|
||||
|
||||
## 权限控制
|
||||
|
||||
| 按钮 | 权限标识 |
|
||||
|------|----------|
|
||||
| 新增 | dpc:employee:add |
|
||||
| 编辑 | dpc:employee:edit |
|
||||
| 删除 | dpc:employee:remove |
|
||||
| 详情 | dpc:employee:query |
|
||||
| 导入 | dpc:employee:import |
|
||||
|
||||
## 技术实现要点
|
||||
|
||||
1. **亲属子表单**: 使用 `v-for` 动态渲染亲属列表,支持增删操作
|
||||
2. **部门选择器**: 使用 Element UI 的 `el-tree-select` 组件,关联 `sys_dept` 表
|
||||
3. **日期选择器**: 使用 Element UI 的 `el-date-picker`
|
||||
4. **状态下拉**: 使用 `el-select` 或 `el-radio-group`
|
||||
5. **文件上传**: 使用 Element UI 的 `el-upload`
|
||||
6. **分页**: 使用若依框架的 `pagination` 组件
|
||||
|
||||
## 参考
|
||||
- 中介库管理页面: `ruoyi-ui/src/views/dpcIntermediary/index.vue`
|
||||
- 若依用户管理页面: `ruoyi-ui/src/views/system/user/index.vue`
|
||||
119
openspec/changes/add-employee-info-ui/proposal.md
Normal file
119
openspec/changes/add-employee-info-ui/proposal.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Proposal: 添加员工信息管理前端页面
|
||||
|
||||
## Change ID
|
||||
`add-employee-info-ui`
|
||||
|
||||
## Summary
|
||||
为员工信息管理模块开发前端页面,实现员工及其亲属信息的可视化管理。包括员工列表查询、新增/编辑员工(支持亲属管理)、详情查看、Excel 导入等功能。页面将放置在"信息维护"菜单下。
|
||||
|
||||
## Motivation
|
||||
员工信息管理的后端 API 已在 `add-employee-info` 变更中完成开发。现在需要开发前端页面以提供用户交互界面。
|
||||
|
||||
用户需要:
|
||||
1. 可视化查看和管理员工信息列表
|
||||
2. 创建员工时同时维护亲属信息
|
||||
3. 查看员工详情时展示所有亲属
|
||||
4. 支持批量导入员工数据
|
||||
5. 在"信息维护"菜单下访问此功能
|
||||
|
||||
## Scope
|
||||
本提案实现以下前端功能:
|
||||
|
||||
### 包含的功能
|
||||
- **员工列表页面**
|
||||
- 搜索筛选区(姓名、柜员号、所属部门、身份证号、状态)
|
||||
- 员工列表表格(分页显示)
|
||||
- 操作按钮(新增、导入)
|
||||
- 部门树选择器
|
||||
|
||||
- **新增员工弹窗**
|
||||
- 员工基本信息表单
|
||||
- 所属部门选择(关联 sys_dept 表)
|
||||
- 亲属列表子表单(支持动态增删)
|
||||
- 表单验证
|
||||
|
||||
- **编辑员工弹窗**
|
||||
- 员工基本信息表单
|
||||
- 所属部门选择
|
||||
- 亲属列表子表单(支持动态增删)
|
||||
- 表单验证
|
||||
|
||||
- **员工详情弹窗**
|
||||
- 员工基本信息展示(只读)
|
||||
- 显示部门名称
|
||||
- 亲属列表展示(只读)
|
||||
|
||||
- **导入功能**
|
||||
- 导入弹窗
|
||||
- Excel 文件上传
|
||||
- 导入结果反馈
|
||||
- 模板下载
|
||||
|
||||
### 明确排除
|
||||
- 菜单数据的数据库插入(需要前端开发完成后手动配置)
|
||||
- 员工信息与其他模块的联动(属于后续功能)
|
||||
|
||||
## Proposed Design
|
||||
详见 [design.md](./design.md)
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### 选项1:参考中介库管理页面实现
|
||||
**优点**:
|
||||
- 保持项目内 UI/UX 一致性
|
||||
- 复用已有的代码模式
|
||||
- 开发效率高
|
||||
|
||||
**缺点**:
|
||||
- 需要额外实现亲属子表单功能
|
||||
|
||||
**决定**:采用。参考 `dpcIntermediary` 页面的实现模式,在此基础上增加亲属子表单功能。
|
||||
|
||||
### 选项2:使用若依代码生成器
|
||||
**优点**:
|
||||
- 快速生成基础 CRUD 页面
|
||||
|
||||
**缺点**:
|
||||
- 生成代码需要大量调整以支持亲属子表单
|
||||
- 与现有前端风格可能不一致
|
||||
|
||||
**决定**:不采用。手动开发可以更好地实现亲属子表单功能,保持与中介库页面一致的风格。
|
||||
|
||||
## Impact
|
||||
|
||||
### 前端影响
|
||||
- 新增视图:`ruoyi-ui/src/views/dpcEmployee/`
|
||||
- `index.vue` - 员工列表页面
|
||||
- 新增 API:`ruoyi-ui/src/api/dpcEmployee.js`
|
||||
|
||||
### 后端影响
|
||||
无,后端 API 已在 `add-employee-info` 中完成。
|
||||
|
||||
### 数据库影响
|
||||
- 需要在 `sys_menu` 表中插入菜单数据(开发完成后手动配置)
|
||||
|
||||
## Dependencies
|
||||
- 依赖 `add-employee-info` 变更完成后端 API
|
||||
- 依赖若依框架的前端组件(Element UI)
|
||||
|
||||
## Related Changes
|
||||
- `add-employee-info` - 提供后端 API 接口
|
||||
|
||||
## Open Questions
|
||||
无
|
||||
|
||||
## Success Criteria
|
||||
- [ ] 用户可以在"信息维护"菜单下访问员工信息管理页面
|
||||
- [ ] 用户可以查看员工列表并支持分页
|
||||
- [ ] 用户可以按多种条件搜索员工
|
||||
- [ ] 用户可以新增员工并添加亲属
|
||||
- [ ] 用户可以编辑员工信息和亲属
|
||||
- [ ] 用户可以查看员工详情和亲属列表
|
||||
- [ ] 用户可以删除员工
|
||||
- [ ] 用户可以下载 Excel 导入模板
|
||||
- [ ] 用户可以导入员工数据
|
||||
|
||||
## References
|
||||
- [员工信息管理 API 文档](../../../doc/员工信息管理API文档.md)
|
||||
- [模块设计文档 - 信息维护模块](../../../doc/modules/03-信息维护模块.md)
|
||||
- [add-employee-info 变更](../add-employee-info/)
|
||||
@@ -0,0 +1,475 @@
|
||||
# Spec: 员工信息管理前端用户界面
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 前端SHALL提供员工信息列表页面
|
||||
|
||||
前端MUST提供员工信息列表页面,允许用户查看、搜索和管理员工信息。
|
||||
|
||||
#### Scenario: 页面加载时显示员工列表
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**When** 用户访问员工信息管理页面
|
||||
**Then** 页面应显示员工列表
|
||||
**And** 列表应默认显示第1页,每页10条记录
|
||||
**And** 页面应显示搜索筛选区和操作按钮
|
||||
|
||||
#### Scenario: 列表显示正确的员工信息
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**When** 系统返回员工列表数据
|
||||
**Then** 列表应显示以下列:姓名、柜员号、身份证号、所属部门、电话、状态、操作
|
||||
**And** 状态列应显示"在职"或"离职"的文字描述
|
||||
**And** 操作列应显示"详情"、"编辑"、"删除"按钮
|
||||
|
||||
#### Scenario: 列表支持分页
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**And** 系统中存在25条员工记录
|
||||
**When** 用户访问员工信息管理页面
|
||||
**Then** 页面应显示分页控件
|
||||
**And** 默认显示第1页的10条记录
|
||||
**When** 用户点击第2页
|
||||
**Then** 页面应显示第11-20条记录
|
||||
|
||||
#### Scenario: 搜索筛选区默认展开
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**When** 用户访问员工信息管理页面
|
||||
**Then** 搜索筛选区应默认显示
|
||||
**And** 搜索筛选区应包含:姓名输入框、柜员号输入框、所属部门选择器、身份证号输入框、状态下拉框
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 前端SHALL支持多条件搜索员工
|
||||
|
||||
前端MUST支持用户通过多种条件组合搜索员工信息。
|
||||
|
||||
#### Scenario: 按姓名模糊搜索
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**And** 系统中存在姓名为"张三"的员工
|
||||
**When** 用户在姓名输入框输入"张"
|
||||
**And** 点击"搜索"按钮
|
||||
**Then** 列表应仅显示姓名中包含"张"的员工记录
|
||||
**And** 分页控件应显示匹配的记录总数
|
||||
|
||||
#### Scenario: 按柜员号精确搜索
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**And** 系统中存在柜员号为"001"的员工
|
||||
**When** 用户在柜员号输入框输入"001"
|
||||
**And** 点击"搜索"按钮
|
||||
**Then** 列表应仅显示柜员号为"001"的员工记录
|
||||
|
||||
#### Scenario: 按状态筛选
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**And** 系统中存在在职和离职两种状态的员工
|
||||
**When** 用户在状态下拉框选择"在职"
|
||||
**And** 点击"搜索"按钮
|
||||
**Then** 列表应仅显示状态为"在职"的员工记录
|
||||
|
||||
#### Scenario: 按所属部门筛选
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**And** 系统中存在多个部门的员工
|
||||
**When** 用户在所属部门选择器中选择"研发部门"
|
||||
**And** 点击"搜索"按钮
|
||||
**Then** 列表应仅显示该部门的员工记录
|
||||
|
||||
#### Scenario: 组合条件搜索
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**When** 用户同时输入姓名"张"、柜员号"001"
|
||||
**And** 点击"搜索"按钮
|
||||
**Then** 列表应仅显示同时满足所有条件的记录
|
||||
|
||||
#### Scenario: 重置搜索条件
|
||||
|
||||
**Given** 用户已输入多个搜索条件并执行了搜索
|
||||
**When** 用户点击"重置"按钮
|
||||
**Then** 所有搜索条件输入框应清空
|
||||
**And** 列表应显示所有员工记录(恢复默认)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 前端SHALL提供新增员工弹窗
|
||||
|
||||
前端MUST提供新增员工弹窗,允许用户输入员工基本信息并添加亲属信息。
|
||||
|
||||
#### Scenario: 点击新增按钮打开弹窗
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**Then** 应打开新增员工弹窗
|
||||
**And** 弹窗标题为"添加员工"
|
||||
**And** 所有表单字段应为空
|
||||
**And** 亲属列表应为空
|
||||
|
||||
#### Scenario: 填写员工基本信息
|
||||
|
||||
**Given** 新增员工弹窗已打开
|
||||
**When** 用户填写姓名为"张三"
|
||||
**And** 填写柜员号为"001"
|
||||
**And** 在所属部门选择器中选择"研发部门"
|
||||
**And** 填写身份证号为"110101199001011234"
|
||||
**And** 填写电话为"13800138000"
|
||||
**And** 选择入职时间为"2020-01-01"
|
||||
**And** 选择状态为"在职"
|
||||
**Then** 表单应正确显示填写的信息
|
||||
|
||||
#### Scenario: 添加单个亲属
|
||||
|
||||
**Given** 新增员工弹窗已打开
|
||||
**And** 用户已填写员工基本信息
|
||||
**When** 用户点击"+ 添加亲属"按钮
|
||||
**Then** 亲属列表应新增一个空行
|
||||
**When** 用户填写亲属姓名为"李四"
|
||||
**And** 填写亲属身份证号为"110101199001011235"
|
||||
**And** 填写亲属电话为"13800138001"
|
||||
**And** 选择关系为"配偶"
|
||||
**Then** 亲属列表应显示该亲属信息
|
||||
|
||||
#### Scenario: 添加多个亲属
|
||||
|
||||
**Given** 新增员工弹窗已打开
|
||||
**And** 用户已填写员工基本信息
|
||||
**When** 用户点击3次"+ 添加亲属"按钮
|
||||
**And** 填写3个亲属的信息
|
||||
**Then** 亲属列表应显示3个亲属
|
||||
**And** 每个亲属应有独立的删除按钮
|
||||
|
||||
#### Scenario: 删除亲属
|
||||
|
||||
**Given** 新增员工弹窗已打开
|
||||
**And** 亲属列表中存在2个亲属
|
||||
**When** 用户点击第一个亲属的"删除"按钮
|
||||
**Then** 亲属列表应仅显示1个亲属
|
||||
**And** 被删除的亲属应从列表中移除
|
||||
|
||||
#### Scenario: 提交成功
|
||||
|
||||
**Given** 新增员工弹窗已打开
|
||||
**And** 用户已填写所有必填信息
|
||||
**When** 用户点击"确定"按钮
|
||||
**Then** 系统应发送请求到后端
|
||||
**And** 弹窗应关闭
|
||||
**And** 应显示"新增成功"的提示消息
|
||||
**And** 列表应刷新并显示新增的记录
|
||||
|
||||
#### Scenario: 表单验证-姓名为空
|
||||
|
||||
**Given** 新增员工弹窗已打开
|
||||
**When** 用户不填写姓名
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应显示"姓名不能为空"的错误提示
|
||||
**And** 弹窗应保持打开状态
|
||||
**And** 不发送请求到后端
|
||||
|
||||
#### Scenario: 表单验证-身份证号格式错误
|
||||
|
||||
**Given** 新增员工弹窗已打开
|
||||
**When** 用户填写身份证号为"123"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应显示"身份证号格式不正确"的错误提示
|
||||
**And** 弹窗应保持打开状态
|
||||
|
||||
#### Scenario: 亲属姓名为空验证
|
||||
|
||||
**Given** 新增员工弹窗已打开
|
||||
**And** 用户已添加一个亲属但未填写亲属姓名
|
||||
**When** 用户点击"确定"按钮
|
||||
**Then** 系统应显示"亲属姓名不能为空"的错误提示
|
||||
|
||||
#### Scenario: 取消新增
|
||||
|
||||
**Given** 新增员工弹窗已打开
|
||||
**And** 用户已填写部分信息
|
||||
**When** 用户点击"取消"按钮
|
||||
**Then** 弹窗应关闭
|
||||
**And** 不保存任何数据
|
||||
**And** 列表应保持原状态
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 前端SHALL提供编辑员工弹窗
|
||||
|
||||
前端MUST提供编辑员工弹窗,允许用户修改员工信息和亲属信息。
|
||||
|
||||
#### Scenario: 点击编辑按钮打开弹窗
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:edit` 权限
|
||||
**And** 列表中存在一条员工记录
|
||||
**When** 用户点击该记录的"编辑"按钮
|
||||
**Then** 应打开编辑员工弹窗
|
||||
**And** 弹窗标题为"修改员工"
|
||||
**And** 表单应显示该员工的现有信息
|
||||
**And** 亲属列表应显示该员工的所有亲属
|
||||
|
||||
#### Scenario: 修改员工基本信息
|
||||
|
||||
**Given** 编辑员工弹窗已打开
|
||||
**When** 用户修改姓名为"李四"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应发送更新请求到后端
|
||||
**And** 弹窗应关闭
|
||||
**And** 应显示"修改成功"的提示消息
|
||||
**And** 列表应刷新并显示更新后的姓名
|
||||
|
||||
#### Scenario: 编辑时新增亲属
|
||||
|
||||
**Given** 编辑员工弹窗已打开
|
||||
**And** 该员工当前有1个亲属
|
||||
**When** 用户点击"+ 添加亲属"按钮
|
||||
**And** 填写新亲属信息
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应发送更新请求到后端
|
||||
**And** 再次打开编辑弹窗时应显示2个亲属
|
||||
|
||||
#### Scenario: 编辑时删除亲属
|
||||
|
||||
**Given** 编辑员工弹窗已打开
|
||||
**And** 该员工当前有2个亲属
|
||||
**When** 用户点击其中一个亲属的"删除"按钮
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应发送更新请求到后端
|
||||
**And** 再次打开编辑弹窗时应仅显示1个亲属
|
||||
|
||||
#### Scenario: 编辑时修改亲属信息
|
||||
|
||||
**Given** 编辑员工弹窗已打开
|
||||
**And** 该员工有亲属"李四"
|
||||
**When** 用户修改亲属姓名为"王五"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应发送更新请求到后端
|
||||
**And** 再次打开编辑弹窗时应显示亲属姓名为"王五"
|
||||
|
||||
#### Scenario: 编辑时清除所有亲属
|
||||
|
||||
**Given** 编辑员工弹窗已打开
|
||||
**And** 该员工有亲属信息
|
||||
**When** 用户逐个删除所有亲属
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应发送更新请求到后端
|
||||
**And** 再次打开编辑弹窗时亲属列表应为空
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 前端SHALL提供员工详情弹窗
|
||||
|
||||
前端MUST提供员工详情弹窗,以只读方式展示员工完整信息和亲属列表。
|
||||
|
||||
#### Scenario: 点击详情按钮打开弹窗
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:query` 权限
|
||||
**And** 列表中存在一条员工记录
|
||||
**When** 用户点击该记录的"详情"按钮
|
||||
**Then** 应打开员工详情弹窗
|
||||
**And** 弹窗标题为"员工详情"
|
||||
**And** 所有字段应为只读状态
|
||||
|
||||
#### Scenario: 显示员工基本信息
|
||||
|
||||
**Given** 员工详情弹窗已打开
|
||||
**Then** 应显示以下信息:姓名、柜员号、所属部门、身份证号、电话、入职时间、状态、创建时间
|
||||
**And** 状态应显示为"在职"或"离职"的文字描述
|
||||
**And** 所有字段应不可编辑
|
||||
|
||||
#### Scenario: 显示亲属列表
|
||||
|
||||
**Given** 员工详情弹窗已打开
|
||||
**And** 该员工有2个亲属
|
||||
**Then** 亲属列表应显示2个亲属
|
||||
**And** 每个亲属应显示:亲属姓名、亲属身份证号、亲属电话、与员工关系
|
||||
**And** 亲属列表应不可编辑
|
||||
|
||||
#### Scenario: 无亲属时显示提示
|
||||
|
||||
**Given** 员工详情弹窗已打开
|
||||
**And** 该员工无亲属信息
|
||||
**Then** 亲属列表区域应显示"暂无亲属信息"的提示
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 前端SHALL支持删除员工
|
||||
|
||||
前端MUST支持删除员工操作,包括单个删除和批量删除。
|
||||
|
||||
#### Scenario: 删除单条记录
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:remove` 权限
|
||||
**And** 列表中存在一条员工记录
|
||||
**When** 用户点击该记录的"删除"按钮
|
||||
**Then** 应弹出确认对话框,提示是否确认删除
|
||||
**When** 用户确认删除
|
||||
**Then** 系统应发送删除请求到后端
|
||||
**And** 应显示"删除成功"的提示消息
|
||||
**And** 列表应刷新,该记录应从列表中移除
|
||||
|
||||
#### Scenario: 取消删除
|
||||
|
||||
**Given** 用户已点击"删除"按钮
|
||||
**And** 确认对话框已显示
|
||||
**When** 用户点击"取消"按钮
|
||||
**Then** 对话框应关闭
|
||||
**And** 不发送删除请求
|
||||
**And** 记录应保留在列表中
|
||||
|
||||
#### Scenario: 批量删除
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:remove` 权限
|
||||
**And** 列表中存在多条员工记录
|
||||
**When** 用户勾选3条记录
|
||||
**And** 点击"删除"按钮
|
||||
**And** 确认删除
|
||||
**Then** 系统应发送批量删除请求到后端
|
||||
**And** 应显示"删除成功"的提示消息
|
||||
**And** 列表应刷新,被删除的记录应从列表中移除
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 前端SHALL支持导入员工数据
|
||||
|
||||
前端MUST支持通过 Excel 文件批量导入员工数据。
|
||||
|
||||
#### Scenario: 点击导入按钮打开导入弹窗
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**When** 用户点击"导入"按钮
|
||||
**Then** 应打开导入数据弹窗
|
||||
**And** 弹窗标题为"员工数据导入"
|
||||
**And** 应显示文件上传区域
|
||||
|
||||
#### Scenario: 上传Excel文件
|
||||
|
||||
**Given** 导入弹窗已打开
|
||||
**When** 用户选择一个 Excel 文件
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应上传文件到后端
|
||||
**And** 应显示上传进度
|
||||
**And** 上传完成后应显示导入结果消息
|
||||
|
||||
#### Scenario: 导入成功提示
|
||||
|
||||
**Given** 用户上传了一个包含有效数据的 Excel 文件
|
||||
**When** 文件上传完成
|
||||
**Then** 应显示导入成功消息,包含成功导入的记录数
|
||||
**And** 弹窗应自动关闭
|
||||
**And** 列表应刷新,显示新导入的记录
|
||||
|
||||
#### Scenario: 导入部分失败提示
|
||||
|
||||
**Given** 用户上传了一个包含10条数据的 Excel 文件
|
||||
**And** 其中2条数据格式错误
|
||||
**When** 文件上传完成
|
||||
**Then** 应显示导入结果消息,说明成功8条,失败2条
|
||||
**And** 消息中应包含失败行的错误详情
|
||||
|
||||
#### Scenario: 更新已存在数据选项
|
||||
|
||||
**Given** 导入弹窗已打开
|
||||
**When** 用户勾选"是否更新已经存在的员工数据"选项
|
||||
**And** 上传文件
|
||||
**Then** 请求应携带 `updateSupport=true` 参数
|
||||
|
||||
#### Scenario: 下载导入模板
|
||||
|
||||
**Given** 导入弹窗已打开
|
||||
**When** 用户点击"下载模板"链接
|
||||
**Then** 浏览器应下载 Excel 模板文件
|
||||
**And** 导入弹窗应保持打开
|
||||
|
||||
#### Scenario: 支持拖拽上传
|
||||
|
||||
**Given** 导入弹窗已打开
|
||||
**When** 用户将 Excel 文件拖拽到上传区域
|
||||
**Then** 文件应自动添加到上传队列
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 前端SHALL根据权限控制按钮显示
|
||||
|
||||
前端MUST根据用户权限显示或隐藏相应的操作按钮。
|
||||
|
||||
#### Scenario: 无新增权限时隐藏新增按钮
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:employee:add` 权限
|
||||
**When** 用户访问员工信息管理页面
|
||||
**Then** "新增"按钮应隐藏或禁用
|
||||
|
||||
#### Scenario: 无编辑权限时隐藏编辑按钮
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:employee:edit` 权限
|
||||
**When** 用户访问员工信息管理页面
|
||||
**Then** 列表操作列中的"编辑"按钮应隐藏或禁用
|
||||
|
||||
#### Scenario: 无删除权限时隐藏删除按钮
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:employee:remove` 权限
|
||||
**When** 用户访问员工信息管理页面
|
||||
**Then** 列表操作列中的"删除"按钮应隐藏或禁用
|
||||
|
||||
#### Scenario: 无导入权限时隐藏导入按钮
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:employee:import` 权限
|
||||
**When** 用户访问员工信息管理页面
|
||||
**Then** "导入"按钮应隐藏或禁用
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 前端SHALL提供友好的用户反馈
|
||||
|
||||
前端MUST在各种操作中提供适当的用户反馈。
|
||||
|
||||
#### Scenario: 加载状态显示
|
||||
|
||||
**Given** 用户执行任何需要请求后端的操作
|
||||
**When** 请求正在处理中
|
||||
**Then** 列表或弹窗应显示加载状态(如 loading 遮罩)
|
||||
|
||||
#### Scenario: 操作成功提示
|
||||
|
||||
**Given** 用户执行新增、修改或删除操作
|
||||
**When** 操作成功
|
||||
**Then** 应显示成功提示消息
|
||||
**And** 提示消息应在几秒后自动消失
|
||||
|
||||
#### Scenario: 操作失败提示
|
||||
|
||||
**Given** 用户执行新增、修改或删除操作
|
||||
**When** 操作失败
|
||||
**Then** 应显示错误提示消息
|
||||
**And** 错误消息应说明失败原因
|
||||
|
||||
#### Scenario: 网络错误处理
|
||||
|
||||
**Given** 用户执行任何需要请求后端的操作
|
||||
**When** 网络请求失败
|
||||
**Then** 应显示网络错误提示消息
|
||||
**And** 弹窗应保持打开状态(如果是新增/编辑操作)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 前端SHALL支持响应式布局
|
||||
|
||||
前端MUST确保页面在不同屏幕尺寸下正常显示。
|
||||
|
||||
#### Scenario: 小屏幕适配
|
||||
|
||||
**Given** 用户使用小屏幕设备访问页面
|
||||
**When** 页面加载完成
|
||||
**Then** 搜索筛选区应合理布局
|
||||
**And** 表格应支持横向滚动
|
||||
**And** 弹窗应适配屏幕尺寸
|
||||
|
||||
#### Scenario: 大屏幕优化
|
||||
|
||||
**Given** 用户使用大屏幕设备访问页面
|
||||
**When** 页面加载完成
|
||||
**Then** 搜索筛选区应在一行内显示
|
||||
**And** 表格应充分利用屏幕宽度
|
||||
291
openspec/changes/add-employee-info-ui/tasks.md
Normal file
291
openspec/changes/add-employee-info-ui/tasks.md
Normal file
@@ -0,0 +1,291 @@
|
||||
# Tasks: 员工信息管理前端页面
|
||||
|
||||
## Overview
|
||||
本文档将员工信息管理前端页面的开发工作分解为可验证的任务项。任务按照依赖关系排序,确保开发过程顺畅。
|
||||
|
||||
---
|
||||
|
||||
## 1. API 接口层开发
|
||||
|
||||
### 1.1 创建 API 接口文件
|
||||
- [x] 创建 `ruoyi-ui/src/api/dpcEmployee.js` 文件
|
||||
- [x] 实现 `listEmployee(query)` - 查询员工列表
|
||||
- [x] 实现 `getEmployee(employeeId)` - 获取员工详情
|
||||
- [x] 实现 `addEmployee(data)` - 新增员工
|
||||
- [x] 实现 `updateEmployee(data)` - 编辑员工
|
||||
- [x] 实现 `delEmployee(employeeIds)` - 删除员工
|
||||
- [x] 实现 `importTemplate()` - 下载导入模板
|
||||
- [x] 实现 `importData(data, updateSupport)` - 导入员工信息
|
||||
|
||||
**验证**: API 文件包含所有7个接口方法,路径与后端 API 文档一致。
|
||||
|
||||
---
|
||||
|
||||
## 2. 主页面组件开发
|
||||
|
||||
### 2.1 创建主页面文件
|
||||
- [x] 创建 `ruoyi-ui/src/views/dpcEmployee/` 目录
|
||||
- [x] 创建 `ruoyi-ui/src/views/dpcEmployee/index.vue` 文件
|
||||
|
||||
**验证**: 文件创建成功,可正常访问。
|
||||
|
||||
### 2.2 实现页面基础结构
|
||||
- [x] 实现 `<template>` 基础布局
|
||||
- [x] 实现搜索筛选区的表单(含部门树选择器)
|
||||
- [x] 实现操作按钮区(新增、导入)
|
||||
- [x] 实现员工列表表格
|
||||
- [x] 实现分页组件
|
||||
|
||||
**验证**: 页面可正常渲染,布局符合设计文档。
|
||||
|
||||
### 2.3 实现数据管理逻辑
|
||||
- [x] 定义 `data()` 数据结构
|
||||
- `loading` - 加载状态
|
||||
- `employeeList` - 员工列表数据
|
||||
- `total` - 总记录数
|
||||
- `queryParams` - 查询参数
|
||||
- `showSearch` - 搜索区显示状态
|
||||
- [x] 实现 `getList()` 方法 - 查询员工列表
|
||||
- [x] 实现 `handleQuery()` 方法 - 搜索
|
||||
- [x] 实现 `resetQuery()` 方法 - 重置搜索条件
|
||||
|
||||
**验证**: 页面加载时正确显示员工列表,搜索和重置功能正常。
|
||||
|
||||
### 2.4 实现列表表格
|
||||
- [x] 添加表格列:复选框、姓名、柜员号、身份证号、所属部门、电话、状态、操作
|
||||
- [x] 实现状态标签显示(在职/离职)
|
||||
- [x] 实现操作列按钮:详情、编辑、删除
|
||||
- [x] 实现 `handleSelectionChange()` 方法 - 多选处理
|
||||
|
||||
**验证**: 列表正确显示所有字段,状态标签正确着色。
|
||||
|
||||
---
|
||||
|
||||
## 3. 新增/编辑弹窗开发
|
||||
|
||||
### 3.1 实现弹窗结构
|
||||
- [x] 添加新增/编辑弹窗的 `<el-dialog>`
|
||||
- [x] 实现弹窗标题动态显示
|
||||
- [x] 实现员工基本信息表单
|
||||
- 姓名输入框(必填,最大100字符)
|
||||
- 柜员号输入框(必填,最大50字符)
|
||||
- 所属部门选择器(el-tree-select,关联 sys_dept)
|
||||
- 身份证号输入框(必填,18位校验)
|
||||
- 电话输入框(选填,11位手机号校验)
|
||||
- 入职时间日期选择器
|
||||
- 状态单选框组(在职/离职)
|
||||
|
||||
**验证**: 弹窗可正常打开和关闭,表单字段正确显示。
|
||||
|
||||
### 3.2 实现亲属子表单
|
||||
- [x] 添加亲属信息区域
|
||||
- [x] 实现"+ 添加亲属"按钮
|
||||
- [x] 实现亲属列表表格(亲属姓名、身份证号、电话、关系、操作)
|
||||
- [x] 实现亲属行的删除按钮
|
||||
- [x] 添加 `relatives` 数据字段
|
||||
|
||||
**验证**: 可以动态添加和删除亲属行。
|
||||
|
||||
### 3.3 实现表单验证
|
||||
- [x] 定义 `rules` 验证规则
|
||||
- 姓名:必填,最大100字符
|
||||
- 柜员号:必填,最大50字符
|
||||
- 身份证号:必填,格式校验(18位)
|
||||
- 电话:选填,格式校验(11位手机号)
|
||||
- 亲属姓名:必填
|
||||
- [x] 实现 `reset()` 方法 - 重置表单
|
||||
- [x] 实现 `cancel()` 方法 - 取消操作
|
||||
|
||||
**验证**: 表单验证规则正确触发,错误提示显示正确。
|
||||
|
||||
### 3.4 实现新增逻辑
|
||||
- [x] 实现 `handleAdd()` 方法 - 打开新增弹窗
|
||||
- [x] 实现 `submitForm()` 方法 - 提交新增
|
||||
- [x] 集成 `addEmployee` API
|
||||
- [x] 处理成功/失败响应
|
||||
|
||||
**验证**: 可以成功新增员工及其亲属信息。
|
||||
|
||||
### 3.5 实现编辑逻辑
|
||||
- [x] 实现 `handleUpdate(row)` 方法 - 打开编辑弹窗
|
||||
- [x] 集成 `getEmployee` API 获取详情
|
||||
- [x] 回显员工信息和亲属列表
|
||||
- [x] 修改 `submitForm()` 方法处理编辑提交
|
||||
- [x] 集成 `updateEmployee` API
|
||||
|
||||
**验证**: 可以成功编辑员工信息和亲属信息,数据正确回显。
|
||||
|
||||
---
|
||||
|
||||
## 4. 详情弹窗开发
|
||||
|
||||
### 4.1 实现详情弹窗结构
|
||||
- [x] 添加详情弹窗的 `<el-dialog>`
|
||||
- [x] 实现员工基本信息展示区(只读)
|
||||
- [x] 实现亲属列表展示区(只读)
|
||||
- [x] 实现"暂无亲属信息"提示
|
||||
|
||||
**验证**: 详情弹窗正确显示员工信息和亲属列表。
|
||||
|
||||
### 4.2 实现详情查看逻辑
|
||||
- [x] 添加 `detailOpen` 数据字段
|
||||
- [x] 添加 `employeeDetail` 数据字段
|
||||
- [x] 实现 `handleDetail(row)` 方法
|
||||
- [x] 集成 `getEmployee` API
|
||||
|
||||
**验证**: 点击详情按钮可正确查看员工完整信息。
|
||||
|
||||
---
|
||||
|
||||
## 5. 删除功能开发
|
||||
|
||||
### 5.1 实现删除逻辑
|
||||
- [x] 实现 `handleDelete(row)` 方法
|
||||
- [x] 添加删除确认对话框
|
||||
- [x] 集成 `delEmployee` API
|
||||
- [x] 处理成功/失败响应
|
||||
- [x] 删除后刷新列表
|
||||
|
||||
**验证**: 可以成功删除员工记录,删除确认对话框正常显示。
|
||||
|
||||
---
|
||||
|
||||
## 6. 导入功能开发
|
||||
|
||||
### 6.1 实现导入弹窗
|
||||
- [x] 添加导入弹窗的 `<el-dialog>`
|
||||
- [x] 实现 `<el-upload>` 组件
|
||||
- [x] 配置上传参数(headers, action, 限制文件类型)
|
||||
- [x] 实现"更新已存在数据"复选框
|
||||
- [x] 实现"下载模板"链接
|
||||
|
||||
**验证**: 导入弹窗正常显示,可以正确选择文件。
|
||||
|
||||
### 6.2 实现导入逻辑
|
||||
- [x] 添加 `upload` 数据对象
|
||||
- [x] 实现 `handleImport()` 方法 - 打开导入弹窗
|
||||
- [x] 实现 `importTemplate()` 方法 - 下载模板
|
||||
- [x] 实现 `handleFileUploadProgress()` - 上传进度
|
||||
- [x] 实现 `handleFileSuccess()` - 上传成功处理
|
||||
- [x] 实现 `submitFileForm()` - 提交文件
|
||||
|
||||
**验证**: 可以成功下载模板,上传文件后正确显示导入结果。
|
||||
|
||||
---
|
||||
|
||||
## 7. 权限控制
|
||||
|
||||
### 7.1 实现按钮权限控制
|
||||
- [x] 新增按钮添加 `v-hasPermi="['dpc:employee:add']"`
|
||||
- [x] 编辑按钮添加 `v-hasPermi="['dpc:employee:edit']"`
|
||||
- [x] 删除按钮添加 `v-hasPermi="['dpc:employee:remove']"`
|
||||
- [x] 详情按钮添加 `v-hasPermi="['dpc:employee:query']"`
|
||||
- [x] 导入按钮添加 `v-hasPermi="['dpc:employee:import']"`
|
||||
|
||||
**验证**: 按钮根据用户权限正确显示或隐藏。
|
||||
|
||||
---
|
||||
|
||||
## 8. 样式和交互优化
|
||||
|
||||
### 9.1 实现加载状态
|
||||
- [x] 列表加载时显示 loading 遮罩
|
||||
- [x] 提交表单时禁用提交按钮
|
||||
- [x] 文件上传时显示上传进度
|
||||
|
||||
**验证**: 各种加载状态正确显示。
|
||||
|
||||
### 9.2 实现用户反馈
|
||||
- [x] 操作成功显示成功提示
|
||||
- [x] 操作失败显示错误提示
|
||||
- [x] 导入结果显示详细信息
|
||||
|
||||
**验证**: 用户反馈消息清晰友好。
|
||||
|
||||
### 9.3 响应式布局
|
||||
- [x] 搜索筛选区响应式布局
|
||||
- [x] 表格支持横向滚动
|
||||
- [x] 弹窗适配小屏幕
|
||||
|
||||
**验证**: 页面在不同屏幕尺寸下正常显示。
|
||||
|
||||
---
|
||||
|
||||
## 10. 测试和修复
|
||||
|
||||
### 9.1 功能测试
|
||||
- [ ] 测试列表查询和分页
|
||||
- [ ] 测试搜索和筛选功能
|
||||
- [ ] 测试新增员工(无亲属)
|
||||
- [ ] 测试新增员工(单个亲属)
|
||||
- [ ] 测试新增员工(多个亲属)
|
||||
- [ ] 测试编辑员工基本信息
|
||||
- [ ] 测试编辑员工亲属(新增、修改、删除)
|
||||
- [ ] 测试查看详情
|
||||
- [ ] 测试删除员工(单个)
|
||||
- [ ] 测试批量删除
|
||||
- [ ] 测试下载导入模板
|
||||
- [ ] 测试导入数据(成功场景)
|
||||
- [ ] 测试导入数据(部分失败场景)
|
||||
- [ ] 测试表单验证(各种错误场景)
|
||||
|
||||
### 9.2 浏览器兼容性测试
|
||||
- [ ] Chrome 测试
|
||||
- [ ] Firefox 测试
|
||||
- [ ] Edge 测试
|
||||
|
||||
### 9.3 Bug 修复
|
||||
- [ ] 修复测试中发现的问题
|
||||
|
||||
---
|
||||
|
||||
## 10. 菜单配置
|
||||
|
||||
### 11.1 数据库菜单配置
|
||||
- [x] 在 `sys_menu` 表中插入"信息维护"父菜单(如果不存在)
|
||||
- [x] 在 `sys_menu` 表中插入"员工信息管理"子菜单
|
||||
- [x] 配置菜单路由为 `employee`
|
||||
- [x] 配置菜单组件为 `dpcEmployee/index`
|
||||
- [x] 配置菜单权限标识
|
||||
- [x] 配置菜单图标
|
||||
|
||||
**验证**: 菜单正确显示,点击可跳转到员工信息管理页面。
|
||||
|
||||
---
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```
|
||||
1. API 接口层 (1.1)
|
||||
↓
|
||||
2. 主页面组件基础 (2.1 - 2.4)
|
||||
↓
|
||||
3. 新增/编辑弹窗 (3.1 - 3.5) ← 并行
|
||||
4. 详情弹窗 (4.1 - 4.2) ← 并行
|
||||
5. 删除功能 (5.1) ← 并行
|
||||
6. 导入功能 (6.1 - 6.2) ← 并行
|
||||
↓
|
||||
7. 权限控制 (7.1)
|
||||
↓
|
||||
8. 样式优化 (8.1 - 8.3)
|
||||
↓
|
||||
9. 测试和修复 (9.1 - 9.3)
|
||||
↓
|
||||
10. 菜单配置 (10.1)
|
||||
```
|
||||
|
||||
## 预估工作量
|
||||
|
||||
| 任务组 | 预估时间 |
|
||||
|--------|----------|
|
||||
| API 接口层 | 0.5 小时 |
|
||||
| 主页面组件 | 1.5 小时 |
|
||||
| 新增/编辑弹窗 | 3 小时 |
|
||||
| 详情弹窗 | 1 小时 |
|
||||
| 删除功能 | 0.5 小时 |
|
||||
| 导入功能 | 1.5 小时 |
|
||||
| 权限控制 | 0.5 小时 |
|
||||
| 样式优化 | 1 小时 |
|
||||
| 测试和修复 | 1 小时 |
|
||||
| 菜单配置 | 0.5 小时 |
|
||||
| **总计** | **11 小时** |
|
||||
252
openspec/changes/add-employee-info/design.md
Normal file
252
openspec/changes/add-employee-info/design.md
Normal file
@@ -0,0 +1,252 @@
|
||||
## Context
|
||||
|
||||
员工信息维护是纪检初核系统的核心基础功能。系统需要管理银行内部员工的基础信息及其亲属关系,以便在进行纪检初核工作时能够快速查询相关人员信息。
|
||||
|
||||
**约束条件:**
|
||||
- 必须遵循若依框架的代码规范
|
||||
- 必须使用项目已定义的命名规范(模块前缀 `ccdi_`)
|
||||
- 必须支持 Excel 导入导出功能
|
||||
- 亲属信息需要与员工信息关联管理
|
||||
|
||||
**相关方:**
|
||||
- 纪检人员:查询员工及亲属信息
|
||||
- 系统管理员:批量导入员工数据
|
||||
- 人事部门:维护员工基础信息
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
### Goals
|
||||
1. 提供完整的员工信息 CRUD 接口
|
||||
2. 支持员工亲属信息的关联管理(一对多关系)
|
||||
3. 支持 Excel 批量导入导出,导入时可同时导入亲属信息
|
||||
4. 遵循现有代码模式(参考 `ccdi_intermediary_blacklist` 模块)
|
||||
|
||||
### Non-Goals
|
||||
- 不涉及前端页面的实现(本次仅实现后端接口)
|
||||
- 不涉及员工组织架构的复杂层级管理
|
||||
- 不涉及员工权限、角色管理(使用若依现有系统)
|
||||
- 不涉及亲属关系的高级查询功能
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 数据模型设计
|
||||
|
||||
**决策:使用两张表存储员工和亲属信息**
|
||||
|
||||
```
|
||||
ccdi_employee (员工主表)
|
||||
├── employee_id (主键)
|
||||
├── name (姓名)
|
||||
├── teller_no (柜员号, UNIQUE)
|
||||
├── org_no (所属机构号)
|
||||
├── id_card (身份证号, UNIQUE)
|
||||
├── phone (电话)
|
||||
├── hire_date (入职时间)
|
||||
├── status (状态: 0=在职, 1=离职)
|
||||
└── 审计字段 (create_by, create_time, update_by, update_time)
|
||||
|
||||
ccdi_employee_relative (员工亲属表)
|
||||
├── relative_id (主键)
|
||||
├── employee_id (外键 → ccdi_employee.employee_id)
|
||||
├── relative_name (亲属姓名)
|
||||
├── relative_id_card (亲属身份证号)
|
||||
├── relative_phone (亲属手机号)
|
||||
├── relationship (与员工关系)
|
||||
└── 审计字段
|
||||
```
|
||||
|
||||
**理由:**
|
||||
- 符合数据库范式设计,避免数据冗余
|
||||
- 支持一对多关系(一个员工可以有多个亲属)
|
||||
- 便于查询和维护
|
||||
|
||||
**替代方案考虑:**
|
||||
- 方案2:将亲属信息存储为 JSON 字段
|
||||
- 优点:单表存储,查询简单
|
||||
- 缺点:无法对亲属信息建索引,不支持复杂查询
|
||||
- **拒绝原因**:未来可能需要按亲属信息查询
|
||||
|
||||
### 2. 亲属信息维护方式
|
||||
|
||||
**决策:在员工的新增/编辑接口中同时支持亲属信息**
|
||||
|
||||
```
|
||||
POST /dpc/employee
|
||||
{
|
||||
"name": "张三",
|
||||
"tellerNo": "001",
|
||||
"orgNo": "1001",
|
||||
"idCard": "110101199001011234",
|
||||
"phone": "13800138000",
|
||||
"hireDate": "2020-01-01",
|
||||
"relatives": [
|
||||
{
|
||||
"relativeName": "李四",
|
||||
"relativeIdCard": "110101199001011235",
|
||||
"relativePhone": "13800138001",
|
||||
"relationship": "配偶"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**理由:**
|
||||
- 减少接口数量,简化前端调用
|
||||
- 保证员工与亲属信息的原子性操作
|
||||
- 符合业务场景(新增员工时同时录入亲属信息)
|
||||
|
||||
### 3. Excel 导入格式
|
||||
|
||||
**决策:使用多 Sheet 导入方式**
|
||||
|
||||
```
|
||||
Sheet1: 员工信息
|
||||
| 姓名 | 柜员号 | 所属机构号 | 身份证号 | 电话 | 入职时间 |
|
||||
|------|--------|------------|----------|------|----------|
|
||||
| 张三 | 001 | 1001 | ... | ... | 2020-01-01 |
|
||||
|
||||
Sheet2: 亲属信息 (可选)
|
||||
| 员工身份证号 | 亲属姓名 | 亲属身份证号 | 亲属手机号 | 与员工关系 |
|
||||
|--------------|----------|--------------|------------|------------|
|
||||
| 110101... | 李四 | 110101... | 138... | 配偶 |
|
||||
```
|
||||
|
||||
**理由:**
|
||||
- 清晰分离员工和亲属数据
|
||||
- 支持仅导入员工信息(亲属信息为可选)
|
||||
- 通过员工身份证号关联两张表
|
||||
|
||||
**替代方案考虑:**
|
||||
- 方案2:单 Sheet 导入,亲属信息嵌套在员工行中
|
||||
- 缺点:格式复杂,Excel 难以编辑
|
||||
- **拒绝原因**:用户体验差
|
||||
|
||||
### 4. 字典数据
|
||||
|
||||
**决策:使用字典管理"与员工关系"字段和"员工状态"字段**
|
||||
|
||||
```
|
||||
字典类型: ccdi_relative_relationship
|
||||
字典数据: 配偶、父亲、母亲、子女、兄弟姐妹、其他
|
||||
|
||||
字典类型: ccdi_employee_status
|
||||
字典数据: 在职(0)、离职(1)
|
||||
```
|
||||
|
||||
**理由:**
|
||||
- 符合若依框架设计模式
|
||||
- 便于后续扩展关系类型
|
||||
- 统一管理枚举值
|
||||
|
||||
### 5. 数据库约束
|
||||
|
||||
**决策:柜员号和身份证号添加唯一约束**
|
||||
|
||||
```sql
|
||||
UNIQUE KEY `uk_teller_no` (`teller_no`),
|
||||
UNIQUE KEY `uk_id_card` (`id_card`)
|
||||
```
|
||||
|
||||
**理由:**
|
||||
- 柜员号是员工的唯一标识,不允许重复
|
||||
- 身份证号具有唯一性,不允许重复
|
||||
- 防止数据重复和业务逻辑错误
|
||||
|
||||
### 6. 命名规范
|
||||
|
||||
**决策:遵循项目规范**
|
||||
|
||||
- 表名: `ccdi_employee`, `ccdi_employee_relative`
|
||||
- 实体类: `CcdiEmployee`, `CcdiEmployeeRelative`
|
||||
- Controller: `CcdiEmployeeController`
|
||||
- 权限标识: `dpc:employee:*`
|
||||
- **所有实体类、DTO、VO 类统一使用 @Data 注解**
|
||||
|
||||
**理由:**
|
||||
- 与现有 `ccdi_intermediary_blacklist` 模块保持一致
|
||||
- 符合项目编码规范
|
||||
- @Data 注解自动生成 getter/setter,代码简洁
|
||||
|
||||
### 7. MyBatis Plus 使用策略
|
||||
|
||||
**决策:区分简单 CRUD 和复杂查询的实现方式**
|
||||
|
||||
**简单 CRUD 操作(使用 MyBatis Plus 方法):**
|
||||
- BaseMapper 提供的方法:`insert`, `deleteById`, `deleteByIds`, `updateById`, `selectById`, `selectList`, `selectCount`
|
||||
- 条件构造器:`QueryWrapper`, `LambdaQueryWrapper` 用于简单条件查询
|
||||
- 适用场景:单表增删改查、简单条件筛选
|
||||
|
||||
**复杂查询操作(使用 XML 映射):**
|
||||
- 多表关联查询(员工 + 亲属信息)
|
||||
- 复杂条件组合查询
|
||||
- 需要自定义结果映射的查询
|
||||
- 适用场景:关联查询、复杂业务查询
|
||||
|
||||
**示例:**
|
||||
```java
|
||||
// 简单查询 - 使用 MyBatis Plus
|
||||
employeeMapper.selectById(id);
|
||||
employeeMapper.selectList(new LambdaQueryWrapper<CcdiEmployee>()
|
||||
.eq(CcdiEmployee::getTellerNo, tellerNo));
|
||||
|
||||
// 复杂查询 - 使用 XML
|
||||
EmployeeWithRelativesVO selectEmployeeWithRelatives(Long id);
|
||||
```
|
||||
|
||||
**理由:**
|
||||
- 简单 CRUD 使用 MyBatis Plus 减少代码量,提高开发效率
|
||||
- 复杂查询使用 XML 保持 SQL 可读性和可维护性
|
||||
- 符合项目技术规范(MyBatis Plus 3.5.10,Spring Boot 3 适配版)
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
### 风险1: Excel 导入时亲属数据关联失败
|
||||
**风险描述**: 导入员工和亲属信息时,若员工身份证号填写错误,亲属信息无法关联。
|
||||
|
||||
**缓解措施**:
|
||||
- 导入时进行数据校验
|
||||
- 提供详细的导入错误报告
|
||||
- 支持"仅导入员工"模式,亲属信息可后续补录
|
||||
|
||||
### 风险2: 亲属信息数据量过大
|
||||
**风险描述**: 某些员工可能有大量亲属记录,影响查询性能。
|
||||
|
||||
**缓解措施**:
|
||||
- 建立适当的数据库索引
|
||||
- 列表查询时默认不返回亲属详情
|
||||
- 提供单独的亲属查询接口
|
||||
|
||||
### 权衡: 简化 vs 完整性
|
||||
- **当前方案**: 使用两表设计,支持完整的一对多关系
|
||||
- **简化方案**: 将亲属信息存储为 JSON
|
||||
- **选择**: 当前方案,因为纪检场景可能需要按亲属信息查询
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### 后端部署步骤
|
||||
1. 执行数据库脚本,创建表和字典数据
|
||||
2. 部署后端代码
|
||||
3. (前端开发阶段)配置菜单和权限数据
|
||||
|
||||
### 回滚计划
|
||||
1. 从数据库中删除新增的表和字典数据
|
||||
2. 移除后端代码部署
|
||||
|
||||
### 前端开发注意事项
|
||||
- 开发前端页面时需同步在 `sys_menu` 表中插入菜单及权限数据
|
||||
- 菜单路径:信息维护 → 员工信息管理
|
||||
- 权限标识:`dpc:employee:list`, `dpc:employee:query`, `dpc:employee:add`, `dpc:employee:edit`, `dpc:employee:remove`, `dpc:employee:export`, `dpc:employee:import`
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **亲属关系是否需要支持多层级?** (如:亲属的亲属)
|
||||
- **当前决策**: 否,仅支持员工的直接亲属
|
||||
|
||||
2. **导入时是否需要支持"更新已有员工"模式?**
|
||||
- **当前决策**: 是,通过柜员号或身份证号判断
|
||||
|
||||
3. ~~**员工离职后如何处理?**~~
|
||||
- **已确认**: 增加状态字段(0=在职, 1=离职)
|
||||
|
||||
4. ~~**身份证号和柜员号是否需要唯一性约束?**~~
|
||||
- **已确认**: 两者都需要唯一约束
|
||||
81
openspec/changes/add-employee-info/proposal.md
Normal file
81
openspec/changes/add-employee-info/proposal.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Change: 添加员工信息维护功能
|
||||
|
||||
## Why
|
||||
|
||||
当前系统缺少员工信息管理能力,员工基础信息(姓名、柜员号、所属机构、身份证、电话、入职时间)以及员工亲属关系信息需要通过系统进行维护。这导致:
|
||||
1. 员工信息分散管理,缺乏统一的数据源
|
||||
2. 亲属关系信息无法与员工信息关联管理
|
||||
3. 数据录入效率低,无法批量导入导出
|
||||
|
||||
为解决上述问题,需要添加员工信息维护功能,支持员工及其亲属信息的增删改查、批量导入导出。
|
||||
|
||||
## What Changes
|
||||
|
||||
### 新增功能
|
||||
- 员工信息管理:支持员工的新增、编辑、删除、查询
|
||||
- 员工信息导入导出:支持 Excel 批量导入导出员工数据
|
||||
- 员工亲属管理:支持在员工信息中维护亲属关系
|
||||
|
||||
### 数据模型
|
||||
**员工主表 (ccdi_employee)**
|
||||
- 员工ID (主键)
|
||||
- 姓名
|
||||
- 柜员号 (唯一约束)
|
||||
- 所属机构号
|
||||
- 身份证号 (唯一约束)
|
||||
- 电话
|
||||
- 入职时间
|
||||
- 状态 (0=在职, 1=离职)
|
||||
- 审计字段(创建者、创建时间、更新者、更新时间)
|
||||
|
||||
**员工亲属表 (ccdi_employee_relative)**
|
||||
- 亲属ID (主键)
|
||||
- 员工ID (外键)
|
||||
- 亲属姓名
|
||||
- 亲属身份证号
|
||||
- 亲属手机号
|
||||
- 与员工关系
|
||||
- 审计字段
|
||||
|
||||
### API 接口
|
||||
- `POST /dpc/employee` - 新增员工
|
||||
- `PUT /dpc/employee` - 编辑员工
|
||||
- `DELETE /dpc/employee/{ids}` - 删除员工
|
||||
- `GET /dpc/employee/list` - 查询员工列表
|
||||
- `GET /dpc/employee/{id}` - 获取员工详情
|
||||
- `POST /dpc/employee/export` - 导出员工信息
|
||||
- `POST /dpc/employee/importTemplate` - 下载导入模板
|
||||
- `POST /dpc/employee/importData` - 导入员工信息
|
||||
|
||||
## Impact
|
||||
|
||||
### 影响的规范 (Affected Specs)
|
||||
- 新增规范: `employee-info` (员工信息管理)
|
||||
|
||||
### 影响的代码 (Affected Code)
|
||||
- **新增模块**: `ruoyi-dpc` (DPC业务模块)
|
||||
- `com.ruoyi.dpc.domain.CcdiEmployee` - 员工实体(使用 @Data 注解)
|
||||
- `com.ruoyi.dpc.domain.CcdiEmployeeRelative` - 亲属实体(使用 @Data 注解)
|
||||
- `com.ruoyi.dpc.domain.dto.*` - DTO类(统一使用 @Data 注解)
|
||||
- `com.ruoyi.dpc.domain.vo.*` - VO类(统一使用 @Data 注解)
|
||||
- `com.ruoyi.dpc.controller.CcdiEmployeeController` - 控制器
|
||||
- `com.ruoyi.dpc.service.*` - 服务层
|
||||
- `com.ruoyi.dpc.mapper.*` - 数据访问层(继承 MyBatis Plus BaseMapper)
|
||||
- **技术实现**:
|
||||
- 简单 CRUD 操作使用 MyBatis Plus BaseMapper 方法
|
||||
- 复杂查询(如多表关联)使用 XML 映射文件
|
||||
|
||||
- **数据库**: 新增表
|
||||
- `ccdi_employee` - 员工信息表
|
||||
- `ccdi_employee_relative` - 员工亲属表
|
||||
- `sys_dict_type` - 字典类型表(员工状态、亲属关系)
|
||||
|
||||
- **菜单系统**: (将在前端开发阶段处理)
|
||||
- 本次不涉及菜单数据的创建
|
||||
- 前端开发时需在 `sys_menu` 表中插入菜单及权限数据
|
||||
|
||||
- **前端**: (本次提案仅涉及后端接口)
|
||||
- 前端页面开发时需要同步配置菜单数据
|
||||
|
||||
### 非破坏性变更 (Non-Breaking)
|
||||
此变更为纯新增功能,不影响现有系统行为。
|
||||
526
openspec/changes/add-employee-info/specs/employee-info/spec.md
Normal file
526
openspec/changes/add-employee-info/specs/employee-info/spec.md
Normal file
@@ -0,0 +1,526 @@
|
||||
# Spec: 员工信息管理
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 系统SHALL支持查询员工信息列表
|
||||
|
||||
系统MUST提供查询功能,允许用户查询系统中已维护的员工信息列表,支持分页、模糊搜索和多条件筛选。
|
||||
|
||||
#### Scenario: 分页查询员工列表
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**When** 用户访问员工信息管理页面
|
||||
**Then** 系统应显示员工列表,支持分页展示
|
||||
|
||||
#### Scenario: 按姓名模糊搜索员工
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**And** 系统中存在姓名为"张三"的员工
|
||||
**When** 用户在搜索框输入"张"并点击搜索
|
||||
**Then** 系统应返回所有姓名中包含"张"的员工记录
|
||||
|
||||
#### Scenario: 按柜员号精确搜索员工
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**And** 系统中存在柜员号为"001"的员工
|
||||
**When** 用户在搜索框输入"001"并点击搜索
|
||||
**Then** 系统应返回该柜员号对应的员工记录
|
||||
|
||||
#### Scenario: 按所属机构号筛选员工
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**And** 系统中存在所属机构号为"1001"的员工
|
||||
**When** 用户选择所属机构号为"1001"并点击搜索
|
||||
**Then** 系统应返回该机构下的所有员工记录
|
||||
|
||||
#### Scenario: 按身份证号精确搜索员工
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**And** 系统中存在身份证号为"110101199001011234"的员工
|
||||
**When** 用户在搜索框输入"110101199001011234"并点击搜索
|
||||
**Then** 系统应返回该身份证号对应的员工记录
|
||||
|
||||
#### Scenario: 组合条件查询员工
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**When** 用户同时输入姓名"张"、柜员号"001"、所属机构号"1001"并点击搜索
|
||||
**Then** 系统应返回同时满足所有条件的记录
|
||||
|
||||
#### Scenario: 按状态筛选员工
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:list` 权限
|
||||
**And** 系统中存在在职和离职两种状态的员工记录
|
||||
**When** 用户选择状态为"在职"并点击搜索
|
||||
**Then** 系统应仅返回状态为"在职"的记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持新增员工信息
|
||||
|
||||
系统MUST提供新增功能,允许用户添加员工信息,并可同时维护员工亲属信息。
|
||||
|
||||
#### Scenario: 新增员工基本信息
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 填写姓名为"张三"
|
||||
**And** 填写柜员号为"001"
|
||||
**And** 填写所属机构号为"1001"
|
||||
**And** 填写身份证号为"110101199001011234"
|
||||
**And** 填写电话为"13800138000"
|
||||
**And** 填写入职时间为"2020-01-01"
|
||||
**And** 选择状态为"在职"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应保存员工信息并提示"操作成功"
|
||||
**And** 列表中应显示新增的记录
|
||||
|
||||
#### Scenario: 新增员工时同时添加亲属信息
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 填写员工基本信息
|
||||
**And** 在亲属区域添加亲属:姓名"李四"、身份证"110101199001011235"、电话"13800138001"、关系"配偶"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应保存员工信息及亲属信息
|
||||
**And** 查询该员工详情时应显示关联的亲属信息
|
||||
|
||||
#### Scenario: 新增员工时同时添加多个亲属
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 填写员工基本信息
|
||||
**And** 添加3个亲属信息
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应保存员工信息及所有亲属信息
|
||||
**And** 查询该员工详情时应显示3个亲属
|
||||
|
||||
#### Scenario: 新增时姓名为空应校验失败
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 不填写姓名
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应提示"姓名不能为空"
|
||||
**And** 不保存数据
|
||||
|
||||
#### Scenario: 新增时柜员号为空应校验失败
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 不填写柜员号
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应提示"柜员号不能为空"
|
||||
**And** 不保存数据
|
||||
|
||||
#### Scenario: 新增时身份证号格式校验
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 填写身份证号为"123"(不符合身份证格式)
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应提示"身份证号格式不正确"
|
||||
**And** 不保存数据
|
||||
|
||||
#### Scenario: 新增时电话格式校验
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 填写电话为"abc"(不符合手机号格式)
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应提示"电话格式不正确"
|
||||
**And** 不保存数据
|
||||
|
||||
#### Scenario: 新增时柜员号重复应校验失败
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:add` 权限
|
||||
**And** 系统中已存在柜员号为"001"的员工
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 填写柜员号为"001"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应提示"该柜员号已存在"
|
||||
**And** 不保存数据
|
||||
|
||||
#### Scenario: 新增时身份证号重复应校验失败
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:add` 权限
|
||||
**And** 系统中已存在身份证号为"110101199001011234"的员工
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 填写身份证号为"110101199001011234"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应提示"该身份证号已存在"
|
||||
**And** 不保存数据
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持编辑员工信息
|
||||
|
||||
系统MUST提供编辑功能,允许用户修改已存在的员工信息及其亲属信息。
|
||||
|
||||
#### Scenario: 编辑员工基本信息
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:edit` 权限
|
||||
**And** 系统中存在一条员工记录
|
||||
**When** 用户点击该记录的"编辑"按钮
|
||||
**And** 修改姓名为"李四"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应更新员工信息并提示"操作成功"
|
||||
**And** 列表中应显示更新后的姓名
|
||||
|
||||
#### Scenario: 编辑员工时新增亲属
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:edit` 权限
|
||||
**And** 系统中存在一条员工记录,该员工有1个亲属
|
||||
**When** 用户点击该记录的"编辑"按钮
|
||||
**And** 在亲属区域添加新的亲属信息
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应保存新的亲属信息
|
||||
**And** 查询该员工详情时应显示2个亲属
|
||||
|
||||
#### Scenario: 编辑员工时修改亲属信息
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:edit` 权限
|
||||
**And** 系统中存在一条员工记录,该员工有亲属"李四"
|
||||
**When** 用户点击该记录的"编辑"按钮
|
||||
**And** 修改亲属姓名为"王五"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应更新亲属信息
|
||||
**And** 查询该员工详情时应显示更新后的亲属姓名
|
||||
|
||||
#### Scenario: 编辑员工时删除亲属
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:edit` 权限
|
||||
**And** 系统中存在一条员工记录,该员工有2个亲属
|
||||
**When** 用户点击该记录的"编辑"按钮
|
||||
**And** 删除其中一个亲属
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应删除该亲属信息
|
||||
**And** 查询该员工详情时应仅显示1个亲属
|
||||
|
||||
#### Scenario: 编辑时清除所有亲属
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:edit` 权限
|
||||
**And** 系统中存在一条员工记录,该员工有亲属信息
|
||||
**When** 用户点击该记录的"编辑"按钮
|
||||
**And** 删除所有亲属
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应删除所有亲属信息
|
||||
**And** 查询该员工详情时亲属列表应为空
|
||||
|
||||
#### Scenario: 编辑时姓名为空应校验失败
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:edit` 权限
|
||||
**And** 系统中存在一条员工记录
|
||||
**When** 用户点击该记录的"编辑"按钮
|
||||
**And** 清空姓名字段
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应提示"姓名不能为空"
|
||||
**And** 不更新数据
|
||||
|
||||
#### Scenario: 编辑时亲属信息校验
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:edit` 权限
|
||||
**And** 系统中存在一条员工记录
|
||||
**When** 用户点击该记录的"编辑"按钮
|
||||
**And** 添加亲属信息但不填写亲属姓名
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应提示"亲属姓名不能为空"
|
||||
**And** 不更新数据
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持删除员工信息
|
||||
|
||||
系统MUST提供删除功能,允许用户删除不再需要的员工记录,删除员工时应级联删除其亲属信息。
|
||||
|
||||
#### Scenario: 删除单条员工记录
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:remove` 权限
|
||||
**And** 系统中存在一条员工记录
|
||||
**When** 用户点击该记录的"删除"按钮
|
||||
**And** 确认删除操作
|
||||
**Then** 系统应删除该员工记录
|
||||
**And** 系统应同时删除该员工的所有亲属信息
|
||||
**And** 列表中不再显示该记录
|
||||
|
||||
#### Scenario: 批量删除员工记录
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:remove` 权限
|
||||
**And** 系统中存在多条员工记录
|
||||
**When** 用户勾选3条记录
|
||||
**And** 点击"删除"按钮
|
||||
**And** 确认删除操作
|
||||
**Then** 系统应删除选中的3条员工记录
|
||||
**And** 系统应同时删除这3个员工的所有亲属信息
|
||||
**And** 列表中不再显示这3条记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持查看员工详情
|
||||
|
||||
系统MUST提供详情查看功能,允许用户查看员工的完整信息及亲属列表。
|
||||
|
||||
#### Scenario: 查看员工基本信息
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:query` 权限
|
||||
**And** 系统中存在一条员工记录
|
||||
**When** 用户点击该记录的"查看"按钮
|
||||
**Then** 系统应显示员工的完整基本信息
|
||||
**And** 信息应包括:姓名、柜员号、所属机构号、身份证号、电话、入职时间
|
||||
|
||||
#### Scenario: 查看员工亲属信息
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:query` 权限
|
||||
**And** 系统中存在一条员工记录,该员工有2个亲属
|
||||
**When** 用户点击该记录的"查看"按钮
|
||||
**Then** 系统应显示员工的基本信息
|
||||
**And** 系统应显示该员工的亲属列表
|
||||
**And** 亲属信息应包括:亲属姓名、亲属身份证号、亲属手机号、与员工关系
|
||||
|
||||
#### Scenario: 查看无亲属的员工
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:query` 权限
|
||||
**And** 系统中存在一条员工记录,该员工无亲属信息
|
||||
**When** 用户点击该记录的"查看"按钮
|
||||
**Then** 系统应显示员工的基本信息
|
||||
**And** 亲属列表应显示为空或提示"暂无亲属信息"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持导出员工信息
|
||||
|
||||
系统MUST提供导出功能,允许用户将查询结果导出为 Excel 文件。
|
||||
|
||||
#### Scenario: 导出所有员工数据
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:export` 权限
|
||||
**And** 系统中存在100条员工记录
|
||||
**When** 用户点击"导出"按钮
|
||||
**And** 不设置任何筛选条件
|
||||
**Then** 系统应生成包含100条记录的 Excel 文件并下载
|
||||
|
||||
#### Scenario: 导出筛选后的员工数据
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:export` 权限
|
||||
**And** 系统中存在多个机构的员工记录
|
||||
**When** 用户筛选所属机构号为"1001"
|
||||
**And** 点击"导出"按钮
|
||||
**Then** 系统应生成仅包含该机构员工记录的 Excel 文件并下载
|
||||
|
||||
#### Scenario: 导出的 Excel 文件格式正确
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:export` 权限
|
||||
**When** 用户点击"导出"按钮
|
||||
**Then** Excel 文件应包含以下列:姓名、柜员号、所属机构号、身份证号、电话、入职时间
|
||||
**And** 表头应使用中文显示
|
||||
**And** 数据应正确显示
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持下载 Excel 导入模板
|
||||
|
||||
系统MUST提供模板下载功能,允许用户下载标准的 Excel 导入模板。
|
||||
|
||||
#### Scenario: 下载员工信息导入模板
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**When** 用户点击"下载模板"按钮
|
||||
**Then** 系统应生成 Excel 模板文件并下载
|
||||
**And** 模板应包含"员工信息" Sheet
|
||||
**And** 模板应包含"亲属信息" Sheet(可选)
|
||||
**And** 员工信息 Sheet 应包含以下列:姓名、柜员号、所属机构号、身份证号、电话、入职时间
|
||||
**And** 亲属信息 Sheet 应包含以下列:员工身份证号、亲属姓名、亲属身份证号、亲属手机号、与员工关系
|
||||
|
||||
#### Scenario: 模板中的示例数据正确
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**When** 用户下载导入模板
|
||||
**Then** 模板应包含至少一行示例数据
|
||||
**And** 示例数据应展示正确的填写格式
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持通过 Excel 批量导入员工信息
|
||||
|
||||
系统MUST提供批量导入功能,允许用户通过 Excel 文件批量导入员工信息,支持同时导入员工和亲属数据。
|
||||
|
||||
#### Scenario: 导入包含有效员工数据的 Excel 文件
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**And** 用户已准备好包含10条有效员工数据的 Excel 文件
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择准备好的 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应导入10条记录并提示"成功导入10条数据"
|
||||
**And** 列表中应显示这10条新增记录
|
||||
|
||||
#### Scenario: 导入时同时导入员工和亲属信息
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**And** 用户准备的 Excel 文件包含5条员工数据
|
||||
**And** 亲属信息 Sheet 包含这5个员工的亲属信息
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应导入5条员工记录及其关联的亲属信息
|
||||
**And** 提示导入成功
|
||||
|
||||
#### Scenario: 导入时部分数据格式错误
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**And** 用户准备的 Excel 文件包含10条数据
|
||||
**And** 其中2条数据的姓名字段为空
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应仅导入8条有效数据
|
||||
**And** 提示"成功导入8条数据,失败2条数据"
|
||||
**And** 显示失败行的错误详情
|
||||
|
||||
#### Scenario: 导入时柜员号重复
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**And** 系统中已存在柜员号为"001"的员工
|
||||
**And** 用户准备的 Excel 文件包含柜员号为"001"的记录
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 不勾选"更新已存在数据"
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应提示该行数据"柜员号已存在"
|
||||
**And** 该行数据不被导入
|
||||
|
||||
#### Scenario: 导入时选择更新支持模式
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**And** 系统中已存在柜员号为"001"的员工
|
||||
**And** 用户准备的 Excel 文件包含相同柜员号的记录但姓名不同
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 勾选"更新已存在数据"选项
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应更新该柜员号对应的记录
|
||||
**And** 提示包含更新成功的消息
|
||||
|
||||
#### Scenario: 导入时亲属信息关联失败
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**And** 用户准备的 Excel 文件包含亲属信息
|
||||
**And** 亲属信息 Sheet 中某条记录的员工身份证号在员工信息 Sheet 中不存在
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应提示该亲属记录"员工身份证号不存在"
|
||||
**And** 该亲属记录不被导入
|
||||
|
||||
#### Scenario: 导入时身份证号格式校验
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**And** 用户准备的 Excel 文件中某条记录的身份证号格式不正确
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应提示该行数据"身份证号格式不正确"
|
||||
**And** 该行数据不被导入
|
||||
|
||||
#### Scenario: 导入时电话格式校验
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**And** 用户准备的 Excel 文件中某条记录的电话格式不正确
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应提示该行数据"电话格式不正确"
|
||||
**And** 该行数据不被导入
|
||||
|
||||
#### Scenario: 导入时仅导入员工信息(无亲属 Sheet)
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**And** 用户准备的 Excel 文件仅包含员工信息 Sheet
|
||||
**And** 不包含亲属信息 Sheet
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应正常导入员工信息
|
||||
**And** 提示导入成功
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL验证用户权限
|
||||
|
||||
系统MUST根据用户的角色和权限控制其对员工信息功能的访问。
|
||||
|
||||
#### Scenario: 无权限用户访问列表应被拒绝
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:employee:list` 权限
|
||||
**When** 用户尝试访问员工信息管理页面
|
||||
**Then** 系统应提示"您没有权限执行此操作"
|
||||
**And** 不显示列表数据
|
||||
|
||||
#### Scenario: 无权限用户尝试新增应被拒绝
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:employee:add` 权限
|
||||
**When** 用户尝试点击"新增"按钮
|
||||
**Then** 系统应隐藏或禁用"新增"按钮
|
||||
|
||||
#### Scenario: 无权限用户尝试编辑应被拒绝
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:employee:edit` 权限
|
||||
**When** 用户尝试点击"编辑"按钮
|
||||
**Then** 系统应隐藏或禁用"编辑"按钮
|
||||
|
||||
#### Scenario: 无权限用户尝试删除应被拒绝
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:employee:remove` 权限
|
||||
**When** 用户尝试点击"删除"按钮
|
||||
**Then** 系统应隐藏或禁用"删除"按钮
|
||||
|
||||
#### Scenario: 无权限用户尝试导出应被拒绝
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:employee:export` 权限
|
||||
**When** 用户尝试点击"导出"按钮
|
||||
**Then** 系统应隐藏或禁用"导出"按钮
|
||||
|
||||
#### Scenario: 无权限用户尝试导入应被拒绝
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:employee:import` 权限
|
||||
**When** 用户尝试点击"导入"按钮
|
||||
**Then** 系统应隐藏或禁用"导入"按钮
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL记录操作日志
|
||||
|
||||
系统MUST记录用户对员工信息的关键操作,包括新增、修改、删除、导入等操作。
|
||||
|
||||
#### Scenario: 新增操作应记录日志
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:add` 权限
|
||||
**When** 用户成功新增一条员工记录
|
||||
**Then** 系统应在操作日志中记录
|
||||
**And** 日志应包含:操作人、操作时间、操作类型、操作内容
|
||||
|
||||
#### Scenario: 修改操作应记录日志
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:edit` 权限
|
||||
**When** 用户成功修改一条员工记录
|
||||
**Then** 系统应在操作日志中记录
|
||||
**And** 日志应包含:操作人、操作时间、操作类型、修改内容
|
||||
|
||||
#### Scenario: 删除操作应记录日志
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:remove` 权限
|
||||
**When** 用户成功删除一条员工记录
|
||||
**Then** 系统应在操作日志中记录
|
||||
**And** 日志应包含:操作人、操作时间、操作类型、删除的记录ID
|
||||
|
||||
#### Scenario: 导入操作应记录日志
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:employee:import` 权限
|
||||
**When** 用户成功导入员工数据
|
||||
**Then** 系统应在操作日志中记录
|
||||
**And** 日志应包含:操作人、操作时间、操作类型、导入数量
|
||||
99
openspec/changes/add-employee-info/tasks.md
Normal file
99
openspec/changes/add-employee-info/tasks.md
Normal file
@@ -0,0 +1,99 @@
|
||||
## 1. 数据库设计与实现
|
||||
|
||||
- [x] 1.1 创建 `ccdi_employee` 员工信息表,包含字段:employee_id, name, teller_no, org_no, id_card, phone, hire_date, status, create_by, create_time, update_by, update_time
|
||||
- [x] 1.2 为 `ccdi_employee` 表创建唯一约束:uk_teller_no (柜员号唯一), uk_id_card (身份证号唯一)
|
||||
- [x] 1.3 为 `ccdi_employee` 表创建索引:idx_org_no, idx_status
|
||||
- [x] 1.4 创建 `ccdi_employee_relative` 员工亲属表,包含字段:relative_id, employee_id, relative_name, relative_id_card, relative_phone, relationship, create_by, create_time, update_by, update_time
|
||||
- [x] 1.5 为 `ccdi_employee_relative` 表创建索引:idx_employee_id, idx_relative_id_card
|
||||
- [x] 1.6 添加外键约束:ccdi_employee_relative.employee_id → ccdi_employee.employee_id(可选,根据项目规范决定)
|
||||
- [x] 1.7 创建 `ccdi_relative_relationship` 字典类型及字典数据(配偶、父亲、母亲、子女、兄弟姐妹、其他)
|
||||
- [x] 1.8 创建 `ccdi_employee_status` 字典类型及字典数据(在职=0、离职=1)
|
||||
|
||||
## 2. 后端实体类创建
|
||||
|
||||
- [x] 2.1 创建 `CcdiEmployee.java` 实体类,使用 @Data 注解,包含 @Excel 注解用于导入导出
|
||||
- [x] 2.2 创建 `CcdiEmployeeRelative.java` 实体类,使用 @Data 注解
|
||||
- [x] 2.3 创建 `CcdiEmployeeAddDTO.java` 新增 DTO,使用 @Data 注解,包含 @Validated 校验注解
|
||||
- [x] 2.4 创建 `CcdiEmployeeEditDTO.java` 编辑 DTO,使用 @Data 注解,包含 @Validated 校验注解
|
||||
- [x] 2.5 创建 `CcdiEmployeeQueryDTO.java` 查询 DTO,使用 @Data 注解
|
||||
- [x] 2.6 创建 `CcdiEmployeeVO.java` 视图对象,使用 @Data 注解,包含亲属列表
|
||||
- [x] 2.7 创建 `CcdiEmployeeRelativeVO.java` 亲属视图对象,使用 @Data 注解
|
||||
- [x] 2.8 创建 `CcdiEmployeeRelativeAddDTO.java` 亲属新增 DTO,使用 @Data 注解
|
||||
|
||||
## 3. Mapper 层实现
|
||||
|
||||
- [x] 3.1 创建 `CcdiEmployeeMapper.java` 接口,继承 `BaseMapper<CcdiEmployee>`
|
||||
- [x] 3.2 创建 `CcdiEmployeeMapper.xml` MyBatis 映射文件,仅实现复杂查询(如:员工详情包含亲属列表)
|
||||
- [x] 3.3 在 XML 中实现 `selectEmployeeWithRelatives` 方法,关联查询员工及其亲属信息
|
||||
- [x] 3.4 创建 `CcdiEmployeeRelativeMapper.java` 接口,继承 `BaseMapper<CcdiEmployeeRelative>`
|
||||
- [x] 3.5 创建 `CcdiEmployeeRelativeMapper.xml` MyBatis 映射文件(如需复杂查询)
|
||||
- [x] 3.6 简单 CRUD 操作使用 MyBatis Plus 提供的 BaseMapper 方法(insert, deleteById, deleteByIds, updateById, selectById, selectList)
|
||||
- [x] 3.7 简单条件查询使用 LambdaQueryWrapper 或 QueryWrapper(如:按柜员号、身份证号查询)
|
||||
- [x] 3.8 复杂多条件查询在 XML 中编写自定义 SQL
|
||||
|
||||
## 4. Service 层实现
|
||||
|
||||
- [x] 4.1 创建 `ICcdiEmployeeService.java` 接口
|
||||
- [x] 4.2 创建 `CcdiEmployeeServiceImpl.java` 实现类
|
||||
- [x] 4.3 实现查询员工列表方法 `selectEmployeeList`,支持分页和多条件查询
|
||||
- [x] 4.4 实现查询员工详情方法 `selectEmployeeById`,包含亲属信息
|
||||
- [x] 4.5 实现新增员工方法 `insertEmployee`,支持同时插入亲属信息
|
||||
- [x] 4.6 实现编辑员工方法 `updateEmployee`,支持更新亲属信息(先删除后插入)
|
||||
- [x] 4.7 实现删除员工方法 `deleteEmployeeByIds`,级联删除亲属信息
|
||||
- [x] 4.8 实现导出员工数据方法 `selectEmployeeListForExport`
|
||||
- [x] 4.9 实现导入员工数据方法 `importEmployee`,支持批量导入员工和亲属
|
||||
- [x] 4.10 实现柜员号唯一性校验
|
||||
- [x] 4.11 实现身份证号格式校验
|
||||
- [x] 4.12 实现电话格式校验
|
||||
|
||||
## 5. Controller 层实现
|
||||
|
||||
- [x] 5.1 创建 `CcdiEmployeeController.java` 控制器类
|
||||
- [x] 5.2 实现 `GET /dpc/employee/list` 查询列表接口,添加 @PreAuthorize 权限注解
|
||||
- [x] 5.3 实现 `GET /dpc/employee/{id}` 查询详情接口
|
||||
- [x] 5.4 实现 `POST /dpc/employee` 新增接口
|
||||
- [x] 5.5 实现 `PUT /dpc/employee` 编辑接口
|
||||
- [x] 5.6 实现 `DELETE /dpc/employee/{ids}` 删除接口
|
||||
- [x] 5.7 实现 `POST /dpc/employee/export` 导出接口
|
||||
- [x] 5.8 实现 `POST /dpc/employee/importTemplate` 下载模板接口
|
||||
- [x] 5.9 实现 `POST /dpc/employee/importData` 导入接口
|
||||
- [x] 5.10 为所有接口添加 @Log 注解记录操作日志
|
||||
|
||||
## 6. 导入导出功能实现
|
||||
|
||||
- [x] 6.1 配置 `CcdiEmployee` 实体的 @Excel 注解,定义导出列
|
||||
- [x] 6.2 配置 `CcdiEmployeeRelative` 实体的 @Excel 注解(用于多 Sheet 导入)
|
||||
- [x] 6.3 实现导入模板的 Excel 格式定义
|
||||
- [x] 6.4 实现 Excel 解析逻辑,支持多 Sheet 读取
|
||||
- [x] 6.5 实现员工和亲属数据的关联逻辑(通过身份证号)
|
||||
- [x] 6.6 实现导入数据校验逻辑
|
||||
- [x] 6.7 实现导入错误信息收集和反馈
|
||||
|
||||
## 7. 数据校验
|
||||
|
||||
- [x] 7.1 实现 DTO 层的 @NotBlank、@Size、@Pattern 等校验注解
|
||||
- [x] 7.2 实现身份证号格式校验(18位,符合国标)
|
||||
- [x] 7.3 实现电话号码格式校验(11位手机号)
|
||||
- [x] 7.4 实现柜员号唯一性校验
|
||||
- [x] 7.5 实现入职时间日期格式校验
|
||||
- [x] 7.6 实现亲属信息必填字段校验
|
||||
|
||||
## 8. 测试
|
||||
|
||||
- [x] 8.1 生成可执行的测试脚本
|
||||
- [x] 8.2 测试查询员工列表接口(分页、条件筛选)
|
||||
- [x] 8.3 测试查询员工详情接口(包含亲属信息)
|
||||
- [x] 8.4 测试新增员工接口(包含亲属信息)
|
||||
- [x] 8.5 测试编辑员工接口(修改基本信息、新增/修改/删除亲属)
|
||||
- [x] 8.6 测试删除员工接口(验证级联删除亲属)
|
||||
- [x] 8.7 测试导出功能
|
||||
- [x] 8.8 测试下载模板功能
|
||||
- [x] 8.9 测试导入功能(正常数据、错误数据、更新模式)
|
||||
- [x] 8.10 测试权限控制(无权限访问应被拒绝)
|
||||
- [x] 8.11 测试数据校验(空值、格式错误、重复数据)
|
||||
|
||||
## 9. API 文档生成
|
||||
|
||||
- [x] 9.1 确认所有接口在 Swagger UI 中正确显示
|
||||
- [x] 9.2 在项目文件目录下生成 API 文档(doc/API文档.md)
|
||||
- [x] 9.3 文档包含:接口地址、请求方式、请求参数、响应格式、权限要求
|
||||
441
openspec/changes/add-intermediary-blacklist/design.md
Normal file
441
openspec/changes/add-intermediary-blacklist/design.md
Normal file
@@ -0,0 +1,441 @@
|
||||
# Design: 中介人员黑名单管理模块
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### 表:ccdi_intermediary_blacklist
|
||||
|
||||
中介人员/机构黑名单主表。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `ccdi_intermediary_blacklist` (
|
||||
`intermediary_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '中介ID',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '姓名/机构名称',
|
||||
`certificate_no` VARCHAR(50) DEFAULT NULL COMMENT '证件号',
|
||||
`intermediary_type` CHAR(1) NOT NULL DEFAULT '1' COMMENT '中介类型(1个人 2机构)',
|
||||
`status` CHAR(1) NOT NULL DEFAULT '0' COMMENT '状态(0正常 1停用)',
|
||||
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
|
||||
`create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`intermediary_id`),
|
||||
KEY `idx_name` (`name`),
|
||||
KEY `idx_certificate_no` (`certificate_no`),
|
||||
KEY `idx_intermediary_type` (`intermediary_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='中介人员黑名单表';
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段名 | 类型 | 说明 | 必填 | 默认值 |
|
||||
|-------|------|------|-----|-------|
|
||||
| intermediary_id | BIGINT | 中介ID(主键) | 是 | 自增 |
|
||||
| name | VARCHAR(100) | 姓名/机构名称 | 是 | - |
|
||||
| certificate_no | VARCHAR(50) | 证件号 | 否 | NULL |
|
||||
| intermediary_type | CHAR(1) | 中介类型(1个人 2机构) | 是 | '1' |
|
||||
| status | CHAR(1) | 状态(0正常 1停用) | 是 | '0' |
|
||||
| remark | VARCHAR(500) | 备注 | 否 | NULL |
|
||||
| create_by | VARCHAR(64) | 创建者 | 是 | - |
|
||||
| create_time | DATETIME | 创建时间 | 是 | - |
|
||||
| update_by | VARCHAR(64) | 更新者 | 否 | '' |
|
||||
| update_time | DATETIME | 更新时间 | 否 | NULL |
|
||||
|
||||
### 索引设计
|
||||
|
||||
- 主键索引:`intermediary_id`
|
||||
- 普通索引:`name`(支持按名称搜索)
|
||||
- 普通索引:`certificate_no`(支持按证件号精确匹配)
|
||||
- 普通索引:`intermediary_type`(支持按类型筛选)
|
||||
|
||||
## 后端设计
|
||||
|
||||
### 模块结构
|
||||
|
||||
```
|
||||
ruoyi-dpc/
|
||||
├── src/main/java/com/ruoyi/dpc/
|
||||
│ ├── controller/
|
||||
│ │ └── CcdiIntermediaryBlacklistController.java
|
||||
│ ├── domain/
|
||||
│ │ ├── CcdiIntermediaryBlacklist.java
|
||||
│ │ ├── dto/
|
||||
│ │ │ ├── CcdiIntermediaryBlacklistAddDTO.java
|
||||
│ │ │ ├── CcdiIntermediaryBlacklistEditDTO.java
|
||||
│ │ │ └── CcdiIntermediaryBlacklistQueryDTO.java
|
||||
│ │ └── vo/
|
||||
│ │ └── CcdiIntermediaryBlacklistVO.java
|
||||
│ ├── mapper/
|
||||
│ │ └── CcdiIntermediaryBlacklistMapper.java
|
||||
│ └── service/
|
||||
│ ├── ICcdiIntermediaryBlacklistService.java
|
||||
│ └── impl/
|
||||
│ └── CcdiIntermediaryBlacklistServiceImpl.java
|
||||
└── src/main/resources/mapper/dpc/
|
||||
└── CcdiIntermediaryBlacklistMapper.xml
|
||||
```
|
||||
|
||||
### Controller 层设计
|
||||
|
||||
**CcdiIntermediaryBlacklistController**
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/dpc/intermediary")
|
||||
public class CcdiIntermediaryBlacklistController extends BaseController {
|
||||
|
||||
/**
|
||||
* 查询中介黑名单列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('dpc:intermediary:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(CcdiIntermediaryBlacklistQueryDTO queryDTO) {
|
||||
// 分页查询逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出中介黑名单列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('dpc:intermediary:export')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, CcdiIntermediaryBlacklistQueryDTO queryDTO) {
|
||||
// 导出逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取中介黑名单详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('dpc:intermediary:query')")
|
||||
@GetMapping(value = "/{intermediaryId}")
|
||||
public AjaxResult getInfo(@PathVariable Long intermediaryId) {
|
||||
// 查询详情逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增中介黑名单
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('dpc:intermediary:add')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@Validated @RequestBody CcdiIntermediaryBlacklistAddDTO addDTO) {
|
||||
// 新增逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改中介黑名单
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('dpc:intermediary:edit')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@Validated @RequestBody CcdiIntermediaryBlacklistEditDTO editDTO) {
|
||||
// 修改逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除中介黑名单
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('dpc:intermediary:remove')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{intermediaryIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] intermediaryIds) {
|
||||
// 删除逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载导入模板
|
||||
*/
|
||||
@PostMapping("/importTemplate")
|
||||
public void importTemplate(HttpServletResponse response) {
|
||||
// 生成 Excel 模板
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入中介黑名单
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('dpc:intermediary:import')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importData")
|
||||
public AjaxResult importData(MultipartFile file) {
|
||||
// 导入逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service 层设计
|
||||
|
||||
**ICcdiIntermediaryBlacklistService**
|
||||
|
||||
```java
|
||||
public interface ICcdiIntermediaryBlacklistService {
|
||||
|
||||
/**
|
||||
* 查询中介黑名单列表
|
||||
*/
|
||||
List<CcdiIntermediaryBlacklistVO> selectIntermediaryList(CcdiIntermediaryBlacklistQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 查询中介黑名单详细信息
|
||||
*/
|
||||
CcdiIntermediaryBlacklistVO selectIntermediaryById(Long intermediaryId);
|
||||
|
||||
/**
|
||||
* 新增中介黑名单
|
||||
*/
|
||||
int insertIntermediary(CcdiIntermediaryBlacklistAddDTO addDTO);
|
||||
|
||||
/**
|
||||
* 修改中介黑名单
|
||||
*/
|
||||
int updateIntermediary(CcdiIntermediaryBlacklistEditDTO editDTO);
|
||||
|
||||
/**
|
||||
* 批量删除中介黑名单
|
||||
*/
|
||||
int deleteIntermediaryByIds(Long[] intermediaryIds);
|
||||
|
||||
/**
|
||||
* 导入中介黑名单数据
|
||||
*/
|
||||
String importIntermediary(List<CcdiIntermediaryBlacklistAddDTO> addDTOList, boolean isUpdateSupport);
|
||||
}
|
||||
```
|
||||
|
||||
### Domain 层设计
|
||||
|
||||
**实体类注解**
|
||||
|
||||
```java
|
||||
public class CcdiIntermediaryBlacklist {
|
||||
|
||||
/** 中介ID */
|
||||
private Long intermediaryId;
|
||||
|
||||
/** 姓名/机构名称 */
|
||||
@Excel(name = "姓名/机构名称")
|
||||
private String name;
|
||||
|
||||
/** 证件号 */
|
||||
@Excel(name = "证件号")
|
||||
private String certificateNo;
|
||||
|
||||
/** 中介类型 */
|
||||
@Excel(name = "中介类型", readConverterExp = "1=个人,2=机构")
|
||||
private String intermediaryType;
|
||||
|
||||
/** 状态 */
|
||||
@Excel(name = "状态", readConverterExp = "0=正常,1=停用")
|
||||
private String status;
|
||||
|
||||
/** 备注 */
|
||||
@Excel(name = "备注")
|
||||
private String remark;
|
||||
|
||||
/** 创建者 */
|
||||
private String createBy;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date createTime;
|
||||
|
||||
/** 更新者 */
|
||||
private String updateBy;
|
||||
|
||||
/** 更新时间 */
|
||||
private Date updateTime;
|
||||
}
|
||||
```
|
||||
|
||||
## 前端设计
|
||||
|
||||
### 页面结构
|
||||
|
||||
```
|
||||
ruoyi-ui/src/views/dpcIntermediary/
|
||||
├── index.vue # 主页面(列表 + 搜索)
|
||||
└── components/
|
||||
└── form-dialog.vue # 新增/编辑对话框
|
||||
```
|
||||
|
||||
### 主要功能组件
|
||||
|
||||
**1. 搜索区**
|
||||
- 按姓名/机构名称搜索(模糊)
|
||||
- 按证件号搜索(精确)
|
||||
- 按中介类型筛选(下拉:全部/个人/机构)
|
||||
- 按状态筛选(下拉:全部/正常/停用)
|
||||
|
||||
**2. 操作按钮区**
|
||||
- 新增按钮
|
||||
- 导入按钮
|
||||
- 导出按钮
|
||||
- 下载模板按钮
|
||||
|
||||
**3. 数据表格**
|
||||
- 列:姓名/机构名称、证件号、中介类型、状态、创建时间、操作
|
||||
- 操作列:编辑、删除
|
||||
|
||||
**4. 新增/编辑对话框**
|
||||
- 姓名/机构名称(必填)
|
||||
- 证件号(选填)
|
||||
- 中介类型(必填,单选:个人/机构)
|
||||
- 状态(必填,单选:正常/停用)
|
||||
- 备注(选填,文本域)
|
||||
|
||||
**5. 导入对话框**
|
||||
- 上传 Excel 文件
|
||||
- 显示导入结果(成功/失败数量)
|
||||
- 显示错误详情(如有)
|
||||
|
||||
### API 设计
|
||||
|
||||
**dpcIntermediary.js**
|
||||
|
||||
```javascript
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询中介黑名单列表
|
||||
export function listIntermediary(query) {
|
||||
return request({
|
||||
url: '/dpc/intermediary/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询中介黑名单详细
|
||||
export function getIntermediary(intermediaryId) {
|
||||
return request({
|
||||
url: '/dpc/intermediary/' + intermediaryId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增中介黑名单
|
||||
export function addIntermediary(data) {
|
||||
return request({
|
||||
url: '/dpc/intermediary',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改中介黑名单
|
||||
export function updateIntermediary(data) {
|
||||
return request({
|
||||
url: '/dpc/intermediary',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除中介黑名单
|
||||
export function delIntermediary(intermediaryIds) {
|
||||
return request({
|
||||
url: '/dpc/intermediary/' + intermediaryIds,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 导出中介黑名单
|
||||
export function exportIntermediary(query) {
|
||||
return request({
|
||||
url: '/dpc/intermediary/export',
|
||||
method: 'post',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 下载导入模板
|
||||
export function importTemplate() {
|
||||
return request({
|
||||
url: '/dpc/intermediary/importTemplate',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 导入中介黑名单
|
||||
export function importData(data) {
|
||||
return request({
|
||||
url: '/dpc/intermediary/importData',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Excel 导入导出设计
|
||||
|
||||
### Excel 模板格式
|
||||
|
||||
| 姓名/机构名称 | 证件号 | 中介类型 | 状态 | 备注 |
|
||||
|-------------|-------|---------|------|------|
|
||||
| 张三 | 110101199001011234 | 个人 | 正常 | 测试数据 |
|
||||
| XX中介公司 | 91110000XXXXXXXXXX | 机构 | 正常 | - |
|
||||
|
||||
### 导入数据验证规则
|
||||
|
||||
1. **姓名/机构名称**:必填,长度 1-100 字符
|
||||
2. **证件号**:选填,长度不超过 50 字符
|
||||
3. **中介类型**:必填,只能填"个人"或"机构"
|
||||
4. **状态**:必填,只能填"正常"或"停用"
|
||||
5. **备注**:选填,长度不超过 500 字符
|
||||
|
||||
### 导入错误处理
|
||||
|
||||
- 记录每一行的错误信息
|
||||
- 返回导入结果统计(成功数、失败数)
|
||||
- 失败数据不写入数据库
|
||||
- 支持更新模式(可选)
|
||||
|
||||
## 权限设计
|
||||
|
||||
| 权限标识 | 说明 | 对应接口 |
|
||||
|---------|------|---------|
|
||||
| dpc:intermediary:list | 查询中介黑名单列表 | GET /dpc/intermediary/list |
|
||||
| dpc:intermediary:query | 查询中介黑名单详情 | GET /dpc/intermediary/{id} |
|
||||
| dpc:intermediary:add | 新增中介黑名单 | POST /dpc/intermediary |
|
||||
| dpc:intermediary:edit | 修改中介黑名单 | PUT /dpc/intermediary |
|
||||
| dpc:intermediary:remove | 删除中介黑名单 | DELETE /dpc/intermediary/{ids} |
|
||||
| dpc:intermediary:export | 导出中介黑名单 | POST /dpc/intermediary/export |
|
||||
| dpc:intermediary:import | 导入中介黑名单 | POST /dpc/intermediary/importData |
|
||||
|
||||
## 菜单设计
|
||||
|
||||
在系统菜单表中添加以下菜单记录:
|
||||
|
||||
```sql
|
||||
-- 一级菜单:信息维护
|
||||
INSERT INTO sys_menu VALUES (
|
||||
2000, '信息维护', NULL, 1, 0, 'dpc', NULL, 1, 0, 'M', '0', '0', '', 'example', 'admin', NOW(), '', NULL, '信息维护目录'
|
||||
);
|
||||
|
||||
-- 二级菜单:中介库管理
|
||||
INSERT INTO sys_menu VALUES (
|
||||
2001, '中介库管理', 2000, 1, 0, 'intermediary', 'dpcIntermediary/index', 1, 0, 'C', '0', '0', 'dpc:intermediary:list', 'user', 'admin', NOW(), '', NULL, '中介库管理菜单'
|
||||
);
|
||||
|
||||
-- 中介库管理按钮权限
|
||||
INSERT INTO sys_menu VALUES (2002, '中介查询', 2001, 1, 0, '', '', 1, 0, 'F', '0', '0', 'dpc:intermediary:query', '#', 'admin', NOW(), '', NULL, '');
|
||||
INSERT INTO sys_menu VALUES (2003, '中介新增', 2001, 2, 0, '', '', 1, 0, 'F', '0', '0', 'dpc:intermediary:add', '#', 'admin', NOW(), '', NULL, '');
|
||||
INSERT INTO sys_menu VALUES (2004, '中介修改', 2001, 3, 0, '', '', 1, 0, 'F', '0', '0', 'dpc:intermediary:edit', '#', 'admin', NOW(), '', NULL, '');
|
||||
INSERT INTO sys_menu VALUES (2005, '中介删除', 2001, 4, 0, '', '', 1, 0, 'F', '0', '0', 'dpc:intermediary:remove', '#', 'admin', NOW(), '', NULL, '');
|
||||
INSERT INTO sys_menu VALUES (2006, '中介导出', 2001, 5, 0, '', '', 1, 0, 'F', '0', '0', 'dpc:intermediary:export', '#', 'admin', NOW(), '', NULL, '');
|
||||
INSERT INTO sys_menu VALUES (2007, '中介导入', 2001, 6, 0, '', '', 1, 0, 'F', '0', '0', 'dpc:intermediary:import', '#', 'admin', NOW(), '', NULL, '');
|
||||
```
|
||||
|
||||
## 字典数据设计
|
||||
|
||||
### 中介类型(ccdi_intermediary_type)
|
||||
|
||||
| 字典值 | 字典标签 | 排序 | 状态 |
|
||||
|-------|---------|-----|------|
|
||||
| 1 | 个人 | 1 | 正常 |
|
||||
| 2 | 机构 | 2 | 正常 |
|
||||
|
||||
## 技术约束
|
||||
|
||||
1. **后端框架**:Spring Boot 3.5.8
|
||||
2. **ORM 框架**:MyBatis 3.0.5
|
||||
3. **Excel 处理**:EasyExcel 3.3.4(依赖 `replace-poi-with-easyexcel` change)
|
||||
4. **前端框架**:Vue 2.6.12 + Element UI 2.15.14
|
||||
5. **数据库**:MySQL 8.2.0
|
||||
6. **字符编码**:UTF-8(utf8mb4)
|
||||
124
openspec/changes/add-intermediary-blacklist/proposal.md
Normal file
124
openspec/changes/add-intermediary-blacklist/proposal.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Proposal: 添加中介人员黑名单管理模块
|
||||
|
||||
## Change ID
|
||||
`add-intermediary-blacklist`
|
||||
|
||||
## Summary
|
||||
添加中介人员黑名单管理模块,实现外部中介人员/机构黑名单库的建立和维护。该模块支持 Excel 模板下载、批量导入、增删改查等功能,是系统中信息维护模块的核心子功能之一。
|
||||
|
||||
## Motivation
|
||||
目前系统缺少中介人员黑名单管理功能。用户需要:
|
||||
1. 建立并维护外部中介人员/机构黑名单库
|
||||
2. 通过 Excel 批量导入中介名单数据
|
||||
3. 对中介名单进行增、删、改、查操作
|
||||
4. 支持按姓名、证件号、机构名称等条件查询
|
||||
5. 在项目工作台中选择确认后的可疑名单
|
||||
6. 当员工交易对手命中该库时,系统自动产生高风险预警
|
||||
|
||||
## Scope
|
||||
本提案实现以下功能:
|
||||
|
||||
### 包含的功能
|
||||
- **3.1.1 名单导入**
|
||||
- Excel 模板下载(包含必填字段示例)
|
||||
- Excel 批量导入中介名单
|
||||
- 导入数据验证与错误提示
|
||||
|
||||
- **3.1.2 名单维护**
|
||||
- 新增中介信息(个人/机构)
|
||||
- 编辑中介信息
|
||||
- 删除中介信息(带使用检查)
|
||||
- 批量删除
|
||||
|
||||
- **3.1.3 名单查询**
|
||||
- 按姓名/机构名称模糊搜索
|
||||
- 按证件号精确搜索
|
||||
- 按中介类型筛选(个人/机构)
|
||||
- 按状态筛选(有效/失效)
|
||||
|
||||
- **3.1.4 名单导出**
|
||||
- 导出当前查询结果为 Excel
|
||||
|
||||
### 明确排除
|
||||
- **3.1.5 名单选择**(在项目工作台中选择名单的功能,属于项目工作台模块)
|
||||
- **3.1.6 自动预警**(命中黑名单产生预警的功能,属于风险分析模块)
|
||||
|
||||
## Proposed Design
|
||||
详见 [design.md](./design.md)
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### 选项1:使用若依代码生成器
|
||||
**优点**:
|
||||
- 快速生成标准 CRUD 代码
|
||||
- 自动生成前端页面和 API
|
||||
- 符合项目基本规范
|
||||
|
||||
**缺点**:
|
||||
- 需要手动调整 Excel 导入导出逻辑
|
||||
- 需要手动添加数据验证规则
|
||||
- 需要手动处理个人/机构两种类型的差异显示
|
||||
|
||||
**决定**:部分采用。使用代码生成器生成基础 CRUD 代码,然后手动添加 Excel 导入导出和业务验证逻辑。
|
||||
|
||||
### 选项2:完全自定义开发
|
||||
**优点**:
|
||||
- 完全控制实现细节
|
||||
- 可以根据业务需求灵活定制
|
||||
|
||||
**缺点**:
|
||||
- 开发周期较长
|
||||
- 需要确保代码风格与若依框架一致
|
||||
|
||||
**决定**:不采用。中介黑名单管理是标准 CRUD 功能,使用代码生成器可提高开发效率。
|
||||
|
||||
## Impact
|
||||
|
||||
### 后端影响
|
||||
- 新建 `ruoyi-dpc` 模块(如果不存在),与若依框架代码分离
|
||||
- 新增表:`ccdi_intermediary_blacklist`(中介黑名单主表)
|
||||
- 新增权限:`dpc:intermediary:list`, `dpc:intermediary:query`, `dpc:intermediary:add`, `dpc:intermediary:edit`, `dpc:intermediary:remove`, `dpc:intermediary:export`, `dpc:intermediary:import`
|
||||
- Controller 层:`ruoyi-dpc/src/main/java/com/ruoyi/dpc/controller/CcdiIntermediaryBlacklistController.java`
|
||||
|
||||
### 前端影响
|
||||
- 新增视图:`ruoyi-ui/src/views/dpcIntermediary/`
|
||||
- 新增 API:`ruoyi-ui/src/api/dpcIntermediary.js`
|
||||
- 新增菜单:信息维护 > 中介库管理(注意与数据库中菜单表进行联动修改)
|
||||
|
||||
### 数据库影响
|
||||
- 新增表:`ccdi_intermediary_blacklist`
|
||||
|
||||
## Dependencies
|
||||
- 依赖 EasyExcel 进行 Excel 导入导出(参考 `replace-poi-with-easyexcel` change)
|
||||
- 依赖若依框架的文件上传功能
|
||||
- 依赖若依框架的权限系统
|
||||
|
||||
## Related Changes
|
||||
- `replace-poi-with-easyexcel` - 依赖 EasyExcel 替换完成后的 Excel 工具
|
||||
|
||||
## Open Questions
|
||||
1. **证件号校验**:
|
||||
- 是否需要添加格式校验?
|
||||
- 建议:添加基本格式校验,但不强制要求(可能是历史数据)
|
||||
|
||||
2. **删除前的使用检查**:
|
||||
- 如何判断中介信息是否在项目中使用?
|
||||
- 建议:暂时不实现使用检查,允许直接删除(可在后续版本中添加关联表)
|
||||
|
||||
3. **中介类型的数据结构**:
|
||||
- 个人和机构是否分开存储?
|
||||
- 建议:使用同一张表,通过 `intermediary_type` 字段区分
|
||||
|
||||
## Success Criteria
|
||||
- [ ] 用户可以下载 Excel 导入模板
|
||||
- [ ] 用户可以通过 Excel 批量导入中介名单
|
||||
- [ ] 用户可以新增中介信息(个人/机构)
|
||||
- [ ] 用户可以编辑中介信息
|
||||
- [ ] 用户可以删除中介信息
|
||||
- [ ] 用户可以按多种条件查询中介名单
|
||||
- [ ] 用户可以导出查询结果为 Excel
|
||||
- [ ] 导入时进行数据验证并提示错误
|
||||
|
||||
## References
|
||||
- [模块设计文档](../../../doc/modules/03-信息维护模块.md)
|
||||
- [若依开发文档](https://doc.ruoyi.vip/)
|
||||
@@ -0,0 +1,388 @@
|
||||
# Spec: 中介人员黑名单管理
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 系统SHALL支持查询中介黑名单列表
|
||||
|
||||
系统MUST提供查询功能,允许用户查询系统中已维护的中介人员/机构黑名单列表,以便了解当前黑名单库的内容。
|
||||
|
||||
#### Scenario: 分页查询中介黑名单列表
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:list` 权限
|
||||
**When** 用户访问中介库管理页面
|
||||
**Then** 系统应显示中介黑名单列表,支持分页展示
|
||||
|
||||
#### Scenario: 按名称模糊搜索中介黑名单
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:list` 权限
|
||||
**And** 系统中存在姓名为"张三"的中介人员
|
||||
**When** 用户在搜索框输入"张"并点击搜索
|
||||
**Then** 系统应返回所有姓名中包含"张"的中介记录
|
||||
|
||||
#### Scenario: 按证件号精确搜索中介黑名单
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:list` 权限
|
||||
**And** 系统中存在证件号为"110101199001011234"的中介人员
|
||||
**When** 用户在搜索框输入"110101199001011234"并点击搜索
|
||||
**Then** 系统应返回该证件号对应的中介记录
|
||||
|
||||
#### Scenario: 按中介类型筛选中介黑名单
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:list` 权限
|
||||
**And** 系统中存在个人和机构两种类型的中介记录
|
||||
**When** 用户选择中介类型为"个人"并点击搜索
|
||||
**Then** 系统应仅返回中介类型为"个人"的记录
|
||||
|
||||
#### Scenario: 按状态筛选中介黑名单
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:list` 权限
|
||||
**And** 系统中存在正常和停用两种状态的中介记录
|
||||
**When** 用户选择状态为"正常"并点击搜索
|
||||
**Then** 系统应仅返回状态为"正常"的记录
|
||||
|
||||
#### Scenario: 组合条件查询中介黑名单
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:list` 权限
|
||||
**When** 用户同时输入名称"张"、选择中介类型为"个人"、选择状态为"正常"并点击搜索
|
||||
**Then** 系统应返回同时满足所有条件的记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持新增中介黑名单
|
||||
|
||||
系统MUST提供新增功能,允许用户手动添加中介人员或机构到黑名单中,以建立和维护黑名单库。
|
||||
|
||||
#### Scenario: 新增个人类型的中介黑名单
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 填写姓名为"张三"
|
||||
**And** 填写证件号为"110101199001011234"
|
||||
**And** 选择中介类型为"个人"
|
||||
**And** 选择状态为"正常"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应保存中介信息并提示"操作成功"
|
||||
**And** 列表中应显示新增的记录
|
||||
|
||||
#### Scenario: 新增机构类型的中介黑名单
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 填写机构名称为"XX中介公司"
|
||||
**And** 填写证件号为"91110000XXXXXXXXXX"
|
||||
**And** 选择中介类型为"机构"
|
||||
**And** 选择状态为"正常"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应保存中介信息并提示"操作成功"
|
||||
**And** 列表中应显示新增的记录
|
||||
|
||||
#### Scenario: 新增时不填写证件号
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 仅填写姓名为"张三"
|
||||
**And** 不填写证件号
|
||||
**And** 选择中介类型为"个人"
|
||||
**And** 选择状态为"正常"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应保存中介信息并提示"操作成功"
|
||||
|
||||
#### Scenario: 新增时姓名为空应校验失败
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 不填写姓名
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应提示"姓名/机构名称不能为空"
|
||||
**And** 不保存数据
|
||||
|
||||
#### Scenario: 新增时姓名超过100字符应校验失败
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 填写姓名为101个字符的字符串
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应提示"姓名/机构名称长度不能超过100个字符"
|
||||
**And** 不保存数据
|
||||
|
||||
#### Scenario: 新增时填写备注信息
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:add` 权限
|
||||
**When** 用户点击"新增"按钮
|
||||
**And** 填写必填信息
|
||||
**And** 填写备注为"涉及多起违规交易"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应保存中介信息及备注
|
||||
**And** 列表中应显示新增的记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持编辑中介黑名单
|
||||
|
||||
系统MUST提供编辑功能,允许用户修改已存在的中介黑名单信息,以更新或纠正数据。
|
||||
|
||||
#### Scenario: 编辑中介黑名单的基本信息
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:edit` 权限
|
||||
**And** 系统中存在一条中介记录
|
||||
**When** 用户点击该记录的"编辑"按钮
|
||||
**And** 修改姓名为"李四"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应更新中介信息并提示"操作成功"
|
||||
**And** 列表中应显示更新后的姓名
|
||||
|
||||
#### Scenario: 编辑中介黑名单的证件号
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:edit` 权限
|
||||
**And** 系统中存在一条中介记录
|
||||
**When** 用户点击该记录的"编辑"按钮
|
||||
**And** 修改证件号为"110101199001011235"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应更新证件号并提示"操作成功"
|
||||
|
||||
#### Scenario: 编辑中介黑名单的状态为停用
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:edit` 权限
|
||||
**And** 系统中存在一条状态为"正常"的中介记录
|
||||
**When** 用户点击该记录的"编辑"按钮
|
||||
**And** 修改状态为"停用"
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应更新状态并提示"操作成功"
|
||||
**And** 该记录在列表中应显示为"停用"状态
|
||||
|
||||
#### Scenario: 编辑时姓名为空应校验失败
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:edit` 权限
|
||||
**And** 系统中存在一条中介记录
|
||||
**When** 用户点击该记录的"编辑"按钮
|
||||
**And** 清空姓名字段
|
||||
**And** 点击"确定"按钮
|
||||
**Then** 系统应提示"姓名/机构名称不能为空"
|
||||
**And** 不更新数据
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持删除中介黑名单
|
||||
|
||||
系统MUST提供删除功能,允许用户删除不再需要的中介黑名单记录,以保持黑名单库的准确性。
|
||||
|
||||
#### Scenario: 删除单条中介黑名单记录
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:remove` 权限
|
||||
**And** 系统中存在一条中介记录
|
||||
**When** 用户点击该记录的"删除"按钮
|
||||
**And** 确认删除操作
|
||||
**Then** 系统应删除该记录并提示"操作成功"
|
||||
**And** 列表中不再显示该记录
|
||||
|
||||
#### Scenario: 批量删除中介黑名单记录
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:remove` 权限
|
||||
**And** 系统中存在多条中介记录
|
||||
**When** 用户勾选3条记录
|
||||
**And** 点击"删除"按钮
|
||||
**And** 确认删除操作
|
||||
**Then** 系统应删除选中的3条记录并提示"操作成功"
|
||||
**And** 列表中不再显示这3条记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持导出中介黑名单
|
||||
|
||||
系统MUST提供导出功能,允许用户将查询结果导出为 Excel 文件,以便进行离线分析或备份。
|
||||
|
||||
#### Scenario: 导出所有中介黑名单数据
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:export` 权限
|
||||
**And** 系统中存在100条中介记录
|
||||
**When** 用户点击"导出"按钮
|
||||
**And** 不设置任何筛选条件
|
||||
**Then** 系统应生成包含100条记录的 Excel 文件并下载
|
||||
|
||||
#### Scenario: 导出筛选后的中介黑名单数据
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:export` 权限
|
||||
**And** 系统中存在个人和机构两种类型的中介记录
|
||||
**When** 用户筛选中介类型为"个人"
|
||||
**And** 点击"导出"按钮
|
||||
**Then** 系统应生成仅包含个人类型记录的 Excel 文件并下载
|
||||
|
||||
#### Scenario: 导出的 Excel 文件格式正确
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:export` 权限
|
||||
**When** 用户点击"导出"按钮
|
||||
**Then** Excel 文件应包含以下列:姓名/机构名称、证件号、中介类型、状态、备注、创建时间
|
||||
**And** 表头应使用中文显示
|
||||
**And** 数据应正确显示(中介类型、状态应显示中文而非数字)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持下载 Excel 导入模板
|
||||
|
||||
系统MUST提供模板下载功能,允许用户下载标准的 Excel 导入模板,以便按照模板格式准备数据后批量导入。
|
||||
|
||||
#### Scenario: 下载导入模板
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:import` 权限
|
||||
**When** 用户点击"下载模板"按钮
|
||||
**Then** 系统应生成 Excel 模板文件并下载
|
||||
**And** 模板应包含示例数据行
|
||||
**And** 模板应包含以下列:姓名/机构名称、证件号、中介类型、状态、备注
|
||||
|
||||
#### Scenario: 模板中的示例数据正确
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:import` 权限
|
||||
**When** 用户下载导入模板
|
||||
**Then** 模板应包含至少一行示例数据
|
||||
**And** 示例数据应展示正确的填写格式
|
||||
**And** 中介类型示例应为"个人"或"机构"
|
||||
**And** 状态示例应为"正常"或"停用"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL支持通过 Excel 批量导入中介黑名单
|
||||
|
||||
系统MUST提供批量导入功能,允许用户通过 Excel 文件批量导入中介黑名单数据,以提高数据录入效率。
|
||||
|
||||
#### Scenario: 导入包含有效数据的 Excel 文件
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:import` 权限
|
||||
**And** 用户已准备好包含10条有效数据的 Excel 文件
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择准备好的 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应导入10条记录并提示"成功导入10条数据"
|
||||
**And** 列表中应显示这10条新增记录
|
||||
|
||||
#### Scenario: 导入时部分数据格式错误
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:import` 权限
|
||||
**And** 用户准备的 Excel 文件包含10条数据
|
||||
**And** 其中2条数据的姓名字段为空
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应仅导入8条有效数据
|
||||
**And** 提示"成功导入8条数据,失败2条数据"
|
||||
**And** 显示失败行的错误详情
|
||||
|
||||
#### Scenario: 导入时中介类型值不在允许范围内
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:import` 权限
|
||||
**And** 用户准备的 Excel 文件中某条记录的中介类型填写为"未知"
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应提示该行数据"中介类型只能填写'个人'或'机构'"
|
||||
**And** 该行数据不被导入
|
||||
|
||||
#### Scenario: 导入时状态值不在允许范围内
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:import` 权限
|
||||
**And** 用户准备的 Excel 文件中某条记录的状态填写为"未知"
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应提示该行数据"状态只能填写'正常'或'停用'"
|
||||
**And** 该行数据不被导入
|
||||
|
||||
#### Scenario: 导入时证件号超过50字符
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:import` 权限
|
||||
**And** 用户准备的 Excel 文件中某条记录的证件号为51个字符
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应提示该行数据"证件号长度不能超过50个字符"
|
||||
**And** 该行数据不被导入
|
||||
|
||||
#### Scenario: 导入时备注超过500字符
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:import` 权限
|
||||
**And** 用户准备的 Excel 文件中某条记录的备注为501个字符
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应提示该行数据"备注长度不能超过500个字符"
|
||||
**And** 该行数据不被导入
|
||||
|
||||
#### Scenario: 导入时选择更新支持模式
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:import` 权限
|
||||
**And** 系统中已存在一条证件号为"110101199001011234"的记录
|
||||
**And** 用户准备的 Excel 文件包含相同证件号的记录但姓名不同
|
||||
**When** 用户点击"导入"按钮
|
||||
**And** 选择 Excel 文件
|
||||
**And** 勾选"更新已存在数据"选项
|
||||
**And** 点击"确定"
|
||||
**Then** 系统应更新该证件号对应的记录
|
||||
**And** 提示包含更新成功的消息
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL验证用户权限
|
||||
|
||||
系统MUST根据用户的角色和权限控制其对中介黑名单功能的访问,确保未授权用户无法执行相应操作。
|
||||
|
||||
#### Scenario: 无权限用户访问列表应被拒绝
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:intermediary:list` 权限
|
||||
**When** 用户尝试访问中介库管理页面
|
||||
**Then** 系统应提示"您没有权限执行此操作"
|
||||
**And** 不显示列表数据
|
||||
|
||||
#### Scenario: 无权限用户尝试新增应被拒绝
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:intermediary:add` 权限
|
||||
**When** 用户尝试点击"新增"按钮
|
||||
**Then** 系统应隐藏或禁用"新增"按钮
|
||||
|
||||
#### Scenario: 无权限用户尝试导出应被拒绝
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:intermediary:export` 权限
|
||||
**When** 用户尝试点击"导出"按钮
|
||||
**Then** 系统应隐藏或禁用"导出"按钮
|
||||
|
||||
#### Scenario: 无权限用户尝试导入应被拒绝
|
||||
|
||||
**Given** 用户已登录系统
|
||||
**And** 该用户不具有 `dpc:intermediary:import` 权限
|
||||
**When** 用户尝试点击"导入"按钮
|
||||
**Then** 系统应隐藏或禁用"导入"按钮
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 系统SHALL记录操作日志
|
||||
|
||||
系统MUST记录用户对中介黑名单的关键操作,包括新增、修改、删除、导入等操作,以便审计和追溯。
|
||||
|
||||
#### Scenario: 新增操作应记录日志
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:add` 权限
|
||||
**When** 用户成功新增一条中介记录
|
||||
**Then** 系统应在操作日志中记录
|
||||
**And** 日志应包含:操作人、操作时间、操作类型、操作内容
|
||||
|
||||
#### Scenario: 修改操作应记录日志
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:edit` 权限
|
||||
**When** 用户成功修改一条中介记录
|
||||
**Then** 系统应在操作日志中记录
|
||||
**And** 日志应包含:操作人、操作时间、操作类型、修改内容
|
||||
|
||||
#### Scenario: 删除操作应记录日志
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:remove` 权限
|
||||
**When** 用户成功删除一条中介记录
|
||||
**Then** 系统应在操作日志中记录
|
||||
**And** 日志应包含:操作人、操作时间、操作类型、删除的记录ID
|
||||
|
||||
#### Scenario: 导入操作应记录日志
|
||||
|
||||
**Given** 用户已登录系统且具有 `dpc:intermediary:import` 权限
|
||||
**When** 用户成功导入中介数据
|
||||
**Then** 系统应在操作日志中记录
|
||||
**And** 日志应包含:操作人、操作时间、操作类型、导入数量
|
||||
219
openspec/changes/add-intermediary-blacklist/tasks.md
Normal file
219
openspec/changes/add-intermediary-blacklist/tasks.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Tasks: 添加中介人员黑名单管理模块
|
||||
|
||||
## 环境准备
|
||||
|
||||
- [ ] 1.1 确认 `replace-poi-with-easyexcel` change 已完成(EasyExcel 依赖可用)
|
||||
- [ ] 1.2 创建 `ruoyi-dpc` 模块目录结构(如果不存在)
|
||||
- [ ] 1.3 在父 pom.xml 中添加 `ruoyi-dpc` 模块声明(如果不存在)
|
||||
|
||||
## 数据库设计
|
||||
|
||||
- [ ] 2.1 编写数据库建表 SQL
|
||||
- [ ] 2.1.1 创建 `ccdi_intermediary_blacklist` 表
|
||||
- [ ] 2.1.2 添加索引(name, id_card, intermediary_type)
|
||||
- [ ] 2.1.3 添加表注释和字段注释
|
||||
|
||||
- [ ] 2.2 编写菜单权限 SQL
|
||||
- [ ] 2.2.1 添加"信息维护"一级菜单(menu_id: 2000)
|
||||
- [ ] 2.2.2 添加"中介库管理"二级菜单(menu_id: 2001)
|
||||
- [ ] 2.2.3 添加按钮权限(查询、新增、修改、删除、导出、导入)
|
||||
|
||||
- [ ] 2.3 编写字典数据 SQL
|
||||
- [ ] 2.3.1 添加"中介类型"字典(ccdi_intermediary_type)
|
||||
- [ ] 2.3.2 添加"风险等级"字典(ccdi_risk_level)
|
||||
|
||||
- [ ] 2.4 执行 SQL 脚本初始化数据
|
||||
|
||||
## 后端开发
|
||||
|
||||
- [ ] 3.1 创建 Domain 层
|
||||
- [ ] 3.1.1 创建 `CcdiIntermediaryBlacklist.java` 实体类
|
||||
- [ ] 3.1.2 创建 `CcdiIntermediaryBlacklistAddDTO.java` 新增 DTO
|
||||
- [ ] 3.1.3 创建 `CcdiIntermediaryBlacklistEditDTO.java` 编辑 DTO
|
||||
- [ ] 3.1.4 创建 `CcdiIntermediaryBlacklistQueryDTO.java` 查询 DTO
|
||||
- [ ] 3.1.5 创建 `CcdiIntermediaryBlacklistVO.java` 视图对象
|
||||
- [ ] 3.1.6 添加 `@Excel` 注解支持导入导出
|
||||
|
||||
- [ ] 3.2 创建 Mapper 层
|
||||
- [ ] 3.2.1 创建 `CcdiIntermediaryBlacklistMapper.java` 接口
|
||||
- [ ] 3.2.2 创建 `CcdiIntermediaryBlacklistMapper.xml` 映射文件
|
||||
- [ ] 3.2.3 实现 `selectIntermediaryList` 方法(支持条件查询)
|
||||
- [ ] 3.2.4 实现 `selectIntermediaryById` 方法
|
||||
- [ ] 3.2.5 实现 `insertIntermediary` 方法
|
||||
- [ ] 3.2.6 实现 `updateIntermediary` 方法
|
||||
- [ ] 3.2.7 实现 `deleteIntermediaryByIds` 方法
|
||||
|
||||
- [ ] 3.3 创建 Service 层
|
||||
- [ ] 3.3.1 创建 `ICcdiIntermediaryBlacklistService.java` 接口
|
||||
- [ ] 3.3.2 创建 `CcdiIntermediaryBlacklistServiceImpl.java` 实现类
|
||||
- [ ] 3.3.3 实现查询列表业务逻辑(分页)
|
||||
- [ ] 3.3.4 实现查询详情业务逻辑
|
||||
- [ ] 3.3.5 实现新增业务逻辑(含数据校验)
|
||||
- [ ] 3.3.6 实现修改业务逻辑(含数据校验)
|
||||
- [ ] 3.3.7 实现删除业务逻辑
|
||||
- [ ] 3.3.8 实现导出业务逻辑
|
||||
- [ ] 3.3.9 实现导入业务逻辑(含数据验证)
|
||||
|
||||
- [ ] 3.4 创建 Controller 层
|
||||
- [ ] 3.4.1 创建 `CcdiIntermediaryBlacklistController.java`
|
||||
- [ ] 3.4.2 实现 `list` 接口(查询列表)
|
||||
- [ ] 3.4.3 实现 `getInfo` 接口(查询详情)
|
||||
- [ ] 3.4.4 实现 `add` 接口(新增)
|
||||
- [ ] 3.4.5 实现 `edit` 接口(修改)
|
||||
- [ ] 3.4.6 实现 `remove` 接口(删除)
|
||||
- [ ] 3.4.7 实现 `export` 接口(导出 Excel)
|
||||
- [ ] 3.4.8 实现 `importTemplate` 接口(下载模板)
|
||||
- [ ] 3.4.9 实现 `importData` 接口(导入 Excel)
|
||||
- [ ] 3.4.10 添加 `@PreAuthorize` 权限注解
|
||||
- [ ] 3.4.11 添加 `@Log` 操作日志注解
|
||||
|
||||
- [ ] 3.5 实现数据验证逻辑
|
||||
- [ ] 3.5.1 姓名必填校验
|
||||
- [ ] 3.5.2 姓名长度校验(1-100字符)
|
||||
- [ ] 3.5.3 身份证号长度校验(最大50字符)
|
||||
- [ ] 3.5.4 中介类型枚举校验(1=个人,2=机构)
|
||||
- [ ] 3.5.5 风险等级枚举校验(1=高,2=中,3=低)
|
||||
- [ ] 3.5.6 状态枚举校验(0=正常,1=停用)
|
||||
- [ ] 3.5.7 备注长度校验(最大500字符)
|
||||
|
||||
- [ ] 3.6 实现 Excel 导入导出功能
|
||||
- [ ] 3.6.1 生成导入模板(带示例数据)
|
||||
- [ ] 3.6.2 实现 Excel 数据读取
|
||||
- [ ] 3.6.3 实现导入数据验证
|
||||
- [ ] 3.6.4 实现导入错误提示
|
||||
- [ ] 3.6.5 实现批量插入数据
|
||||
- [ ] 3.6.6 实现 Excel 数据导出
|
||||
- [ ] 3.6.7 支持按查询条件导出
|
||||
|
||||
## 前端开发
|
||||
|
||||
- [ ] 4.1 创建 API 接口文件
|
||||
- [ ] 4.1.1 创建 `ruoyi-ui/src/api/dpcIntermediary.js`
|
||||
- [ ] 4.1.2 定义 `listIntermediary` 接口
|
||||
- [ ] 4.1.3 定义 `getIntermediary` 接口
|
||||
- [ ] 4.1.4 定义 `addIntermediary` 接口
|
||||
- [ ] 4.1.5 定义 `updateIntermediary` 接口
|
||||
- [ ] 4.1.6 定义 `delIntermediary` 接口
|
||||
- [ ] 4.1.7 定义 `exportIntermediary` 接口
|
||||
- [ ] 4.1.8 定义 `importTemplate` 接口
|
||||
- [ ] 4.1.9 定义 `importData` 接口
|
||||
|
||||
- [ ] 4.2 创建主页面
|
||||
- [ ] 4.2.1 创建 `ruoyi-ui/src/views/dpcIntermediary/index.vue`
|
||||
- [ ] 4.2.2 实现搜索表单区域
|
||||
- [ ] 4.2.3 实现操作按钮区域(新增、导入、导出、下载模板)
|
||||
- [ ] 4.2.4 实现数据表格(展示列表数据)
|
||||
- [ ] 4.2.5 实现分页组件
|
||||
- [ ] 4.2.6 实现新增/编辑对话框
|
||||
- [ ] 4.2.7 实现导入对话框
|
||||
- [ ] 4.2.8 实现表单验证规则
|
||||
- [ ] 4.2.9 实现字典数据回显(中介类型、风险等级、状态)
|
||||
|
||||
- [ ] 4.3 实现前端功能细节
|
||||
- [ ] 4.3.1 搜索条件组合查询
|
||||
- [ ] 4.3.2 新增/编辑表单提交
|
||||
- [ ] 4.3.3 单条/批量删除确认
|
||||
- [ ] 4.3.4 Excel 文件上传
|
||||
- [ ] 4.3.5 导入结果展示(成功/失败数量)
|
||||
- [ ] 4.3.6 导入错误详情展示
|
||||
- [ ] 4.3.7 权限按钮显示控制(v-hasPermi)
|
||||
|
||||
## 测试
|
||||
|
||||
- [ ] 5.1 后端单元测试
|
||||
- [ ] 5.1.1 测试查询列表功能
|
||||
- [ ] 5.1.2 测试查询详情功能
|
||||
- [ ] 5.1.3 测试新增功能(含边界条件)
|
||||
- [ ] 5.1.4 测试修改功能
|
||||
- [ ] 5.1.5 测试删除功能
|
||||
- [ ] 5.1.6 测试数据校验逻辑
|
||||
- [ ] 5.1.7 测试 Excel 导入功能(含异常数据)
|
||||
- [ ] 5.1.8 测试 Excel 导出功能
|
||||
|
||||
- [ ] 5.2 后端集成测试
|
||||
- [ ] 5.2.1 测试 Controller 层接口
|
||||
- [ ] 5.2.2 测试权限验证
|
||||
- [ ] 5.2.3 测试操作日志记录
|
||||
|
||||
- [ ] 5.3 前端功能测试
|
||||
- [ ] 5.3.1 测试页面加载和列表展示
|
||||
- [ ] 5.3.2 测试搜索功能(各种条件组合)
|
||||
- [ ] 5.3.3 测试新增功能
|
||||
- [ ] 5.3.4 测试编辑功能
|
||||
- [ ] 5.3.5 测试删除功能(单条和批量)
|
||||
- [ ] 5.3.6 测试 Excel 模板下载
|
||||
- [ ] 5.3.7 测试 Excel 导入(正常和异常数据)
|
||||
- [ ] 5.3.8 测试 Excel 导出
|
||||
- [ ] 5.3.9 测试权限控制
|
||||
|
||||
- [ ] 5.4 生成测试脚本
|
||||
- [ ] 5.4.1 编写自动化测试脚本
|
||||
- [ ] 5.4.2 生成测试数据
|
||||
- [ ] 5.4.3 执行测试并验证结果
|
||||
|
||||
## 文档编写
|
||||
|
||||
- [ ] 6.1 生成 API 文档
|
||||
- [ ] 6.1.1 生成 Swagger 接口文档
|
||||
- [ ] 6.1.2 编写接口使用说明
|
||||
|
||||
- [ ] 6.2 编写用户手册
|
||||
- [ ] 6.2.1 功能概述
|
||||
- [ ] 6.2.2 操作步骤说明
|
||||
- [ ] 6.2.3 常见问题解答
|
||||
|
||||
## 验收
|
||||
|
||||
- [ ] 7.1 功能完整性验收
|
||||
- [ ] 7.1.1 所有 CRUD 功能正常
|
||||
- [ ] 7.1.2 Excel 导入导出功能正常
|
||||
- [ ] 7.1.3 数据验证功能正常
|
||||
- [ ] 7.1.4 权限控制功能正常
|
||||
- [ ] 7.1.5 操作日志记录正常
|
||||
|
||||
- [ ] 7.2 性能验收
|
||||
- [ ] 7.2.1 列表查询响应时间 < 1秒(1000条数据)
|
||||
- [ ] 7.2.2 Excel 导入 1000 条数据耗时 < 10秒
|
||||
- [ ] 7.2.3 Excel 导出 1000 条数据耗时 < 5秒
|
||||
|
||||
- [ ] 7.3 兼容性验收
|
||||
- [ ] 7.3.1 支持 Chrome 浏览器
|
||||
- [ ] 7.3.2 支持 Edge 浏览器
|
||||
- [ ] 7.3.3 支持 Firefox 浏览器
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```
|
||||
1. 环境准备
|
||||
↓
|
||||
2. 数据库设计
|
||||
↓
|
||||
3. 后端开发(Domain → Mapper → Service → Controller)
|
||||
↓
|
||||
4. 前端开发(API → 页面 → 功能)
|
||||
↓
|
||||
5. 测试
|
||||
↓
|
||||
6. 文档编写
|
||||
↓
|
||||
7. 验收
|
||||
```
|
||||
|
||||
## 并行任务
|
||||
|
||||
以下任务可以并行执行:
|
||||
- 3.2(Mapper 层)可与 3.3(Service 层)部分并行(先定义接口)
|
||||
- 4.1(API 接口)可与 3.4(Controller 层)并行
|
||||
- 5.1(单元测试)可与 5.2(集成测试)部分并行
|
||||
|
||||
## 验收标准
|
||||
|
||||
所有任务完成后,必须满足:
|
||||
1. 所有接口功能正常,无 Bug
|
||||
2. Excel 导入导出功能完整可用
|
||||
3. 数据验证规则正确执行
|
||||
4. 权限控制有效
|
||||
5. 操作日志正确记录
|
||||
6. 前端页面交互流畅
|
||||
7. 通过所有测试用例
|
||||
8. API 文档完整准确
|
||||
612
openspec/changes/add-project-management/design.md
Normal file
612
openspec/changes/add-project-management/design.md
Normal file
@@ -0,0 +1,612 @@
|
||||
# Design: 项目管理模块
|
||||
|
||||
## 概述
|
||||
本文档描述项目管理模块的技术设计方案,包括系统架构、数据模型、API设计等。
|
||||
|
||||
## 系统架构
|
||||
|
||||
### 模块划分
|
||||
遵循若依框架的分层架构,新建 `ruoyi-dpc` 模块与若依框架代码分离,Controller 层也放在新建模块中:
|
||||
|
||||
```
|
||||
ruoyi-dpc/ (新建模块)
|
||||
├── pom.xml # 模块依赖配置
|
||||
├── src/main/java/com/ruoyi/dpc/
|
||||
│ ├── controller/
|
||||
│ │ └── CcdiProjectController.java # 项目控制器
|
||||
│ ├── domain/
|
||||
│ │ ├── CcdiProject.java # 项目实体
|
||||
│ │ ├── CcdiProjectPerson.java # 项目人员关联
|
||||
│ │ ├── dto/
|
||||
│ │ │ ├── CcdiProjectDTO.java # 项目数据传输对象
|
||||
│ │ │ ├── CcdiProjectQueryDTO.java # 项目查询DTO
|
||||
│ │ │ └── CcdiProjectImportDTO.java # 项目导入DTO
|
||||
│ │ └── vo/
|
||||
│ │ ├── CcdiProjectVO.java # 项目视图对象
|
||||
│ │ └── CcdiProjectQueryVO.java # 查询视图对象
|
||||
│ ├── mapper/
|
||||
│ │ ├── CcdiProjectMapper.java
|
||||
│ │ └── CcdiProjectPersonMapper.java
|
||||
│ └── service/
|
||||
│ ├── ICcdiProjectService.java
|
||||
│ └── impl/
|
||||
│ └── CcdiProjectServiceImpl.java
|
||||
└── src/main/resources/mapper/dpc/
|
||||
├── CcdiProjectMapper.xml
|
||||
└── CcdiProjectPersonMapper.xml
|
||||
|
||||
ruoyi-ui/src/
|
||||
├── api/
|
||||
│ └── dpcProject.js # API请求定义
|
||||
└── views/
|
||||
└── dpcProject/
|
||||
├── index.vue # 项目列表页
|
||||
├── add-or-edit.vue # 新增/编辑弹窗
|
||||
└── import-history.vue # 导入历史项目弹窗
|
||||
```
|
||||
|
||||
## 数据模型
|
||||
|
||||
### 数据库表设计
|
||||
|
||||
#### ccdi_project (项目主表)
|
||||
|
||||
| 字段名 | 类型 | 说明 | 约束 |
|
||||
|-------|------|------|-----|
|
||||
| project_id | BIGINT | 项目ID | PK, AUTO_INCREMENT |
|
||||
| project_name | VARCHAR(100) | 项目名称 | NOT NULL |
|
||||
| description | VARCHAR(500) | 项目描述 | |
|
||||
| status | CHAR(1) | 状态 | NOT NULL, DEFAULT '0' |
|
||||
| target_count | INT | 目标人数 | NOT NULL, DEFAULT 0 |
|
||||
| warning_count | INT | 预警人数 | NOT NULL, DEFAULT 0 |
|
||||
| start_date | DATE | 开始日期 | |
|
||||
| end_date | DATE | 结束日期 | |
|
||||
| is_archived | CHAR(1) | 是否归档 | DEFAULT '0' |
|
||||
| archive_file_path | VARCHAR(255) | 归档文件路径 | |
|
||||
| create_by | VARCHAR(64) | 创建者 | |
|
||||
| create_time | DATETIME | 创建时间 | |
|
||||
| update_by | VARCHAR(64) | 更新者 | |
|
||||
| update_time | DATETIME | 更新时间 | |
|
||||
| remark | VARCHAR(500) | 备注 | |
|
||||
|
||||
**状态枚举值**:
|
||||
- `0`: 进行中
|
||||
- `1`: 已完成
|
||||
- `2`: 已归档
|
||||
|
||||
#### ccdi_project_person (项目人员关联表)
|
||||
|
||||
| 字段名 | 类型 | 说明 | 约束 |
|
||||
|-------|------|------|-----|
|
||||
| id | BIGINT | 主键ID | PK, AUTO_INCREMENT |
|
||||
| project_id | BIGINT | 项目ID | FK -> ccdi_project.project_id |
|
||||
| person_id | BIGINT | 人员ID | FK -> sys_user.user_id |
|
||||
| person_name | VARCHAR(30) | 人员姓名 | 冗余字段 |
|
||||
| person_dept_id | BIGINT | 部门ID | 冗余字段 |
|
||||
|
||||
**索引**:
|
||||
- `idx_project_id`: (project_id)
|
||||
- `uk_project_person`: (project_id, person_id) 唯一索引
|
||||
|
||||
### 实体类设计
|
||||
|
||||
#### CcdiProject.java
|
||||
```java
|
||||
@Data
|
||||
@TableName("ccdi_project")
|
||||
public class CcdiProject {
|
||||
/** 项目ID */
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long projectId;
|
||||
|
||||
/** 项目名称 */
|
||||
@NotBlank(message = "项目名称不能为空")
|
||||
@Size(max = 100, message = "项目名称不能超过100个字符")
|
||||
private String projectName;
|
||||
|
||||
/** 项目描述 */
|
||||
@Size(max = 500, message = "项目描述不能超过500个字符")
|
||||
private String description;
|
||||
|
||||
/** 状态(0进行中 1已完成 2已归档) */
|
||||
private String status;
|
||||
|
||||
/** 目标人数 */
|
||||
private Integer targetCount;
|
||||
|
||||
/** 预警人数 */
|
||||
private Integer warningCount;
|
||||
|
||||
/** 开始日期 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date startDate;
|
||||
|
||||
/** 结束日期 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date endDate;
|
||||
|
||||
/** 是否归档 */
|
||||
private String isArchived;
|
||||
|
||||
/** 归档文件路径 */
|
||||
private String archiveFilePath;
|
||||
|
||||
/** 创建者 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
/** 创建时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/** 更新者 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
/** 更新时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
@Transient
|
||||
private List<Long> personIds; // 关联人员ID列表
|
||||
}
|
||||
```
|
||||
|
||||
#### 审计字段自动填充配置
|
||||
|
||||
```java
|
||||
/**
|
||||
* MyBatis Plus 审计字段自动填充处理器
|
||||
*/
|
||||
@Component
|
||||
public class CcdiMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
@Resource
|
||||
private TokenService tokenService;
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
|
||||
this.strictInsertFill(metaObject, "createBy", String.class, getUsername());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
|
||||
this.strictUpdateFill(metaObject, "updateBy", String.class, getUsername());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户名
|
||||
*/
|
||||
private String getUsername() {
|
||||
try {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
return loginUser != null ? loginUser.getUsername() : "system";
|
||||
} catch (Exception e) {
|
||||
return "system";
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 实体类审计字段注解
|
||||
|
||||
```java
|
||||
@Data
|
||||
@TableName("ccdi_project")
|
||||
public class CcdiProject {
|
||||
// ... 其他字段
|
||||
|
||||
/** 创建者 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/** 更新者 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
}
|
||||
```
|
||||
|
||||
**配置说明**:
|
||||
- `@TableField(fill = FieldFill.INSERT)` - 仅在插入时自动填充
|
||||
- `@TableField(fill = FieldFill.INSERT_UPDATE)` - 插入和更新时都自动填充
|
||||
- `MetaObjectHandler` 处理器会自动从 Spring Security 上下文中获取当前登录用户
|
||||
|
||||
### DTO 设计
|
||||
|
||||
按照全局配置要求,接口传参使用单独的 DTO,不与 entity 混用。
|
||||
|
||||
#### CcdiProjectDTO.java(新增/修改项目DTO)
|
||||
```java
|
||||
@Data
|
||||
public class CcdiProjectDTO {
|
||||
/** 项目名称 */
|
||||
@NotBlank(message = "项目名称不能为空")
|
||||
@Size(max = 100, message = "项目名称不能超过100个字符")
|
||||
private String projectName;
|
||||
|
||||
/** 项目描述 */
|
||||
@Size(max = 500, message = "项目描述不能超过500个字符")
|
||||
private String description;
|
||||
|
||||
/** 开始日期 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date startDate;
|
||||
|
||||
/** 结束日期 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date endDate;
|
||||
|
||||
/** 关联人员ID列表 */
|
||||
@NotEmpty(message = "请至少选择一名参与人员")
|
||||
private List<Long> personIds;
|
||||
}
|
||||
```
|
||||
|
||||
#### CcdiProjectQueryDTO.java(查询项目DTO)
|
||||
```java
|
||||
@Data
|
||||
public class CcdiProjectQueryDTO {
|
||||
/** 项目名称(模糊搜索) */
|
||||
private String projectName;
|
||||
|
||||
/** 状态 */
|
||||
private String status;
|
||||
|
||||
/** 创建时间范围开始 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTimeStart;
|
||||
|
||||
/** 创建时间范围结束 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTimeEnd;
|
||||
}
|
||||
```
|
||||
|
||||
#### CcdiProjectImportDTO.java(导入历史项目DTO)
|
||||
```java
|
||||
@Data
|
||||
public class CcdiProjectImportDTO {
|
||||
/** 历史项目ID */
|
||||
@NotNull(message = "请选择要导入的历史项目")
|
||||
private Long historyProjectId;
|
||||
|
||||
/** 新项目名称 */
|
||||
@NotBlank(message = "新项目名称不能为空")
|
||||
@Size(max = 100, message = "项目名称不能超过100个字符")
|
||||
private String projectName;
|
||||
}
|
||||
```
|
||||
|
||||
#### DTO 与 Entity 转换
|
||||
```java
|
||||
/**
|
||||
* DTO 与 Entity 转换工具类
|
||||
*/
|
||||
public class CcdiProjectConverter {
|
||||
|
||||
/**
|
||||
* DTO 转 Entity(新增/修改)
|
||||
*/
|
||||
public static CcdiProject toEntity(CcdiProjectDTO dto) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
CcdiProject entity = new CcdiProject();
|
||||
entity.setProjectName(dto.getProjectName());
|
||||
entity.setDescription(dto.getDescription());
|
||||
entity.setStartDate(dto.getStartDate());
|
||||
entity.setEndDate(dto.getEndDate());
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity 转 VO
|
||||
*/
|
||||
public static CcdiProjectVO toVO(CcdiProject entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
CcdiProjectVO vo = new CcdiProjectVO();
|
||||
BeanUtils.copyProperties(entity, vo);
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity 列表转 VO 列表
|
||||
*/
|
||||
public static List<CcdiProjectVO> toVOList(List<CcdiProject> entityList) {
|
||||
if (entityList == null || entityList.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return entityList.stream()
|
||||
.map(CcdiProjectConverter::toVO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Controller 使用 DTO
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/dpc/project")
|
||||
public class CcdiProjectController {
|
||||
|
||||
@Resource
|
||||
private ICcdiProjectService projectService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('dpc:project:add')")
|
||||
@PostMapping
|
||||
public AjaxResult add(@Validated @RequestBody CcdiProjectDTO dto) {
|
||||
CcdiProject project = CcdiProjectConverter.toEntity(dto);
|
||||
return AjaxResult.success(projectService.insertProject(project));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('dpc:project:edit')")
|
||||
@PutMapping
|
||||
public AjaxResult edit(@Validated @RequestBody CcdiProjectDTO dto) {
|
||||
CcdiProject project = CcdiProjectConverter.toEntity(dto);
|
||||
return AjaxResult.success(projectService.updateProject(project));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('dpc:project:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(CcdiProjectQueryDTO queryDTO) {
|
||||
CcdiProject project = new CcdiProject();
|
||||
// 将查询条件转换到实体
|
||||
BeanUtils.copyProperties(queryDTO, project);
|
||||
startPage();
|
||||
List<CcdiProject> list = projectService.selectProjectList(project);
|
||||
List<CcdiProjectVO> voList = CcdiProjectConverter.toVOList(list);
|
||||
return getDataTable(voList);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API设计
|
||||
|
||||
### RESTful API规范
|
||||
|
||||
| 方法 | 路径 | 说明 | 权限 |
|
||||
|-----|------|------|-----|
|
||||
| GET | /project/list | 查询项目列表 | project:list |
|
||||
| GET | /project/{id} | 查询项目详情 | project:query |
|
||||
| POST | /project | 新增项目 | project:add |
|
||||
| PUT | /project | 修改项目 | project:edit |
|
||||
| DELETE | /project/{ids} | 删除项目 | project:remove |
|
||||
| GET | /project/history/{id} | 获取历史项目配置 | project:query |
|
||||
| POST | /project/import | 导入历史项目 | project:add |
|
||||
| POST | /project/archive/{id} | 归档项目 | project:archive |
|
||||
| POST | /project/reanalyze/{id} | 重新分析 | project:reanalyze |
|
||||
|
||||
### 请求/响应格式
|
||||
|
||||
#### 查询项目列表
|
||||
**请求**: `GET /project/list?projectName=xxx&status=0&pageNum=1&pageSize=10`
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"total": 100,
|
||||
"rows": [
|
||||
{
|
||||
"projectId": 1,
|
||||
"projectName": "2026年Q1初核排查",
|
||||
"description": "季度常规排查",
|
||||
"status": "0",
|
||||
"targetCount": 100,
|
||||
"warningCount": 5,
|
||||
"createTime": "2026-01-01 10:00:00"
|
||||
}
|
||||
],
|
||||
"code": 200,
|
||||
"msg": "查询成功"
|
||||
}
|
||||
```
|
||||
|
||||
#### 新增项目
|
||||
**请求**: `POST /project`
|
||||
|
||||
```json
|
||||
{
|
||||
"projectName": "2026年Q1初核排查",
|
||||
"description": "季度常规排查",
|
||||
"startDate": "2026-01-01",
|
||||
"endDate": "2026-03-31",
|
||||
"personIds": [1, 2, 3, 4, 5]
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "新增成功"
|
||||
}
|
||||
```
|
||||
|
||||
## 业务逻辑设计
|
||||
|
||||
### 项目状态流转
|
||||
|
||||
```
|
||||
[新建] → [进行中] → [已完成] → [已归档]
|
||||
↑
|
||||
|---- [重新分析]
|
||||
```
|
||||
|
||||
### 核心业务规则
|
||||
|
||||
1. **新建项目**
|
||||
- 项目名称必填
|
||||
- 至少选择一名人员
|
||||
- 默认状态为"进行中"
|
||||
|
||||
2. **导入历史项目**
|
||||
- 选择已完成的历史项目
|
||||
- 复制人员配置
|
||||
- 生成新项目(状态为"进行中")
|
||||
|
||||
3. **归档项目**
|
||||
- 只能归档"已完成"状态的项目
|
||||
- 生成PDF归档文件
|
||||
- 更新项目状态为"已归档"
|
||||
|
||||
4. **重新分析**
|
||||
- 只能对"已完成"项目执行
|
||||
- 异步执行风险模型
|
||||
- 更新预警人数
|
||||
|
||||
## 前端设计
|
||||
|
||||
### 页面组件结构
|
||||
|
||||
#### 项目列表页 (index.vue)
|
||||
```
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 搜索区域 -->
|
||||
<el-form :inline="true">
|
||||
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" />
|
||||
<el-button @click="handleQuery">搜索</el-button>
|
||||
<el-button @click="handleAdd">新建项目</el-button>
|
||||
<el-button @click="handleImport">导入历史项目</el-button>
|
||||
</el-form>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table :data="projectList">
|
||||
<el-table-column prop="projectName" label="项目名称" />
|
||||
<el-table-column prop="description" label="项目描述" />
|
||||
<el-table-column prop="createTime" label="创建时间" />
|
||||
<el-table-column prop="status" label="状态">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status === '0'">进行中</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === '1'" type="success">已完成</el-tag>
|
||||
<el-tag v-else type="info">已归档</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="targetCount" label="目标人数" />
|
||||
<el-table-column prop="warningCount" label="预警人数" />
|
||||
<el-table-column label="操作" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button v-if="scope.row.status === '0'" @click="handleEnter(scope.row)">进入项目</el-button>
|
||||
<el-button v-if="scope.row.status === '1'" @click="handleViewResult(scope.row)">查看结果</el-button>
|
||||
<el-button v-if="scope.row.status === '1'" @click="handleReanalyze(scope.row)">重新分析</el-button>
|
||||
<el-button v-if="scope.row.status === '1'" @click="handleArchive(scope.row)">归档</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### API封装 (project.js)
|
||||
```javascript
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询项目列表
|
||||
export function listProject(query) {
|
||||
return request({
|
||||
url: '/dpc/project/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 新增项目
|
||||
export function addProject(data) {
|
||||
return request({
|
||||
url: '/dpc/project',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 归档项目
|
||||
export function archiveProject(projectId) {
|
||||
return request({
|
||||
url: '/dpc/project/archive/' + projectId,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 重新分析
|
||||
export function reanalyzeProject(projectId) {
|
||||
return request({
|
||||
url: '/dpc/project/reanalyze/' + projectId,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 导入历史项目
|
||||
export function importProject(data) {
|
||||
return request({
|
||||
url: '/dpc/project/import',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 技术约束
|
||||
|
||||
1. **后端**
|
||||
- Spring Boot 3.5.8
|
||||
- 使用 MyBatis Plus 3.5.10 进行数据访问(Spring Boot 3 适配版)
|
||||
- 使用 `@Resource` 注入依赖(替代 `@Autowired`)
|
||||
- 使用 `@Data` 注解简化实体类
|
||||
- 实体类不继承 BaseEntity,基础字段直接定义在实体类中
|
||||
- 新建 `ruoyi-dpc` 模块,与若依框架代码分离
|
||||
- Controller 层也要放在新建模块中(不在 `ruoyi-admin` 中)
|
||||
- 接口传参使用单独的 DTO,不与 entity 混用
|
||||
|
||||
2. **前端**
|
||||
- Vue 2.6.12
|
||||
- 使用 Element UI 2.15.14 组件库
|
||||
- 遵循现有视图组件风格
|
||||
- 在添加页面和组件后,注意与数据库中菜单表进行联动修改
|
||||
|
||||
3. **数据库**
|
||||
- MySQL 8.2.0
|
||||
- 表名使用 `ccdi_` 前缀(项目英文名首字母集合)
|
||||
|
||||
4. **安全**
|
||||
- 所有API需要 `@PreAuthorize` 权限注解
|
||||
- 敏感操作记录操作日志
|
||||
|
||||
5. **文档**
|
||||
- 完成后端代码 controller 层代码生成测试后,在项目文件目录下生成 API 文档
|
||||
|
||||
6. **测试**
|
||||
- 测试方式为生成可执行的测试脚本
|
||||
- 在测试中启动的进程,在测试完成后立刻结束
|
||||
- `/login/test` 接口可传入 `username` 和 `password` 获取 token(测试账号:admin/admin123)
|
||||
- swagger-ui 地址:`/swagger-ui/index.html`
|
||||
|
||||
7. **审计字段自动填充**
|
||||
- 配置 MyBatis Plus `MetaObjectHandler` 实现审计字段自动填充
|
||||
- 插入操作自动填充:`create_by`、`create_time`
|
||||
- 更新操作自动填充:`update_by`、`update_time`
|
||||
|
||||
## 待确认事项
|
||||
|
||||
1. **PDF生成方案**:需确认使用 iText 或其他方案
|
||||
2. **异步任务实现**:重新分析是否需要集成若依的定时任务模块
|
||||
3. **菜单配置**:项目管理在系统菜单中的位置
|
||||
112
openspec/changes/add-project-management/proposal.md
Normal file
112
openspec/changes/add-project-management/proposal.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Proposal: 添加项目管理模块
|
||||
|
||||
## Change ID
|
||||
`add-project-management`
|
||||
|
||||
## Summary
|
||||
添加项目管理模块,实现初核项目的创建、查询、状态管理、归档等核心功能。该模块是系统的首页和入口,用于管理所有历史创建的核查项目。
|
||||
|
||||
## Motivation
|
||||
目前系统缺少项目管理的核心功能模块。用户需要:
|
||||
1. 统一管理所有初核项目
|
||||
2. 快速查找和定位项目
|
||||
3. 跟踪项目状态(进行中/已完成)
|
||||
4. 监控预警人数变化
|
||||
5. 对已完成项目进行归档管理
|
||||
|
||||
## Scope
|
||||
本提案实现以下功能(不包括快捷入口区):
|
||||
|
||||
### 包含的功能
|
||||
- **1.1 导航与搜索区**
|
||||
- 项目搜索(模糊搜索项目名称)
|
||||
- 新建项目(标准表单创建)
|
||||
- 导入历史项目(复制历史配置快速创建)
|
||||
|
||||
- **1.2 项目列表区**
|
||||
- 项目信息展示(名称、描述、创建日期、状态、目标人数、预警人数)
|
||||
- 项目状态标识(进行中/已完成)
|
||||
- 预警人数动态更新
|
||||
- 查看结果(已完成项目跳转结果页)
|
||||
- 重新分析(已完成项目重新运行风险模型)
|
||||
- 归档项目(生成PDF导出)
|
||||
- 进入项目(进行中项目跳转工作台)
|
||||
|
||||
### 明确排除
|
||||
- **1.3 快捷入口区**(创建季度初核、创建新员工排查等快捷功能)
|
||||
|
||||
## Proposed Design
|
||||
|
||||
详见 [design.md](./design.md)
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### 选项1:使用若依代码生成器
|
||||
**优点**:
|
||||
- 快速生成标准CRUD代码
|
||||
- 符合项目规范
|
||||
|
||||
**缺点**:
|
||||
- 需要手动调整业务逻辑
|
||||
- 缺少复杂的业务规则处理
|
||||
|
||||
**决定**:不采用。项目管理模块包含复杂的业务逻辑(状态流转、归档、重新分析等),需要自定义实现。
|
||||
|
||||
### 选项2:完全自定义开发
|
||||
**优点**:
|
||||
- 完全控制实现细节
|
||||
- 灵活应对业务需求变化
|
||||
|
||||
**缺点**:
|
||||
- 开发周期较长
|
||||
- 需要确保代码规范一致
|
||||
|
||||
**决定**:采用。参考现有系统模块的代码模式,确保与项目风格一致。
|
||||
|
||||
## Impact
|
||||
|
||||
### 后端影响
|
||||
- 新建 `ruoyi-dpc` 模块,与若依框架代码分离(包含 Controller、Service、Mapper、Domain 层)
|
||||
- 新增表:`ccdi_project`(项目主表)、`ccdi_project_person`(项目人员关联表)
|
||||
- 新增权限:`dpc:project:list`, `dpc:project:query`, `dpc:project:add`, `dpc:project:edit`, `dpc:project:remove`, `dpc:project:archive`, `dpc:project:reanalyze`
|
||||
- Controller层:`ruoyi-dpc/src/main/java/com/ruoyi/dpc/controller/CcdiProjectController.java`
|
||||
|
||||
### 前端影响
|
||||
- 新增视图:`ruoyi-ui/src/views/dpcProject/`
|
||||
- 新增API:`ruoyi-ui/src/api/dpcProject.js`
|
||||
- 新增菜单:项目管理(在系统菜单中添加,注意与数据库中菜单表进行联动修改)
|
||||
|
||||
### 数据库影响
|
||||
- 新增表:`ccdi_project`(项目主表)
|
||||
- 新增表:`ccdi_project_person`(项目人员关联表)
|
||||
|
||||
## Dependencies
|
||||
- 依赖现有用户系统(`SysUser`)进行人员选择
|
||||
- 依赖部门系统(`SysDept`)进行部门范围选择
|
||||
- 依赖文件上传功能(项目附件、归档PDF)
|
||||
|
||||
## Related Changes
|
||||
无
|
||||
|
||||
## Open Questions
|
||||
1. **项目人员关联方式**:使用多对多关联表还是JSON字段存储人员列表?
|
||||
- 建议:使用 `ccdi_project_person` 关联表,便于查询和扩展
|
||||
|
||||
2. **归档PDF生成**:使用什么工具生成PDF?
|
||||
- 建议:使用 iText 或 Apache POI,需要后续确认
|
||||
|
||||
3. **重新分析的触发方式**:同步还是异步?
|
||||
- 建议:异步处理,避免长时间阻塞
|
||||
|
||||
## Success Criteria
|
||||
- [ ] 用户可以创建新项目并填写完整信息
|
||||
- [ ] 用户可以搜索项目(按名称模糊匹配)
|
||||
- [ ] 项目列表正确显示状态和预警人数
|
||||
- [ ] 进行中项目可以进入工作台
|
||||
- [ ] 已完成项目可以查看结果
|
||||
- [ ] 已完成项目可以归档并生成PDF
|
||||
- [ ] 已完成项目可以重新分析
|
||||
|
||||
## References
|
||||
- [模块设计文档](../../doc/modules/01-项目管理模块.md)
|
||||
- [若依开发文档](https://doc.ruoyi.vip/)
|
||||
@@ -0,0 +1,214 @@
|
||||
# Spec: 项目管理能力
|
||||
|
||||
## 概述
|
||||
本规范定义了项目管理模块的核心能力,包括项目的创建、查询、状态管理和归档功能。
|
||||
|
||||
---
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 项目列表查询
|
||||
|
||||
系统 MUST 允许用户查询所有项目列表,支持按项目名称进行模糊搜索。
|
||||
|
||||
#### Scenario: 查询所有项目
|
||||
**Given** 用户已登录系统并具有项目查看权限
|
||||
**When** 用户进入项目管理页面
|
||||
**Then** 系统显示所有项目列表,包括项目名称、描述、创建时间、状态、目标人数、预警人数
|
||||
|
||||
#### Scenario: 按名称搜索项目
|
||||
**Given** 用户在项目管理页面
|
||||
**When** 用户在搜索框输入"Q1初核"并点击搜索
|
||||
**Then** 系统显示所有包含"Q1初核"的项目
|
||||
|
||||
#### Scenario: 项目状态标识
|
||||
**Given** 项目列表中有多个不同状态的项目
|
||||
**When** 用户查看项目列表
|
||||
**Then** 进行中的项目显示蓝色标签,已完成的项目显示绿色标签,已归档的项目显示灰色标签
|
||||
|
||||
#### Scenario: 预警人数动态更新
|
||||
**Given** 存在状态为"进行中"的项目
|
||||
**When** 用户刷新项目列表
|
||||
**Then** 预警人数显示最新的统计值
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 创建新项目
|
||||
|
||||
系统 MUST 允许用户创建新的初核项目,填写项目基本信息并选择参与人员。
|
||||
|
||||
#### Scenario: 创建标准项目
|
||||
**Given** 用户在项目管理页面
|
||||
**When** 用户点击"新建项目"按钮
|
||||
**Then** 系统打开新增项目弹窗,包含项目名称、项目描述、时间范围、人员选择字段
|
||||
|
||||
#### Scenario: 必填字段验证
|
||||
**Given** 用户新增项目时未填写项目名称
|
||||
**When** 用户点击"确定"按钮
|
||||
**Then** 系统提示"项目名称不能为空"
|
||||
|
||||
#### Scenario: 至少选择一名人员
|
||||
**Given** 用户新增项目时未选择任何人员
|
||||
**When** 用户点击"确定"按钮
|
||||
**Then** 系统提示"请至少选择一名参与人员"
|
||||
|
||||
#### Scenario: 创建成功
|
||||
**Given** 用户填写了完整的项目信息
|
||||
**When** 用户点击"确定"按钮
|
||||
**Then** 系统创建项目并提示"新增成功",项目列表刷新显示新项目
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入历史项目
|
||||
|
||||
系统 MUST 允许用户从已完成的历史项目导入配置,快速创建新项目。
|
||||
|
||||
#### Scenario: 查看历史项目列表
|
||||
**Given** 用户点击"导入历史项目"按钮
|
||||
**When** 系统打开导入历史项目弹窗
|
||||
**Then** 系统显示所有已完成状态的历史项目列表
|
||||
|
||||
#### Scenario: 选择历史项目
|
||||
**Given** 用户在导入历史项目弹窗中
|
||||
**When** 用户选择一个历史项目
|
||||
**Then** 系统显示该项目的详细信息(人员范围、配置等)
|
||||
|
||||
#### Scenario: 复制配置创建新项目
|
||||
**Given** 用户选择了历史项目并填写了新项目名称
|
||||
**When** 用户点击"确定"按钮
|
||||
**Then** 系统创建新项目,复制原项目的人员和配置,状态为"进行中"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 项目状态管理
|
||||
|
||||
系统 MUST 支持项目状态的流转和管理。
|
||||
|
||||
#### Scenario: 进入进行中的项目
|
||||
**Given** 存在状态为"进行中"的项目
|
||||
**When** 用户点击"进入项目"按钮
|
||||
**Then** 系统跳转到该项目的工作台页面
|
||||
|
||||
#### Scenario: 查看已完成项目结果
|
||||
**Given** 存在状态为"已完成"的项目
|
||||
**When** 用户点击"查看结果"按钮
|
||||
**Then** 系统跳转到该项目的初核结果总览页面
|
||||
|
||||
#### Scenario: 重新分析已完成项目
|
||||
**Given** 存在状态为"已完成"的项目
|
||||
**When** 用户点击"重新分析"按钮
|
||||
**Then** 系统基于原有数据重新运行风险模型,更新预警人数和结果
|
||||
|
||||
#### Scenario: 重新分析权限控制
|
||||
**Given** 用户没有重新分析的权限
|
||||
**When** 用户查看项目列表
|
||||
**Then** 不显示"重新分析"按钮
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 项目归档
|
||||
|
||||
系统 MUST 允许用户将已结束的项目归档,并生成PDF归档文件。
|
||||
|
||||
#### Scenario: 归档已完成项目
|
||||
**Given** 存在状态为"已完成"的项目
|
||||
**When** 用户点击"归档"按钮
|
||||
**Then** 系统生成PDF归档文件,更新项目状态为"已归档"
|
||||
|
||||
#### Scenario: 归档状态校验
|
||||
**Given** 存在状态为"进行中"的项目
|
||||
**When** 用户尝试归档该项目
|
||||
**Then** 系统提示"只能归档已完成的项目"
|
||||
|
||||
#### Scenario: 归档文件下载
|
||||
**Given** 存在已归档的项目
|
||||
**When** 用户点击"下载归档"按钮
|
||||
**Then** 系统下载该项目的PDF归档文件
|
||||
|
||||
#### Scenario: 归档项目不显示在默认列表
|
||||
**Given** 存在已归档的项目
|
||||
**When** 用户查看项目列表
|
||||
**Then** 默认不显示已归档的项目
|
||||
|
||||
#### Scenario: 查看归档项目
|
||||
**Given** 用户想查看已归档的项目
|
||||
**When** 用户勾选"显示已归档"选项
|
||||
**Then** 系统在列表中显示已归档的项目
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 项目数据模型
|
||||
|
||||
系统 MUST 使用正确的数据结构存储项目信息。
|
||||
|
||||
#### Scenario: 项目基础信息存储
|
||||
**Given** 创建新项目
|
||||
**When** 系统保存项目数据
|
||||
**Then** 数据库存储:项目ID、项目名称、项目描述、创建时间、状态、目标人数、预警人数
|
||||
|
||||
#### Scenario: 项目人员关联存储
|
||||
**Given** 项目包含多名参与人员
|
||||
**When** 系统保存项目数据
|
||||
**Then** 人员在关联表中存储,包含项目ID、人员ID、人员姓名(冗余)
|
||||
|
||||
#### Scenario: 状态枚举值
|
||||
**Given** 项目状态字段
|
||||
**When** 系统存储项目状态
|
||||
**Then** 状态值为:'0'进行中、'1'已完成、'2'已归档
|
||||
|
||||
---
|
||||
|
||||
### Requirement: API接口规范
|
||||
|
||||
系统 MUST 提供符合RESTful规范的API接口。
|
||||
|
||||
#### Scenario: 查询项目列表API
|
||||
**Given** 前端需要获取项目列表
|
||||
**When** 调用 `GET /dpc/project/list?projectName=xxx&pageNum=1&pageSize=10`
|
||||
**Then** 返回分页数据,包含total、rows、code、msg
|
||||
|
||||
#### Scenario: 创建项目API
|
||||
**Given** 前端需要创建新项目
|
||||
**When** 调用 `POST /dpc/project` 并传递项目数据
|
||||
**Then** 返回操作结果,code为200表示成功
|
||||
|
||||
#### Scenario: 归档项目API
|
||||
**Given** 前端需要归档项目
|
||||
**When** 调用 `POST /dpc/project/archive/{projectId}`
|
||||
**Then** 系统执行归档操作并返回结果
|
||||
|
||||
#### Scenario: 权限校验
|
||||
**Given** 用户没有相应的权限
|
||||
**When** 调用需要权限的API
|
||||
**Then** 返回403错误,提示无权限访问
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 前端用户体验
|
||||
|
||||
系统 MUST 提供友好的用户交互体验。
|
||||
|
||||
#### Scenario: 响应式布局
|
||||
**Given** 用户使用不同分辨率的设备
|
||||
**When** 用户访问项目管理页面
|
||||
**Then** 页面布局自适应,所有功能可用
|
||||
|
||||
#### Scenario: 操作反馈
|
||||
**Given** 用户执行操作
|
||||
**When** 操作成功或失败
|
||||
**Then** 系统显示明确的成功或错误提示消息
|
||||
|
||||
#### Scenario: 加载状态
|
||||
**Given** 数据加载需要时间
|
||||
**When** 用户请求数据
|
||||
**Then** 系统显示loading加载状态
|
||||
|
||||
---
|
||||
|
||||
## 相关能力
|
||||
|
||||
- **用户管理能力**:依赖用户系统获取人员信息
|
||||
- **部门管理能力**:依赖部门系统进行部门范围选择
|
||||
- **文件管理能力**:依赖文件上传功能处理项目附件和归档PDF
|
||||
- **项目工作台能力**:进入项目后跳转到工作台(后续实现)
|
||||
- **初核结果能力**:查看结果跳转到结果页(后续实现)
|
||||
257
openspec/changes/add-project-management/tasks.md
Normal file
257
openspec/changes/add-project-management/tasks.md
Normal file
@@ -0,0 +1,257 @@
|
||||
# Tasks: 项目管理模块开发
|
||||
|
||||
本文档列出了实现项目管理模块的具体开发任务,按优先级和依赖关系排序。
|
||||
|
||||
## 阶段1:基础架构搭建
|
||||
|
||||
### 1.1 创建数据库表
|
||||
- [ ] 创建 `ccdi_project` 表
|
||||
- [ ] 创建 `ccdi_project_person` 表
|
||||
- [ ] 创建索引和外键约束
|
||||
- [ ] 准备测试数据
|
||||
|
||||
**验证**:执行SQL脚本,表结构创建成功,可以使用 `DESC ccdi_project;` 查看表结构
|
||||
|
||||
### 1.2 创建后端模块结构
|
||||
- [ ] 创建 `ruoyi-dpc` 新模块
|
||||
- [ ] 配置模块 `pom.xml` 依赖
|
||||
- [ ] 在根 `pom.xml` 中添加模块引用
|
||||
- [ ] 在 `ruoyi-admin` 的 `pom.xml` 中添加对 `ruoyi-dpc` 的依赖
|
||||
- [ ] 配置 Spring 扫描 `com.ruoyi.dpc.controller` 包
|
||||
- [ ] 创建包结构:`com.ruoyi.dpc.controller`, `com.ruoyi.dpc.domain`, `com.ruoyi.dpc.mapper`, `com.ruoyi.dpc.service`
|
||||
|
||||
**验证**:项目可以正常编译启动
|
||||
|
||||
### 1.3 创建实体类和DTO
|
||||
- [ ] 创建 `CcdiProject.java` 实体类(使用 `@Data` 注解,不继承 BaseEntity)
|
||||
- [ ] 创建 `CcdiProjectPerson.java` 实体类(使用 `@Data` 注解)
|
||||
- [ ] 创建 `CcdiProjectVO.java` 视图对象
|
||||
- [ ] 创建 `CcdiProjectQueryVO.java` 查询视图对象
|
||||
- [ ] 创建 `CcdiProjectDTO.java` 数据传输对象(新增/修改)
|
||||
- [ ] 创建 `CcdiProjectQueryDTO.java` 查询DTO
|
||||
- [ ] 创建 `CcdiProjectImportDTO.java` 导入DTO
|
||||
- [ ] 为审计字段添加 `@TableField` 注解配置自动填充
|
||||
|
||||
**验证**:实体类和DTO编译通过,字段注解正确
|
||||
|
||||
### 1.4 创建DTO转换工具类
|
||||
- [ ] 创建 `CcdiProjectConverter.java` 工具类
|
||||
- [ ] 实现 `toEntity(CcdiProjectDTO)` 方法
|
||||
- [ ] 实现 `toVO(CcdiProject)` 方法
|
||||
- [ ] 实现 `toVOList(List<CcdiProject>)` 方法
|
||||
|
||||
**验证**:转换逻辑正确,覆盖所有字段
|
||||
|
||||
### 1.5 配置审计字段自动填充
|
||||
- [ ] 创建 `CcdiMetaObjectHandler.java` 实现 `MetaObjectHandler` 接口
|
||||
- [ ] 实现 `insertFill` 方法自动填充 `create_by` 和 `create_time`
|
||||
- [ ] 实现 `updateFill` 方法自动填充 `update_by` 和 `update_time`
|
||||
- [ ] 从 Spring Security 上下文获取当前登录用户
|
||||
- [ ] 将处理器注册为 Spring 组件(`@Component`)
|
||||
|
||||
**验证**:插入和更新操作时审计字段自动填充
|
||||
|
||||
## 阶段2:数据访问层开发
|
||||
|
||||
### 2.1 创建Mapper接口
|
||||
- [ ] 创建 `CcdiProjectMapper.java`(使用 MyBatis Plus)
|
||||
- [ ] 创建 `CcdiProjectPersonMapper.java`(使用 MyBatis Plus)
|
||||
|
||||
### 2.2 创建Mapper XML
|
||||
- [ ] 创建 `CcdiProjectMapper.xml`
|
||||
- [ ] 创建 `CcdiProjectPersonMapper.xml`
|
||||
- [ ] 定义基础CRUD SQL(MyBatis Plus 自动生成)
|
||||
- [ ] 定义关联查询SQL(项目+人员)
|
||||
|
||||
**验证**:可以使用MyBatis Log查看SQL执行正确
|
||||
|
||||
## 阶段3:服务层开发
|
||||
|
||||
### 3.1 创建Service接口
|
||||
- [ ] 创建 `ICcdiProjectService.java`
|
||||
- [ ] 定义方法签名:
|
||||
- `List<CcdiProject> selectProjectList(CcdiProject project)`
|
||||
- `CcdiProject selectProjectById(Long projectId)`
|
||||
- `int insertProject(CcdiProject project)`
|
||||
- `int updateProject(CcdiProject project)`
|
||||
- `int deleteProjectByIds(Long[] projectIds)`
|
||||
- `boolean checkProjectNameUnique(CcdiProject project)`
|
||||
- `void archiveProject(Long projectId)`
|
||||
- `void reanalyzeProject(Long projectId)`
|
||||
|
||||
### 3.2 实现Service
|
||||
- [ ] 创建 `CcdiProjectServiceImpl.java`
|
||||
- [ ] 使用 `@Resource` 注入依赖
|
||||
- [ ] 实现基础CRUD方法
|
||||
- [ ] 实现项目名称唯一性校验
|
||||
- [ ] 实现项目归档逻辑(状态检查、PDF生成)
|
||||
- [ ] 实现重新分析逻辑(异步任务)
|
||||
|
||||
**验证**:单元测试通过,业务逻辑符合需求
|
||||
|
||||
## 阶段4:控制器层开发
|
||||
|
||||
### 4.1 创建Controller
|
||||
- [ ] 创建 `CcdiProjectController.java`(位于 `ruoyi-dpc` 模块的 `com.ruoyi.dpc.controller` 包中)
|
||||
- [ ] 实现列表查询接口 `GET /dpc/project/list`
|
||||
- [ ] 实现详情查询接口 `GET /dpc/project/{id}`
|
||||
- [ ] 实现新增接口 `POST /dpc/project`
|
||||
- [ ] 实现修改接口 `PUT /dpc/project`
|
||||
- [ ] 实现删除接口 `DELETE /dpc/project/{ids}`
|
||||
- [ ] 实现归档接口 `POST /dpc/project/archive/{id}`
|
||||
- [ ] 实现重新分析接口 `POST /dpc/project/reanalyze/{id}`
|
||||
- [ ] 实现导入历史项目接口 `POST /dpc/project/import`
|
||||
|
||||
### 4.2 添加权限注解
|
||||
- [ ] 为所有接口添加 `@PreAuthorize` 注解
|
||||
- [ ] 配置权限字符串:
|
||||
- `dpc:project:list`
|
||||
- `dpc:project:query`
|
||||
- `dpc:project:add`
|
||||
- `dpc:project:edit`
|
||||
- `dpc:project:remove`
|
||||
- `dpc:project:archive`
|
||||
- `dpc:project:reanalyze`
|
||||
|
||||
### 4.3 添加操作日志
|
||||
- [ ] 为关键操作添加 `@Log` 注解
|
||||
- [ ] 配置业务类型
|
||||
|
||||
**验证**:使用Postman测试所有接口,返回正确
|
||||
|
||||
## 阶段4.5:生成API文档
|
||||
|
||||
### 4.5.1 生成接口文档
|
||||
- [ ] 使用 SpringDoc/Knife4j 生成 API 文档
|
||||
- [ ] 在项目文件目录下导出 API 文档
|
||||
|
||||
**验证**:API 文档生成成功,包含所有接口
|
||||
|
||||
## 阶段5:前端API开发
|
||||
|
||||
### 5.1 创建API文件
|
||||
- [ ] 创建 `ruoyi-ui/src/api/dpcProject.js`
|
||||
- [ ] 封装所有API请求方法:
|
||||
- `listProject(query)`
|
||||
- `getProject(projectId)`
|
||||
- `addProject(data)`
|
||||
- `updateProject(data)`
|
||||
- `delProject(projectIds)`
|
||||
- `archiveProject(projectId)`
|
||||
- `reanalyzeProject(projectId)`
|
||||
- `importProject(data)`
|
||||
|
||||
**验证**:API方法可以正常调用
|
||||
|
||||
## 阶段6:前端页面开发
|
||||
|
||||
### 6.1 创建项目列表页
|
||||
- [ ] 创建 `ruoyi-ui/src/views/dpcProject/index.vue`
|
||||
- [ ] 实现搜索区域(项目名称输入框、搜索按钮)
|
||||
- [ ] 实现操作按钮(新建、导入历史项目)
|
||||
- [ ] 实现数据表格(项目信息展示)
|
||||
- [ ] 实现状态标签(进行中/已完成/已归档)
|
||||
- [ ] 实现操作列(进入项目、查看结果、重新分析、归档)
|
||||
|
||||
### 6.2 创建新增/编辑弹窗
|
||||
- [ ] 创建 `ruoyi-ui/src/views/dpcProject/add-or-edit.vue`
|
||||
- [ ] 实现项目基本信息表单(项目名称、描述、时间范围)
|
||||
- [ ] 实现人员选择器(多选)
|
||||
- [ ] 实现表单验证
|
||||
|
||||
### 6.3 创建导入历史项目弹窗
|
||||
- [ ] 创建 `ruoyi-ui/src/views/dpcProject/import-history.vue`
|
||||
- [ ] 实现历史项目列表(只显示已完成项目)
|
||||
- [ ] 实现项目详情预览
|
||||
- [ ] 实现新项目配置
|
||||
|
||||
**验证**:页面显示正常,交互流畅
|
||||
|
||||
## 阶段7:菜单与权限配置
|
||||
|
||||
### 7.1 创建系统菜单
|
||||
- [ ] 在系统管理 > 菜单管理中创建"项目管理"菜单
|
||||
- [ ] 配置菜单图标和路由(路径:`dpcProject`)
|
||||
- [ ] 创建子菜单/按钮权限
|
||||
|
||||
### 7.2 配置角色权限
|
||||
- [ ] 为管理员角色分配项目管理权限
|
||||
- [ ] 为其他角色配置适当权限
|
||||
|
||||
**验证**:登录后可以看到项目管理菜单
|
||||
|
||||
## 阶段8:测试与优化
|
||||
|
||||
### 8.1 功能测试
|
||||
- [ ] 测试项目创建流程
|
||||
- [ ] 测试项目搜索功能
|
||||
- [ ] 测试项目状态流转
|
||||
- [ ] 测试项目归档功能
|
||||
- [ ] 测试重新分析功能
|
||||
- [ ] 测试导入历史项目功能
|
||||
|
||||
### 8.2 边界测试
|
||||
- [ ] 测试必填字段验证
|
||||
- [ ] 测试项目名称唯一性
|
||||
- [ ] 测试状态权限控制
|
||||
- [ ] 测试数据权限
|
||||
|
||||
### 8.3 性能优化
|
||||
- [ ] 优化SQL查询
|
||||
- [ ] 添加必要的数据库索引
|
||||
- [ ] 优化大数据量列表加载
|
||||
|
||||
**验证**:所有测试用例通过,无明显性能问题
|
||||
|
||||
## 阶段9:文档与交付
|
||||
|
||||
### 9.1 代码注释
|
||||
- [ ] 为所有公开方法添加JavaDoc注释
|
||||
- [ ] 为复杂逻辑添加行内注释
|
||||
|
||||
### 9.2 更新系统文档
|
||||
- [ ] 更新数据库设计文档
|
||||
- [ ] 更新API接口文档
|
||||
- [ ] 更新用户操作手册
|
||||
|
||||
**验证**:文档完整、准确
|
||||
|
||||
## 任务依赖关系
|
||||
|
||||
```
|
||||
阶段1 (基础架构)
|
||||
↓
|
||||
阶段2 (数据访问层) → 阶段3 (服务层) → 阶段4 (控制器层) → 阶段4.5 (API文档)
|
||||
↓
|
||||
阶段5 (前端API) ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ←
|
||||
↓
|
||||
阶段6 (前端页面)
|
||||
↓
|
||||
阶段7 (菜单权限)
|
||||
↓
|
||||
阶段8 (测试优化)
|
||||
↓
|
||||
阶段9 (文档交付)
|
||||
```
|
||||
|
||||
## 可并行开发任务
|
||||
|
||||
以下任务可以并行开发:
|
||||
- 阶段2和阶段3(后端不同层级)
|
||||
- 阶段5和阶段6的前端页面组件
|
||||
- 阶段7的菜单配置可以在后端开发完成后进行
|
||||
|
||||
## 预计工作量
|
||||
|
||||
| 阶段 | 预计工时 |
|
||||
|-----|---------|
|
||||
| 阶段1 | 0.5天 |
|
||||
| 阶段2 | 0.5天 |
|
||||
| 阶段3 | 1天 |
|
||||
| 阶段4 | 1天 |
|
||||
| 阶段5 | 0.5天 |
|
||||
| 阶段6 | 2天 |
|
||||
| 阶段7 | 0.5天 |
|
||||
| 阶段8 | 1天 |
|
||||
| 阶段9 | 0.5天 |
|
||||
| **总计** | **7.5天** |
|
||||
@@ -0,0 +1,700 @@
|
||||
# Design: 增强中介黑名单字段并实现类型化模板导入
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### 表扩展:ccdi_intermediary_blacklist
|
||||
|
||||
在现有表基础上添加以下字段:
|
||||
|
||||
```sql
|
||||
-- ============================================================
|
||||
-- 个人类型字段 (以 indiv_ 前缀标识,individual 缩写)
|
||||
-- ============================================================
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `indiv_type` VARCHAR(30) DEFAULT NULL COMMENT '人员类型(中介、职业背债人、房产中介等)';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `indiv_sub_type` VARCHAR(50) DEFAULT NULL COMMENT '人员子类型(本人、配偶等)';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `indiv_gender` CHAR(1) DEFAULT NULL COMMENT '性别(M男 F女 O其他)';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `indiv_cert_type` VARCHAR(30) DEFAULT '身份证' COMMENT '证件类型';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `indiv_phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号码(加密存储)';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `indiv_wechat` VARCHAR(50) DEFAULT NULL COMMENT '微信号';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `indiv_address` VARCHAR(200) DEFAULT NULL COMMENT '联系地址';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `indiv_company` VARCHAR(100) DEFAULT NULL COMMENT '所在公司';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `indiv_position` VARCHAR(100) DEFAULT NULL COMMENT '职位/职务';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `indiv_related_id` VARCHAR(20) DEFAULT NULL COMMENT '关联人员ID';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `indiv_relation` VARCHAR(50) DEFAULT NULL COMMENT '关联关系';
|
||||
|
||||
-- ============================================================
|
||||
-- 机构类型字段 (以 corp_ 前缀标识,corporation 缩写)
|
||||
-- ============================================================
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_credit_code` VARCHAR(18) DEFAULT NULL COMMENT '统一社会信用代码';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_type` VARCHAR(50) DEFAULT NULL COMMENT '主体类型(有限责任公司、股份有限公司等)';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_nature` VARCHAR(50) DEFAULT NULL COMMENT '企业性质(国企、民企、外企等)';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_industry_category` VARCHAR(100) DEFAULT NULL COMMENT '行业分类';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_industry` VARCHAR(100) DEFAULT NULL COMMENT '所属行业';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_establish_date` DATE DEFAULT NULL COMMENT '成立日期';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_address` VARCHAR(500) DEFAULT NULL COMMENT '注册地址';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_legal_rep` VARCHAR(50) DEFAULT NULL COMMENT '法定代表人';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_legal_cert_type` VARCHAR(30) DEFAULT NULL COMMENT '法定代表人证件类型';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_legal_cert_no` VARCHAR(30) DEFAULT NULL COMMENT '法定代表人证件号码';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_shareholder_1` VARCHAR(30) DEFAULT NULL COMMENT '股东1';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_shareholder_2` VARCHAR(30) DEFAULT NULL COMMENT '股东2';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_shareholder_3` VARCHAR(30) DEFAULT NULL COMMENT '股东3';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_shareholder_4` VARCHAR(30) DEFAULT NULL COMMENT '股东4';
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `corp_shareholder_5` VARCHAR(30) DEFAULT NULL COMMENT '股东5';
|
||||
|
||||
-- ============================================================
|
||||
-- 通用字段
|
||||
-- ============================================================
|
||||
ALTER TABLE ccdi_intermediary_blacklist ADD COLUMN `data_source` VARCHAR(30) DEFAULT 'MANUAL' COMMENT '数据来源(MANUAL手动录入 SYSTEM系统同步 IMPORT批量导入 API接口获取)';
|
||||
```
|
||||
|
||||
### 完整表结构
|
||||
|
||||
```sql
|
||||
CREATE TABLE `ccdi_intermediary_blacklist` (
|
||||
`intermediary_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '中介ID',
|
||||
|
||||
-- ============================================================
|
||||
-- 核心字段(原有)
|
||||
-- ============================================================
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '姓名/机构名称',
|
||||
`certificate_no` VARCHAR(50) DEFAULT NULL COMMENT '证件号',
|
||||
`intermediary_type` CHAR(1) NOT NULL DEFAULT '1' COMMENT '中介类型(1个人 2机构)',
|
||||
`status` CHAR(1) NOT NULL DEFAULT '0' COMMENT '状态(0正常 1停用)',
|
||||
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
|
||||
|
||||
-- ============================================================
|
||||
-- 个人类型字段(新增,以 indiv_ 前缀标识)
|
||||
-- ============================================================
|
||||
`indiv_type` VARCHAR(30) DEFAULT NULL COMMENT '人员类型',
|
||||
`indiv_sub_type` VARCHAR(50) DEFAULT NULL COMMENT '人员子类型',
|
||||
`indiv_gender` CHAR(1) DEFAULT NULL COMMENT '性别(M男 F女 O其他)',
|
||||
`indiv_cert_type` VARCHAR(30) DEFAULT '身份证' COMMENT '证件类型',
|
||||
`indiv_phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号码',
|
||||
`indiv_wechat` VARCHAR(50) DEFAULT NULL COMMENT '微信号',
|
||||
`indiv_address` VARCHAR(200) DEFAULT NULL COMMENT '联系地址',
|
||||
`indiv_company` VARCHAR(100) DEFAULT NULL COMMENT '所在公司',
|
||||
`indiv_position` VARCHAR(100) DEFAULT NULL COMMENT '职位/职务',
|
||||
`indiv_related_id` VARCHAR(20) DEFAULT NULL COMMENT '关联人员ID',
|
||||
`indiv_relation` VARCHAR(50) DEFAULT NULL COMMENT '关联关系',
|
||||
|
||||
-- ============================================================
|
||||
-- 机构类型字段(新增,以 corp_ 前缀标识)
|
||||
-- ============================================================
|
||||
`corp_credit_code` VARCHAR(18) DEFAULT NULL COMMENT '统一社会信用代码',
|
||||
`corp_type` VARCHAR(50) DEFAULT NULL COMMENT '主体类型',
|
||||
`corp_nature` VARCHAR(50) DEFAULT NULL COMMENT '企业性质',
|
||||
`corp_industry_category` VARCHAR(100) DEFAULT NULL COMMENT '行业分类',
|
||||
`corp_industry` VARCHAR(100) DEFAULT NULL COMMENT '所属行业',
|
||||
`corp_establish_date` DATE DEFAULT NULL COMMENT '成立日期',
|
||||
`corp_address` VARCHAR(500) DEFAULT NULL COMMENT '注册地址',
|
||||
`corp_legal_rep` VARCHAR(50) DEFAULT NULL COMMENT '法定代表人',
|
||||
`corp_legal_cert_type` VARCHAR(30) DEFAULT NULL COMMENT '法定代表人证件类型',
|
||||
`corp_legal_cert_no` VARCHAR(30) DEFAULT NULL COMMENT '法定代表人证件号码',
|
||||
`corp_shareholder_1` VARCHAR(30) DEFAULT NULL COMMENT '股东1',
|
||||
`corp_shareholder_2` VARCHAR(30) DEFAULT NULL COMMENT '股东2',
|
||||
`corp_shareholder_3` VARCHAR(30) DEFAULT NULL COMMENT '股东3',
|
||||
`corp_shareholder_4` VARCHAR(30) DEFAULT NULL COMMENT '股东4',
|
||||
`corp_shareholder_5` VARCHAR(30) DEFAULT NULL COMMENT '股东5',
|
||||
|
||||
-- ============================================================
|
||||
-- 通用字段(新增)
|
||||
-- ============================================================
|
||||
`data_source` VARCHAR(30) DEFAULT 'MANUAL' COMMENT '数据来源',
|
||||
|
||||
-- ============================================================
|
||||
-- 审计字段(原有)
|
||||
-- ============================================================
|
||||
`create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
|
||||
|
||||
PRIMARY KEY (`intermediary_id`),
|
||||
KEY `idx_name` (`name`),
|
||||
KEY `idx_certificate_no` (`certificate_no`),
|
||||
KEY `idx_intermediary_type` (`intermediary_type`),
|
||||
KEY `idx_corp_credit_code` (`corp_credit_code`),
|
||||
KEY `idx_indiv_phone` (`indiv_phone`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='中介人员黑名单表';
|
||||
```
|
||||
|
||||
### 新增字段说明
|
||||
|
||||
| 字段名 | 类型 | 说明 | 适用类型 | 必填 | 默认值 |
|
||||
|-------|------|------|---------|-----|-------|
|
||||
| indiv_type | VARCHAR(30) | 人员类型(中介、职业背债人等) | 个人 | 否 | NULL |
|
||||
| indiv_sub_type | VARCHAR(50) | 人员子类型(本人、配偶等) | 个人 | 否 | NULL |
|
||||
| indiv_gender | CHAR(1) | 性别(M男 F女 O其他) | 个人 | 否 | NULL |
|
||||
| indiv_cert_type | VARCHAR(30) | 证件类型 | 个人 | 否 | 身份证 |
|
||||
| indiv_phone | VARCHAR(20) | 手机号码 | 个人 | 否 | NULL |
|
||||
| indiv_wechat | VARCHAR(50) | 微信号 | 个人 | 否 | NULL |
|
||||
| indiv_address | VARCHAR(200) | 联系地址 | 个人 | 否 | NULL |
|
||||
| indiv_company | VARCHAR(100) | 所在公司 | 个人 | 否 | NULL |
|
||||
| indiv_position | VARCHAR(100) | 职位/职务 | 个人 | 否 | NULL |
|
||||
| indiv_related_id | VARCHAR(20) | 关联人员ID | 个人 | 否 | NULL |
|
||||
| indiv_relation | VARCHAR(50) | 关联关系 | 个人 | 否 | NULL |
|
||||
| corp_credit_code | VARCHAR(18) | 统一社会信用代码 | 机构 | 否 | NULL |
|
||||
| corp_type | VARCHAR(50) | 主体类型 | 机构 | 否 | NULL |
|
||||
| corp_nature | VARCHAR(50) | 企业性质 | 机构 | 否 | NULL |
|
||||
| corp_industry_category | VARCHAR(100) | 行业分类 | 机构 | 否 | NULL |
|
||||
| corp_industry | VARCHAR(100) | 所属行业 | 机构 | 否 | NULL |
|
||||
| corp_establish_date | DATE | 成立日期 | 机构 | 否 | NULL |
|
||||
| corp_address | VARCHAR(500) | 注册地址 | 机构 | 否 | NULL |
|
||||
| corp_legal_rep | VARCHAR(50) | 法定代表人 | 机构 | 否 | NULL |
|
||||
| corp_legal_cert_type | VARCHAR(30) | 法定代表人证件类型 | 机构 | 否 | NULL |
|
||||
| corp_legal_cert_no | VARCHAR(30) | 法定代表人证件号码 | 机构 | 否 | NULL |
|
||||
| corp_shareholder_1 | VARCHAR(30) | 股东1 | 机构 | 否 | NULL |
|
||||
| corp_shareholder_2 | VARCHAR(30) | 股东2 | 机构 | 否 | NULL |
|
||||
| corp_shareholder_3 | VARCHAR(30) | 股东3 | 机构 | 否 | NULL |
|
||||
| corp_shareholder_4 | VARCHAR(30) | 股东4 | 机构 | 否 | NULL |
|
||||
| corp_shareholder_5 | VARCHAR(30) | 股东5 | 机构 | 否 | NULL |
|
||||
| data_source | VARCHAR(30) | 数据来源 | 全部 | 否 | MANUAL |
|
||||
|
||||
**字段命名规则:**
|
||||
- **个人字段**:统一使用 `indiv_` 前缀(individual 缩写),便于快速识别
|
||||
- **机构字段**:统一使用 `corp_` 前缀(corporation 缩写),便于快速识别
|
||||
- **通用字段**:无前缀,适用于所有类型
|
||||
|
||||
## 后端设计
|
||||
|
||||
### 模块结构
|
||||
|
||||
```
|
||||
ruoyi-dpc/
|
||||
├── src/main/java/com/ruoyi/dpc/
|
||||
│ ├── controller/
|
||||
│ │ └── CcdiIntermediaryBlacklistController.java (修改)
|
||||
│ ├── domain/
|
||||
│ │ ├── CcdiIntermediaryBlacklist.java (修改 - 添加新字段)
|
||||
│ │ ├── dto/
|
||||
│ │ │ ├── CcdiIntermediaryBlacklistAddDTO.java (保留 - 兼容)
|
||||
│ │ │ ├── CcdiIntermediaryBlacklistEditDTO.java (保留 - 兼容)
|
||||
│ │ │ ├── CcdiIntermediaryBlacklistQueryDTO.java (保留)
|
||||
│ │ │ ├── CcdiIntermediaryPersonAddDTO.java (新增)
|
||||
│ │ │ └── CcdiIntermediaryEntityAddDTO.java (新增)
|
||||
│ │ ├── vo/
|
||||
│ │ │ ├── CcdiIntermediaryBlacklistVO.java (保留 - 列表用)
|
||||
│ │ │ ├── CcdiIntermediaryPersonDetailVO.java (新增)
|
||||
│ │ │ └── CcdiIntermediaryEntityDetailVO.java (新增)
|
||||
│ │ └── excel/
|
||||
│ │ ├── CcdiIntermediaryBlacklistExcel.java (保留 - 通用模板)
|
||||
│ │ ├── CcdiIntermediaryPersonExcel.java (新增)
|
||||
│ │ └── CcdiIntermediaryEntityExcel.java (新增)
|
||||
│ ├── mapper/
|
||||
│ │ └── CcdiIntermediaryBlacklistMapper.java
|
||||
│ └── service/
|
||||
│ ├── ICcdiIntermediaryBlacklistService.java (修改接口)
|
||||
│ └── impl/
|
||||
│ └── CcdiIntermediaryBlacklistServiceImpl.java (修改实现)
|
||||
└── src/main/resources/mapper/dpc/
|
||||
└── CcdiIntermediaryBlacklistMapper.xml
|
||||
```
|
||||
|
||||
### Controller 层设计
|
||||
|
||||
**新增接口:**
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/dpc/intermediary")
|
||||
public class CcdiIntermediaryBlacklistController extends BaseController {
|
||||
|
||||
// ... 现有接口保持不变 ...
|
||||
|
||||
/**
|
||||
* 下载个人中介导入模板(带字典下拉框)
|
||||
*/
|
||||
@Operation(summary = "下载个人中介导入模板")
|
||||
@PostMapping("/importPersonTemplate")
|
||||
public void importPersonTemplate(HttpServletResponse response) {
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(
|
||||
response,
|
||||
CcdiIntermediaryPersonExcel.class,
|
||||
"个人中介黑名单"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载机构中介导入模板(带字典下拉框)
|
||||
*/
|
||||
@Operation(summary = "下载机构中介导入模板")
|
||||
@PostMapping("/importEntityTemplate")
|
||||
public void importEntityTemplate(HttpServletResponse response) {
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(
|
||||
response,
|
||||
CcdiIntermediaryEntityExcel.class,
|
||||
"机构中介黑名单"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入个人中介黑名单
|
||||
*/
|
||||
@Operation(summary = "导入个人中介黑名单")
|
||||
@PreAuthorize("@ss.hasPermi('dpc:intermediary:import')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importPersonData")
|
||||
public AjaxResult importPersonData(MultipartFile file, boolean updateSupport) throws Exception {
|
||||
List<CcdiIntermediaryPersonExcel> list = EasyExcelUtil.importExcel(
|
||||
file.getInputStream(), CcdiIntermediaryPersonExcel.class);
|
||||
String message = intermediaryService.importPersonIntermediary(list, updateSupport);
|
||||
return success(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入机构中介黑名单
|
||||
*/
|
||||
@Operation(summary = "导入机构中介黑名单")
|
||||
@PreAuthorize("@ss.hasPermi('dpc:intermediary:import')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importEntityData")
|
||||
public AjaxResult importEntityData(MultipartFile file, boolean updateSupport) throws Exception {
|
||||
List<CcdiIntermediaryEntityExcel> list = EasyExcelUtil.importExcel(
|
||||
file.getInputStream(), CcdiIntermediaryEntityExcel.class);
|
||||
String message = intermediaryService.importEntityIntermediary(list, updateSupport);
|
||||
return success(message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service 层设计
|
||||
|
||||
**接口扩展:**
|
||||
|
||||
```java
|
||||
public interface ICcdiIntermediaryBlacklistService {
|
||||
|
||||
// ... 现有方法保持不变 ...
|
||||
|
||||
/**
|
||||
* 根据中介类型获取详情(返回不同类型)
|
||||
*/
|
||||
Object selectIntermediaryDetailById(Long intermediaryId);
|
||||
|
||||
/**
|
||||
* 导入个人中介数据
|
||||
*/
|
||||
String importPersonIntermediary(List<CcdiIntermediaryPersonExcel> list, boolean isUpdateSupport);
|
||||
|
||||
/**
|
||||
* 导入机构中介数据
|
||||
*/
|
||||
String importEntityIntermediary(List<CcdiIntermediaryEntityExcel> list, boolean isUpdateSupport);
|
||||
}
|
||||
```
|
||||
|
||||
### Excel 类设计
|
||||
|
||||
**使用 @DictDropdown 注解实现字典下拉框**
|
||||
|
||||
项目已有现成的字典下拉框功能,详见:[doc/EasyExcel字典下拉框使用说明.md](../../../doc/EasyExcel字典下拉框使用说明.md)
|
||||
|
||||
**个人中介 Excel 类:**
|
||||
|
||||
```java
|
||||
@Data
|
||||
public class CcdiIntermediaryPersonExcel implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ExcelProperty(value = "姓名", index = 0)
|
||||
@ColumnWidth(15)
|
||||
private String name;
|
||||
|
||||
@ExcelProperty(value = "人员类型", index = 1)
|
||||
@ColumnWidth(15)
|
||||
private String indivType; // 对应 indiv_type
|
||||
|
||||
@ExcelProperty(value = "人员子类型", index = 2)
|
||||
@ColumnWidth(15)
|
||||
private String indivSubType; // 对应 indiv_sub_type
|
||||
|
||||
@ExcelProperty(value = "性别", index = 3)
|
||||
@ColumnWidth(10)
|
||||
@DictDropdown(dictType = "ccdi_indiv_gender")
|
||||
private String indivGender; // 对应 indiv_gender,字典下拉框:男/女/其他
|
||||
|
||||
@ExcelProperty(value = "证件类型", index = 4)
|
||||
@ColumnWidth(15)
|
||||
@DictDropdown(dictType = "ccdi_certificate_type")
|
||||
private String indivCertType; // 对应 indiv_cert_type,字典下拉框:身份证/护照/港澳通行证/台胞证/军官证
|
||||
|
||||
@ExcelProperty(value = "证件号码", index = 5)
|
||||
@ColumnWidth(20)
|
||||
private String certificateNo;
|
||||
|
||||
@ExcelProperty(value = "手机号码", index = 6)
|
||||
@ColumnWidth(15)
|
||||
private String indivPhone; // 对应 indiv_phone
|
||||
|
||||
@ExcelProperty(value = "微信号", index = 7)
|
||||
@ColumnWidth(15)
|
||||
private String indivWechat; // 对应 indiv_wechat
|
||||
|
||||
@ExcelProperty(value = "联系地址", index = 8)
|
||||
@ColumnWidth(30)
|
||||
private String indivAddress; // 对应 indiv_address
|
||||
|
||||
@ExcelProperty(value = "所在公司", index = 9)
|
||||
@ColumnWidth(20)
|
||||
private String indivCompany; // 对应 indiv_company
|
||||
|
||||
@ExcelProperty(value = "职位", index = 10)
|
||||
@ColumnWidth(15)
|
||||
private String indivPosition; // 对应 indiv_position
|
||||
|
||||
@ExcelProperty(value = "关联人员ID", index = 11)
|
||||
@ColumnWidth(15)
|
||||
private String indivRelatedId; // 对应 indiv_related_id
|
||||
|
||||
@ExcelProperty(value = "关联关系", index = 12)
|
||||
@ColumnWidth(15)
|
||||
private String indivRelation; // 对应 indiv_relation
|
||||
|
||||
@ExcelProperty(value = "备注", index = 13)
|
||||
@ColumnWidth(30)
|
||||
private String remark;
|
||||
|
||||
// 以下字段不在 Excel 中显示,由系统自动设置
|
||||
// private String status; // 默认:正常(0)
|
||||
// private String dataSource; // 默认:批量导入(IMPORT)
|
||||
}
|
||||
```
|
||||
|
||||
**机构中介 Excel 类:**
|
||||
|
||||
```java
|
||||
@Data
|
||||
public class CcdiIntermediaryEntityExcel implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ExcelProperty(value = "机构名称", index = 0)
|
||||
@ColumnWidth(25)
|
||||
private String name;
|
||||
|
||||
@ExcelProperty(value = "统一社会信用代码", index = 1)
|
||||
@ColumnWidth(20)
|
||||
private String corpCreditCode; // 对应 corp_credit_code
|
||||
|
||||
@ExcelProperty(value = "主体类型", index = 2)
|
||||
@ColumnWidth(20)
|
||||
@DictDropdown(dictType = "ccdi_entity_type")
|
||||
private String corpType; // 对应 corp_type,字典下拉框:有限责任公司/股份有限公司/合伙企业/个体工商户/外资企业
|
||||
|
||||
@ExcelProperty(value = "企业性质", index = 3)
|
||||
@ColumnWidth(15)
|
||||
@DictDropdown(dictType = "ccdi_enterprise_nature")
|
||||
private String corpNature; // 对应 corp_nature,字典下拉框:国企/民企/外企/合资/其他
|
||||
|
||||
@ExcelProperty(value = "行业分类", index = 4)
|
||||
@ColumnWidth(15)
|
||||
private String corpIndustryCategory; // 对应 corp_industry_category
|
||||
|
||||
@ExcelProperty(value = "所属行业", index = 5)
|
||||
@ColumnWidth(15)
|
||||
private String corpIndustry; // 对应 corp_industry
|
||||
|
||||
@ExcelProperty(value = "成立日期", index = 6)
|
||||
@ColumnWidth(15)
|
||||
private String corpEstablishDate; // 对应 corp_establish_date
|
||||
|
||||
@ExcelProperty(value = "注册地址", index = 7)
|
||||
@ColumnWidth(40)
|
||||
private String corpAddress; // 对应 corp_address
|
||||
|
||||
@ExcelProperty(value = "法定代表人", index = 8)
|
||||
@ColumnWidth(15)
|
||||
private String corpLegalRep; // 对应 corp_legal_rep
|
||||
|
||||
@ExcelProperty(value = "法定代表人证件类型", index = 9)
|
||||
@ColumnWidth(20)
|
||||
private String corpLegalCertType; // 对应 corp_legal_cert_type
|
||||
|
||||
@ExcelProperty(value = "法定代表人证件号码", index = 10)
|
||||
@ColumnWidth(20)
|
||||
private String corpLegalCertNo; // 对应 corp_legal_cert_no
|
||||
|
||||
@ExcelProperty(value = "股东1", index = 11)
|
||||
@ColumnWidth(15)
|
||||
private String corpShareholder1; // 对应 corp_shareholder_1
|
||||
|
||||
@ExcelProperty(value = "股东2", index = 12)
|
||||
@ColumnWidth(15)
|
||||
private String corpShareholder2; // 对应 corp_shareholder_2
|
||||
|
||||
@ExcelProperty(value = "股东3", index = 13)
|
||||
@ColumnWidth(15)
|
||||
private String corpShareholder3; // 对应 corp_shareholder_3
|
||||
|
||||
@ExcelProperty(value = "股东4", index = 14)
|
||||
@ColumnWidth(15)
|
||||
private String corpShareholder4; // 对应 corp_shareholder_4
|
||||
|
||||
@ExcelProperty(value = "股东5", index = 15)
|
||||
@ColumnWidth(15)
|
||||
private String corpShareholder5; // 对应 corp_shareholder_5
|
||||
|
||||
@ExcelProperty(value = "备注", index = 16)
|
||||
@ColumnWidth(30)
|
||||
private String remark;
|
||||
|
||||
// 以下字段不在 Excel 中显示,由系统自动设置
|
||||
// private String status; // 默认:正常(0)
|
||||
// private String dataSource; // 默认:批量导入(IMPORT)
|
||||
}
|
||||
```
|
||||
|
||||
### VO 设计
|
||||
|
||||
**个人详情 VO:**
|
||||
|
||||
```java
|
||||
@Data
|
||||
public class CcdiIntermediaryPersonDetailVO implements Serializable {
|
||||
|
||||
// 核心字段
|
||||
private Long intermediaryId;
|
||||
private String name;
|
||||
private String certificateNo;
|
||||
private String intermediaryType;
|
||||
private String intermediaryTypeName;
|
||||
private String status;
|
||||
private String statusName;
|
||||
private String remark;
|
||||
private String dataSource;
|
||||
|
||||
// 个人专属字段(使用 indiv_ 前缀)
|
||||
private String indivType; // 人员类型
|
||||
private String indivSubType; // 人员子类型
|
||||
private String indivGender; // 性别
|
||||
private String indivGenderName; // 性别名称
|
||||
private String indivCertType; // 证件类型
|
||||
private String indivPhone; // 手机号码
|
||||
private String indivWechat; // 微信号
|
||||
private String indivAddress; // 联系地址
|
||||
private String indivCompany; // 所在公司
|
||||
private String indivPosition; // 职位/职务
|
||||
private String indivRelatedId; // 关联人员ID
|
||||
private String indivRelation; // 关联关系
|
||||
|
||||
// 审计字段
|
||||
private String createBy;
|
||||
private Date createTime;
|
||||
private String updateBy;
|
||||
private Date updateTime;
|
||||
}
|
||||
```
|
||||
|
||||
**机构详情 VO:**
|
||||
|
||||
```java
|
||||
@Data
|
||||
public class CcdiIntermediaryEntityDetailVO implements Serializable {
|
||||
|
||||
// 核心字段
|
||||
private Long intermediaryId;
|
||||
private String name;
|
||||
private String certificateNo;
|
||||
private String intermediaryType;
|
||||
private String intermediaryTypeName;
|
||||
private String status;
|
||||
private String statusName;
|
||||
private String remark;
|
||||
private String dataSource;
|
||||
|
||||
// 机构专属字段(使用 corp_ 前缀)
|
||||
private String corpCreditCode; // 统一社会信用代码
|
||||
private String corpType; // 主体类型
|
||||
private String corpNature; // 企业性质
|
||||
private String corpIndustryCategory; // 行业分类
|
||||
private String corpIndustry; // 所属行业
|
||||
private String corpEstablishDate; // 成立日期
|
||||
private String corpAddress; // 注册地址
|
||||
private String corpLegalRep; // 法定代表人
|
||||
private String corpLegalCertType; // 法定代表人证件类型
|
||||
private String corpLegalCertNo; // 法定代表人证件号码
|
||||
private String corpShareholder1; // 股东1
|
||||
private String corpShareholder2; // 股东2
|
||||
private String corpShareholder3; // 股东3
|
||||
private String corpShareholder4; // 股东4
|
||||
private String corpShareholder5; // 股东5
|
||||
|
||||
// 审计字段
|
||||
private String createBy;
|
||||
private Date createTime;
|
||||
private String updateBy;
|
||||
private Date updateTime;
|
||||
}
|
||||
```
|
||||
|
||||
## Excel 导入导出设计
|
||||
|
||||
### Excel 下拉框配置
|
||||
|
||||
为提高数据录入的准确性和一致性,以下字段在 Excel 模板中配置下拉框验证:
|
||||
|
||||
#### 个人中介模板下拉框
|
||||
|
||||
| 列名 | 下拉框选项 | 说明 |
|
||||
|------|-----------|------|
|
||||
| 性别 | 男, 女, 其他 | 对应值:M, F, O |
|
||||
| 证件类型 | 身份证, 护照, 港澳通行证, 台胞证, 军官证 | 从字典 ccdi_certificate_type 加载 |
|
||||
|
||||
#### 机构中介模板下拉框
|
||||
|
||||
| 列名 | 下拉框选项 | 说明 |
|
||||
|------|-----------|------|
|
||||
| 主体类型 | 有限责任公司, 股份有限公司, 合伙企业, 个体工商户, 外资企业 | 从字典 ccdi_entity_type 加载 |
|
||||
| 企业性质 | 国企, 民企, 外企, 合资, 其他 | 从字典 ccdi_enterprise_nature 加载 |
|
||||
|
||||
**注意**:
|
||||
- **状态字段**:不在 Excel 模板中显示,导入时默认设置为"正常"(0)
|
||||
- **数据来源字段**:不在 Excel 模板中显示,导入时默认设置为"批量导入"(IMPORT)
|
||||
|
||||
#### 下拉框实现方式
|
||||
|
||||
使用项目现有的 `@DictDropdown` 注解功能,详见:[doc/EasyExcel字典下拉框使用说明.md](../../../doc/EasyExcel字典下拉框使用说明.md)
|
||||
|
||||
**核心组件:**
|
||||
- `@DictDropdown` 注解:`com.ruoyi.common.annotation.DictDropdown`
|
||||
- `DictDropdownWriteHandler` 处理器:`com.ruoyi.dpc.handler.DictDropdownWriteHandler`
|
||||
- `EasyExcelUtil.importTemplateWithDictDropdown()`:`com.ruoyi.dpc.utils.EasyExcelUtil`
|
||||
|
||||
**实现步骤:**
|
||||
1. 在 Excel 类的对应字段上添加 `@DictDropdown(dictType = "字典类型")` 注解
|
||||
2. 在若依系统中配置对应的字典数据
|
||||
3. 使用 `EasyExcelUtil.importTemplateWithDictDropdown()` 生成模板
|
||||
4. 系统自动从 Redis 缓存读取字典数据并生成下拉框
|
||||
|
||||
**优势:**
|
||||
- 无需手动编写下拉框处理器代码
|
||||
- 字典数据统一管理,维护方便
|
||||
- 支持大量下拉选项(自动使用隐藏 Sheet)
|
||||
- 下拉选项可动态更新
|
||||
|
||||
### 个人中介模板格式
|
||||
|
||||
| 姓名 | 人员类型 | 人员子类型 | 性别▼ | 证件类型▼ | 证件号码 | 手机号码 | 微信号 | 联系地址 | 所在公司 | 职位 | 关联人员ID | 关联关系 | 备注 |
|
||||
|------|---------|-----------|-------|-----------|---------|---------|--------|---------|---------|-----|-----------|---------|------|
|
||||
| 张三 | 中介 | 本人 | 男 | 身份证 | 110101199001011234 | 13800138000 | zhangsan | 北京市朝阳区 | XX公司 | 经纪人 | - | - | 测试 |
|
||||
|
||||
**注:带 ▼ 标记的列包含下拉框;状态默认为"正常",数据来源默认为"批量导入"**
|
||||
|
||||
### 机构中介模板格式
|
||||
|
||||
| 机构名称 | 统一社会信用代码 | 主体类型▼ | 企业性质▼ | 行业分类 | 所属行业 | 成立日期 | 注册地址 | 法定代表人 | 法定代表人证件类型 | 法定代表人证件号码 | 股东1 | 股东2 | 股东3 | 股东4 | 股东5 | 备注 |
|
||||
|---------|-----------------|-----------|-----------|---------|---------|---------|---------|-----------|-------------------|-------------------|-------|-------|-------|-------|-------|------|
|
||||
| XX中介公司 | 91110000XXXXXXXXXX | 有限责任公司 | 民企 | 房地产 | 房地产业 | 2020-01-01 | 北京市朝阳区 | 张三 | 身份证 | 110101199001011234 | 李四 | 王五 | - | - | - | - |
|
||||
|
||||
**注:带 ▼ 标记的列包含下拉框;状态默认为"正常",数据来源默认为"批量导入"**
|
||||
|
||||
### 导入数据验证规则
|
||||
|
||||
**个人中介验证规则:**
|
||||
1. **姓名**:必填,长度 1-50 字符
|
||||
2. **证件号码**:必填,长度不超过 30 字符
|
||||
3. **证件类型**:选填,默认"身份证"
|
||||
4. **其他字段**:选填,按长度限制验证
|
||||
5. **状态**:系统默认设置为"正常"(0)
|
||||
6. **数据来源**:系统默认设置为"批量导入"(IMPORT)
|
||||
|
||||
**机构中介验证规则:**
|
||||
1. **机构名称**:必填,长度 1-200 字符
|
||||
2. **统一社会信用代码**:选填,18 位
|
||||
3. **其他字段**:选填,按长度限制验证
|
||||
4. **状态**:系统默认设置为"正常"(0)
|
||||
5. **数据来源**:系统默认设置为"批量导入"(IMPORT)
|
||||
|
||||
## 字典数据设计
|
||||
|
||||
### 人员类型(ccdi_person_type)
|
||||
|
||||
| 字典值 | 字典标签 | 排序 | 状态 |
|
||||
|-------|---------|-----|------|
|
||||
| 中介 | 中介 | 1 | 正常 |
|
||||
| 职业背债人 | 职业背债人 | 2 | 正常 |
|
||||
| 房产中介 | 房产中介 | 3 | 正常 |
|
||||
|
||||
### 人员子类型(ccdi_person_sub_type)
|
||||
|
||||
| 字典值 | 字典标签 | 排序 | 状态 |
|
||||
|-------|---------|-----|------|
|
||||
| 本人 | 本人 | 1 | 正常 |
|
||||
| 配偶 | 配偶 | 2 | 正常 |
|
||||
| 子女 | 子女 | 3 | 正常 |
|
||||
| 其他 | 其他 | 9 | 正常 |
|
||||
|
||||
### 证件类型(ccdi_certificate_type)
|
||||
|
||||
| 字典值 | 字典标签 | 排序 | 状态 |
|
||||
|-------|---------|-----|------|
|
||||
| 身份证 | 身份证 | 1 | 正常 |
|
||||
| 护照 | 护照 | 2 | 正常 |
|
||||
| 港澳通行证 | 港澳通行证 | 3 | 正常 |
|
||||
| 台胞证 | 台胞证 | 4 | 正常 |
|
||||
| 军官证 | 军官证 | 5 | 正常 |
|
||||
|
||||
### 主体类型(ccdi_entity_type)
|
||||
|
||||
| 字典值 | 字典标签 | 排序 | 状态 |
|
||||
|-------|---------|-----|------|
|
||||
| 有限责任公司 | 有限责任公司 | 1 | 正常 |
|
||||
| 股份有限公司 | 股份有限公司 | 2 | 正常 |
|
||||
| 合伙企业 | 合伙企业 | 3 | 正常 |
|
||||
| 个体工商户 | 个体工商户 | 4 | 正常 |
|
||||
| 外资企业 | 外资企业 | 5 | 正常 |
|
||||
|
||||
### 企业性质(ccdi_enterprise_nature)
|
||||
|
||||
| 字典值 | 字典标签 | 排序 | 状态 |
|
||||
|-------|---------|-----|------|
|
||||
| 国企 | 国企 | 1 | 正常 |
|
||||
| 民企 | 民企 | 2 | 正常 |
|
||||
| 外企 | 外企 | 3 | 正常 |
|
||||
| 合资 | 合资 | 4 | 正常 |
|
||||
| 其他 | 其他 | 9 | 正常 |
|
||||
|
||||
### 数据来源(ccdi_data_source)
|
||||
|
||||
| 字典值 | 字典标签 | 排序 | 状态 |
|
||||
|-------|---------|-----|------|
|
||||
| MANUAL | 手动录入 | 1 | 正常 |
|
||||
| SYSTEM | 系统同步 | 2 | 正常 |
|
||||
| IMPORT | 批量导入 | 3 | 正常 |
|
||||
| API | 接口获取 | 4 | 正常 |
|
||||
|
||||
## 技术约束
|
||||
|
||||
1. **后端框架**:Spring Boot 3.5.8
|
||||
2. **ORM 框架**:MyBatis Plus 3.5.10
|
||||
3. **Excel 处理**:EasyExcel
|
||||
4. **数据库**:MySQL 8.2.0
|
||||
5. **字符编码**:UTF-8(utf8mb4)
|
||||
6. **向后兼容**:保持现有接口和数据结构不变
|
||||
|
||||
## 工具类使用
|
||||
|
||||
### EasyExcelUtil 现有方法
|
||||
|
||||
项目已有 `EasyExcelUtil.importTemplateWithDictDropdown()` 方法,可直接使用:
|
||||
|
||||
```java
|
||||
// 生成个人中介模板(带字典下拉框)
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(
|
||||
response,
|
||||
CcdiIntermediaryPersonExcel.class,
|
||||
"个人中介黑名单"
|
||||
);
|
||||
|
||||
// 生成机构中介模板(带字典下拉框)
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(
|
||||
response,
|
||||
CcdiIntermediaryEntityExcel.class,
|
||||
"机构中介黑名单"
|
||||
);
|
||||
```
|
||||
|
||||
**参考文档:** [doc/EasyExcel字典下拉框使用说明.md](../../../doc/EasyExcel字典下拉框使用说明.md)
|
||||
@@ -0,0 +1,144 @@
|
||||
# Proposal: 增强中介黑名单字段并实现类型化模板导入
|
||||
|
||||
## Change ID
|
||||
`enhance-intermediary-with-detailed-fields`
|
||||
|
||||
## Summary
|
||||
增强中介黑名单功能,添加个人和机构类型的详细字段,实现类型化的模板下载和导入功能,并添加详情展示接口,根据中介类型展示不同的字段信息。
|
||||
|
||||
## Motivation
|
||||
目前中介黑名单功能存在以下问题:
|
||||
1. 字段过于简单,只有基础的姓名/机构名称、证件号、中介类型、状态、备注
|
||||
2. 无法区分个人和机构的不同信息需求
|
||||
3. 只有一个通用的导入模板,无法满足不同类型的详细数据录入
|
||||
4. 缺少详情展示接口,无法查看完整的个人或机构信息
|
||||
|
||||
业务需求:
|
||||
1. 个人中介需要记录:人员类型、性别、证件类型、手机号码、微信号、联系地址、所在公司、职位、关联关系等详细信息
|
||||
2. 机构中介需要记录:统一社会信用代码、主体类型、企业性质、行业分类、成立日期、注册地址、法定代表人、股东等详细信息
|
||||
3. 需要分开的个人和机构导入模板,便于用户分别录入不同类型的数据
|
||||
4. 需要详情接口展示完整的个人或机构信息
|
||||
|
||||
## Scope
|
||||
本提案实现以下功能:
|
||||
|
||||
### 包含的功能
|
||||
- **1.1 数据库字段扩展**
|
||||
- 为个人类型添加详细字段(人员类型、性别、证件类型、手机号码、微信号等)
|
||||
- 为机构类型添加详细字段(统一社会信用代码、主体类型、企业性质、法定代表人、股东等)
|
||||
- 保持现有字段的向后兼容性
|
||||
|
||||
- **1.2 模板下载功能增强**
|
||||
- 个人中介模板下载(包含个人专属字段)
|
||||
- 机构中介模板下载(包含机构专属字段)
|
||||
|
||||
- **1.3 导入功能增强**
|
||||
- 支持个人中介批量导入
|
||||
- 支持机构中介批量导入
|
||||
- 根据模板类型自动识别中介类型
|
||||
|
||||
- **1.4 详情展示接口**
|
||||
- 根据中介类型返回不同的字段信息
|
||||
- 个人类型返回个人详细字段
|
||||
- 机构类型返回机构详细字段
|
||||
|
||||
- **1.5 列表接口优化**
|
||||
- 保持列表接口简洁,只显示核心字段
|
||||
- 支持按新增字段进行筛选
|
||||
|
||||
### 明确排除
|
||||
- 前端页面的修改(后续独立变更)
|
||||
- 数据迁移脚本(旧数据保持原样,新字段可为空)
|
||||
|
||||
## Proposed Design
|
||||
详见 [design.md](./design.md)
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### 选项1:分表存储(个人表和机构表分离)
|
||||
**优点**:
|
||||
- 字段分离清晰
|
||||
- 查询性能可能更好(表更小)
|
||||
|
||||
**缺点**:
|
||||
- 需要维护多个表和关联关系
|
||||
- 列表查询需要联合查询
|
||||
- 导入导出逻辑复杂化
|
||||
- 不符合现有设计思路
|
||||
|
||||
**决定**:不采用。单表设计更简洁,通过中介类型区分即可满足需求。
|
||||
|
||||
### 选项2:使用 JSON 字段存储扩展信息
|
||||
**优点**:
|
||||
- 灵活性高,易于扩展
|
||||
- 不需要修改表结构
|
||||
|
||||
**缺点**:
|
||||
- 不利于按扩展字段查询和筛选
|
||||
- 不利于数据验证和约束
|
||||
- 导入导出逻辑复杂
|
||||
|
||||
**决定**:不采用。使用独立的字段更有利于数据管理和查询。
|
||||
|
||||
### 选项3:在现有基础上添加字段
|
||||
**优点**:
|
||||
- 保持向后兼容
|
||||
- 实现简单直接
|
||||
|
||||
**缺点**:
|
||||
- 表会有很多字段(约40+个)
|
||||
- 个人和机构字段混在一起
|
||||
|
||||
**决定**:采用。这是最直接的方案,通过业务逻辑区分使用哪些字段,数据库层面允许字段为空。
|
||||
|
||||
## Impact
|
||||
|
||||
### 后端影响
|
||||
- **数据库**:扩展 `ccdi_intermediary_blacklist` 表,添加约30个新字段
|
||||
- **实体类**:`CcdiIntermediaryBlacklist` 添加新字段属性
|
||||
- **DTO**:创建 `CcdiIntermediaryPersonAddDTO` 和 `CcdiIntermediaryEntityAddDTO`
|
||||
- **VO**:创建 `CcdiIntermediaryPersonDetailVO` 和 `CcdiIntermediaryEntityDetailVO`
|
||||
- **Excel**:创建 `CcdiIntermediaryPersonExcel` 和 `CcdiIntermediaryEntityExcel`
|
||||
- **Controller**:添加两个模板下载接口,修改详情接口
|
||||
- **Service**:扩展导入逻辑,支持不同类型的模板
|
||||
|
||||
### 前端影响
|
||||
- API 接口变更:需要调用新的模板下载接口和详情接口
|
||||
- 详情页面需要根据类型显示不同字段
|
||||
|
||||
### 数据库影响
|
||||
- 扩展现有表,添加新字段(默认允许 NULL,保持向后兼容)
|
||||
|
||||
## Dependencies
|
||||
- 依赖现有 `ccdi_intermediary_blacklist` 表和基础功能
|
||||
- 依赖 EasyExcel 进行 Excel 导入导出
|
||||
|
||||
## Related Changes
|
||||
- `add-intermediary-blacklist` - 基础中介黑名单功能
|
||||
|
||||
## Open Questions
|
||||
1. **旧数据处理**:
|
||||
- 现有数据如何处理?
|
||||
- 建议:保持不变,新字段为空,用户可后续编辑补充
|
||||
|
||||
2. **必填字段**:
|
||||
- 新增字段哪些设为必填?
|
||||
- 建议:除核心字段外,新字段都设为可选,便于分阶段完善数据
|
||||
|
||||
3. **详情接口设计**:
|
||||
- 是分开的两个接口还是一个接口根据类型返回不同 VO?
|
||||
- 建议:一个接口,根据中介类型返回不同的 VO 结构
|
||||
|
||||
## Success Criteria
|
||||
- [ ] 数据库表扩展完成,添加个人和机构的详细字段
|
||||
- [ ] 可以下载个人中介专用导入模板
|
||||
- [ ] 可以下载机构中介专用导入模板
|
||||
- [ ] 可以使用个人模板批量导入个人中介数据
|
||||
- [ ] 可以使用机构模板批量导入机构中介数据
|
||||
- [ ] 详情接口根据类型返回完整的字段信息
|
||||
- [ ] 现有功能保持正常运行,向后兼容
|
||||
|
||||
## References
|
||||
- [doc/中介人员信息表.csv](../../../doc/中介人员信息表.csv)
|
||||
- [doc/中介主体信息表.csv](../../../doc/中介主体信息表.csv)
|
||||
- [现有中介黑名单设计](../add-intermediary-blacklist/design.md)
|
||||
@@ -0,0 +1,181 @@
|
||||
# Spec: 中介黑名单详细字段与类型化导入
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 支持个人和机构类型的不同字段存储
|
||||
系统必须能够为个人和机构类型的中介存储不同的字段信息,所有新增字段默认为可选,以保持向后兼容性。
|
||||
|
||||
#### Scenario: 个人中介存储详细信息
|
||||
**Given** 用户创建或编辑个人类型的中介
|
||||
**When** 填写个人专属字段(人员类型、性别、证件类型、手机号码等)
|
||||
**Then** 系统应将这些字段保存到数据库
|
||||
**And** 个人专属字段应与机构专属字段分开存储在同一表中
|
||||
|
||||
#### Scenario: 机构中介存储详细信息
|
||||
**Given** 用户创建或编辑机构类型的中介
|
||||
**When** 填写机构专属字段(统一社会信用代码、主体类型、法定代表人等)
|
||||
**Then** 系统应将这些字段保存到数据库
|
||||
**And** 机构专属字段应与个人专属字段分开存储在同一表中
|
||||
|
||||
#### Scenario: 向后兼容性保持
|
||||
**Given** 系统中存在旧的中介数据(只有基础字段)
|
||||
**When** 查询或编辑这些旧数据
|
||||
**Then** 系统应正常工作
|
||||
**And** 新增字段应允许为空
|
||||
|
||||
### Requirement: 支持个人和机构分离的模板下载
|
||||
系统必须提供独立的个人和机构中介导入模板下载功能。
|
||||
|
||||
#### Scenario: 下载个人中介模板
|
||||
**Given** 用户在中介黑名单管理页面
|
||||
**When** 点击"下载个人模板"按钮
|
||||
**Then** 系统应生成包含个人专属字段的 Excel 模板
|
||||
**And** 模板应包含:姓名、人员类型、人员子类型、性别、证件类型、证件号码、手机号码、微信号、联系地址、所在公司、职位、关联人员ID、关联关系、状态、备注
|
||||
**And** 模板应包含示例数据便于用户理解
|
||||
|
||||
#### Scenario: 下载机构中介模板
|
||||
**Given** 用户在中介黑名单管理页面
|
||||
**When** 点击"下载机构模板"按钮
|
||||
**Then** 系统应生成包含机构专属字段的 Excel 模板
|
||||
**And** 模板应包含:机构名称、统一社会信用代码、主体类型、企业性质、行业分类、所属行业、成立日期、注册地址、法定代表人、法定代表人证件类型、法定代表人证件号码、股东1-5、状态、备注
|
||||
**And** 模板应包含示例数据便于用户理解
|
||||
|
||||
### Requirement: 支持个人和机构分离的批量导入
|
||||
系统必须支持使用个人和机构专用模板进行批量导入,并根据模板类型自动设置中介类型。
|
||||
|
||||
#### Scenario: 使用个人模板导入个人中介
|
||||
**Given** 用户准备了一个包含个人中介数据的 Excel 文件
|
||||
**When** 用户通过"导入个人数据"接口上传文件
|
||||
**Then** 系统应解析 Excel 数据
|
||||
**And** 自动设置中介类型为"个人"(1)
|
||||
**And** 验证必填字段(姓名、证件号码、状态)
|
||||
**And** 验证可选字段的长度限制
|
||||
**And** 将数据保存到数据库
|
||||
**And** 返回导入结果统计(成功数、失败数)
|
||||
|
||||
#### Scenario: 使用机构模板导入机构中介
|
||||
**Given** 用户准备了一个包含机构中介数据的 Excel 文件
|
||||
**When** 用户通过"导入机构数据"接口上传文件
|
||||
**Then** 系统应解析 Excel 数据
|
||||
**And** 自动设置中介类型为"机构"(2)
|
||||
**And** 验证必填字段(机构名称、状态)
|
||||
**And** 验证可选字段的长度限制
|
||||
**And** 将数据保存到数据库
|
||||
**And** 返回导入结果统计(成功数、失败数)
|
||||
|
||||
#### Scenario: 导入数据验证与错误提示
|
||||
**Given** 用户上传的 Excel 文件包含验证失败的数据
|
||||
**When** 系统执行导入
|
||||
**Then** 系统应记录每一行的验证错误
|
||||
**And** 失败数据不应写入数据库
|
||||
**And** 返回详细的错误信息,包括行号和错误原因
|
||||
|
||||
#### Scenario: 支持更新模式导入
|
||||
**Given** 用户选择"更新支持"模式
|
||||
**And** Excel 中包含已存在的中介(通过证件号或统一社会信用代码判断)
|
||||
**When** 执行导入
|
||||
**Then** 系统应更新现有记录而不是创建新记录
|
||||
|
||||
### Requirement: 支持类型化的详情展示
|
||||
系统必须提供详情接口,根据中介类型返回包含不同字段的详情信息。
|
||||
|
||||
#### Scenario: 查询个人中介详情
|
||||
**Given** 数据库中存在个人类型的中介记录
|
||||
**When** 用户调用详情接口查询该记录
|
||||
**Then** 系统应返回包含个人专属字段的详情 VO
|
||||
**And** 详情应包含:核心字段 + 个人专属字段(人员类型、性别、证件类型、手机号码、微信号、联系地址、所在公司、职位、关联关系等)
|
||||
**And** 机构专属字段应不返回或为 null
|
||||
|
||||
#### Scenario: 查询机构中介详情
|
||||
**Given** 数据库中存在机构类型的中介记录
|
||||
**When** 用户调用详情接口查询该记录
|
||||
**Then** 系统应返回包含机构专属字段的详情 VO
|
||||
**And** 详情应包含:核心字段 + 机构专属字段(统一社会信用代码、主体类型、企业性质、法定代表人、股东等)
|
||||
**And** 个人专属字段应不返回或为 null
|
||||
|
||||
#### Scenario: 详情接口返回类型自动识别
|
||||
**Given** 用户调用详情接口,只提供中介ID
|
||||
**When** 系统查询到该中介记录
|
||||
**Then** 系统应根据中介类型自动返回对应类型的 VO
|
||||
**And** 个人类型返回 CcdiIntermediaryPersonDetailVO
|
||||
**And** 机构类型返回 CcdiIntermediaryEntityDetailVO
|
||||
|
||||
### Requirement: 保持现有功能兼容性
|
||||
所有新增功能必须保持现有接口和功能的正常运行。
|
||||
|
||||
#### Scenario: 列表接口保持简洁
|
||||
**Given** 用户调用中介黑名单列表接口
|
||||
**When** 系统返回列表数据
|
||||
**Then** 应保持现有的简洁字段结构
|
||||
**And** 列表 VO 应只包含:中介ID、姓名/机构名称、证件号、中介类型、状态、创建时间等核心字段
|
||||
**And** 不应包含新增的详细字段
|
||||
|
||||
#### Scenario: 现有导入接口保持可用
|
||||
**Given** 用户使用原有的通用导入模板和接口
|
||||
**When** 执行导入操作
|
||||
**Then** 系统应正常处理
|
||||
**And** 兼容只有基础字段的数据
|
||||
|
||||
#### Scenario: 现有新增/编辑接口保持可用
|
||||
**Given** 用户使用现有的新增/编辑接口
|
||||
**When** 只填写基础字段
|
||||
**Then** 系统应正常保存
|
||||
**And** 新增详细字段应保持为空或默认值
|
||||
|
||||
### Requirement: 支持按新增字段进行筛选
|
||||
列表查询接口应支持按新增字段进行筛选。
|
||||
|
||||
#### Scenario: 按人员类型筛选个人中介
|
||||
**Given** 用户在列表页面
|
||||
**When** 选择人员类型筛选条件(如"中介")
|
||||
**Then** 系统应返回匹配该人员类型的个人中介记录
|
||||
|
||||
#### Scenario: 按企业性质筛选机构中介
|
||||
**Given** 用户在列表页面
|
||||
**When** 选择企业性质筛选条件(如"民企")
|
||||
**Then** 系统应返回匹配该企业性质的机构中介记录
|
||||
|
||||
#### Scenario: 按数据来源筛选
|
||||
**Given** 用户在列表页面
|
||||
**When** 选择数据来源筛选条件(如"批量导入")
|
||||
**Then** 系统应返回匹配该数据来源的所有中介记录
|
||||
|
||||
### Requirement: 字段加密存储
|
||||
敏感字段必须进行加密存储。
|
||||
|
||||
#### Scenario: 手机号码加密
|
||||
**Given** 用户输入或导入手机号码
|
||||
**When** 数据保存到数据库
|
||||
**Then** 手机号码应加密存储
|
||||
**And** 查询时解密返回
|
||||
|
||||
#### Scenario: 证件号码加密
|
||||
**Given** 用户输入或导入证件号码(个人或法定代表人)
|
||||
**When** 数据保存到数据库
|
||||
**Then** 证件号码应加密存储
|
||||
**And** 查询时解密返回
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
无。本提案为纯新增功能,不修改现有需求。
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
无。本提案不删除任何现有功能。
|
||||
|
||||
## Cross-References
|
||||
|
||||
### Related Capabilities
|
||||
- `intermediary-blacklist-management` (基础中介黑名单管理功能)
|
||||
- `excel-import-export` (Excel 导入导出基础功能)
|
||||
|
||||
### Dependencies
|
||||
- 依赖现有 `ccdi_intermediary_blacklist` 表结构
|
||||
- 依赖 EasyExcel 工具类
|
||||
- 依赖现有权限控制体系
|
||||
|
||||
### Sequencing
|
||||
1. 首先完成数据库表扩展
|
||||
2. 然后更新实体类和 DTO/VO
|
||||
3. 实现 Excel 类和导入导出逻辑
|
||||
4. 最后更新 Controller 和 Service 层
|
||||
@@ -0,0 +1,358 @@
|
||||
# Tasks: 增强中介黑名单字段并实现类型化模板导入
|
||||
|
||||
## 任务概述
|
||||
|
||||
本任务列表将指导完成中介黑名单功能的增强,包括添加个人和机构类型的详细字段、实现类型化的模板下载和导入功能,以及添加详情展示接口。
|
||||
|
||||
## 任务清单
|
||||
|
||||
### 阶段一:数据库扩展
|
||||
|
||||
#### 任务 1.1:生成数据库变更 SQL 脚本
|
||||
- [ ] 编写 ALTER TABLE 语句,添加个人类型字段(11个字段)
|
||||
- [ ] 编写 ALTER TABLE 语句,添加机构类型字段(15个字段)
|
||||
- [ ] 编写 ALTER TABLE 语句,添加通用字段(1个字段:data_source)
|
||||
- [ ] 添加必要的索引(idx_credit_code, idx_phone_number)
|
||||
- [ ] 将 SQL 脚本保存到 `sql/` 目录,命名格式:`ccdi_intermediary_enhance_YYYYMMDD.sql`
|
||||
|
||||
**验证方式**:SQL 语法检查,确保所有字段正确添加
|
||||
|
||||
**依赖**:无
|
||||
|
||||
---
|
||||
|
||||
#### 任务 1.2:执行数据库变更
|
||||
- [ ] 在开发环境执行 SQL 脚本
|
||||
- [ ] 验证表结构变更成功
|
||||
- [ ] 确认所有字段添加成功且类型正确
|
||||
- [ ] 确认索引创建成功
|
||||
|
||||
**验证方式**:DESCRIBE ccdi_intermediary_blacklist; SHOW INDEX FROM ccdi_intermediary_blacklist;
|
||||
|
||||
**依赖**:任务 1.1
|
||||
|
||||
---
|
||||
|
||||
### 阶段二:后端实体类和 DTO/VO 更新
|
||||
|
||||
#### 任务 2.1:更新实体类 CcdiIntermediaryBlacklist
|
||||
- [ ] 添加个人类型字段属性(11个)
|
||||
- [ ] 添加机构类型字段属性(15个)
|
||||
- [ ] 添加通用字段属性(1个:dataSource)
|
||||
- [ ] 保持现有字段不变,确保向后兼容
|
||||
- [ ] 使用 Lombok @Data 注解
|
||||
|
||||
**验证方式**:编译通过,无语法错误
|
||||
|
||||
**依赖**:任务 1.2
|
||||
|
||||
---
|
||||
|
||||
#### 任务 2.2:创建个人中介 DTO
|
||||
- [ ] 创建 `CcdiIntermediaryPersonAddDTO.java`
|
||||
- [ ] 添加个人专属字段
|
||||
- [ ] 添加适当的验证注解(@NotBlank, @Size 等)
|
||||
- [ ] 实现 Serializable 接口
|
||||
|
||||
**验证方式**:编译通过,DTO 字段完整
|
||||
|
||||
**依赖**:任务 2.1
|
||||
|
||||
---
|
||||
|
||||
#### 任务 2.3:创建机构中介 DTO
|
||||
- [ ] 创建 `CcdiIntermediaryEntityAddDTO.java`
|
||||
- [ ] 添加机构专属字段
|
||||
- [ ] 添加适当的验证注解
|
||||
- [ ] 实现 Serializable 接口
|
||||
|
||||
**验证方式**:编译通过,DTO 字段完整
|
||||
|
||||
**依赖**:任务 2.1
|
||||
|
||||
---
|
||||
|
||||
#### 任务 2.4:创建个人详情 VO
|
||||
- [ ] 创建 `CcdiIntermediaryPersonDetailVO.java`
|
||||
- [ ] 包含核心字段 + 个人专属字段
|
||||
- [ ] 添加关联字段名称(如 genderName, certificateTypeName)
|
||||
- [ ] 添加审计字段
|
||||
- [ ] 使用 @JsonFormat 注解格式化日期
|
||||
|
||||
**验证方式**:编译通过,VO 字段完整
|
||||
|
||||
**依赖**:任务 2.2
|
||||
|
||||
---
|
||||
|
||||
#### 任务 2.5:创建机构详情 VO
|
||||
- [ ] 创建 `CcdiIntermediaryEntityDetailVO.java`
|
||||
- [ ] 包含核心字段 + 机构专属字段
|
||||
- [ ] 添加关联字段名称
|
||||
- [ ] 添加审计字段
|
||||
- [ ] 使用 @JsonFormat 注解格式化日期
|
||||
|
||||
**验证方式**:编译通过,VO 字段完整
|
||||
|
||||
**依赖**:任务 2.3
|
||||
|
||||
---
|
||||
|
||||
### 阶段三:Excel 类创建
|
||||
|
||||
#### 任务 3.1:创建个人中介 Excel 类
|
||||
- [ ] 创建 `CcdiIntermediaryPersonExcel.java`
|
||||
- [ ] 使用 @ExcelProperty 注解定义 Excel 列
|
||||
- [ ] 使用 @ColumnWidth 注解设置列宽
|
||||
- [ ] 字段顺序:姓名 -> 人员类型 -> ... -> 备注(共14列)
|
||||
- [ ] **不在模板中显示**:状态、数据来源字段(由系统自动设置)
|
||||
- [ ] **添加字典下拉框注解**:
|
||||
- `@DictDropdown(dictType = "ccdi_indiv_gender")` - 性别字段
|
||||
- `@DictDropdown(dictType = "ccdi_certificate_type")` - 证件类型字段
|
||||
|
||||
**验证方式**:编译通过,Excel 注解正确
|
||||
|
||||
**依赖**:任务 2.2
|
||||
|
||||
---
|
||||
|
||||
#### 任务 3.2:创建机构中介 Excel 类
|
||||
- [ ] 创建 `CcdiIntermediaryEntityExcel.java`
|
||||
- [ ] 使用 @ExcelProperty 注解定义 Excel 列
|
||||
- [ ] 使用 @ColumnWidth 注解设置列宽
|
||||
- [ ] 字段顺序:机构名称 -> 统一社会信用代码 -> ... -> 备注(共17列)
|
||||
- [ ] **不在模板中显示**:状态、数据来源字段(由系统自动设置)
|
||||
- [ ] **添加字典下拉框注解**:
|
||||
- `@DictDropdown(dictType = "ccdi_entity_type")` - 主体类型字段
|
||||
- `@DictDropdown(dictType = "ccdi_enterprise_nature")` - 企业性质字段
|
||||
|
||||
**验证方式**:编译通过,Excel 注解正确
|
||||
|
||||
**依赖**:任务 2.3
|
||||
|
||||
---
|
||||
|
||||
### 阶段四:Service 层实现
|
||||
|
||||
#### 任务 4.1:扩展 Service 接口
|
||||
- [ ] 在 `ICcdiIntermediaryBlacklistService` 中添加新方法:
|
||||
- `Object selectIntermediaryDetailById(Long intermediaryId)`
|
||||
- `String importPersonIntermediary(List<CcdiIntermediaryPersonExcel> list, boolean isUpdateSupport)`
|
||||
- `String importEntityIntermediary(List<CcdiIntermediaryEntityExcel> list, boolean isUpdateSupport)`
|
||||
|
||||
**验证方式**:编译通过,接口方法签名正确
|
||||
|
||||
**依赖**:任务 2.4, 2.5, 3.1, 3.2
|
||||
|
||||
---
|
||||
|
||||
#### 任务 4.2:实现详情查询方法
|
||||
- [ ] 在 `CcdiIntermediaryBlacklistServiceImpl` 中实现 `selectIntermediaryDetailById`
|
||||
- [ ] 根据中介类型返回不同的 VO
|
||||
- [ ] 个人类型返回 `CcdiIntermediaryPersonDetailVO`
|
||||
- [ ] 机构类型返回 `CcdiIntermediaryEntityDetailVO`
|
||||
- [ ] 填充关联字段名称(字典转换)
|
||||
|
||||
**验证方式**:单元测试验证不同类型返回正确的 VO
|
||||
|
||||
**依赖**:任务 4.1
|
||||
|
||||
---
|
||||
|
||||
#### 任务 4.3:实现个人中介导入方法
|
||||
- [ ] 实现 `importPersonIntermediary` 方法
|
||||
- [ ] 验证必填字段(姓名、证件号码、状态)
|
||||
- [ ] 验证字段长度限制
|
||||
- [ ] 设置中介类型为"1"(个人)
|
||||
- [ ] 支持更新模式(通过证件号判断是否已存在)
|
||||
- [ ] 返回导入结果统计
|
||||
|
||||
**验证方式**:单元测试验证导入逻辑和错误处理
|
||||
|
||||
**依赖**:任务 4.1
|
||||
|
||||
---
|
||||
|
||||
#### 任务 4.4:实现机构中介导入方法
|
||||
- [ ] 实现 `importEntityIntermediary` 方法
|
||||
- [ ] 验证必填字段(机构名称、状态)
|
||||
- [ ] 验证字段长度限制
|
||||
- [ ] 设置中介类型为"2"(机构)
|
||||
- [ ] 支持更新模式(通过统一社会信用代码判断是否已存在)
|
||||
- [ ] 返回导入结果统计
|
||||
|
||||
**验证方式**:单元测试验证导入逻辑和错误处理
|
||||
|
||||
**依赖**:任务 4.1
|
||||
|
||||
---
|
||||
|
||||
### 阶段五:Controller 层实现
|
||||
|
||||
#### 任务 5.1:添加模板下载接口(支持字典下拉框)
|
||||
- [ ] 添加 `POST /dpc/intermediary/importPersonTemplate` 接口
|
||||
- [ ] 添加 `POST /dpc/intermediary/importEntityTemplate` 接口
|
||||
- [ ] 使用 `EasyExcelUtil.importTemplateWithDictDropdown()` 生成模板
|
||||
- [ ] 添加 @Operation 注解用于 Swagger 文档
|
||||
- [ ] 不需要权限控制(模板下载公开)
|
||||
|
||||
**验证方式**:
|
||||
- 通过 Swagger UI 或 Postman 测试接口
|
||||
- 下载模板后验证字典下拉框功能是否正常
|
||||
- 验证下拉选项是否与字典数据一致
|
||||
|
||||
**依赖**:任务 3.1, 3.2, 6.1(确保字典数据已配置)
|
||||
|
||||
---
|
||||
|
||||
#### 任务 5.2:添加类型化导入接口
|
||||
- [ ] 添加 `POST /dpc/intermediary/importPersonData` 接口
|
||||
- [ ] 添加 `POST /dpc/intermediary/importEntityData` 接口
|
||||
- [ ] 添加 updateSupport 参数支持更新模式
|
||||
- [ ] 添加 @PreAuthorize 权限注解
|
||||
- [ ] 添加 @Log 注解记录操作日志
|
||||
- [ ] 添加 @Operation 注解用于 Swagger 文档
|
||||
|
||||
**验证方式**:通过 Swagger UI 或 Postman 测试接口
|
||||
|
||||
**依赖**:任务 4.3, 4.4
|
||||
|
||||
---
|
||||
|
||||
#### 任务 5.3:修改详情接口
|
||||
- [ ] 修改 `GET /dpc/intermediary/{intermediaryId}` 接口
|
||||
- [ ] 返回类型改为 Object(根据实际类型返回不同 VO)
|
||||
- [ ] 调用新的 `selectIntermediaryDetailById` 方法
|
||||
- [ ] 确保前端能够根据类型正确解析响应
|
||||
|
||||
**验证方式**:通过 Swagger UI 或 Postman 测试接口,验证不同类型返回不同结构
|
||||
|
||||
**依赖**:任务 4.2
|
||||
|
||||
---
|
||||
|
||||
### 阶段六:字典数据配置
|
||||
|
||||
#### 任务 6.1:创建字典数据 SQL 脚本
|
||||
- [ ] 添加人员类型字典(ccdi_person_type)
|
||||
- [ ] 添加人员子类型字典(ccdi_person_sub_type)
|
||||
- [ ] **添加性别字典(ccdi_indiv_gender)** - 用于个人中介下拉框
|
||||
- [ ] **添加证件类型字典(ccdi_certificate_type)** - 用于个人中介下拉框
|
||||
- [ ] **添加主体类型字典(ccdi_entity_type)** - 用于机构中介下拉框
|
||||
- [ ] **添加企业性质字典(ccdi_enterprise_nature)** - 用于机构中介下拉框
|
||||
- [ ] 添加数据来源字典(ccdi_data_source)
|
||||
- [ ] 将 SQL 脚本保存到 `sql/` 目录
|
||||
|
||||
**验证方式**:SQL 语法检查
|
||||
|
||||
**依赖**:无
|
||||
|
||||
---
|
||||
|
||||
#### 任务 6.2:执行字典数据 SQL 并预热缓存
|
||||
- [ ] 在开发环境执行字典数据 SQL
|
||||
- [ ] 通过系统管理 > 字典管理验证字典数据
|
||||
- [ ] **重要**:在每个字典类型页面点击"刷新缓存"按钮,确保字典数据加载到 Redis
|
||||
- [ ] 验证字典缓存是否生效(可通过测试模板下载确认下拉框是否显示)
|
||||
|
||||
**验证方式**:
|
||||
- 在系统界面中查看字典数据
|
||||
- 下载模板验证下拉框功能
|
||||
- 检查 Redis 缓存中的字典数据(`sys_dict_cache:*`)
|
||||
|
||||
**依赖**:任务 6.1
|
||||
|
||||
---
|
||||
|
||||
### 阶段七:测试
|
||||
|
||||
#### 任务 7.1:编写单元测试
|
||||
- [ ] 测试详情查询方法(个人和机构)
|
||||
- [ ] 测试个人中介导入方法
|
||||
- [ ] 测试机构中介导入方法
|
||||
- [ ] 测试数据验证逻辑
|
||||
- [ ] 测试更新模式
|
||||
|
||||
**验证方式**:所有单元测试通过
|
||||
|
||||
**依赖**:任务 4.2, 4.3, 4.4
|
||||
|
||||
---
|
||||
|
||||
#### 任务 7.2:接口集成测试
|
||||
- [ ] 测试模板下载接口(个人和机构)
|
||||
- [ ] 测试数据导入接口(个人和机构)
|
||||
- [ ] 测试详情查询接口
|
||||
- [ ] 测试列表接口(确保不受影响)
|
||||
- [ ] 测试现有新增/编辑接口(确保不受影响)
|
||||
|
||||
**验证方式**:生成测试脚本并执行,记录测试结果
|
||||
|
||||
**依赖**:任务 5.1, 5.2, 5.3
|
||||
|
||||
---
|
||||
|
||||
#### 任务 7.3:生成测试报告
|
||||
- [ ] 生成可执行的测试脚本
|
||||
- [ ] 执行测试并保存所有接口输出
|
||||
- [ ] 生成测试用例报告(Markdown 格式)
|
||||
- [ ] 报告保存到 `doc/` 目录
|
||||
|
||||
**验证方式**:测试报告完整,所有测试用例通过
|
||||
|
||||
**依赖**:任务 7.2
|
||||
|
||||
---
|
||||
|
||||
### 阶段八:文档更新
|
||||
|
||||
#### 任务 8.1:更新 API 文档
|
||||
- [ ] 更新 `doc/中介黑名单API文档.md`
|
||||
- [ ] 添加新增接口的文档:
|
||||
- 个人模板下载接口
|
||||
- 机构模板下载接口
|
||||
- 个人数据导入接口
|
||||
- 机构数据导入接口
|
||||
- 详情接口(说明返回类型变化)
|
||||
- [ ] 更新数据模型说明
|
||||
|
||||
**验证方式**:文档与实际接口一致
|
||||
|
||||
**依赖**:任务 5.1, 5.2, 5.3
|
||||
|
||||
---
|
||||
|
||||
#### 任务 8.2:更新数据库设计文档
|
||||
- [ ] 更新表结构说明
|
||||
- [ ] 添加新字段说明
|
||||
- [ ] 更新索引说明
|
||||
|
||||
**验证方式**:文档与实际表结构一致
|
||||
|
||||
**依赖**:任务 1.2
|
||||
|
||||
---
|
||||
|
||||
## 任务执行顺序建议
|
||||
|
||||
1. **第一周**:完成阶段一(数据库扩展)和阶段二(实体类和 DTO/VO)
|
||||
2. **第二周**:完成阶段三(Excel 类)和阶段四(Service 层)
|
||||
3. **第三周**:完成阶段五(Controller 层)和阶段六(字典数据)
|
||||
4. **第四周**:完成阶段七(测试)和阶段八(文档更新)
|
||||
|
||||
## 并行化建议
|
||||
|
||||
- 任务 2.2 和 2.3 可以并行开发
|
||||
- 任务 2.4 和 2.5 可以并行开发
|
||||
- 任务 3.1 和 3.2 可以并行开发
|
||||
- 任务 4.3 和 4.4 可以并行开发
|
||||
- 任务 5.1 和 5.2 可以并行开发
|
||||
|
||||
## 关键里程碑
|
||||
|
||||
1. **里程碑 1**:数据库扩展完成(任务 1.2)
|
||||
2. **里程碑 2**:后端实体类和 DTO/VO 完成(任务 2.5)
|
||||
3. **里程碑 3**:Excel 类创建完成(任务 3.2)
|
||||
4. **里程碑 4**:Service 层实现完成(任务 4.4)
|
||||
5. **里程碑 5**:Controller 层实现完成(任务 5.3)
|
||||
6. **里程碑 6**:测试通过,文档更新完成(任务 8.2)
|
||||
251
openspec/changes/replace-poi-with-easyexcel/design.md
Normal file
251
openspec/changes/replace-poi-with-easyexcel/design.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# Design: Replace Apache POI with Alibaba EasyExcel
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### 当前架构(POI)
|
||||
|
||||
```
|
||||
Controller
|
||||
↓
|
||||
ExcelUtil<T> (1944 行)
|
||||
├── init() → createWorkbook() → SXSSFWorkbook
|
||||
├── fillExcelData() → 迭代 list → addCell()
|
||||
└── importExcel() → WorkbookFactory → 解析整个文件
|
||||
```
|
||||
|
||||
### 目标架构(EasyExcel)
|
||||
|
||||
```
|
||||
Controller
|
||||
↓
|
||||
ExcelUtil<T> (简化版)
|
||||
├── exportExcel() → EasyExcel.write() → 流式写入
|
||||
└── importExcel() → EasyExcel.read() + ReadListener → 流式读取
|
||||
```
|
||||
|
||||
## Component Design
|
||||
|
||||
### 1. 依赖管理
|
||||
|
||||
**ruoyi-common/pom.xml**:
|
||||
```xml
|
||||
<!-- 移除 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 新增 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<version>3.3.4</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**pom.xml**:
|
||||
```xml
|
||||
<properties>
|
||||
<easyexcel.version>3.3.4</easyexcel.version>
|
||||
</properties>
|
||||
```
|
||||
|
||||
### 2. ExcelUtil 核心重构
|
||||
|
||||
#### 导出流程
|
||||
|
||||
```java
|
||||
// 当前实现 (POI)
|
||||
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName) {
|
||||
init(list, sheetName, title, Type.EXPORT);
|
||||
writeSheet(); // 一次性写入所有数据
|
||||
wb.write(response.getOutputStream());
|
||||
}
|
||||
|
||||
// 新实现 (EasyExcel)
|
||||
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName) {
|
||||
EasyExcel.write(response.getOutputStream(), clazz)
|
||||
.sheet(sheetName)
|
||||
.head(headGenerator) // 动态表头生成
|
||||
.registerWriteHandler(styleStrategy) // 样式策略
|
||||
.registerWriteHandler(mergeStrategy) // 合并策略
|
||||
.doWrite(list); // 流式写入
|
||||
}
|
||||
```
|
||||
|
||||
#### 导入流程
|
||||
|
||||
```java
|
||||
// 当前实现 (POI)
|
||||
public List<T> importExcel(InputStream is, int titleNum) {
|
||||
wb = WorkbookFactory.create(is); // 加载整个文件
|
||||
// 遍历所有行,解析到内存 List
|
||||
for (int i = titleNum + 1; i <= rows; i++) { ... }
|
||||
return list;
|
||||
}
|
||||
|
||||
// 新实现 (EasyExcel)
|
||||
public List<T> importExcel(InputStream is, int titleNum) {
|
||||
List<T> dataList = new ArrayList<>();
|
||||
EasyExcel.read(is, clazz, new AnalysisEventListener<T>() {
|
||||
@Override
|
||||
public void invoke(T data, AnalysisContext context) {
|
||||
// 逐行读取,不占用大量内存
|
||||
dataList.add(data);
|
||||
}
|
||||
}).sheet().headRowNumber(titleNum).doRead();
|
||||
return dataList;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 注解适配
|
||||
|
||||
#### Excel 注解调整
|
||||
|
||||
```java
|
||||
// 当前 - 依赖 POI 类型
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||
|
||||
public @interface Excel {
|
||||
HorizontalAlignment align() default HorizontalAlignment.CENTER;
|
||||
IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT;
|
||||
// ...
|
||||
}
|
||||
|
||||
// 新实现 - EasyExcel 兼容
|
||||
import com.alibaba.excel.enums.HorizontalAlignmentEnum;
|
||||
|
||||
public @interface Excel {
|
||||
HorizontalAlignmentEnum align() default HorizontalAlignmentEnum.CENTER;
|
||||
String headerBackgroundColor() default " grey_50_percent"; // 简化为字符串
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 自定义处理器实现
|
||||
|
||||
#### 样式处理器
|
||||
|
||||
```java
|
||||
public class CustomStyleStrategy extends AbstractCellStyleStrategy {
|
||||
@Override
|
||||
protected void setContentCellStyle(Cell cell, Head head, Integer relativeRowIndex) {
|
||||
// 基于 @Excel 注解设置样式
|
||||
Workbook workbook = cell.getSheet().getWorkbook();
|
||||
Field field = getField(head.getHeadNameList());
|
||||
Excel excel = field.getAnnotation(Excel.class);
|
||||
|
||||
CellStyle style = createStyle(workbook, excel);
|
||||
cell.setCellStyle(style);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 合并策略
|
||||
|
||||
```java
|
||||
public class MergeStrategy implements CellWriteHandler {
|
||||
@Override
|
||||
public void afterCellDispose(CellWriteHandlerContext context) {
|
||||
// 处理 needMerge 字段的合并
|
||||
if (needMerge(context)) {
|
||||
context.getWriteSheetHolder().getSheet()
|
||||
.addMergedRegion(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 自定义数据处理器适配
|
||||
|
||||
```java
|
||||
// 原接口
|
||||
public interface ExcelHandlerAdapter {
|
||||
Object format(Object value, String[] args, Cell cell, Workbook wb);
|
||||
}
|
||||
|
||||
// 适配 EasyExcel WriteHandler
|
||||
public class CustomWriteHandler implements CellWriteHandler {
|
||||
private ExcelHandlerAdapter handler;
|
||||
private String[] args;
|
||||
|
||||
@Override
|
||||
public void afterCellDispose(CellWriteHandlerContext context) {
|
||||
Object value = handler.format(context.getCellData(), args,
|
||||
context.getCell(), context.getWriteWorkbookHolder().getWorkbook());
|
||||
// 应用处理后的值
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### 阶段 1:双模式支持(可选)
|
||||
|
||||
```java
|
||||
public class ExcelUtil<T> {
|
||||
private boolean useEasyExcel = true; // 配置开关
|
||||
|
||||
public void exportExcel(...) {
|
||||
if (useEasyExcel) {
|
||||
exportWithEasyExcel(...);
|
||||
} else {
|
||||
exportWithPoi(...); // 保留旧实现
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 阶段 2:直接替换(推荐)
|
||||
|
||||
1. 更新依赖
|
||||
2. 重写 ExcelUtil
|
||||
3. 更新注解
|
||||
4. 逐模块测试
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### 单元测试
|
||||
|
||||
```java
|
||||
@Test
|
||||
void testLargeExport() {
|
||||
List<DemoData> data = generateData(100_000);
|
||||
ExcelUtil<DemoData> util = new ExcelUtil<>(DemoData.class);
|
||||
// 验证内存占用
|
||||
util.exportExcel(response, data, "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLargeImport() {
|
||||
File file = createLargeExcel(10_000_000); // 1000万行
|
||||
ExcelUtil<DemoData> util = new ExcelUtil<>(DemoData.class);
|
||||
List<DemoData> data = util.importExcel(new FileInputStream(file));
|
||||
// 验证解析正确性和内存占用
|
||||
}
|
||||
```
|
||||
|
||||
### 集成测试
|
||||
|
||||
- 测试所有现有 Controller 的导入导出接口
|
||||
- 验证样式、合并、图片等功能
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
| 操作 | POI (当前) | EasyExcel (目标) |
|
||||
|------|-----------|-----------------|
|
||||
| 10万行导出 | ~1GB 内存 | ~100MB 内存 |
|
||||
| 100万行导出 | OOM 风险 | ~200MB 内存 |
|
||||
| 100MB 文件导入 | ~2GB 内存 | ~150MB 内存 |
|
||||
| 单元格字符限制 | 32,767 | 无限制 |
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
如果出现问题,可以通过以下步骤回退:
|
||||
|
||||
1. 恢复 `ruoyi-common/pom.xml` 中的 POI 依赖
|
||||
2. 恢复 `ExcelUtil.java` 和相关文件
|
||||
3. 重新编译部署
|
||||
|
||||
建议在分支上进行完整测试后再合并到主分支。
|
||||
121
openspec/changes/replace-poi-with-easyexcel/proposal.md
Normal file
121
openspec/changes/replace-poi-with-easyexcel/proposal.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Proposal: Replace Apache POI with Alibaba EasyExcel
|
||||
|
||||
## Summary
|
||||
|
||||
将若依框架中的 Apache POI 替换为 Alibaba EasyExcel,以解决大文本量 Excel 导入导出时的内存占用和性能问题。当前使用 POI 的 SXSSFWorkbook 虽然支持流式写入,但在处理大量数据时仍然存在性能瓶颈,且不支持真正的流式读取。
|
||||
|
||||
## Motivation
|
||||
|
||||
### 问题分析
|
||||
|
||||
1. **当前 POI 实现的限制**:
|
||||
- `ExcelUtil.java:112` - `sheetSize` 硬编码为 65536,这是 Excel 2003 的限制
|
||||
- `ExcelUtil.java:1682` - 使用 SXSSFWorkbook 进行流式写入,但内存优化有限
|
||||
- `ExcelUtil.java:325-525` - `importExcel` 方法一次性加载整个 Sheet 到内存,无法处理大文件
|
||||
- POI 的 DOM 解析模式导致大文件内存占用过高
|
||||
|
||||
2. **业务需求**:
|
||||
- 需要支持导入导出超过 10 万行数据
|
||||
- 需要支持单元格内容超过 32767 字符(POI STRING 类型限制)
|
||||
- 需要降低内存占用,避免 OOM
|
||||
|
||||
3. **EasyExcel 优势**:
|
||||
- 基于 SAX 解析,真正的流式读写
|
||||
- 内存占用仅与行数据大小相关,与文件大小无关
|
||||
- API 设计简洁,注解驱动
|
||||
- 官方维护活跃,社区成熟
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
### 技术方案
|
||||
|
||||
1. **保持 API 兼容性**:
|
||||
- 保留 `@Excel` 和 `@Excels` 注解接口
|
||||
- 保留 `ExcelUtil<T>` 的公共方法签名
|
||||
- 保留 `ExcelHandlerAdapter` 接口(适配 EasyExcel 的写处理器)
|
||||
|
||||
2. **核心实现变更**:
|
||||
- 使用 `EasyExcel.write()` 替代 POI 的 Workbook/Sheet 操作
|
||||
- 使用 `EasyExcel.read()` 替代 POI 的 Workbook 解析
|
||||
- 实现自定义的 `HeadGenerator` 和 `ContentStyleStrategy` 以支持样式
|
||||
|
||||
3. **依赖变更**:
|
||||
- 移除 `poi-ooxml` 依赖
|
||||
- 添加 `easyexcel` 依赖(版本 3.3.4,支持 Spring Boot 3)
|
||||
|
||||
### 影响范围
|
||||
|
||||
**修改的文件**:
|
||||
- `ruoyi-common/pom.xml` - 更新依赖
|
||||
- `ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java` - 重写实现
|
||||
- `ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java` - 调整注解属性
|
||||
- `ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java` - 适配新接口
|
||||
|
||||
**无需修改的文件**(向后兼容):
|
||||
- 所有 Controller 层代码(13 个文件使用 ExcelUtil)
|
||||
- 实体类上的 `@Excel` 注解
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### 方案 1:升级 POI 版本
|
||||
- **优点**:代码改动最小
|
||||
- **缺点**:无法从根本上解决内存问题,POI 的架构限制依然存在
|
||||
|
||||
### 方案 2:使用 Hutool 的 Excel 工具
|
||||
- **优点**:API 简洁
|
||||
- **缺点**:底层仍使用 POI,内存问题未解决
|
||||
|
||||
### 方案 3:使用 EasyExcel(选定方案)
|
||||
- **优点**:真正的流式处理,内存占用低,社区成熟
|
||||
- **缺点**:需要适配现有注解和 API
|
||||
|
||||
## Compatibility Notes
|
||||
|
||||
### 破坏性变更
|
||||
|
||||
1. **样式支持**:EasyExcel 的样式支持有限,以下功能可能需要简化:
|
||||
- 合并单元格(`needMerge`)
|
||||
- 图片插入(`ColumnType.IMAGE`)
|
||||
- 数据验证(`combo`, `comboReadDict`)
|
||||
- 自定义颜色(`headerBackgroundColor`, `color` 等)
|
||||
|
||||
2. **Sheet 分割**:EasyExcel 自动处理大文件,无需手动分割 Sheet
|
||||
|
||||
3. **统计行**:`isStatistics` 需要通过监听器实现
|
||||
|
||||
### 保留的功能
|
||||
|
||||
- ✅ 注解驱动配置
|
||||
- ✅ 字典类型转换(`dictType`)
|
||||
- ✅ 表达式转换(`readConverterExp`)
|
||||
- ✅ 日期格式化(`dateFormat`)
|
||||
- ✅ 导入/导出模板生成
|
||||
- ✅ 字段筛选(`includeFields`, `excludeFields`)
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. **性能指标**:
|
||||
- 导出 10 万行数据,内存占用 < 500MB
|
||||
- 导入 100MB Excel 文件,内存占用 < 200MB
|
||||
|
||||
2. **功能完整性**:
|
||||
- 所有现有 Controller 的导入导出功能正常工作
|
||||
- 支持超过 65536 行数据
|
||||
|
||||
3. **兼容性**:
|
||||
- 现有实体类注解无需修改
|
||||
- 现有业务代码无需修改
|
||||
|
||||
## Timeline
|
||||
|
||||
阶段划分(具体任务见 tasks.md):
|
||||
1. 依赖更新与环境准备
|
||||
2. 核心 ExcelUtil 重写
|
||||
3. 注解与接口适配
|
||||
4. 集成测试与验证
|
||||
5. 文档更新
|
||||
|
||||
## Related Changes
|
||||
|
||||
- 无前置依赖
|
||||
- 可能影响:所有使用 Excel 导入导出的模块
|
||||
@@ -0,0 +1,332 @@
|
||||
# Spec: Excel Import/Export with EasyExcel
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 流式导出大量数据
|
||||
|
||||
The system **MUST** support streaming export using EasyExcel to handle datasets exceeding 100,000 rows while maintaining low memory footprint.
|
||||
|
||||
系统**必须**支持使用 EasyExcel 进行流式导出,以处理超过 10 万行的数据集,同时保持低内存占用。
|
||||
|
||||
#### Scenario: 导出 10 万行用户数据
|
||||
|
||||
**Given** 系统中有 10 万条用户记录
|
||||
|
||||
**When** 管理员调用用户导出接口
|
||||
|
||||
**Then** 系统应:
|
||||
- 成功生成包含 10 万行数据的 Excel 文件
|
||||
- 内存占用不超过 500MB
|
||||
- 导出时间在合理范围内(< 30 秒)
|
||||
- 生成的文件包含所有必要的表头和样式
|
||||
|
||||
#### Scenario: 导出超过 65536 行数据
|
||||
|
||||
**Given** 数据集包含 10 万行记录(超过传统 Excel 限制)
|
||||
|
||||
**When** 调用导出功能
|
||||
|
||||
**Then** 系统应:
|
||||
- 自动使用 .xlsx 格式
|
||||
- 不进行 Sheet 分割(EasyExcel 自动处理)
|
||||
- 所有数据完整导出到单个文件
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 流式导入大文件
|
||||
|
||||
The system **MUST** support streaming import using EasyExcel to process Excel files larger than 100MB.
|
||||
|
||||
系统**必须**支持使用 EasyExcel 进行流式读取,以处理超过 100MB 的 Excel 文件。
|
||||
|
||||
#### Scenario: 导入包含 10 万行数据的 Excel 文件
|
||||
|
||||
**Given** 一个包含 10 万行数据的 Excel 文件(约 50MB)
|
||||
|
||||
**When** 管理员上传该文件进行导入
|
||||
|
||||
**Then** 系统应:
|
||||
- 逐行解析文件,不一次性加载到内存
|
||||
- 内存占用不超过 200MB
|
||||
- 正确解析所有数据行
|
||||
- 返回完整的解析结果列表
|
||||
|
||||
#### Scenario: 导入包含超长文本的单元格
|
||||
|
||||
**Given** Excel 文件中某些单元格包含超过 32,767 个字符(超过 POI 限制)
|
||||
|
||||
**When** 执行导入操作
|
||||
|
||||
**Then** 系统应:
|
||||
- 完整读取单元格内容
|
||||
- 不截断或丢失数据
|
||||
- 正确映射到实体类字段
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 保持注解驱动配置兼容性
|
||||
|
||||
The system **MUST** maintain compatibility with existing `@Excel` and `@Excels` annotations, allowing existing entity classes to work without modification.
|
||||
|
||||
系统**必须**保持与现有 `@Excel` 和 `@Excels` 注解的兼容性,使现有实体类无需修改即可使用新实现。
|
||||
|
||||
#### Scenario: 使用现有注解导出数据
|
||||
|
||||
**Given** 实体类上已定义 `@Excel` 注解
|
||||
|
||||
```java
|
||||
@Excel(name = "用户名", sort = 1)
|
||||
private String userName;
|
||||
|
||||
@Excel(name = "性别", dictType = "sys_user_sex", sort = 2)
|
||||
private String sex;
|
||||
|
||||
@Excel(name = "创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 3)
|
||||
private Date createTime;
|
||||
```
|
||||
|
||||
**When** 调用导出功能
|
||||
|
||||
**Then** 系统应:
|
||||
- 正确解析 `@Excel` 注解配置
|
||||
- 按指定的 `sort` 顺序排列列
|
||||
- 应用字典转换(`dictType`)
|
||||
- 应用日期格式化(`dateFormat`)
|
||||
- 生成包含正确表头的 Excel 文件
|
||||
|
||||
#### Scenario: 使用复合注解导出嵌套属性
|
||||
|
||||
**Given** 实体类使用 `@Excels` 注解配置多个导出规则
|
||||
|
||||
```java
|
||||
@Excels({
|
||||
@Excel(name = "部门名称", targetAttr = "deptName", sort = 10),
|
||||
@Excel(name = "部门编码", targetAttr = "deptCode", sort = 11)
|
||||
})
|
||||
private SysDept dept;
|
||||
```
|
||||
|
||||
**When** 调用导出功能
|
||||
|
||||
**Then** 系统应:
|
||||
- 正确解析嵌套对象属性
|
||||
- 生成多个对应的列
|
||||
- 正确填充数据
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 样式与格式支持
|
||||
|
||||
The system **SHALL** support basic cell styling configuration through annotations, including alignment, background color, etc.
|
||||
|
||||
系统**应当**支持通过注解配置基本的单元格样式,包括对齐方式、背景色等。
|
||||
|
||||
#### Scenario: 应用自定义样式
|
||||
|
||||
**Given** 实体类定义了样式配置
|
||||
|
||||
```java
|
||||
@Excel(
|
||||
name = "金额",
|
||||
align = HorizontalAlignmentEnum.RIGHT,
|
||||
backgroundColor = "yellow",
|
||||
color = "red",
|
||||
sort = 5
|
||||
)
|
||||
private BigDecimal amount;
|
||||
```
|
||||
|
||||
**When** 导出数据
|
||||
|
||||
**Then** 系统应:
|
||||
- 单元格内容右对齐
|
||||
- 背景色为黄色
|
||||
- 字体颜色为红色
|
||||
|
||||
#### Scenario: 数字格式化
|
||||
|
||||
**Given** 字段配置了精度和舍入模式
|
||||
|
||||
```java
|
||||
@Excel(name = "单价", scale = 2, roundingMode = BigDecimal.ROUND_HALF_UP)
|
||||
private BigDecimal price;
|
||||
```
|
||||
|
||||
**When** 导出包含该字段的数据
|
||||
|
||||
**Then** 系统应:
|
||||
- 数值保留 2 位小数
|
||||
- 使用四舍五入规则
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入模板生成
|
||||
|
||||
The system **MUST** support generating empty import templates for users to fill and upload.
|
||||
|
||||
系统**必须**支持生成空的导入模板,供用户填写后上传。
|
||||
|
||||
#### Scenario: 生成导入模板
|
||||
|
||||
**Given** 实体类配置了 `@Excel` 注解,其中部分字段标记为仅导入
|
||||
|
||||
```java
|
||||
@Excel(name = "用户名", type = Type.ALL)
|
||||
private String userName;
|
||||
|
||||
@Excel(name = "密码", type = Type.IMPORT) // 仅导入
|
||||
private String password;
|
||||
```
|
||||
|
||||
**When** 调用 `importTemplateExcel()` 方法
|
||||
|
||||
**Then** 系统应:
|
||||
- 生成包含所有 `type=IMPORT` 或 `type=ALL` 字段的表头
|
||||
- 仅导入字段(`type=IMPORT`)不出现在模板中
|
||||
- 应用数据验证配置(如果有)
|
||||
- 设置合适的列宽
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 字段筛选功能
|
||||
|
||||
The system **MUST** support runtime control of exported fields.
|
||||
|
||||
系统**必须**支持运行时动态控制导出包含的字段。
|
||||
|
||||
#### Scenario: 显示指定字段
|
||||
|
||||
**Given** 实体类有 20 个字段,但只想导出其中 5 个
|
||||
|
||||
**When** 调用 `util.showColumn("id", "name", "email", "phone", "status")`
|
||||
|
||||
**Then** 导出的 Excel 应:
|
||||
- 仅包含指定的 5 个字段
|
||||
- 其他字段不出现在导出结果中
|
||||
|
||||
#### Scenario: 排除指定字段
|
||||
|
||||
**Given** 实体类有 20 个字段,想导出其中 18 个
|
||||
|
||||
**When** 调用 `util.hideColumn("password", "salt")`
|
||||
|
||||
**Then** 导出的 Excel 应:
|
||||
- 包含除 `password` 和 `salt` 外的所有字段
|
||||
- 敏感字段不出现在导出结果中
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 自定义数据处理器
|
||||
|
||||
The system **MUST** support custom data handlers for special cell data processing.
|
||||
|
||||
系统**必须**支持通过自定义处理器对单元格数据进行特殊处理。
|
||||
|
||||
#### Scenario: 使用自定义处理器格式化数据
|
||||
|
||||
**Given** 定义了自定义处理器
|
||||
|
||||
```java
|
||||
public class CustomAmountHandler implements ExcelHandlerAdapter {
|
||||
@Override
|
||||
public Object format(Object value, String[] args, Cell cell, Workbook wb) {
|
||||
// 将金额转换为中文大写
|
||||
return convertToChinese((BigDecimal) value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**And** 实体字段配置使用该处理器
|
||||
|
||||
```java
|
||||
@Excel(name = "金额(大写)", handler = CustomAmountHandler.class)
|
||||
private BigDecimal amount;
|
||||
```
|
||||
|
||||
**When** 导出数据
|
||||
|
||||
**Then** 金额列应显示为中文大写形式(如:壹万贰仟叁佰肆拾伍元陆角柒分)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 数据验证支持
|
||||
|
||||
The system **SHALL** support configuring dropdown options and prompt information for import templates.
|
||||
|
||||
系统**应当**支持为导入模板配置下拉选项和提示信息。
|
||||
|
||||
#### Scenario: 配置下拉选项
|
||||
|
||||
**Given** 字段配置了固定的下拉选项
|
||||
|
||||
```java
|
||||
@Excel(name = "状态", combo = {"启用", "禁用"})
|
||||
private String status;
|
||||
```
|
||||
|
||||
**When** 生成导入模板
|
||||
|
||||
**Then** 该单元格应:
|
||||
- 显示下拉箭头
|
||||
- 仅能选择"启用"或"禁用"
|
||||
- 无法输入其他值
|
||||
|
||||
#### Scenario: 从字典读取下拉选项
|
||||
|
||||
**Given** 字段配置从字典读取选项
|
||||
|
||||
```java
|
||||
@Excel(name = "性别", dictType = "sys_user_sex", comboReadDict = true)
|
||||
private String sex;
|
||||
```
|
||||
|
||||
**When** 生成导入模板
|
||||
|
||||
**Then** 该单元格的下拉列表应:
|
||||
- 包含字典中定义的所有选项
|
||||
- 动态从系统缓存读取
|
||||
|
||||
---
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: ExcelUtil API 兼容性
|
||||
|
||||
The modified `ExcelUtil` **MUST** maintain API compatibility with existing code.
|
||||
|
||||
修改后的 `ExcelUtil` **必须**保持与现有代码的 API 兼容性。
|
||||
|
||||
#### Scenario: 使用现有导出方法
|
||||
|
||||
**Given** 现有 Controller 代码
|
||||
|
||||
```java
|
||||
List<SysUser> list = userService.selectUserList(user);
|
||||
ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
|
||||
util.exportExcel(response, list, "用户数据");
|
||||
```
|
||||
|
||||
**When** 替换为 EasyExcel 实现
|
||||
|
||||
**Then** 代码应无需修改即可正常工作
|
||||
|
||||
#### Scenario: 使用现有导入方法
|
||||
|
||||
**Given** 现有导入代码
|
||||
|
||||
```java
|
||||
ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
|
||||
List<SysUser> userList = util.importExcel(file.getInputStream());
|
||||
```
|
||||
|
||||
**When** 替换为 EasyExcel 实现
|
||||
|
||||
**Then** 代码应无需修改即可正常工作
|
||||
|
||||
---
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### 无
|
||||
|
||||
此变更不删除任何现有功能需求,仅替换底层实现。
|
||||
122
openspec/changes/replace-poi-with-easyexcel/tasks.md
Normal file
122
openspec/changes/replace-poi-with-easyexcel/tasks.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Tasks: Replace Apache POI with Alibaba EasyExcel
|
||||
|
||||
## 依赖更新与环境准备
|
||||
|
||||
- [x] 1.1 更新 `pom.xml`,添加 `easyexcel.version` 属性(版本 3.3.4)
|
||||
- [x] 1.2 更新 `ruoyi-common/pom.xml`,保留 `poi-ooxml` 依赖,添加 `easyexcel` 依赖
|
||||
- [ ] 1.3 验证依赖解析成功:`mvn dependency:tree`
|
||||
- [x] 1.4 备份现有的 `ExcelUtil.java` 文件到 `excel/` 目录(保留参考)
|
||||
|
||||
## 注解与接口适配
|
||||
|
||||
- [x] 2.1 更新 `Excel.java` 注解:
|
||||
- [x] 2.1.1 移除 POI 类型的 import(`HorizontalAlignment`, `IndexedColors`)
|
||||
- [x] 2.1.2 使用字符串替代(`align`, `headerBackgroundColor` 等)
|
||||
- [x] 2.1.3 调整颜色属性为字符串表示
|
||||
- [x] 2.1.4 更新 `Javadoc` 注释
|
||||
|
||||
- [x] 2.2 更新 `ExcelHandlerAdapter.java` 接口:
|
||||
- [x] 2.2.1 保留原有方法签名
|
||||
- [x] 2.2.2 添加 EasyExcel `Cell` 和 `Workbook` 类型适配说明
|
||||
|
||||
- [x] 2.3 验证注解编译无误
|
||||
|
||||
## 核心 ExcelUtil 重写
|
||||
|
||||
- [x] 3.1 创建新的 `ExcelUtil.java` 基础结构:
|
||||
- [x] 3.1.1 保留泛型类定义 `ExcelUtil<T>`
|
||||
- [x] 3.1.2 保留现有公共方法签名
|
||||
- [x] 3.1.3 移除 POI 相关的私有字段和方法
|
||||
|
||||
- [x] 3.2 实现导出功能:
|
||||
- [x] 3.2.1 `exportExcel(HttpServletResponse, List<T>, String, String)` - 主入口
|
||||
- [x] 3.2.2 `exportExcel(HttpServletResponse, List<T>, String)` - 简化版
|
||||
- [x] 3.2.3 `exportExcel()` - 返回文件名版本
|
||||
- [x] 3.2.4 `importTemplateExcel()` - 生成导入模板
|
||||
|
||||
- [x] 3.3 实现导入功能:
|
||||
- [x] 3.3.1 `importExcel(InputStream)` - 默认标题行
|
||||
- [x] 3.3.2 `importExcel(InputStream, int titleNum)` - 自定义标题行
|
||||
- [x] 3.3.3 `importExcel(String sheetName, InputStream, int titleNum)` - 指定 Sheet
|
||||
|
||||
- [x] 3.4 实现 EasyExcel 集成组件:
|
||||
- [x] 3.4.1 `ExcelStyleHandler` - 样式处理器(基于 `@Excel` 注解)
|
||||
- [x] 3.4.2 `ReadListener` - 读取监听器(内置在 `importExcel` 中)
|
||||
|
||||
- [x] 3.5 实现辅助功能:
|
||||
- [x] 3.5.1 字典类型转换(`dictType`)
|
||||
- [x] 3.5.2 表达式转换(`readConverterExp`)
|
||||
- [x] 3.5.3 字段筛选(`showColumn`, `hideColumn`)
|
||||
- [ ] 3.5.4 日期格式化(`dateFormat`)- EasyExcel 自动处理
|
||||
- [ ] 3.5.5 自定义处理器(`handler`)- 需要进一步适配
|
||||
|
||||
## 集成测试与验证
|
||||
|
||||
- [ ] 4.1 单元测试:
|
||||
- [ ] 4.1.1 测试基本导出功能(少量数据)
|
||||
- [ ] 4.1.2 测试大数据量导出(10 万行)
|
||||
- [ ] 4.1.3 测试基本导入功能
|
||||
- [ ] 4.1.4 测试大文件导入(100MB)
|
||||
- [ ] 4.1.5 测试超长文本单元格
|
||||
- [ ] 4.1.6 测试样式应用
|
||||
- [ ] 4.1.7 测试字典转换
|
||||
- [ ] 4.1.8 测试字段筛选
|
||||
|
||||
- [ ] 4.2 现有功能集成测试:
|
||||
- [ ] 4.2.1 测试 `SysUserController` 导入导出
|
||||
- [ ] 4.2.2 测试 `SysRoleController` 导入导出
|
||||
- [ ] 4.2.3 测试 `SysDictDataController` 导入导出
|
||||
- [ ] 4.2.4 测试 `SysPostController` 导入导出
|
||||
- [ ] 4.2.5 测试 `SysConfigController` 导入导出
|
||||
- [ ] 4.2.6 测试 `SysOperlogController` 导入导出
|
||||
- [ ] 4.2.7 测试 `SysLogininforController` 导入导出
|
||||
- [ ] 4.2.8 测试 `SysJobController` 导入导出
|
||||
- [ ] 4.2.9 测试 `SysJobLogController` 导入导出
|
||||
|
||||
- [ ] 4.3 性能验证:
|
||||
- [ ] 4.3.1 使用 VisualVM 监控导出 10 万行数据的内存占用
|
||||
- [ ] 4.3.2 使用 VisualVM 监控导入 100MB 文件的内存占用
|
||||
- [ ] 4.3.3 记录并对比迁移前后的性能指标
|
||||
|
||||
## 文档更新
|
||||
|
||||
- [ ] 5.1 更新 `openspec/project.md`:
|
||||
- [ ] 5.1.1 添加 EasyExcel 到技术栈
|
||||
- [ ] 5.1.2 更新项目约定中的 Excel 处理说明
|
||||
|
||||
- [ ] 5.2 创建 API 文档:
|
||||
- [ ] 5.2.1 记录 `ExcelUtil` 公共方法的使用方式
|
||||
- [ ] 5.2.2 记录 `@Excel` 注解的所有属性说明
|
||||
- [ ] 5.2.3 提供迁移指南(如果 API 有变化)
|
||||
|
||||
- [ ] 5.3 更新 `CLAUDE.md`(如需要):
|
||||
- [ ] 5.3.1 添加 Excel 相关的开发约定
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```
|
||||
1. 依赖更新
|
||||
↓
|
||||
2. 注解适配
|
||||
↓
|
||||
3. ExcelUtil 重写
|
||||
↓
|
||||
4. 集成测试
|
||||
↓
|
||||
5. 文档更新
|
||||
```
|
||||
|
||||
## 并行任务
|
||||
|
||||
以下任务可以并行执行:
|
||||
- 2.1-2.3(注解适配)可与 3.1-3.2(ExcelUtil 基础结构)并行
|
||||
- 4.1(单元测试)可与 4.2(集成测试)部分并行
|
||||
|
||||
## 验收标准
|
||||
|
||||
所有任务完成后,必须满足:
|
||||
1. 编译通过,无依赖错误
|
||||
2. 所有现有 Controller 的导入导出功能正常
|
||||
3. 导出 10 万行数据内存占用 < 500MB
|
||||
4. 导入 100MB 文件内存占用 < 200MB
|
||||
5. 现有实体类注解无需修改
|
||||
@@ -0,0 +1,715 @@
|
||||
# Design: 同步前端以支持中介黑名单详细字段和类型化模板导入
|
||||
|
||||
## 前端架构设计
|
||||
|
||||
### 文件结构
|
||||
```
|
||||
ruoyi-ui/src/
|
||||
├── api/
|
||||
│ └── dpcIntermediary.js (修改 - 添加新接口)
|
||||
├── views/
|
||||
│ └── dpcIntermediary/
|
||||
│ └── index.vue (修改 - 添加详情对话框和表单增强)
|
||||
```
|
||||
|
||||
## API 接口层设计
|
||||
|
||||
### 新增接口函数
|
||||
|
||||
在 `ruoyi-ui/src/api/dpcIntermediary.js` 中添加:
|
||||
|
||||
```javascript
|
||||
// 下载个人中介导入模板
|
||||
export function importPersonTemplate() {
|
||||
return request({
|
||||
url: '/dpc/intermediary/importPersonTemplate',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 下载机构中介导入模板
|
||||
export function importEntityTemplate() {
|
||||
return request({
|
||||
url: '/dpc/intermediary/importEntityTemplate',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 导入个人中介黑名单
|
||||
export function importPersonData(data, updateSupport) {
|
||||
return request({
|
||||
url: '/dpc/intermediary/importPersonData?updateSupport=' + updateSupport,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 导入机构中介黑名单
|
||||
export function importEntityData(data, updateSupport) {
|
||||
return request({
|
||||
url: '/dpc/intermediary/importEntityData?updateSupport=' + updateSupport,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 视图层设计
|
||||
|
||||
### 列表页面修改
|
||||
|
||||
#### 1. 操作列添加"查看详情"按钮
|
||||
|
||||
```vue
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-view"
|
||||
@click="handleDetail(scope.row)"
|
||||
>详情</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['dpc:intermediary:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['dpc:intermediary:remove']"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
#### 2. 导入对话框改造
|
||||
|
||||
支持选择导入类型,并根据类型下载对应模板:
|
||||
|
||||
```vue
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog :title="upload.title" :visible.sync="upload.open" width="450px" append-to-body>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="导入类型" prop="importType">
|
||||
<el-radio-group v-model="upload.importType">
|
||||
<el-radio label="person">个人中介</el-radio>
|
||||
<el-radio label="entity">机构中介</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-upload
|
||||
ref="upload"
|
||||
:limit="1"
|
||||
accept=".xlsx, .xls"
|
||||
:headers="upload.headers"
|
||||
:action="upload.url"
|
||||
:disabled="upload.isUploading"
|
||||
:on-progress="handleFileUploadProgress"
|
||||
:on-success="handleFileSuccess"
|
||||
:auto-upload="false"
|
||||
drag
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<div class="el-upload__tip" slot="tip">
|
||||
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的数据
|
||||
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline;" @click="downloadImportTemplate">下载模板</el-link>
|
||||
</div>
|
||||
<div class="el-upload__tip" slot="tip">
|
||||
<span>仅允许导入"xls"或"xlsx"格式文件。</span>
|
||||
</div>
|
||||
</el-upload>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="upload.open = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
```
|
||||
|
||||
### 详情对话框设计
|
||||
|
||||
#### 对话框结构
|
||||
|
||||
```vue
|
||||
<!-- 详情对话框 -->
|
||||
<el-dialog title="中介黑名单详情" :visible.sync="detailOpen" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<!-- 核心字段 -->
|
||||
<el-descriptions-item label="中介ID">{{ detailData.intermediaryId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中介类型">{{ detailData.intermediaryTypeName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名/机构名称">{{ detailData.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件号/信用代码">{{ detailData.certificateNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag v-if="detailData.status === '0'" type="success">正常</el-tag>
|
||||
<el-tag v-else type="danger">停用</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="数据来源">{{ detailData.dataSourceName }}</el-descriptions-item>
|
||||
|
||||
<!-- 个人类型专属字段 -->
|
||||
<template v-if="detailData.intermediaryType === '1'">
|
||||
<el-descriptions-item label="人员类型">{{ detailData.indivType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="人员子类型">{{ detailData.indivSubType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ detailData.indivGenderName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件类型">{{ detailData.indivCertType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号码">{{ detailData.indivPhone || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="微信号">{{ detailData.indivWechat || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系地址" :span="2">{{ detailData.indivAddress || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在公司">{{ detailData.indivCompany || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="职位">{{ detailData.indivPosition || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="关联人员ID">{{ detailData.indivRelatedId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="关联关系">{{ detailData.indivRelation || '-' }}</el-descriptions-item>
|
||||
</template>
|
||||
|
||||
<!-- 机构类型专属字段 -->
|
||||
<template v-if="detailData.intermediaryType === '2'">
|
||||
<el-descriptions-item label="统一社会信用代码" :span="2">{{ detailData.corpCreditCode || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="主体类型">{{ detailData.corpType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="企业性质">{{ detailData.corpNature || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="行业分类">{{ detailData.corpIndustryCategory || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属行业">{{ detailData.corpIndustry || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="成立日期">{{ detailData.corpEstablishDate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="注册地址" :span="2">{{ detailData.corpAddress || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法定代表人">{{ detailData.corpLegalRep || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法定代表人证件类型">{{ detailData.corpLegalCertType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法定代表人证件号码" :span="2">{{ detailData.corpLegalCertNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东1">{{ detailData.corpShareholder1 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东2">{{ detailData.corpShareholder2 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东3">{{ detailData.corpShareholder3 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东4">{{ detailData.corpShareholder4 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东5">{{ detailData.corpShareholder5 || '-' }}</el-descriptions-item>
|
||||
</template>
|
||||
|
||||
<!-- 通用字段 -->
|
||||
<el-descriptions-item label="备注" :span="2">{{ detailData.remark || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detailData.createTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">{{ detailData.createBy }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="detailOpen = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
```
|
||||
|
||||
#### 数据属性
|
||||
|
||||
```javascript
|
||||
data() {
|
||||
return {
|
||||
// ... 现有数据 ...
|
||||
|
||||
// 详情对话框
|
||||
detailOpen: false,
|
||||
detailData: {},
|
||||
|
||||
// 导入参数
|
||||
upload: {
|
||||
open: false,
|
||||
title: "",
|
||||
isUploading: false,
|
||||
updateSupport: 0,
|
||||
importType: "person", // person 或 entity
|
||||
headers: { Authorization: "Bearer " + getToken() },
|
||||
url: "" // 动态设置
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 方法实现
|
||||
|
||||
```javascript
|
||||
methods: {
|
||||
/** 查看详情操作 */
|
||||
handleDetail(row) {
|
||||
const intermediaryId = row.intermediaryId;
|
||||
getIntermediary(intermediaryId).then(response => {
|
||||
this.detailData = response.data;
|
||||
this.detailOpen = true;
|
||||
});
|
||||
},
|
||||
|
||||
/** 导入按钮操作 */
|
||||
handleImport() {
|
||||
this.upload.title = "中介黑名单数据导入";
|
||||
this.upload.importType = "person"; // 默认个人
|
||||
this.upload.updateSupport = 0;
|
||||
this.upload.open = true;
|
||||
},
|
||||
|
||||
/** 下载导入模板 */
|
||||
downloadImportTemplate() {
|
||||
if (this.upload.importType === 'person') {
|
||||
this.download('dpc/intermediary/importPersonTemplate', {}, `个人中介黑名单模板_${new Date().getTime()}.xlsx`);
|
||||
} else {
|
||||
this.download('dpc/intermediary/importEntityTemplate', {}, `机构中介黑名单模板_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
},
|
||||
|
||||
// 文件上传中处理
|
||||
handleFileUploadProgress(event, file, fileList) {
|
||||
this.upload.isUploading = true;
|
||||
},
|
||||
|
||||
// 文件上传成功处理
|
||||
handleFileSuccess(response, file, fileList) {
|
||||
this.upload.isUploading = false;
|
||||
this.upload.open = false;
|
||||
this.getList();
|
||||
this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果");
|
||||
},
|
||||
|
||||
// 提交上传文件
|
||||
submitFileForm() {
|
||||
// 根据导入类型设置上传地址
|
||||
if (this.upload.importType === 'person') {
|
||||
this.upload.url = process.env.VUE_APP_BASE_API + "/dpc/intermediary/importPersonData?updateSupport=" + this.upload.updateSupport;
|
||||
} else {
|
||||
this.upload.url = process.env.VUE_APP_BASE_API + "/dpc/intermediary/importEntityData?updateSupport=" + this.upload.updateSupport;
|
||||
}
|
||||
this.$refs.upload.submit();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 新增/编辑对话框增强
|
||||
|
||||
#### 使用 Tabs 分组展示字段
|
||||
|
||||
```vue
|
||||
<!-- 添加或修改对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="900px" append-to-body>
|
||||
<el-tabs v-model="activeTab" type="border-card">
|
||||
<!-- 基本信息标签页 -->
|
||||
<el-tab-pane label="基本信息" name="basic">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名/机构名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入姓名/机构名称" maxlength="100"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="中介类型" prop="intermediaryType">
|
||||
<el-radio-group v-model="form.intermediaryType" @change="handleTypeChange">
|
||||
<el-radio label="1">个人</el-radio>
|
||||
<el-radio label="2">机构</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="证件号" prop="certificateNo">
|
||||
<el-input v-model="form.certificateNo" placeholder="请输入证件号" maxlength="50"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio label="0">正常</el-radio>
|
||||
<el-radio label="1">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" maxlength="500"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 个人详细信息标签页 -->
|
||||
<el-tab-pane label="个人信息" name="person" v-if="form.intermediaryType === '1'">
|
||||
<el-form ref="personForm" :model="form" label-width="120px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="人员类型">
|
||||
<el-input v-model="form.indivType" placeholder="请输入人员类型" maxlength="30"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="人员子类型">
|
||||
<el-input v-model="form.indivSubType" placeholder="请输入人员子类型" maxlength="50"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="性别">
|
||||
<el-select v-model="form.indivGender" placeholder="请选择性别" clearable>
|
||||
<el-option label="男" value="M" />
|
||||
<el-option label="女" value="F" />
|
||||
<el-option label="其他" value="O" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="证件类型">
|
||||
<el-input v-model="form.indivCertType" placeholder="请输入证件类型" maxlength="30"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号码">
|
||||
<el-input v-model="form.indivPhone" placeholder="请输入手机号码" maxlength="20"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="微信号">
|
||||
<el-input v-model="form.indivWechat" placeholder="请输入微信号" maxlength="50"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="联系地址">
|
||||
<el-input v-model="form.indivAddress" placeholder="请输入联系地址" maxlength="200"/>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所在公司">
|
||||
<el-input v-model="form.indivCompany" placeholder="请输入所在公司" maxlength="100"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="职位">
|
||||
<el-input v-model="form.indivPosition" placeholder="请输入职位" maxlength="100"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="关联人员ID">
|
||||
<el-input v-model="form.indivRelatedId" placeholder="请输入关联人员ID" maxlength="20"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="关联关系">
|
||||
<el-input v-model="form.indivRelation" placeholder="请输入关联关系" maxlength="50"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 机构详细信息标签页 -->
|
||||
<el-tab-pane label="机构信息" name="entity" v-if="form.intermediaryType === '2'">
|
||||
<el-form ref="entityForm" :model="form" label-width="140px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="统一社会信用代码">
|
||||
<el-input v-model="form.corpCreditCode" placeholder="请输入统一社会信用代码" maxlength="18"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="主体类型">
|
||||
<el-input v-model="form.corpType" placeholder="请输入主体类型" maxlength="50"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="企业性质">
|
||||
<el-input v-model="form.corpNature" placeholder="请输入企业性质" maxlength="50"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="行业分类">
|
||||
<el-input v-model="form.corpIndustryCategory" placeholder="请输入行业分类" maxlength="100"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所属行业">
|
||||
<el-input v-model="form.corpIndustry" placeholder="请输入所属行业" maxlength="100"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="成立日期">
|
||||
<el-date-picker
|
||||
v-model="form.corpEstablishDate"
|
||||
type="date"
|
||||
placeholder="选择成立日期"
|
||||
value-format="yyyy-MM-dd"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="注册地址">
|
||||
<el-input v-model="form.corpAddress" type="textarea" placeholder="请输入注册地址" maxlength="500"/>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="法定代表人">
|
||||
<el-input v-model="form.corpLegalRep" placeholder="请输入法定代表人" maxlength="50"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="法定代表人证件类型">
|
||||
<el-input v-model="form.corpLegalCertType" placeholder="请输入证件类型" maxlength="30"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="法定代表人证件号码">
|
||||
<el-input v-model="form.corpLegalCertNo" placeholder="请输入法定代表人证件号码" maxlength="30"/>
|
||||
</el-form-item>
|
||||
<el-divider content-position="left">股东信息</el-divider>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="股东1">
|
||||
<el-input v-model="form.corpShareholder1" placeholder="请输入股东1" maxlength="30"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="股东2">
|
||||
<el-input v-model="form.corpShareholder2" placeholder="请输入股东2" maxlength="30"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="股东3">
|
||||
<el-input v-model="form.corpShareholder3" placeholder="请输入股东3" maxlength="30"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="股东4">
|
||||
<el-input v-model="form.corpShareholder4" placeholder="请输入股东4" maxlength="30"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="股东5">
|
||||
<el-input v-model="form.corpShareholder5" placeholder="请输入股东5" maxlength="30"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
```
|
||||
|
||||
#### 数据属性和方法
|
||||
|
||||
```javascript
|
||||
data() {
|
||||
return {
|
||||
// ... 现有数据 ...
|
||||
activeTab: 'basic'
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
intermediaryId: null,
|
||||
name: null,
|
||||
certificateNo: null,
|
||||
intermediaryType: "1",
|
||||
status: "0",
|
||||
remark: null,
|
||||
// 个人字段
|
||||
indivType: null,
|
||||
indivSubType: null,
|
||||
indivGender: null,
|
||||
indivCertType: null,
|
||||
indivPhone: null,
|
||||
indivWechat: null,
|
||||
indivAddress: null,
|
||||
indivCompany: null,
|
||||
indivPosition: null,
|
||||
indivRelatedId: null,
|
||||
indivRelation: null,
|
||||
// 机构字段
|
||||
corpCreditCode: null,
|
||||
corpType: null,
|
||||
corpNature: null,
|
||||
corpIndustryCategory: null,
|
||||
corpIndustry: null,
|
||||
corpEstablishDate: null,
|
||||
corpAddress: null,
|
||||
corpLegalRep: null,
|
||||
corpLegalCertType: null,
|
||||
corpLegalCertNo: null,
|
||||
corpShareholder1: null,
|
||||
corpShareholder2: null,
|
||||
corpShareholder3: null,
|
||||
corpShareholder4: null,
|
||||
corpShareholder5: null
|
||||
};
|
||||
this.activeTab = 'basic';
|
||||
this.resetForm("form");
|
||||
},
|
||||
|
||||
// 中介类型切换处理
|
||||
handleTypeChange(value) {
|
||||
// 切换到对应的标签页
|
||||
if (value === '1') {
|
||||
this.activeTab = 'person';
|
||||
} else if (value === '2') {
|
||||
this.activeTab = 'entity';
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 数据流设计
|
||||
|
||||
### 详情数据流
|
||||
|
||||
```
|
||||
用户点击"详情"按钮
|
||||
↓
|
||||
调用 getIntermediary(id)
|
||||
↓
|
||||
后端根据 intermediaryType 返回不同的 VO
|
||||
↓
|
||||
前端接收响应并存储到 detailData
|
||||
↓
|
||||
详情对话框根据 detailData.intermediaryType 渲染不同字段
|
||||
```
|
||||
|
||||
### 导入数据流
|
||||
|
||||
```
|
||||
用户点击"导入"按钮
|
||||
↓
|
||||
打开导入对话框,默认选择"个人"类型
|
||||
↓
|
||||
用户选择导入类型(个人/机构)
|
||||
↓
|
||||
用户点击"下载模板"
|
||||
↓
|
||||
根据类型调用对应的模板下载接口
|
||||
↓
|
||||
用户填写 Excel 并上传
|
||||
↓
|
||||
根据类型设置上传地址
|
||||
↓
|
||||
调用对应的导入接口
|
||||
↓
|
||||
后端处理并返回结果
|
||||
```
|
||||
|
||||
### 表单提交数据流
|
||||
|
||||
```
|
||||
用户点击"新增"或"修改"
|
||||
↓
|
||||
打开表单对话框
|
||||
↓
|
||||
用户选择中介类型(自动切换到对应标签页)
|
||||
↓
|
||||
用户填写基本信息和详细字段
|
||||
↓
|
||||
用户点击"确定"
|
||||
↓
|
||||
前端合并所有字段(核心字段 + 类型专属字段)
|
||||
↓
|
||||
调用 addIntermediary 或 updateIntermediary
|
||||
↓
|
||||
后端根据 intermediaryType 保存对应字段
|
||||
```
|
||||
|
||||
## 字段映射表
|
||||
|
||||
### 个人字段映射
|
||||
|
||||
| 前端表单字段 | 后端字段 | 显示名称 |
|
||||
|------------|---------|---------|
|
||||
| indivType | indiv_type | 人员类型 |
|
||||
| indivSubType | indiv_sub_type | 人员子类型 |
|
||||
| indivGender | indiv_gender | 性别 |
|
||||
| indivCertType | indiv_cert_type | 证件类型 |
|
||||
| indivPhone | indiv_phone | 手机号码 |
|
||||
| indivWechat | indiv_wechat | 微信号 |
|
||||
| indivAddress | indiv_address | 联系地址 |
|
||||
| indivCompany | indiv_company | 所在公司 |
|
||||
| indivPosition | indiv_position | 职位 |
|
||||
| indivRelatedId | indiv_related_id | 关联人员ID |
|
||||
| indivRelation | indiv_relation | 关联关系 |
|
||||
|
||||
### 机构字段映射
|
||||
|
||||
| 前端表单字段 | 后端字段 | 显示名称 |
|
||||
|------------|---------|---------|
|
||||
| corpCreditCode | corp_credit_code | 统一社会信用代码 |
|
||||
| corpType | corp_type | 主体类型 |
|
||||
| corpNature | corp_nature | 企业性质 |
|
||||
| corpIndustryCategory | corp_industry_category | 行业分类 |
|
||||
| corpIndustry | corp_industry | 所属行业 |
|
||||
| corpEstablishDate | corp_establish_date | 成立日期 |
|
||||
| corpAddress | corp_address | 注册地址 |
|
||||
| corpLegalRep | corp_legal_rep | 法定代表人 |
|
||||
| corpLegalCertType | corp_legal_cert_type | 法定代表人证件类型 |
|
||||
| corpLegalCertNo | corp_legal_cert_no | 法定代表人证件号码 |
|
||||
| corpShareholder1 | corp_shareholder_1 | 股东1 |
|
||||
| corpShareholder2 | corp_shareholder_2 | 股东2 |
|
||||
| corpShareholder3 | corp_shareholder_3 | 股东3 |
|
||||
| corpShareholder4 | corp_shareholder_4 | 股东4 |
|
||||
| corpShareholder5 | corp_shareholder_5 | 股东5 |
|
||||
|
||||
## 用户体验设计
|
||||
|
||||
### 交互细节
|
||||
|
||||
1. **类型切换自动跳转**
|
||||
- 用户选择"个人"类型,自动跳转到"个人信息"标签页
|
||||
- 用户选择"机构"类型,自动跳转到"机构信息"标签页
|
||||
|
||||
2. **导入类型提示**
|
||||
- 导入对话框顶部显示当前选择的导入类型
|
||||
- 下载模板链接根据类型变化
|
||||
|
||||
3. **详情字段空值处理**
|
||||
- 字段值为空时显示 "-"
|
||||
- 避免显示空白或 undefined
|
||||
|
||||
4. **表单验证**
|
||||
- 基本信息标签页的字段保持原有验证规则
|
||||
- 详细信息字段暂时设为可选,减少录入压力
|
||||
|
||||
### 样式优化
|
||||
|
||||
- 使用 `el-tabs` 组织大量字段,提高可读性
|
||||
- 使用 `el-row` 和 `el-col` 实现响应式布局
|
||||
- 详情对话框使用 `el-descriptions` 组件,展示更美观
|
||||
- 对话框宽度适当增加,容纳更多字段
|
||||
|
||||
## 技术约束
|
||||
|
||||
1. **前端框架**:Vue 2.6.12
|
||||
2. **UI 组件库**:Element UI 2.15.14
|
||||
3. **HTTP 客户端**:Axios 0.28.1
|
||||
4. **向后兼容**:保持现有功能不变,只做增强
|
||||
|
||||
## 测试要点
|
||||
|
||||
### 功能测试
|
||||
- [ ] 详情对话框正确显示个人类型字段
|
||||
- [ ] 详情对话框正确显示机构类型字段
|
||||
- [ ] 新增/编辑对话框正确切换标签页
|
||||
- [ ] 个人中介模板下载正常
|
||||
- [ ] 机构中介模板下载正常
|
||||
- [ ] 个人中介数据导入正常
|
||||
- [ ] 机构中介数据导入正常
|
||||
|
||||
### 兼容性测试
|
||||
- [ ] 旧数据详情显示正常(字段为空时显示"-")
|
||||
- [ ] 现有列表查询功能正常
|
||||
- [ ] 现有新增/编辑功能正常
|
||||
- [ ] 现有删除功能正常
|
||||
- [ ] 现有导出功能正常
|
||||
@@ -0,0 +1,159 @@
|
||||
# Proposal: 同步前端以支持中介黑名单详细字段和类型化模板导入
|
||||
|
||||
## Change ID
|
||||
`sync-intermediary-frontend-with-detailed-fields`
|
||||
|
||||
## Summary
|
||||
同步前端代码,使其与后端 API 文档中定义的中介黑名单增强功能保持一致,支持个人和机构类型的详细字段展示,以及类型化的模板下载和导入功能。
|
||||
|
||||
## Motivation
|
||||
|
||||
### 问题背景
|
||||
后端已完成中介黑名单的增强(变更 `enhance-intermediary-with-detailed-fields`),新增了:
|
||||
1. 个人类型的详细字段(人员类型、性别、证件类型、手机号码、微信号等)
|
||||
2. 机构类型的详细字段(统一社会信用代码、主体类型、企业性质、法定代表人、股东等)
|
||||
3. 分离的个人和机构导入模板下载接口
|
||||
4. 分离的个人和机构导入接口
|
||||
|
||||
### 现状
|
||||
当前前端代码 [ruoyi-ui/src/views/dpcIntermediary/index.vue](../../../../ruoyi-ui/src/views/dpcIntermediary/index.vue) 和 [ruoyi-ui/src/api/dpcIntermediary.js](../../../../ruoyi-ui/src/api/dpcIntermediary.js) 仅支持基础字段:
|
||||
- 只显示核心字段(姓名/机构名称、证件号、中介类型、状态、备注)
|
||||
- 只有一个通用的导入模板下载接口
|
||||
- 只有一个通用的导入接口
|
||||
- 详情对话框不支持根据类型显示不同字段
|
||||
|
||||
### 业务需求
|
||||
1. 列表页面保持简洁,显示核心字段
|
||||
2. 详情对话框需要根据中介类型显示不同的详细字段
|
||||
3. 导入功能需要支持分别下载和导入个人/机构模板
|
||||
4. 新增/编辑对话框需要支持详细字段的录入
|
||||
|
||||
## Scope
|
||||
|
||||
### 包含的功能
|
||||
|
||||
#### 2.1 API 接口层修改
|
||||
- 添加个人中介模板下载接口调用
|
||||
- 添加机构中介模板下载接口调用
|
||||
- 添加个人中介数据导入接口调用
|
||||
- 添加机构中介数据导入接口调用
|
||||
- 保留原有通用接口以保持向后兼容
|
||||
|
||||
#### 2.2 列表页面修改
|
||||
- 保持列表显示简洁(核心字段)
|
||||
- 添加"查看详情"按钮
|
||||
- 导入功能改为支持类型选择(个人/机构)
|
||||
|
||||
#### 2.3 详情对话框新增
|
||||
- 根据中介类型动态显示不同的字段
|
||||
- 个人类型显示:人员类型、人员子类型、性别、证件类型、手机号码、微信号等
|
||||
- 机构类型显示:统一社会信用代码、主体类型、企业性质、法定代表人、股东等
|
||||
- 支持只读模式,不提供编辑功能
|
||||
|
||||
#### 2.4 新增/编辑对话框增强
|
||||
- 根据中介类型显示对应的字段组
|
||||
- 个人类型显示个人相关字段
|
||||
- 机构类型显示机构相关字段
|
||||
- 使用表单验证确保数据完整性
|
||||
|
||||
### 明确排除
|
||||
- 后端代码修改(已在 `enhance-intermediary-with-detailed-fields` 中完成)
|
||||
- 数据库结构修改(已在 `enhance-intermediary-with-detailed-fields` 中完成)
|
||||
- 路由和菜单配置修改(保持现有配置)
|
||||
|
||||
## Proposed Design
|
||||
详见 [design.md](./design.md)
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### 选项1:在列表中展开显示所有字段
|
||||
**优点**:
|
||||
- 用户无需点击详情即可看到完整信息
|
||||
|
||||
**缺点**:
|
||||
- 列表过于拥挤,不利于快速浏览
|
||||
- 个人和机构字段混杂,难以阅读
|
||||
|
||||
**决定**:不采用。保持列表简洁,详情通过对话框展示。
|
||||
|
||||
### 选项2:使用两个独立的页面(个人列表和机构列表)
|
||||
**优点**:
|
||||
- 页面结构清晰,各自独立
|
||||
|
||||
**缺点**:
|
||||
- 需要添加新的路由和菜单配置
|
||||
- 代码重复度高
|
||||
- 不符合单一数据源的管理模式
|
||||
|
||||
**决定**:不采用。使用同一个列表页面,通过中介类型区分。
|
||||
|
||||
### 选项3:在现有对话框中内嵌详情视图
|
||||
**优点**:
|
||||
- 减少对话框数量
|
||||
|
||||
**缺点**:
|
||||
- 编辑和查看模式混合,逻辑复杂
|
||||
- 表单验证逻辑难以处理
|
||||
|
||||
**决定**:不采用。分离详情对话框和编辑对话框,职责更清晰。
|
||||
|
||||
## Impact
|
||||
|
||||
### 前端影响
|
||||
|
||||
#### API 层
|
||||
- 新增 4 个接口调用函数(模板下载 × 2,数据导入 × 2)
|
||||
- 修改导入对话框以支持类型选择
|
||||
|
||||
#### 视图层
|
||||
- 新增详情对话框组件
|
||||
- 修改列表操作列(添加"查看详情"按钮)
|
||||
- 修改新增/编辑对话框(添加详细字段)
|
||||
- 修改导入对话框(支持类型选择和对应的模板下载)
|
||||
|
||||
#### 数据流
|
||||
- 详情接口返回的数据结构根据类型不同
|
||||
- 表单提交需要包含对应类型的字段
|
||||
|
||||
### 用户体验影响
|
||||
- **正面**:可以看到更完整的中介信息
|
||||
- **正面**:导入模板更符合实际需求,减少录入错误
|
||||
- **中性**:导入时需要先选择类型,增加一步操作
|
||||
|
||||
## Dependencies
|
||||
- 依赖后端变更 `enhance-intermediary-with-detailed-fields` 必须先完成
|
||||
- 依赖后端 API 接口按 [doc/中介黑名单管理API文档.md](../../../../doc/中介黑名单管理API文档.md) 定义实现
|
||||
|
||||
## Related Changes
|
||||
- `enhance-intermediary-with-detailed-fields` - 后端增强功能
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **详情对话框是否支持编辑?**
|
||||
- 建议:不支持编辑,只做展示。编辑使用现有的编辑对话框。
|
||||
|
||||
2. **新增/编辑对话框如何处理大量字段?**
|
||||
- 建议:使用 el-tabs 或 el-row/el-col 分组展示,提高可读性。
|
||||
|
||||
3. **导入时是否需要类型验证?**
|
||||
- 建议:在后端验证,前端只负责选择正确的模板和接口。
|
||||
|
||||
4. **旧数据的详情如何处理?**
|
||||
- 建议:旧数据没有详细字段,详情对话框显示空值或占位符(如"未填写")。
|
||||
|
||||
## Success Criteria
|
||||
- [ ] API 接口层添加新的接口调用函数
|
||||
- [ ] 列表页面添加"查看详情"按钮
|
||||
- [ ] 详情对话框根据类型正确显示不同字段
|
||||
- [ ] 新增/编辑对话框支持详细字段录入
|
||||
- [ ] 导入功能支持个人/机构类型选择
|
||||
- [ ] 可以下载个人中介专用模板
|
||||
- [ ] 可以下载机构中介专用模板
|
||||
- [ ] 可以使用个人模板导入数据
|
||||
- [ ] 可以使用机构模板导入数据
|
||||
- [ ] 现有功能保持正常运行
|
||||
|
||||
## References
|
||||
- [doc/中介黑名单管理API文档.md](../../../../doc/中介黑名单管理API文档.md)
|
||||
- [openspec/changes/enhance-intermediary-with-detailed-fields/proposal.md](../enhance-intermediary-with-detailed-fields/proposal.md)
|
||||
- [openspec/changes/enhance-intermediary-with-detailed-fields/design.md](../enhance-intermediary-with-detailed-fields/design.md)
|
||||
@@ -0,0 +1,67 @@
|
||||
# Spec: 前端 API 层扩展
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 添加个人中介模板下载接口调用
|
||||
|
||||
前端 SHALL 提供调用个人中介模板下载接口的函数。
|
||||
|
||||
#### Scenario: 用户下载个人中介导入模板
|
||||
|
||||
**Given** 用户在中介黑名单列表页面
|
||||
**And** 用户点击"导入"按钮
|
||||
**And** 用户选择"个人中介"类型
|
||||
**When** 用户点击"下载模板"链接
|
||||
**Then** 系统调用 `/dpc/intermediary/importPersonTemplate` 接口
|
||||
**And** 系统下载个人中介黑名单模板 Excel 文件
|
||||
**And** 文件名格式为 `个人中介黑名单模板_{timestamp}.xlsx`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 添加机构中介模板下载接口调用
|
||||
|
||||
前端 SHALL 提供调用机构中介模板下载接口的函数。
|
||||
|
||||
#### Scenario: 用户下载机构中介导入模板
|
||||
|
||||
**Given** 用户在中介黑名单列表页面
|
||||
**And** 用户点击"导入"按钮
|
||||
**And** 用户选择"机构中介"类型
|
||||
**When** 用户点击"下载模板"链接
|
||||
**Then** 系统调用 `/dpc/intermediary/importEntityTemplate` 接口
|
||||
**And** 系统下载机构中介黑名单模板 Excel 文件
|
||||
**And** 文件名格式为 `机构中介黑名单模板_{timestamp}.xlsx`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 添加个人中介数据导入接口调用
|
||||
|
||||
前端 SHALL 提供调用个人中介数据导入接口的函数。
|
||||
|
||||
#### Scenario: 用户导入个人中介数据
|
||||
|
||||
**Given** 用户在中介黑名单列表页面
|
||||
**And** 用户点击"导入"按钮
|
||||
**And** 用户选择"个人中介"类型
|
||||
**And** 用户已下载并填写了个人中介模板
|
||||
**When** 用户上传 Excel 文件
|
||||
**Then** 系统调用 `/dpc/intermediary/importPersonData` 接口
|
||||
**And** 系统传递 `updateSupport` 参数
|
||||
**And** 系统显示导入结果
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 添加机构中介数据导入接口调用
|
||||
|
||||
前端 SHALL 提供调用机构中介数据导入接口的函数。
|
||||
|
||||
#### Scenario: 用户导入机构中介数据
|
||||
|
||||
**Given** 用户在中介黑名单列表页面
|
||||
**And** 用户点击"导入"按钮
|
||||
**And** 用户选择"机构中介"类型
|
||||
**And** 用户已下载并填写了机构中介模板
|
||||
**When** 用户上传 Excel 文件
|
||||
**Then** 系统调用 `/dpc/intermediary/importEntityData` 接口
|
||||
**And** 系统传递 `updateSupport` 参数
|
||||
**And** 系统显示导入结果
|
||||
@@ -0,0 +1,58 @@
|
||||
# Spec: 前端详情视图
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 添加中介详情查看功能
|
||||
|
||||
前端 SHALL 提供查看中介详细信息的对话框,根据中介类型显示不同的字段。
|
||||
|
||||
#### Scenario: 查看个人类型中介详情
|
||||
|
||||
**Given** 用户在中介黑名单列表页面
|
||||
**And** 列表中存在一条个人类型的中介记录
|
||||
**When** 用户点击该记录的"详情"按钮
|
||||
**Then** 系统打开详情对话框
|
||||
**And** 标题为"中介黑名单详情"
|
||||
**And** 显示核心字段(中介ID、中介类型、姓名、证件号、状态、数据来源)
|
||||
**And** 显示个人专属字段(人员类型、人员子类型、性别、证件类型、手机号码、微信号、联系地址、所在公司、职位、关联人员ID、关联关系)
|
||||
**And** 不显示机构专属字段
|
||||
**And** 空值字段显示"-"
|
||||
**And** 对话框只有"关闭"按钮
|
||||
|
||||
#### Scenario: 查看机构类型中介详情
|
||||
|
||||
**Given** 用户在中介黑名单列表页面
|
||||
**And** 列表中存在一条机构类型的中介记录
|
||||
**When** 用户点击该记录的"详情"按钮
|
||||
**Then** 系统打开详情对话框
|
||||
**And** 标题为"中介黑名单详情"
|
||||
**And** 显示核心字段(中介ID、中介类型、机构名称、统一社会信用代码、状态、数据来源)
|
||||
**And** 显示机构专属字段(主体类型、企业性质、行业分类、所属行业、成立日期、注册地址、法定代表人、法定代表人证件类型、法定代表人证件号码、股东1-5)
|
||||
**And** 不显示个人专属字段
|
||||
**And** 空值字段显示"-"
|
||||
**And** 对话框只有"关闭"按钮
|
||||
|
||||
#### Scenario: 查看旧数据详情(无详细字段)
|
||||
|
||||
**Given** 用户在中介黑名单列表页面
|
||||
**And** 列表中存在一条旧数据记录(详细字段为空)
|
||||
**When** 用户点击该记录的"详情"按钮
|
||||
**Then** 系统打开详情对话框
|
||||
**And** 核心字段正常显示
|
||||
**And** 详细字段显示"-"
|
||||
**And** 对话框正常关闭
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 列表操作列添加详情按钮
|
||||
|
||||
列表 SHALL 在操作列中添加"详情"按钮。
|
||||
|
||||
#### Scenario: 操作列显示详情按钮
|
||||
|
||||
**Given** 用户在中介黑名单列表页面
|
||||
**When** 列表加载完成
|
||||
**Then** 操作列显示"详情"按钮
|
||||
**And** "详情"按钮不需要权限控制
|
||||
**And** "详情"按钮图标为 "el-icon-view"
|
||||
**And** 操作列宽度调整为 240px(原 180px)
|
||||
@@ -0,0 +1,139 @@
|
||||
# Spec: 前端表单增强
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 新增/编辑对话框支持详细字段录入
|
||||
|
||||
新增和编辑对话框 SHALL 扩展以支持个人和机构类型的详细字段录入。
|
||||
|
||||
#### Scenario: 新增个人类型中介
|
||||
|
||||
**Given** 用户点击"新增"按钮
|
||||
**And** 对话框打开,默认选择"个人"类型
|
||||
**When** 用户查看对话框
|
||||
**Then** 显示"基本信息"、"个人信息"两个标签页
|
||||
**And** 自动切换到"个人信息"标签页
|
||||
**When** 用户填写个人信息字段
|
||||
**And** 用户点击"确定"按钮
|
||||
**Then** 系统提交核心字段和个人字段到后端
|
||||
**And** 显示"新增成功"提示
|
||||
**And** 列表刷新
|
||||
|
||||
#### Scenario: 新增机构类型中介
|
||||
|
||||
**Given** 用户点击"新增"按钮
|
||||
**And** 用户将中介类型切换为"机构"
|
||||
**When** 用户查看对话框
|
||||
**Then** 显示"基本信息"、"机构信息"两个标签页
|
||||
**And** 自动切换到"机构信息"标签页
|
||||
**When** 用户填写机构信息字段
|
||||
**And** 用户点击"确定"按钮
|
||||
**Then** 系统提交核心字段和机构字段到后端
|
||||
**And** 显示"新增成功"提示
|
||||
**And** 列表刷新
|
||||
|
||||
#### Scenario: 编辑个人类型中介
|
||||
|
||||
**Given** 用户点击一条个人类型记录的"修改"按钮
|
||||
**When** 对话框打开
|
||||
**Then** 系统加载该记录的详细信息
|
||||
**And** "个人信息"标签页显示已保存的个人字段值
|
||||
**When** 用户修改个人信息字段
|
||||
**And** 用户点击"确定"按钮
|
||||
**Then** 系统提交修改后的数据到后端
|
||||
**And** 显示"修改成功"提示
|
||||
**And** 列表刷新
|
||||
|
||||
#### Scenario: 编辑机构类型中介
|
||||
|
||||
**Given** 用户点击一条机构类型记录的"修改"按钮
|
||||
**When** 对话框打开
|
||||
**Then** 系统加载该记录的详细信息
|
||||
**And** "机构信息"标签页显示已保存的机构字段值
|
||||
**When** 用户修改机构信息字段
|
||||
**And** 用户点击"确定"按钮
|
||||
**Then** 系统提交修改后的数据到后端
|
||||
**And** 显示"修改成功"提示
|
||||
**And** 列表刷新
|
||||
|
||||
#### Scenario: 切换中介类型时自动跳转标签页
|
||||
|
||||
**Given** 用户在新增或编辑对话框中
|
||||
**And** 当前选择的是"个人"类型
|
||||
**When** 用户将中介类型切换为"机构"
|
||||
**Then** 对话框自动切换到"机构信息"标签页
|
||||
**And** 个人字段保持不变(隐藏但保留值)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入对话框支持类型选择
|
||||
|
||||
导入对话框 SHALL 支持选择导入类型(个人/机构),并根据类型下载对应模板。
|
||||
|
||||
#### Scenario: 选择个人类型导入
|
||||
|
||||
**Given** 用户点击"导入"按钮
|
||||
**When** 对话框打开
|
||||
**Then** 默认选择"个人中介"类型
|
||||
**And** 显示"是否更新已经存在的数据"复选框
|
||||
**And** 显示"下载模板"链接
|
||||
**When** 用户保持"个人中介"类型
|
||||
**And** 用户点击"下载模板"链接
|
||||
**Then** 系统下载个人中介模板
|
||||
|
||||
#### Scenario: 选择机构类型导入
|
||||
|
||||
**Given** 用户点击"导入"按钮
|
||||
**When** 对话框打开
|
||||
**And** 用户选择"机构中介"类型
|
||||
**And** 用户点击"下载模板"链接
|
||||
**Then** 系统下载机构中介模板
|
||||
|
||||
#### Scenario: 上传个人中介文件
|
||||
|
||||
**Given** 用户在导入对话框中
|
||||
**And** 用户选择"个人中介"类型
|
||||
**And** 用户勾选"是否更新已经存在的数据"
|
||||
**When** 用户上传 Excel 文件
|
||||
**And** 用户点击"确定"按钮
|
||||
**Then** 系统调用 `/dpc/intermediary/importPersonData` 接口
|
||||
**And** 系统传递 `updateSupport=true` 参数
|
||||
**And** 显示导入结果
|
||||
|
||||
#### Scenario: 上传机构中介文件
|
||||
|
||||
**Given** 用户在导入对话框中
|
||||
**And** 用户选择"机构中介"类型
|
||||
**And** 用户不勾选"是否更新已经存在的数据"
|
||||
**When** 用户上传 Excel 文件
|
||||
**And** 用户点击"确定"按钮
|
||||
**Then** 系统调用 `/dpc/intermediary/importEntityData` 接口
|
||||
**And** 系统传递 `updateSupport=false` 参数
|
||||
**And** 显示导入结果
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 表单字段布局优化
|
||||
|
||||
表单 SHALL 使用标签页和响应式布局优化大量字段的展示。
|
||||
|
||||
#### Scenario: 个人信息标签页布局
|
||||
|
||||
**Given** 用户在新增或编辑对话框中
|
||||
**And** 中介类型为"个人"
|
||||
**When** 用户查看"个人信息"标签页
|
||||
**Then** 字段使用两列布局(el-col :span="12")
|
||||
**And** 长文本字段(如联系地址)使用整行布局
|
||||
**And** 字段标签宽度为 120px
|
||||
**And** 输入框有合适的 maxlength 限制
|
||||
|
||||
#### Scenario: 机构信息标签页布局
|
||||
|
||||
**Given** 用户在新增或编辑对话框中
|
||||
**And** 中介类型为"机构"
|
||||
**When** 用户查看"机构信息"标签页
|
||||
**Then** 字段使用两列布局
|
||||
**And** 长文本字段(如注册地址)使用整行布局
|
||||
**And** 股东信息区域使用分隔线(el-divider)分隔
|
||||
**And** 字段标签宽度为 140px( accommodate 更长的标签名称)
|
||||
**And** 输入框有合适的 maxlength 限制
|
||||
@@ -0,0 +1,139 @@
|
||||
# Tasks: 同步前端以支持中介黑名单详细字段和类型化模板导入
|
||||
|
||||
## 任务列表
|
||||
|
||||
### 阶段 1:API 接口层扩展
|
||||
|
||||
- [x] 1.1 在 `ruoyi-ui/src/api/dpcIntermediary.js` 中添加 `importPersonTemplate` 函数
|
||||
- 调用 `POST /dpc/intermediary/importPersonTemplate`
|
||||
- 返回个人中介模板文件
|
||||
|
||||
- [x] 1.2 在 `ruoyi-ui/src/api/dpcIntermediary.js` 中添加 `importEntityTemplate` 函数
|
||||
- 调用 `POST /dpc/intermediary/importEntityTemplate`
|
||||
- 返回机构中介模板文件
|
||||
|
||||
- [x] 1.3 在 `ruoyi-ui/src/api/dpcIntermediary.js` 中添加 `importPersonData` 函数
|
||||
- 调用 `POST /dpc/intermediary/importPersonData`
|
||||
- 支持传递 `updateSupport` 参数
|
||||
|
||||
- [x] 1.4 在 `ruoyi-ui/src/api/dpcIntermediary.js` 中添加 `importEntityData` 函数
|
||||
- 调用 `POST /dpc/intermediary/importEntityData`
|
||||
- 支持传递 `updateSupport` 参数
|
||||
|
||||
### 阶段 2:详情对话框实现
|
||||
|
||||
- [x] 2.1 在 `ruoyi-ui/src/views/dpcIntermediary/index.vue` 的 data 中添加详情相关属性
|
||||
- 添加 `detailOpen: false`
|
||||
- 添加 `detailData: {}`
|
||||
|
||||
- [x] 2.2 在 `ruoyi-ui/src/views/dpcIntermediary/index.vue` 的 template 中添加详情对话框
|
||||
- 使用 `el-descriptions` 组件展示字段
|
||||
- 根据中介类型条件渲染个人/机构专属字段
|
||||
|
||||
- [x] 2.3 实现 `handleDetail` 方法
|
||||
- 调用 `getIntermediary` 接口获取详情
|
||||
- 将数据存储到 `detailData`
|
||||
- 打开详情对话框
|
||||
|
||||
- [x] 2.4 在列表操作列添加"详情"按钮
|
||||
- 使用 `el-icon-view` 图标
|
||||
- 绑定 `handleDetail` 方法
|
||||
- 调整操作列宽度为 240px
|
||||
|
||||
### 阶段 3:新增/编辑对话框增强
|
||||
|
||||
- [x] 3.1 在 `ruoyi-ui/src/views/dpcIntermediary/index.vue` 的 data 中添加 `activeTab` 属性
|
||||
- 默认值为 `'basic'`
|
||||
|
||||
- [x] 3.2 在表单中添加 `el-tabs` 组件
|
||||
- 添加"基本信息"标签页(name="basic")
|
||||
- 添加"个人信息"标签页(name="person")
|
||||
- 添加"机构信息"标签页(name="entity")
|
||||
|
||||
- [x] 3.3 实现"个人信息"标签页内容
|
||||
- 添加人员类型、人员子类型、性别、证件类型字段
|
||||
- 添加手机号码、微信号、联系地址字段
|
||||
- 添加所在公司、职位、关联人员ID、关联关系字段
|
||||
- 使用两列响应式布局
|
||||
|
||||
- [x] 3.4 实现"机构信息"标签页内容
|
||||
- 添加统一社会信用代码、主体类型、企业性质字段
|
||||
- 添加行业分类、所属行业、成立日期、注册地址字段
|
||||
- 添加法定代表人及其证件信息字段
|
||||
- 添加股东1-5字段,使用分隔线分组
|
||||
|
||||
- [x] 3.5 更新 `reset` 方法
|
||||
- 添加所有个人字段的初始化
|
||||
- 添加所有机构字段的初始化
|
||||
- 重置 `activeTab` 为 `'basic'`
|
||||
|
||||
- [x] 3.6 实现 `handleTypeChange` 方法
|
||||
- 监听中介类型变化
|
||||
- 自动切换到对应的标签页
|
||||
|
||||
- [x] 3.7 调整对话框宽度
|
||||
- 将宽度从 600px 调整为 900px
|
||||
- 适应更多字段的展示
|
||||
|
||||
### 阶段 4:导入对话框改造
|
||||
|
||||
- [x] 4.1 在导入对话框中添加类型选择单选框组
|
||||
- 选项:个人中介(person)、机构中介(entity)
|
||||
- 默认选择"个人中介"
|
||||
|
||||
- [x] 4.2 在 `upload` 数据中添加 `importType` 属性
|
||||
- 默认值为 `'person'`
|
||||
|
||||
- [x] 4.3 修改 `downloadImportTemplate` 方法
|
||||
- 根据 `upload.importType` 调用对应的模板下载接口
|
||||
- 个人类型调用 `importPersonTemplate`
|
||||
- 机构类型调用 `importEntityTemplate`
|
||||
|
||||
- [x] 4.4 修改 `submitFileForm` 方法
|
||||
- 根据 `upload.importType` 动态设置 `upload.url`
|
||||
- 个人类型使用 `/dpc/intermediary/importPersonData`
|
||||
- 机构类型使用 `/dpc/intermediary/importEntityData`
|
||||
- 添加 `updateSupport` 参数到 URL
|
||||
|
||||
- [x] 4.5 调整导入对话框宽度
|
||||
- 从 400px 调整为 450px
|
||||
- 适应新增的类型选择区域
|
||||
|
||||
### 阶段 5:测试验证
|
||||
|
||||
- [x] 5.1 测试详情对话框功能
|
||||
- 测试个人类型详情显示
|
||||
- 测试机构类型详情显示
|
||||
- 测试旧数据详情显示(字段为空)
|
||||
|
||||
- [x] 5.2 测试新增/编辑功能
|
||||
- 测试新增个人类型中介
|
||||
- 测试新增机构类型中介
|
||||
- 测试编辑个人类型中介
|
||||
- 测试编辑机构类型中介
|
||||
- 测试类型切换时标签页自动跳转
|
||||
|
||||
- [x] 5.3 测试导入功能
|
||||
- 测试个人类型模板下载
|
||||
- 测试机构类型模板下载
|
||||
- 测试个人类型数据导入
|
||||
- 测试机构类型数据导入
|
||||
- 测试更新支持选项
|
||||
|
||||
- [x] 5.4 测试兼容性
|
||||
- 测试现有列表查询功能
|
||||
- 测试现有删除功能
|
||||
- 测试现有导出功能
|
||||
|
||||
## 依赖关系
|
||||
|
||||
- 阶段 1 必须首先完成,为后续阶段提供 API 接口
|
||||
- 阶段 2、3、4 可以并行开发
|
||||
- 阶段 5 必须在所有开发阶段完成后进行
|
||||
|
||||
## 验收标准
|
||||
|
||||
每个任务完成后应满足:
|
||||
- 代码符合 Vue 2.x 和 Element UI 规范
|
||||
- 功能与设计文档描述一致
|
||||
- 不影响现有功能的正常运行
|
||||
Reference in New Issue
Block a user