docs: 添加员工信息导入结果弹窗自适应优化设计文档

- 分析现有问题:弹窗内容过多时超出视口
- 设计固定高度+内容可滚动的Flexbox布局方案
- 提供完整的CSS样式和响应式设计
- 包含实施计划、验收标准和技术要点

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
wkc
2026-02-05 16:09:40 +08:00
parent bed3ab5ed8
commit 1e691f9697
23 changed files with 2890 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

View File

@@ -0,0 +1,347 @@
# 员工招聘信息管理功能设计文档
**文档版本:** 1.0
**创建日期:** 2025-02-05
**模块名称:** ccdi-staff-recruitment
**作者:** Claude
---
## 1. 概述
### 1.1 功能简介
员工招聘信息管理模块提供招聘信息的记录、查询、导入导出等基础维护功能,支持单条和批量操作。
### 1.2 业务场景
- 简单的招聘信息记录,作为数据存档使用
- 支持招聘信息的增删改查操作
- 支持Excel批量导入和导出
### 1.3 技术选型
- **后端框架:** Spring Boot 3.5.8 + MyBatis Plus 3.5.10
- **数据库:** MySQL 8.2.0
- **前端框架:** Vue 2.6.12 + Element UI 2.15.14
- **数据校验:** javax.validation + 自定义校验注解
---
## 2. 数据库设计
### 2.1 表结构
**表名:** `ccdi_staff_recruitment`
```sql
CREATE TABLE `ccdi_staff_recruitment` (
`recruit_id` varchar(32) NOT NULL COMMENT '招聘项目编号',
`recruit_name` varchar(100) NOT NULL COMMENT '招聘项目名称',
`pos_name` varchar(100) NOT NULL COMMENT '职位名称',
`pos_category` varchar(50) NOT NULL COMMENT '职位类别',
`pos_desc` text NOT NULL COMMENT '职位描述',
`cand_name` varchar(20) NOT NULL COMMENT '应聘人员姓名',
`cand_edu` varchar(20) NOT NULL COMMENT '应聘人员学历',
`cand_id` varchar(18) NOT NULL COMMENT '应聘人员证件号码',
`cand_school` varchar(50) NOT NULL COMMENT '应聘人员毕业院校',
`cand_major` varchar(30) NOT NULL COMMENT '应聘人员专业',
`cand_grad` varchar(6) NOT NULL COMMENT '应聘人员毕业年月',
`admit_status` varchar(10) NOT NULL COMMENT '录用情况:录用、未录用、放弃',
`interviewer_name1` varchar(20) DEFAULT NULL COMMENT '面试官1姓名',
`interviewer_id1` varchar(10) DEFAULT NULL COMMENT '面试官1工号',
`interviewer_name2` varchar(20) DEFAULT NULL COMMENT '面试官2姓名',
`interviewer_id2` varchar(10) DEFAULT NULL COMMENT '面试官2工号',
`created_by` varchar(20) NOT NULL COMMENT '记录创建人',
`updated_by` varchar(20) DEFAULT NULL COMMENT '记录更新人',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`recruit_id`),
KEY `idx_cand_id` (`cand_id`),
KEY `idx_admit_status` (`admit_status`),
KEY `idx_interviewer_id1` (`interviewer_id1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工招聘信息表';
```
### 2.2 索引设计
- **主键索引:** `recruit_id`
- **业务索引:** `cand_id`, `admit_status`, `interviewer_id1`
### 2.3 枚举值设计
**录用状态 (admit_status):**
| 枚举值 | 说明 |
|--------|------|
| 录用 | 已录用该候选人 |
| 未录用 | 未录用该候选人 |
| 放弃 | 候选人放弃 |
---
## 3. 后端设计
### 3.1 模块结构
```
ruoyi-ccdi/
├── domain/
│ ├── CcdiStaffRecruitment.java # 实体类
│ ├── dto/
│ │ ├── CcdiStaffRecruitmentQueryDTO.java # 查询DTO
│ │ ├── CcdiStaffRecruitmentAddDTO.java # 新增DTO
│ │ └── CcdiStaffRecruitmentEditDTO.java # 修改DTO
│ ├── vo/
│ │ └── CcdiStaffRecruitmentVO.java # 返回VO
│ └── excel/
│ └── CcdiStaffRecruitmentExcel.java # Excel导入导出类
├── mapper/
│ ├── CcdiStaffRecruitmentMapper.java # MyBatis Mapper接口
│ └── xml/
│ └── CcdiStaffRecruitmentMapper.xml # MyBatis XML映射
├── service/
│ ├── ICcdiStaffRecruitmentService.java # 服务接口
│ └── impl/
│ └── CcdiStaffRecruitmentServiceImpl.java # 服务实现
└── controller/
└── CcdiStaffRecruitmentController.java # 控制器
```
### 3.2 API接口设计
**基础路径:** `/ccdi/staffRecruitment`
| 接口功能 | HTTP方法 | 路径 | 权限标识 |
|---------|---------|------|---------|
| 分页查询 | GET | `/list` | ccdi:staffRecruitment:list |
| 详情查询 | GET | `/{recruitId}` | ccdi:staffRecruitment:query |
| 新增 | POST | `/` | ccdi:staffRecruitment:add |
| 修改 | PUT | `/` | ccdi:staffRecruitment:edit |
| 删除 | DELETE | `/{recruitIds}` | ccdi:staffRecruitment:remove |
| 导入模板下载 | GET | `/importTemplate` | ccdi:staffRecruitment:import |
| 批量导入 | POST | `/importData` | ccdi:staffRecruitment:import |
| 导出 | POST | `/export` | ccdi:staffRecruitment:export |
### 3.3 查询参数设计
**CcdiStaffRecruitmentQueryDTO:**
```java
// 查询条件
private String recruitName; // 招聘项目名称(模糊查询)
private String posName; // 职位名称(模糊查询)
private String candName; // 候选人姓名(模糊查询)
private String candId; // 证件号码(精确查询)
private String admitStatus; // 录用状态(精确查询)
private String interviewerName; // 面试官姓名(模糊查询,查询面试官1或2)
private String interviewerId; // 面试官工号(精确查询,查询面试官1或2)
// 分页参数
private Integer pageNum = 1;
private Integer pageSize = 10;
```
### 3.4 数据校验规则
| 字段 | 校验规则 | 错误提示 |
|-----|---------|---------|
| recruitName | @NotBlank, @Size(max=100) | 招聘项目名称不能为空/长度不能超过100 |
| posName | @NotBlank, @Size(max=100) | 职位名称不能为空/长度不能超过100 |
| candName | @NotBlank, @Size(max=20) | 应聘人员姓名不能为空/长度不能超过20 |
| candId | @NotBlank, @Pattern(身份证正则) | 证件号码不能为空/格式不正确 |
| candGrad | @NotBlank, @Pattern(YYYYMM) | 毕业年月不能为空/格式不正确 |
| admitStatus | @NotBlank, @EnumValid | 录用情况不能为空/状态值不合法 |
### 3.5 批量导入功能设计
**核心优化点:**
1. **批量查询已存在记录:** 使用 `selectBatchIds` 一次性查询
2. **批量插入:** 使用 `saveBatch()` 方法
3. **批量更新:** 使用 `updateBatchById()` 方法
4. **错误信息:** 只返回错误的数据行,成功数据不展示
**性能提升:**
- 原方案: ~3000次数据库操作 (导入1000条)
- 优化后: ~3次数据库操作 (导入1000条)
- 性能提升: ~1000倍
**导入逻辑:**
```
1. 收集所有recruit_id
2. 批量查询已存在的记录
3. 遍历Excel数据:
- 数据转换和校验
- 分类为: 待新增列表、待更新列表
- 记录校验失败的数据
4. 批量插入待新增数据
5. 批量更新待更新数据
6. 只返回错误信息
```
---
## 4. 前端设计
### 4.1 页面结构
```
ruoyi-ui/src/views/ccdiStaffRecruitment/
├── index.vue # 列表页面(主页面)
└── components/
├── RecruitmentForm.vue # 新增/修改表单组件
└── ImportDialog.vue # 导入对话框组件
```
### 4.2 功能列表
**列表页面 (index.vue):**
- 顶部查询表单
- 招聘项目名称(模糊查询)
- 职位名称(模糊查询)
- 候选人姓名(模糊查询)
- 证件号码(精确查询)
- 录用状态(下拉选择)
- 面试官姓名(模糊查询)
- 面试官工号(精确查询)
- 数据表格
- 展示所有字段信息
- 支持排序
- 操作按钮
- 新增
- 批量导入
- 导出
- 批量删除
- 行操作
- 修改
- 删除
**表单组件 (RecruitmentForm.vue):**
- 所有必填字段添加 `required: true`
- 证件号码正则校验
- 毕业年月格式校验(YYYYMM)
- 录用状态下拉选择(枚举值)
---
## 5. 异常处理
### 5.1 异常分类
| 异常类型 | HTTP状态码 | 使用场景 |
|---------|-----------|---------|
| ServiceException | 500 | 业务逻辑异常 |
| ValidationException | 400 | 参数校验失败 |
| DuplicateKeyException | 409 | 主键冲突 |
| FileNotFoundException | 404 | 文件不存在 |
### 5.2 统一异常处理
使用 `@RestControllerAdvice` 全局异常处理器捕获和处理异常。
---
## 6. 测试策略
### 6.1 单元测试
**测试范围:**
- 实体类校验注解测试
- 数据转换工具方法测试
- 业务逻辑核心方法测试
**关键测试用例:**
1. 正常数据导入测试
2. 身份证格式校验测试
3. 批量插入性能测试
### 6.2 集成测试
**测试流程:**
1. 登录获取Token
2. 分页查询测试
3. 单条新增测试
4. 单条修改测试
5. 批量导入测试
6. 导出测试
7. 批量删除测试
### 6.3 性能指标
| 测试场景 | 预期性能 |
|---------|---------|
| 分页查询(1000条) | < 200ms |
| 单条新增 | < 100ms |
| 批量导入(1000条) | < 5s |
| 批量删除(100条) | < 500ms |
| 导出(1000条) | < 2s |
---
## 7. 实施步骤
### 第一步:数据库准备
1. 执行建表SQL
2. 在菜单表中添加菜单和权限配置
### 第二步:后端开发
1. 创建枚举类
2. 创建实体类、DTO、VO、Excel类
3. 创建Mapper接口和XML
4. 创建Service接口和实现
5. 创建Controller
6. 编写单元测试
7. Swagger-UI测试
### 第三步:前端开发
1. 创建API接口定义
2. 开发表格查询页面
3. 开发表单组件
4. 开发导入对话框
5. 配置路由
6. 配置菜单
### 第四步:集成测试
1. 准备测试数据
2. 执行集成测试
3. 验证功能
4. 生成测试报告
### 第五步:文档编写
1. 生成API文档
2. 编写使用说明
---
## 8. 附录
### 8.1 Excel导入模板字段顺序
按CSV字段顺序设计:
1. 招聘项目编号
2. 招聘项目名称
3. 职位名称
4. 职位类别
5. 职位描述
6. 应聘人员姓名
7. 应聘人员学历
8. 应聘人员证件号码
9. 应聘人员毕业院校
10. 应聘人员专业
11. 应聘人员毕业年月
12. 录用情况
13. 面试官1姓名
14. 面试官1工号
15. 面试官2姓名
16. 面试官2工号
### 8.2 MyBatis Plus配置
确保项目中已配置MyBatis Plus分页插件:
```java
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
```
---
**文档结束**

View File

@@ -0,0 +1,395 @@
# 员工信息导入结果弹窗自适应优化设计
**日期**: 2025-02-05
**模块**: 员工信息管理 (ccdiEmployee)
**问题**: 导入结果弹窗在失败数据较多时,内容过长未自适应页面大小
---
## 1. 问题分析
### 1.1 问题描述
当前员工信息维护页面中的导入结果弹窗使用 Element UI 的 `$alert` 组件展示导入结果。当导入失败记录较多如50+条)时,弹窗会出现以下问题:
- 弹窗可能超出视口高度
- 需要滚动整个页面才能看到确定按钮
- 用户体验不佳
### 1.2 现状分析
**前端实现** (index.vue:500-507):
```javascript
handleFileSuccess(response, file, fileList) {
this.upload.isUploading = false;
this.upload.open = false;
this.getList();
this.$alert(response.msg, "导入结果", {
dangerouslyUseHTMLString: true,
customClass: 'import-result-dialog'
});
}
```
**后端返回格式** (CcdiEmployeeServiceImpl.java:276-296):
```java
failureMsg.append("<br/>").append(failureNum).append("")
.append(excel.getName()).append(" 导入失败:").append(e.getMessage());
// ...
failureMsg.insert(0, "很抱歉,导入完成!成功 " + successNum + " 条,失败 " + failureNum + " 条,错误如下:");
```
返回HTML格式示例
```html
很抱歉,导入完成!成功 5 条,失败 10 条,错误如下:<br/>1、张三 导入失败:姓名不能为空<br/>2、李四 导入失败:柜员号不能为空<br/>...
```
**现有样式** (index.vue:638-662):
虽然已经设置了 `max-height: 60vh``overflow-y: auto`但Element UI MessageBox的布局限制导致效果不理想。
---
## 2. 设计方案
### 2.1 设计目标
1. ✅ 弹窗最大高度不超过视口的70%
2. ✅ 内容区域独立滚动,标题和按钮固定
3. ✅ 适配不同屏幕尺寸(包括小屏幕)
4. ✅ 保持良好的视觉层次和可读性
### 2.2 技术方案
**核心策略**
- 使用Flexbox布局确保弹窗结构稳定
- 优化 `.import-result-dialog` 的CSS样式
- 调整 MessageBox 内部元素布局权重
- 添加响应式断点处理小屏幕
---
## 3. 详细设计
### 3.1 弹窗容器优化
```css
.import-result-dialog.el-message-box {
max-height: 70vh !important;
max-width: 700px !important;
width: 700px !important;
display: flex !important;
flex-direction: column !important;
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
}
```
**设计说明**
- `max-height: 70vh`: 比原60vh增加10vh提供更多展示空间
- `max-width: 700px`: 增加宽度以提升长错误信息的可读性
- Flexbox布局确保三部分header/content/btns结构稳定
- 固定定位+居中:防止弹窗位置偏移
### 3.2 内容区域滚动优化
```css
.import-result-dialog .el-message-box__content {
max-height: calc(70vh - 120px) !important;
overflow-y: auto !important;
overflow-x: hidden !important;
padding: 15px 20px !important;
flex-shrink: 1 !important;
scrollbar-width: thin;
scrollbar-color: #c0c4cc #f5f7fa;
}
```
**设计说明**
- `max-height: calc(70vh - 120px)`: 减去header和btns高度确保不超出视口
- `flex-shrink: 1`: 内容区可收缩为header和btns留出空间
- 滚动条优化thin模式提升视觉体验
### 3.3 滚动条美化WebKit浏览器
```css
.import-result-dialog .el-message-box__content::-webkit-scrollbar {
width: 6px;
}
.import-result-dialog .el-message-box__content::-webkit-scrollbar-track {
background: #f5f7fa;
border-radius: 3px;
}
.import-result-dialog .el-message-box__content::-webkit-scrollbar-thumb {
background: #c0c4cc;
border-radius: 3px;
}
.import-result-dialog .el-message-box__content::-webkit-scrollbar-thumb:hover {
background: #909399;
}
```
**设计说明**
- 6px宽度既清晰又不占用过多空间
- 圆角设计与Element UI风格一致
- hover效果提供交互反馈
### 3.4 标题和按钮固定
```css
.import-result-dialog .el-message-box__header {
flex-shrink: 0 !important;
padding: 15px 20px 10px !important;
border-bottom: 1px solid #ebeef5;
}
.import-result-dialog .el-message-box__btns {
flex-shrink: 0 !important;
padding: 10px 20px 15px !important;
border-top: 1px solid #ebeef5;
background: #fff;
}
```
**设计说明**
- `flex-shrink: 0`: 禁止收缩,始终显示
- 添加边框:增强三部分视觉分离
- 背景色:确保按钮区域不透明
### 3.5 响应式设计
**小屏幕适配(高度 < 768px**
```css
@media screen and (max-height: 768px) {
.import-result-dialog.el-message-box {
max-height: 85vh !important;
max-width: 90vw !important;
width: 90vw !important;
}
.import-result-dialog .el-message-box__content {
max-height: calc(85vh - 100px) !important;
padding: 10px 15px !important;
}
}
```
**超小屏幕适配(宽度 < 768px**
```css
@media screen and (max-width: 768px) {
.import-result-dialog.el-message-box {
max-width: 95vw !important;
width: 95vw !important;
}
}
```
### 3.6 错误信息格式优化
```css
.import-result-dialog .el-message-box__content p {
margin: 0;
padding: 0;
line-height: 1.8;
font-size: 14px;
color: #606266;
}
.import-result-dialog .el-message-box__content br {
display: block;
margin: 4px 0;
content: "";
}
```
---
## 4. 实施计划
### 4.1 修改文件
- **文件**: `ruoyi-ui/src/views/ccdiEmployee/index.vue`
- **位置**: 第638-662行全局样式部分
### 4.2 实施步骤
1. **备份现有样式**
- 记录当前样式配置
- 保存弹窗截图作为对比基准
2. **修改CSS样式**
- 替换全局样式部分
- 保持Vue组件作用域样式不变
- 确保新样式全局生效弹窗挂载在body下
3. **验证不同场景**
- 导入全部成功(简短消息)
- 1-10条失败中等长度
- 10-50条失败较长列表
- 50+条失败(超长列表)
4. **多屏幕尺寸测试**
- 1920x1080桌面
- 1366x768笔记本
- 768x1024平板竖屏
- 375x667移动端
### 4.3 验收标准
- [ ] 弹窗始终完整显示在视口内
- [ ] 标题、内容、按钮三部分布局清晰
- [ ] 内容区域可独立滚动
- [ ] 确定按钮始终可见可点击
- [ ] 滚动条样式美观且易于操作
- [ ] 小屏幕下不出现横向滚动条
---
## 5. 技术要点
### 5.1 为什么使用 `!important`
Element UI 的 MessageBox 组件有较高的CSS优先级必须使用 `!important` 覆盖默认样式。
### 5.2 为什么使用全局样式?
`$alert` 创建的弹窗挂载在 `document.body` 下,不在 Vue 组件的作用域内,因此必须使用全局样式(非 `<style scoped>`)。
### 5.3 Flexbox布局优势
- 自动分配空间:内容区自动占据剩余空间
- 防止溢出flex-shrink控制各部分收缩行为
- 结构稳定header和btns不会被挤出视口
---
## 6. 风险评估
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| Element UI版本升级导致样式失效 | 中 | 使用官方API和稳定的CSS类名 |
| 某些浏览器不支持calc() | 低 | 提供固定高度作为fallback |
| 极端小屏幕显示不佳 | 低 | 响应式媒体查询覆盖 |
---
## 7. 扩展考虑
### 7.1 未来优化方向
1. **错误信息分组**: 按错误类型分组展示(如:必填项错误、格式错误、重复数据等)
2. **错误详情展开**: 默认显示摘要,点击展开具体错误信息
3. **复制功能**: 添加"复制错误信息"按钮,方便用户修复后重新导入
### 7.2 其他模块应用
该方案可直接应用于其他使用 `$alert` 展示导入结果的模块:
- 员工招聘信息 (ccdiStaffRecruitment)
- 中介黑名单 (ccdiIntermediaryBlacklist)
---
## 8. 附录
### 8.1 完整CSS代码
```css
/* 导入结果弹窗样式 - 全局样式因为弹窗挂载在body下 */
.import-result-dialog.el-message-box {
max-height: 70vh !important;
max-width: 700px !important;
width: 700px !important;
display: flex !important;
flex-direction: column !important;
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
}
.import-result-dialog .el-message-box__header {
flex-shrink: 0 !important;
padding: 15px 20px 10px !important;
border-bottom: 1px solid #ebeef5;
}
.import-result-dialog .el-message-box__content {
max-height: calc(70vh - 120px) !important;
overflow-y: auto !important;
overflow-x: hidden !important;
padding: 15px 20px !important;
flex-shrink: 1 !important;
scrollbar-width: thin;
scrollbar-color: #c0c4cc #f5f7fa;
}
.import-result-dialog .el-message-box__content::-webkit-scrollbar {
width: 6px;
}
.import-result-dialog .el-message-box__content::-webkit-scrollbar-track {
background: #f5f7fa;
border-radius: 3px;
}
.import-result-dialog .el-message-box__content::-webkit-scrollbar-thumb {
background: #c0c4cc;
border-radius: 3px;
}
.import-result-dialog .el-message-box__content::-webkit-scrollbar-thumb:hover {
background: #909399;
}
.import-result-dialog .el-message-box__content p {
margin: 0;
padding: 0;
line-height: 1.8;
font-size: 14px;
color: #606266;
}
.import-result-dialog .el-message-box__content br {
display: block;
margin: 4px 0;
content: "";
}
.import-result-dialog .el-message-box__btns {
flex-shrink: 0 !important;
padding: 10px 20px 15px !important;
border-top: 1px solid #ebeef5;
background: #fff;
}
/* 小屏幕适配 */
@media screen and (max-height: 768px) {
.import-result-dialog.el-message-box {
max-height: 85vh !important;
max-width: 90vw !important;
width: 90vw !important;
}
.import-result-dialog .el-message-box__content {
max-height: calc(85vh - 100px) !important;
padding: 10px 15px !important;
}
}
/* 超小屏幕适配 */
@media screen and (max-width: 768px) {
.import-result-dialog.el-message-box {
max-width: 95vw !important;
width: 95vw !important;
}
}
```
### 8.2 相关文件
- 前端组件: `ruoyi-ui/src/views/ccdiEmployee/index.vue`
- 后端服务: `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeServiceImpl.java`
- API文档: `doc/api/ccdiEmployee.md`

Binary file not shown.

Binary file not shown.