diff --git a/doc/other/ScreenShot_2026-02-05_154534_027.png b/doc/other/ScreenShot_2026-02-05_154534_027.png new file mode 100644 index 0000000..e0857c7 Binary files /dev/null and b/doc/other/ScreenShot_2026-02-05_154534_027.png differ diff --git a/doc/plans/2025-02-05-ccdi_staff_recruitment.md b/doc/plans/2025-02-05-ccdi_staff_recruitment.md new file mode 100644 index 0000000..87f09e0 --- /dev/null +++ b/doc/plans/2025-02-05-ccdi_staff_recruitment.md @@ -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; +} +``` + +--- + +**文档结束** diff --git a/doc/plans/2025-02-05-employee-import-result-dialog-optimization.md b/doc/plans/2025-02-05-employee-import-result-dialog-optimization.md new file mode 100644 index 0000000..b8e3064 --- /dev/null +++ b/doc/plans/2025-02-05-employee-import-result-dialog-optimization.md @@ -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("
").append(failureNum).append("、") + .append(excel.getName()).append(" 导入失败:").append(e.getMessage()); +// ... +failureMsg.insert(0, "很抱歉,导入完成!成功 " + successNum + " 条,失败 " + failureNum + " 条,错误如下:"); +``` + +返回HTML格式示例: +```html +很抱歉,导入完成!成功 5 条,失败 10 条,错误如下:
1、张三 导入失败:姓名不能为空
2、李四 导入失败:柜员号不能为空
... +``` + +**现有样式** (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 组件的作用域内,因此必须使用全局样式(非 `