From fd8fa2f72123f3752cb940ea9d04e9acbc569797 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Tue, 2 Sep 2025 11:05:27 +0800 Subject: [PATCH 01/29] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/pom.xml b/backend/pom.xml index 3f903412..261234a4 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -85,6 +85,16 @@ true + + prod-local + + + prod-local + warn + ruoyi + 123456 + + prod From 4de2f07351b608ac5688065ee385befe358028bb Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Wed, 3 Sep 2025 09:20:25 +0800 Subject: [PATCH 02/29] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mobile/src/pages/enrollment/index.vue | 3207 +++++++++++++------------ 1 file changed, 1639 insertions(+), 1568 deletions(-) diff --git a/mobile/src/pages/enrollment/index.vue b/mobile/src/pages/enrollment/index.vue index 4c4a86a4..843a370c 100644 --- a/mobile/src/pages/enrollment/index.vue +++ b/mobile/src/pages/enrollment/index.vue @@ -1,1694 +1,1765 @@ From 99b771bb51ee378a8a5afad2b023f45b4b06ede7 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Wed, 3 Sep 2025 11:45:24 +0800 Subject: [PATCH 03/29] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/xg/domain/XgDormRoom.java | 5 ++++ .../dromara/xg/domain/bo/XgDormRoomBo.java | 6 +++- .../dromara/xg/domain/vo/XgDormRoomVo.java | 30 +++++++++++++++++-- .../service/impl/XgDormRoomServiceImpl.java | 6 ++-- docs/开发文档/API/宿舍房间管理API.md | 18 ++++++++--- docs/设计文档/database/hzdyjs_xg0820.sql | 1 + web/src/api/xg/xgss/dormRoom/types.ts | 15 ++++++++++ .../xgss/xsssfjgl/components/RoomDialog.vue | 11 +++++++ web/src/views/xg/xgss/xsssfjgl/index.vue | 1 + 9 files changed, 84 insertions(+), 9 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormRoom.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormRoom.java index 1346dc08..4dd7000c 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormRoom.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormRoom.java @@ -67,6 +67,11 @@ public class XgDormRoom extends TenantEntity { */ private String roomStatus; + /** + * 排序号 + */ + private Long sort; + /** * 删除标志(0代表存在 1代表删除) */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormRoomBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormRoomBo.java index c870cba6..a0eedb5d 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormRoomBo.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormRoomBo.java @@ -70,6 +70,11 @@ public class XgDormRoomBo extends BaseEntity { */ private String roomStatus; + /** + * 排序号 + */ + private Long sort; + /** * 删除标志(0代表存在 1代表删除) */ @@ -80,5 +85,4 @@ public class XgDormRoomBo extends BaseEntity { */ private String remark; - } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormRoomVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormRoomVo.java index aae4aabc..f71cd261 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormRoomVo.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormRoomVo.java @@ -11,8 +11,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; - - +import com.fasterxml.jackson.annotation.JsonFormat; /** * 宿舍房间信息视图对象 xg_dorm_room @@ -82,6 +81,12 @@ public class XgDormRoomVo implements Serializable { @ExcelProperty(value = "房间状态(0可用 1维修中 2停用)") private String roomStatus; + /** + * 排序号 + */ + @ExcelProperty(value = "排序号") + private Long sort; + /** * 删除标志(0代表存在 1代表删除) */ @@ -94,5 +99,26 @@ public class XgDormRoomVo implements Serializable { @ExcelProperty(value = "备注") private String remark; + /** + * 创建者 + */ + private Long createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 更新者 + */ + private Long updateBy; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java index dddcfed0..81a5ab8e 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java @@ -144,8 +144,9 @@ public class XgDormRoomServiceImpl implements IXgDormRoomService { private LambdaQueryWrapper buildQueryWrapper(XgDormRoomBo bo) { Map params = bo.getParams(); LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.orderByAsc(XgDormRoom::getFloorNum); // 优先按楼层排序 - lqw.orderByAsc(XgDormRoom::getRoomName); // 其次按房间名称排序 + lqw.orderByAsc(XgDormRoom::getSort); // 第一优先级:按排序号升序 + lqw.orderByAsc(XgDormRoom::getFloorNum); // 第二优先级:按楼层升序 + lqw.orderByAsc(XgDormRoom::getRoomName); // 第三优先级:按房间名称升序 lqw.like(StringUtils.isNotBlank(bo.getRoomCode()), XgDormRoom::getRoomCode, bo.getRoomCode()); lqw.like(StringUtils.isNotBlank(bo.getRoomName()), XgDormRoom::getRoomName, bo.getRoomName()); lqw.eq(bo.getBuildingId() != null, XgDormRoom::getBuildingId, bo.getBuildingId()); @@ -154,6 +155,7 @@ public class XgDormRoomServiceImpl implements IXgDormRoomService { lqw.eq(bo.getActualBeds() != null, XgDormRoom::getActualBeds, bo.getActualBeds()); lqw.eq(bo.getRoomArea() != null, XgDormRoom::getRoomArea, bo.getRoomArea()); lqw.eq(StringUtils.isNotBlank(bo.getRoomStatus()), XgDormRoom::getRoomStatus, bo.getRoomStatus()); + lqw.eq(bo.getSort() != null, XgDormRoom::getSort, bo.getSort()); lqw.eq(StringUtils.isNotBlank(bo.getDeleted()), XgDormRoom::getDeleted, bo.getDeleted()); return lqw; } diff --git a/docs/开发文档/API/宿舍房间管理API.md b/docs/开发文档/API/宿舍房间管理API.md index b60ce95e..b26aec8f 100644 --- a/docs/开发文档/API/宿舍房间管理API.md +++ b/docs/开发文档/API/宿舍房间管理API.md @@ -302,6 +302,7 @@ xgss:dormRoom:list | floorNum | Integer | 否 | 楼层号,精确查询 | | roomType | String | 否 | 房间类型(1标准间 2单人间 3套间) | | roomStatus | String | 否 | 房间状态(0正常 1维修中 2停用) | +| sort | Integer | 否 | 排序号,精确查询 | #### 返回结果 @@ -320,6 +321,7 @@ xgss:dormRoom:list "actualBeds": 4, "roomArea": 20.5, "roomStatus": "0", + "sort": 0, "remark": "标准四人间", "createBy": 1, "createTime": "2025-07-22 09:15:30", @@ -337,6 +339,7 @@ xgss:dormRoom:list "actualBeds": 4, "roomArea": 20.5, "roomStatus": "0", + "sort": 0, "remark": "标准四人间", "createBy": 1, "createTime": "2025-07-22 09:15:30", @@ -363,6 +366,7 @@ xgss:dormRoom:list | actualBeds | Integer | 实际床位数 | | roomArea | Double | 房间面积(平方米) | | roomStatus | String | 房间状态(0正常 1维修中 2停用) | +| sort | Integer | 排序号,数字越小排序越靠前 | | remark | String | 备注 | | createBy | Long | 创建者ID | | createTime | Date | 创建时间 | @@ -371,11 +375,17 @@ xgss:dormRoom:list #### 排序规则 -房间列表按照以下顺序排序: -1. 优先按楼层号(floorNum)升序排列 -2. 其次按房间名称(roomName)升序排列 +房间列表按照以下优先级顺序排序: +1. **第一优先级**:按排序号(sort)升序排列 +2. **第二优先级**:按楼层号(floorNum)升序排列 +3. **第三优先级**:按房间名称(roomName)升序排列 -这确保了在宿舍管理系统中房间按照从低楼层到高楼层的顺序展示,相同楼层的房间按照名称顺序排列,提供更直观的浏览体验。 +**排序逻辑说明**: +- **自定义排序**:`sort` 字段允许管理员为房间设置自定义排序,数字越小排序越靠前 +- **默认排序**:当 `sort` 值相同或为0时,按楼层和房间名称的自然顺序排列 +- **灵活管理**:支持同一楼层内房间的个性化排序需求 + +这确保了宿舍管理系统既能保持传统的楼层顺序展示,又能支持管理员根据实际需要进行个性化排序调整。 #### 错误码 diff --git a/docs/设计文档/database/hzdyjs_xg0820.sql b/docs/设计文档/database/hzdyjs_xg0820.sql index 21c308f0..052f60d3 100644 --- a/docs/设计文档/database/hzdyjs_xg0820.sql +++ b/docs/设计文档/database/hzdyjs_xg0820.sql @@ -4531,6 +4531,7 @@ CREATE TABLE `xg_dorm_room` ( `actual_beds` int(11) DEFAULT '4' COMMENT '实际床位数', `room_area` decimal(10,2) DEFAULT NULL COMMENT '房间面积(平方米)', `room_status` char(1) DEFAULT '0' COMMENT '房间状态(0可用 1维修中 2停用)', + `sort` int(11) DEFAULT '0' COMMENT '排序号', `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', `tenant_id` bigint(20) DEFAULT '0' COMMENT '租户编号', `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', diff --git a/web/src/api/xg/xgss/dormRoom/types.ts b/web/src/api/xg/xgss/dormRoom/types.ts index 2ce0a6f9..4d8bdd5a 100644 --- a/web/src/api/xg/xgss/dormRoom/types.ts +++ b/web/src/api/xg/xgss/dormRoom/types.ts @@ -49,6 +49,11 @@ export interface DormRoomVO { */ roomStatus: string; + /** + * 排序号 + */ + sort: number; + /** * 删除标志(0代表存在 1代表删除) */ @@ -106,6 +111,11 @@ export interface DormRoomForm extends BaseEntity { */ roomStatus?: string; + /** + * 排序号 + */ + sort?: number; + /** * 删除标志(0代表存在 1代表删除) */ @@ -158,6 +168,11 @@ export interface DormRoomQuery extends PageQuery { */ roomStatus?: string; + /** + * 排序号 + */ + sort?: number; + /** * 删除标志(0代表存在 1代表删除) */ diff --git a/web/src/views/xg/xgss/xsssfjgl/components/RoomDialog.vue b/web/src/views/xg/xgss/xsssfjgl/components/RoomDialog.vue index 99977aaf..9b95928e 100644 --- a/web/src/views/xg/xgss/xsssfjgl/components/RoomDialog.vue +++ b/web/src/views/xg/xgss/xsssfjgl/components/RoomDialog.vue @@ -34,6 +34,16 @@ 停用 + + + @@ -78,6 +88,7 @@ const initFormData: DormRoomForm = { roomArea: 0, roomType: '1', roomStatus: '0', + sort: 0, remark: '' }; diff --git a/web/src/views/xg/xgss/xsssfjgl/index.vue b/web/src/views/xg/xgss/xsssfjgl/index.vue index d4c578c8..21d5a931 100644 --- a/web/src/views/xg/xgss/xsssfjgl/index.vue +++ b/web/src/views/xg/xgss/xsssfjgl/index.vue @@ -130,6 +130,7 @@ 类型:{{ room.roomType === '1' ? '标准间' : room.roomType === '2' ? '单人间' : '套间' }}
床位:{{ room.currentBeds || 0 }}/{{ room.actualBeds }}
+
排序:{{ room.sort || 0 }}
床位管理 From 2a38245ae25e81edd54228e12160408b0b10bd2f Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Thu, 4 Sep 2025 12:29:51 +0800 Subject: [PATCH 04/29] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=AE=BF=E8=88=8D?= =?UTF-8?q?=E5=8C=BA=E5=8E=BB=E9=99=A4=E7=9A=84=E4=B8=9A=E5=8A=A1=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../XgDormBedStudentController.java | 4 +- .../controller/XgDormBuildingController.java | 24 +- .../XgDormBuildingTreeController.java | 57 +++- .../dromara/xg/domain/XgDormHygieneCheck.java | 2 +- .../xg/domain/XgDormHygieneStudentScore.java | 2 +- .../xg/domain/bo/XgDormBuildingBo.java | 3 +- .../vo/DormBuildingTreeSimplifiedVo.java | 104 ++++++ .../domain/vo/XgDormBuildingSimplifiedVo.java | 103 ++++++ .../vo/XgDormHygieneCheckSimplifiedVo.java | 183 ++++++++++ .../xg/service/IXgDormBedStudentService.java | 2 +- .../xg/service/IXgDormBuildingService.java | 20 ++ .../service/IXgDormBuildingTreeService.java | 19 ++ .../impl/XgDormBedStudentServiceImpl.java | 63 +++- .../impl/XgDormBuildingServiceImpl.java | 117 ++++--- .../impl/XgDormBuildingTreeServiceImpl.java | 39 +++ .../service/impl/XgDormRoomServiceImpl.java | 4 +- docs/开发文档/API/宿舍卫生检查管理API.md | 81 ++++- docs/开发文档/API/宿舍楼树形结构API.md | 176 +++++++--- docs/设计文档/database/hzdyjs_xg0820.sql | 4 +- .../DormHygieneCheckDialog.vue | 185 ++++------ .../dormHygieneCheck/index.vue | 12 +- .../xg/xgss/sslxx/dormBuilding/index.vue | 321 +++--------------- web/src/views/xg/xgss/xsssfjgl/index.vue | 168 ++++++--- web/src/views/xg/xgss/xsssgly/index.vue | 3 +- 24 files changed, 1139 insertions(+), 557 deletions(-) create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/DormBuildingTreeSimplifiedVo.java create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormBuildingSimplifiedVo.java create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckSimplifiedVo.java diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java index 7de403d4..2c02ae9f 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java @@ -157,7 +157,7 @@ public class XgDormBedStudentController extends BaseController { /** * 获取当前用户二级学院的宿舍楼树形结构 - * 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择 + * 校区-宿舍楼二级结构,校区不可选,只有宿舍楼可以选择 */ @SaCheckPermission("dormBedStudent:dormBedStudent:list") @GetMapping("/currentUserCollegeDormTree") @@ -168,7 +168,7 @@ public class XgDormBedStudentController extends BaseController { /** * 获取班主任管理的班级宿舍楼树形结构 - * 只显示班主任管理班级分配到的宿舍楼 + * 校区-宿舍楼二级结构,只显示班主任管理班级分配到的宿舍楼 */ @SaCheckPermission("dormBedStudent:dormBedStudent:list") @GetMapping("/getClassTeacherDormTree") diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBuildingController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBuildingController.java index 30420e62..9d1b519b 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBuildingController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBuildingController.java @@ -18,6 +18,7 @@ import org.dromara.common.core.validate.EditGroup; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.excel.utils.ExcelUtil; import org.dromara.xg.domain.vo.XgDormBuildingVo; +import org.dromara.xg.domain.vo.XgDormBuildingSimplifiedVo; import org.dromara.xg.domain.bo.XgDormBuildingBo; import org.dromara.xg.domain.vo.BuildingTreeSelectVo; import org.dromara.xg.service.IXgDormBuildingService; @@ -104,7 +105,7 @@ public class XgDormBuildingController extends BaseController { /** * 获取宿舍楼树形结构数据 - * 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择 + * 校区-宿舍楼二级结构,校区不可选,只有宿舍楼可以选择 */ @SaCheckPermission("sslxx:dormBuilding:list") @GetMapping("/buildingTree") @@ -112,4 +113,25 @@ public class XgDormBuildingController extends BaseController { List treeData = xgDormBuildingService.getBuildingTreeData(); return R.ok(treeData); } + + /** + * 查询宿舍楼信息简化列表 + * 用于简化界面显示,隐藏宿舍区层级,直接显示校区→宿舍楼结构 + */ + @SaCheckPermission("sslxx:dormBuilding:list") + @GetMapping("/simplifiedList") + public TableDataInfo getSimplifiedList(XgDormBuildingBo bo, PageQuery pageQuery) { + return xgDormBuildingService.querySimplifiedPageList(bo, pageQuery); + } + + /** + * 查询宿舍楼信息简化列表(不分页) + * 用于下拉选择等场景 + */ + @SaCheckPermission("sslxx:dormBuilding:list") + @GetMapping("/simplifiedListAll") + public R> getSimplifiedListAll(XgDormBuildingBo bo) { + List list = xgDormBuildingService.querySimplifiedList(bo); + return R.ok(list); + } } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBuildingTreeController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBuildingTreeController.java index 01f01f94..a1cf2805 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBuildingTreeController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBuildingTreeController.java @@ -9,6 +9,7 @@ import org.springframework.validation.annotation.Validated; import org.dromara.common.web.core.BaseController; import org.dromara.common.core.domain.R; import org.dromara.xg.domain.vo.DormBuildingTreeVo; +import org.dromara.xg.domain.vo.DormBuildingTreeSimplifiedVo; import org.dromara.xg.domain.bo.DormBuildingTreeBo; import org.dromara.xg.service.IXgDormBuildingTreeService; @@ -28,24 +29,70 @@ public class XgDormBuildingTreeController extends BaseController { /** * 获取宿舍楼树形结构数据 - * 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择 + * 校区-宿舍楼二级结构,校区不可选,只有宿舍楼可以选择 */ @SaCheckPermission("xgss:dormBuildingTree:list") @GetMapping("/list") - public R> getDormBuildingTree(DormBuildingTreeBo bo) { - List treeData = xgDormBuildingTreeService.getDormBuildingTree(bo); + public R> getDormBuildingTree(DormBuildingTreeBo bo) { + List treeData = xgDormBuildingTreeService.getSimplifiedDormBuildingTree(bo); return R.ok(treeData); } /** * 获取所有宿舍楼树形结构数据(不考虑权限) - * 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择 + * 校区-宿舍楼二级结构,校区不可选,只有宿舍楼可以选择 */ @SaCheckPermission("xgss:dormBuildingTree:list") @GetMapping("/listAll") - public R> getAllDormBuildingTree(DormBuildingTreeBo bo) { + public R> getAllDormBuildingTree(DormBuildingTreeBo bo) { + List treeData = xgDormBuildingTreeService.getAllSimplifiedDormBuildingTree(bo); + return R.ok(treeData); + } + + /** + * 获取宿舍楼树形结构完整数据(包含宿舍区层级) + * 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择 + * 注意:此接口仅用于特殊需求,一般情况请使用/list接口 + */ + @SaCheckPermission("xgss:dormBuildingTree:list") + @GetMapping("/fullList") + public R> getFullDormBuildingTree(DormBuildingTreeBo bo) { + List treeData = xgDormBuildingTreeService.getDormBuildingTree(bo); + return R.ok(treeData); + } + + /** + * 获取所有宿舍楼树形结构完整数据(不考虑权限,包含宿舍区层级) + * 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择 + * 注意:此接口仅用于特殊需求,一般情况请使用/listAll接口 + */ + @SaCheckPermission("xgss:dormBuildingTree:list") + @GetMapping("/fullListAll") + public R> getAllFullDormBuildingTree(DormBuildingTreeBo bo) { List treeData = xgDormBuildingTreeService.getAllDormBuildingTree(bo); return R.ok(treeData); } + /** + * 获取宿舍楼树形结构简化数据 + * 校区-宿舍楼二级结构,隐藏宿舍区层级,校区不可选,只有宿舍楼可以选择 + */ + @SaCheckPermission("xgss:dormBuildingTree:list") + @GetMapping("/simplifiedList") + public R> getSimplifiedDormBuildingTree(DormBuildingTreeBo bo) { + List treeData = xgDormBuildingTreeService.getSimplifiedDormBuildingTree(bo); + return R.ok(treeData); + } + + /** + * 获取所有宿舍楼树形结构简化数据(不考虑权限) + * 校区-宿舍楼二级结构,隐藏宿舍区层级,校区不可选,只有宿舍楼可以选择 + */ + @SaCheckPermission("xgss:dormBuildingTree:list") + @GetMapping("/simplifiedListAll") + public R> getAllSimplifiedDormBuildingTree(DormBuildingTreeBo bo) { + List treeData = xgDormBuildingTreeService.getAllSimplifiedDormBuildingTree(bo); + return R.ok(treeData); + } + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneCheck.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneCheck.java index 87fa3502..e80ba06a 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneCheck.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneCheck.java @@ -111,7 +111,7 @@ public class XgDormHygieneCheck extends TenantEntity { private String campusName; /** - * 宿舍总分(0-100分) + * 宿舍扣分(0-100分) */ private Double totalScore; diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneStudentScore.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneStudentScore.java index 4bfc2c15..57c02d0d 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneStudentScore.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneStudentScore.java @@ -94,7 +94,7 @@ public class XgDormHygieneStudentScore extends TenantEntity { private String bedCode; /** - * 学生个人得分(0-100分) + * 学生个人扣分(0-100分) */ private Double studentScore; diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormBuildingBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormBuildingBo.java index 499f6725..503e3944 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormBuildingBo.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormBuildingBo.java @@ -41,7 +41,8 @@ public class XgDormBuildingBo extends BaseEntity { /** * 所属宿舍区ID */ - @NotNull(message = "所属宿舍区ID不能为空", groups = { AddGroup.class, EditGroup.class }) + // @NotNull(message = "所属宿舍区ID不能为空", groups = { AddGroup.class, EditGroup.class + // }) // 宿舍区轻量化改造:移除必填验证 private Long areaId; /** diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/DormBuildingTreeSimplifiedVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/DormBuildingTreeSimplifiedVo.java new file mode 100644 index 00000000..8d9ac7f4 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/DormBuildingTreeSimplifiedVo.java @@ -0,0 +1,104 @@ +package org.dromara.xg.domain.vo; + +import java.io.Serializable; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * 宿舍楼树形结构简化VO + * 用于简化界面显示,只显示校区→宿舍楼的二级结构,隐藏宿舍区层级 + * + * @author wuzilong + * @date 2025-01-03 + */ +@Data +public class DormBuildingTreeSimplifiedVo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 节点ID + */ + private Long id; + + /** + * 节点名称 + */ + private String label; + + /** + * 节点类型 (campus:校区, building:宿舍楼) + * 注意:简化版本中移除了 area:宿舍区 类型 + */ + private String type; + + /** + * 是否禁用节点(校区节点禁用点击,宿舍楼节点可点击) + */ + private Boolean disabled; + + /** + * 宿舍楼层数(仅宿舍楼节点有此字段) + */ + private Integer buildingFloors; + + /** + * 宿舍楼类型(1男生楼 2女生楼 3混合楼)(仅宿舍楼节点有此字段) + */ + private String buildingType; + + /** + * 子节点 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List children; + + /** + * 从完整树形VO转换为简化树形VO + * 将三级结构(校区→宿舍区→宿舍楼)转换为二级结构(校区→宿舍楼) + */ + public static DormBuildingTreeSimplifiedVo fromFullTreeVo(DormBuildingTreeVo fullTreeVo) { + if (fullTreeVo == null) { + return null; + } + + DormBuildingTreeSimplifiedVo simplified = new DormBuildingTreeSimplifiedVo(); + simplified.setId(fullTreeVo.getId()); + simplified.setLabel(fullTreeVo.getLabel()); + simplified.setBuildingFloors(fullTreeVo.getBuildingFloors()); + simplified.setBuildingType(fullTreeVo.getBuildingType()); + + // 根据节点类型进行转换 + if ("campus".equals(fullTreeVo.getType())) { + // 校区节点:保持不变 + simplified.setType("campus"); + simplified.setDisabled(true); + // 处理子节点:跳过宿舍区层级,直接提取宿舍楼 + simplified.setChildren(flattenAreaChildren(fullTreeVo.getChildren())); + } else if ("building".equals(fullTreeVo.getType())) { + // 宿舍楼节点:保持不变 + simplified.setType("building"); + simplified.setDisabled(false); + } + + return simplified; + } + + /** + * 扁平化宿舍区子节点,将宿舍区下的宿舍楼直接提升为校区的子节点 + */ + private static List flattenAreaChildren(List areaNodes) { + if (areaNodes == null || areaNodes.isEmpty()) { + return null; + } + + return areaNodes.stream() + .filter(node -> "area".equals(node.getType())) // 找到宿舍区节点 + .flatMap(areaNode -> areaNode.getChildren() != null ? areaNode.getChildren().stream() + : java.util.stream.Stream.empty()) // 提取宿舍楼 + .filter(buildingNode -> "building".equals(buildingNode.getType())) // 确保是宿舍楼节点 + .map(DormBuildingTreeSimplifiedVo::fromFullTreeVo) // 转换为简化VO + .toList(); + } +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormBuildingSimplifiedVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormBuildingSimplifiedVo.java new file mode 100644 index 00000000..dccc0607 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormBuildingSimplifiedVo.java @@ -0,0 +1,103 @@ +package org.dromara.xg.domain.vo; + +import org.dromara.xg.domain.XgDormBuilding; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 宿舍楼信息简化视图对象 xg_dorm_building + * 用于简化界面显示,隐藏宿舍区层级,直接显示校区→宿舍楼结构 + * + * @author zlwu + * @date 2025-01-03 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = XgDormBuilding.class) +public class XgDormBuildingSimplifiedVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 宿舍楼编码 + */ + @ExcelProperty(value = "宿舍楼编码") + private String buildingCode; + + /** + * 宿舍楼名称 + */ + @ExcelProperty(value = "宿舍楼名称") + private String buildingName; + + /** + * 所属校区ID + */ + @ExcelProperty(value = "所属校区ID") + private Long campusId; + + /** + * 校区名称 + */ + @ExcelProperty(value = "校区名称") + private String campusName; + + /** + * 宿舍楼类型(1男生楼 2女生楼 3混合楼) + */ + @ExcelProperty(value = "宿舍楼类型(1男生楼 2女生楼 3混合楼)", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "dorm_type") + private String buildingType; + + /** + * 宿舍楼描述 + */ + @ExcelProperty(value = "宿舍楼描述") + private String buildingDescription; + + /** + * 排序号 + */ + @ExcelProperty(value = "排序号") + private Long sortOrder; + + /** + * 总楼层数 + */ + @ExcelProperty(value = "总楼层数") + private Integer floors; + + // 基础字段 + private Long createBy; // 创建者 + private Date createTime; // 创建时间 + private Long updateBy; // 更新者 + private Date updateTime; // 更新时间 + + /** + * 从完整VO转换为简化VO + */ + public static XgDormBuildingSimplifiedVo fromFullVo(XgDormBuildingVo fullVo) { + if (fullVo == null) { + return null; + } + XgDormBuildingSimplifiedVo simplified = new XgDormBuildingSimplifiedVo(); + BeanUtils.copyProperties(fullVo, simplified); + return simplified; + } +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckSimplifiedVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckSimplifiedVo.java new file mode 100644 index 00000000..51b8d632 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckSimplifiedVo.java @@ -0,0 +1,183 @@ +package org.dromara.xg.domain.vo; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import org.dromara.xg.domain.XgDormHygieneCheck; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 宿舍卫生检查简化视图对象 xg_dorm_hygiene_check + * 用于简化界面显示,隐藏宿舍区层级,直接显示校区→宿舍楼→房间结构 + * + * @author zlwu + * @date 2025-01-03 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = XgDormHygieneCheck.class) +public class XgDormHygieneCheckSimplifiedVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @ExcelProperty(value = "删除标志", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "0=代表存在,1=代表删除") + private Long deleted; + + /** + * 检查标题 + */ + @ExcelProperty(value = "检查标题") + private String checkTitle; + + /** + * 检查日期 + */ + @ExcelProperty(value = "检查日期") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date checkDate; + + /** + * 检查员ID,关联sys_user.id + */ + @ExcelProperty(value = "检查员ID,关联sys_user.id") + private Long checkerId; + + /** + * 检查员姓名 + */ + @ExcelProperty(value = "检查员姓名") + private String checkerName; + + /** + * 检查类型(1日常检查 2专项检查 3突击检查 4复查) + */ + @ExcelProperty(value = "检查类型", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "xg_dorm_hygiene_check_type") + private String checkType; + + /** + * 检查级别(1宿管员 2学院 3学校) + */ + @ExcelProperty(value = "检查级别", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "xg_dorm_hygiene_check_level") + private String checkLevel; + + /** + * 宿舍房间ID,关联xg_dorm_room.id + */ + @ExcelProperty(value = "宿舍房间ID,关联xg_dorm_room.id") + private Long roomId; + + /** + * 房间编号 + */ + @ExcelProperty(value = "房间编号") + private String roomCode; + + /** + * 房间名称 + */ + @ExcelProperty(value = "房间名称") + private String roomName; + + /** + * 宿舍楼ID,关联xg_dorm_building.id + */ + @ExcelProperty(value = "宿舍楼ID,关联xg_dorm_building.id") + private Long buildingId; + + /** + * 宿舍楼名称 + */ + @ExcelProperty(value = "宿舍楼名称") + private String buildingName; + + /** + * 校区ID,关联xg_dorm_campus.id + */ + @ExcelProperty(value = "校区ID,关联xg_dorm_campus.id") + private Long campusId; + + /** + * 校区名称 + */ + @ExcelProperty(value = "校区名称") + private String campusName; + + /** + * 宿舍总分(0-100分) + */ + @ExcelProperty(value = "宿舍总分", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "0=-100分") + private Double totalScore; + + /** + * 卫生等级(A优秀 B良好 C合格 D不合格) + */ + @ExcelProperty(value = "卫生等级", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "xg_dorm_hygiene_grade") + private String hygieneGrade; + + /** + * 附件OSS ID列表,逗号分隔 + */ + @ExcelProperty(value = "附件OSS ID列表,逗号分隔") + private String attachmentIds; + + /** + * 检查总结 + */ + @ExcelProperty(value = "检查总结") + private String checkSummary; + + /** + * 整改建议 + */ + @ExcelProperty(value = "整改建议") + private String improvementSuggestions; + + /** + * 检查备注 + */ + @ExcelProperty(value = "检查备注") + private String checkRemarks; + + // 基础字段 + private Long createBy; // 创建者 + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; // 创建时间 + private Long updateBy; // 更新者 + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; // 更新时间 + + /** + * 从完整VO转换为简化VO + */ + public static XgDormHygieneCheckSimplifiedVo fromFullVo(XgDormHygieneCheckVo fullVo) { + if (fullVo == null) { + return null; + } + XgDormHygieneCheckSimplifiedVo simplified = new XgDormHygieneCheckSimplifiedVo(); + BeanUtils.copyProperties(fullVo, simplified); + return simplified; + } +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java index bc972308..92c7523c 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java @@ -107,7 +107,7 @@ public interface IXgDormBedStudentService { /** * 获取当前用户二级学院的宿舍楼树形结构 - * 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择 + * 校区-宿舍楼二级结构,校区不可选,只有宿舍楼可以选择 * * @return 宿舍楼树形结构数据 */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBuildingService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBuildingService.java index d15bdb61..d1203a18 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBuildingService.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBuildingService.java @@ -1,6 +1,7 @@ package org.dromara.xg.service; import org.dromara.xg.domain.vo.XgDormBuildingVo; +import org.dromara.xg.domain.vo.XgDormBuildingSimplifiedVo; import org.dromara.xg.domain.bo.XgDormBuildingBo; import org.dromara.xg.domain.vo.BuildingTreeSelectVo; import org.dromara.common.mybatis.core.page.TableDataInfo; @@ -74,4 +75,23 @@ public interface IXgDormBuildingService { * @return 树形结构数据 */ List getBuildingTreeData(); + + /** + * 分页查询宿舍楼信息简化列表 + * 用于简化界面显示,隐藏宿舍区层级,直接显示校区→宿舍楼结构 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 宿舍楼信息简化分页列表 + */ + TableDataInfo querySimplifiedPageList(XgDormBuildingBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的宿舍楼信息简化列表 + * 用于简化界面显示,隐藏宿舍区层级,直接显示校区→宿舍楼结构 + * + * @param bo 查询条件 + * @return 宿舍楼信息简化列表 + */ + List querySimplifiedList(XgDormBuildingBo bo); } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBuildingTreeService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBuildingTreeService.java index 8aea57c5..8f7c86ce 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBuildingTreeService.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBuildingTreeService.java @@ -2,6 +2,7 @@ package org.dromara.xg.service; import java.util.List; import org.dromara.xg.domain.vo.DormBuildingTreeVo; +import org.dromara.xg.domain.vo.DormBuildingTreeSimplifiedVo; import org.dromara.xg.domain.bo.DormBuildingTreeBo; /** @@ -28,4 +29,22 @@ public interface IXgDormBuildingTreeService { */ List getAllDormBuildingTree(DormBuildingTreeBo bo); + /** + * 获取宿舍楼树形结构简化数据 + * 校区-宿舍楼二级结构,隐藏宿舍区层级 + * + * @param bo 查询参数 + * @return 简化树形结构数据 + */ + List getSimplifiedDormBuildingTree(DormBuildingTreeBo bo); + + /** + * 获取所有宿舍楼树形结构简化数据(不考虑权限) + * 校区-宿舍楼二级结构,隐藏宿舍区层级 + * + * @param bo 查询参数 + * @return 简化树形结构数据 + */ + List getAllSimplifiedDormBuildingTree(DormBuildingTreeBo bo); + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java index 232f1775..fa0226b2 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java @@ -983,7 +983,8 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { Long roomId = entry.getKey(); List roomUnallocatedBeds = entry.getValue(); String roomCode = roomCodeMap.get(roomId); - log.error("房间{}({})有{}个床位未分配:{}", roomCode, roomId, roomUnallocatedBeds.size(), roomUnallocatedBeds); + log.error("房间{}({})有{}个床位未分配:{}", roomCode, roomId, roomUnallocatedBeds.size(), + roomUnallocatedBeds); } throw new RuntimeException("床位分配不完整,存在" + (totalExpectedBeds - totalAllocatedBeds) + "个床位未分配"); @@ -1008,7 +1009,7 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { /** * 获取当前用户二级学院的宿舍楼树形结构 - * 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择 + * 校区-宿舍楼二级结构,校区不可选,只有宿舍楼可以选择 * * @return 宿舍楼树形结构数据 */ @@ -1159,8 +1160,8 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { .orderByAsc(XgDormCampus::getSortOrder, XgDormCampus::getId); List campuses = xgDormCampusMapper.selectList(campusWrapper); - // 构建树形结构 - return buildTreeStructure(campuses, areas, buildings); + // 构建简化版树形结构(校区-宿舍楼,去除宿舍区层级) + return buildSimplifiedTreeStructure(campuses, areas, buildings); } catch (Exception e) { log.error("构建宿舍楼树形结构失败", e); @@ -1232,6 +1233,60 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { return new ArrayList<>(campusMap.values()); } + /** + * 构建简化树形结构:校区-宿舍楼(去除宿舍区层级) + */ + private List buildSimplifiedTreeStructure(List campuses, + List areas, + List buildings) { + + // 构建校区节点(第一层) + Map campusMap = new LinkedHashMap<>(); + for (XgDormCampus campus : campuses) { + // 检查该校区下是否有宿舍楼(通过宿舍区关联) + boolean hasBuildings = buildings.stream().anyMatch(building -> areas.stream().anyMatch( + area -> area.getId().equals(building.getAreaId()) && area.getCampusId().equals(campus.getId()))); + + if (hasBuildings) { + DormBuildingTreeVo campusNode = new DormBuildingTreeVo(); + campusNode.setId(campus.getId()); + campusNode.setLabel(campus.getCampusName()); + campusNode.setType("campus"); + campusNode.setDisabled(true); // 校区节点禁止点击 + campusNode.setChildren(new ArrayList<>()); + campusMap.put(campus.getId(), campusNode); + } + } + + // 直接构建宿舍楼节点(第二层),跳过宿舍区 + for (XgDormBuilding building : buildings) { + // 通过宿舍区找到对应的校区 + XgDormArea area = areas.stream() + .filter(a -> a.getId().equals(building.getAreaId())) + .findFirst() + .orElse(null); + + if (area != null) { + DormBuildingTreeVo buildingNode = new DormBuildingTreeVo(); + buildingNode.setId(building.getId()); + buildingNode.setLabel(building.getBuildingName()); + buildingNode.setType("building"); + buildingNode.setDisabled(false); // 宿舍楼节点可以点击 + buildingNode.setBuildingFloors(building.getFloors()); + buildingNode.setBuildingType(building.getBuildingType()); + buildingNode.setChildren(new ArrayList<>()); + + // 直接添加到校区下,跳过宿舍区层级 + DormBuildingTreeVo campusNode = campusMap.get(area.getCampusId()); + if (campusNode != null) { + campusNode.getChildren().add(buildingNode); + } + } + } + + return new ArrayList<>(campusMap.values()); + } + /** * 获取班主任管理的班级宿舍楼树 * 通过当前用户所在的班级,获取该班级分配的宿舍楼 diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBuildingServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBuildingServiceImpl.java index 3cca3511..07d9e674 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBuildingServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBuildingServiceImpl.java @@ -12,6 +12,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.dromara.xg.domain.bo.XgDormBuildingBo; import org.dromara.xg.domain.vo.XgDormBuildingVo; +import org.dromara.xg.domain.vo.XgDormBuildingSimplifiedVo; import org.dromara.xg.domain.XgDormBuilding; import org.dromara.xg.domain.XgDormArea; import org.dromara.xg.domain.XgDormCampus; @@ -145,7 +146,7 @@ public class XgDormBuildingServiceImpl implements IXgDormBuildingService { /** * 获取宿舍楼树形结构数据 - * 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择 + * 校区-宿舍楼二级结构,校区不可选,只有宿舍楼可以选择 * * @return 宿舍楼树形结构数据 */ @@ -156,59 +157,41 @@ public class XgDormBuildingServiceImpl implements IXgDormBuildingService { Wrappers.lambdaQuery() .orderByAsc(XgDormCampus::getSortOrder, XgDormCampus::getId)); - // 2. 获取所有宿舍区数据 - List areaList = areaMapper.selectList( - Wrappers.lambdaQuery() - .orderByAsc(XgDormArea::getSortOrder, XgDormArea::getId)); - - // 3. 获取所有宿舍楼数据 + // 2. 获取所有宿舍楼数据 List buildingList = baseMapper.selectList( Wrappers.lambdaQuery() .orderByAsc(XgDormBuilding::getSortOrder, XgDormBuilding::getId)); - // 4. 构建树形结构 + // 3. 构建树形结构 List treeList = new ArrayList<>(); - // 5. 先构建校区节点 + // 4. 先构建校区节点 for (XgDormCampus campus : campusList) { - BuildingTreeSelectVo campusNode = new BuildingTreeSelectVo(); - campusNode.setId(campus.getId()); - campusNode.setLabel(campus.getCampusName()); - campusNode.setNodeType("campus"); - campusNode.setDisabled(true); // 校区不可选 - campusNode.setChildren(new ArrayList<>()); - - // 设置创建者和更新者字段 - campusNode.setCreateBy(campus.getCreateBy()); - campusNode.setCreateTime(campus.getCreateTime()); - campusNode.setUpdateBy(campus.getUpdateBy()); - campusNode.setUpdateTime(campus.getUpdateTime()); - - // 添加该校区下的宿舍区 - List campusAreas = areaList.stream() - .filter(area -> area.getCampusId().equals(campus.getId())) + // 检查该校区下是否有宿舍楼 + List campusBuildings = buildingList.stream() + .filter(building -> { + // 通过宿舍区找到校区 + XgDormArea area = areaMapper.selectById(building.getAreaId()); + return area != null && area.getCampusId().equals(campus.getId()); + }) .collect(Collectors.toList()); - for (XgDormArea area : campusAreas) { - BuildingTreeSelectVo areaNode = new BuildingTreeSelectVo(); - areaNode.setId(area.getId()); - areaNode.setLabel(area.getAreaName()); - areaNode.setNodeType("area"); - areaNode.setDisabled(true); // 宿舍区不可选 - areaNode.setChildren(new ArrayList<>()); + if (!campusBuildings.isEmpty()) { + BuildingTreeSelectVo campusNode = new BuildingTreeSelectVo(); + campusNode.setId(campus.getId()); + campusNode.setLabel(campus.getCampusName()); + campusNode.setNodeType("campus"); + campusNode.setDisabled(true); // 校区不可选 + campusNode.setChildren(new ArrayList<>()); // 设置创建者和更新者字段 - areaNode.setCreateBy(area.getCreateBy()); - areaNode.setCreateTime(area.getCreateTime()); - areaNode.setUpdateBy(area.getUpdateBy()); - areaNode.setUpdateTime(area.getUpdateTime()); + campusNode.setCreateBy(campus.getCreateBy()); + campusNode.setCreateTime(campus.getCreateTime()); + campusNode.setUpdateBy(campus.getUpdateBy()); + campusNode.setUpdateTime(campus.getUpdateTime()); - // 添加该宿舍区下的宿舍楼 - List areaBuildings = buildingList.stream() - .filter(building -> building.getAreaId().equals(area.getId())) - .collect(Collectors.toList()); - - for (XgDormBuilding building : areaBuildings) { + // 直接添加该校区下的宿舍楼,跳过宿舍区层级 + for (XgDormBuilding building : campusBuildings) { BuildingTreeSelectVo buildingNode = new BuildingTreeSelectVo(); buildingNode.setId(building.getId()); buildingNode.setLabel(building.getBuildingName()); @@ -222,15 +205,57 @@ public class XgDormBuildingServiceImpl implements IXgDormBuildingService { buildingNode.setUpdateBy(building.getUpdateBy()); buildingNode.setUpdateTime(building.getUpdateTime()); - areaNode.getChildren().add(buildingNode); + campusNode.getChildren().add(buildingNode); } - campusNode.getChildren().add(areaNode); + treeList.add(campusNode); } - - treeList.add(campusNode); } return treeList; } + + /** + * 分页查询宿舍楼信息简化列表 + * 用于简化界面显示,隐藏宿舍区层级,直接显示校区→宿舍楼结构 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 宿舍楼信息简化分页列表 + */ + @Override + public TableDataInfo querySimplifiedPageList(XgDormBuildingBo bo, PageQuery pageQuery) { + // 先查询完整的分页数据 + TableDataInfo fullPageList = queryPageList(bo, pageQuery); + + // 转换为简化版VO + List simplifiedList = fullPageList.getRows().stream() + .map(XgDormBuildingSimplifiedVo::fromFullVo) + .collect(Collectors.toList()); + + // 创建简化版分页结果 + TableDataInfo simplifiedPageList = new TableDataInfo<>(); + simplifiedPageList.setRows(simplifiedList); + simplifiedPageList.setTotal(fullPageList.getTotal()); + + return simplifiedPageList; + } + + /** + * 查询符合条件的宿舍楼信息简化列表 + * 用于简化界面显示,隐藏宿舍区层级,直接显示校区→宿舍楼结构 + * + * @param bo 查询条件 + * @return 宿舍楼信息简化列表 + */ + @Override + public List querySimplifiedList(XgDormBuildingBo bo) { + // 先查询完整的数据 + List fullList = queryList(bo); + + // 转换为简化版VO + return fullList.stream() + .map(XgDormBuildingSimplifiedVo::fromFullVo) + .collect(Collectors.toList()); + } } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBuildingTreeServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBuildingTreeServiceImpl.java index 46cd2244..c96263fb 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBuildingTreeServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBuildingTreeServiceImpl.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import org.dromara.xg.service.IXgDormBuildingTreeService; import org.dromara.xg.mapper.XgDormBuildingTreeMapper; import org.dromara.xg.domain.vo.DormBuildingTreeVo; +import org.dromara.xg.domain.vo.DormBuildingTreeSimplifiedVo; import org.dromara.xg.domain.vo.DormBuildingInfoVo; import org.dromara.xg.domain.bo.DormBuildingTreeBo; import org.dromara.common.satoken.utils.LoginHelper; @@ -231,4 +232,42 @@ public class XgDormBuildingTreeServiceImpl implements IXgDormBuildingTreeService return new ArrayList<>(campusMap.values()); } + /** + * 获取宿舍楼树形结构简化数据 + * 校区-宿舍楼二级结构,隐藏宿舍区层级 + * + * @param bo 查询参数 + * @return 简化树形结构数据 + */ + @Override + public List getSimplifiedDormBuildingTree(DormBuildingTreeBo bo) { + // 先获取完整的树形结构数据 + List fullTreeData = getDormBuildingTree(bo); + + // 转换为简化版树形结构 + return fullTreeData.stream() + .map(DormBuildingTreeSimplifiedVo::fromFullTreeVo) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** + * 获取所有宿舍楼树形结构简化数据(不考虑权限) + * 校区-宿舍楼二级结构,隐藏宿舍区层级 + * + * @param bo 查询参数 + * @return 简化树形结构数据 + */ + @Override + public List getAllSimplifiedDormBuildingTree(DormBuildingTreeBo bo) { + // 先获取完整的树形结构数据(不考虑权限) + List fullTreeData = getAllDormBuildingTree(bo); + + // 转换为简化版树形结构 + return fullTreeData.stream() + .map(DormBuildingTreeSimplifiedVo::fromFullTreeVo) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java index 81a5ab8e..e254bb53 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java @@ -144,8 +144,8 @@ public class XgDormRoomServiceImpl implements IXgDormRoomService { private LambdaQueryWrapper buildQueryWrapper(XgDormRoomBo bo) { Map params = bo.getParams(); LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.orderByAsc(XgDormRoom::getSort); // 第一优先级:按排序号升序 - lqw.orderByAsc(XgDormRoom::getFloorNum); // 第二优先级:按楼层升序 + lqw.orderByAsc(XgDormRoom::getFloorNum); // 第一优先级:按楼层升序 + lqw.orderByAsc(XgDormRoom::getSort); // 第二优先级:按排序号升序 lqw.orderByAsc(XgDormRoom::getRoomName); // 第三优先级:按房间名称升序 lqw.like(StringUtils.isNotBlank(bo.getRoomCode()), XgDormRoom::getRoomCode, bo.getRoomCode()); lqw.like(StringUtils.isNotBlank(bo.getRoomName()), XgDormRoom::getRoomName, bo.getRoomName()); diff --git a/docs/开发文档/API/宿舍卫生检查管理API.md b/docs/开发文档/API/宿舍卫生检查管理API.md index a3ae778d..15961142 100644 --- a/docs/开发文档/API/宿舍卫生检查管理API.md +++ b/docs/开发文档/API/宿舍卫生检查管理API.md @@ -333,4 +333,83 @@ public interface DormHygieneStudentScoreMapper extends BaseMapperPlus { + // 验证宿舍总分范围 + if (form.totalScore !== null && form.totalScore !== undefined && form.totalScore !== '') { + // 处理非0分情况 + const score = parseFloat(form.totalScore.toString()); + if (isNaN(score) || score < 0 || score > 100) { + ElMessage.warning('宿舍总分必须在0-100分之间'); + form.totalScore = 0; + return; + } + // 自动为所有学生设置相同的个人得分(包括0分) + if (roomStudents.value.length > 0) { + roomStudents.value.forEach((student: any) => { + student.student_score = score; + }); + ElMessage.success(`已自动为${roomStudents.value.length}名学生设置个人得分:${score}分`); + } + } else if (form.totalScore === 0 || form.totalScore === '0') { + // 特别处理0分的情况 + if (roomStudents.value.length > 0) { + roomStudents.value.forEach((student: any) => { + student.student_score = 0; + }); + ElMessage.success(`已自动为${roomStudents.value.length}名学生设置个人得分:0分`); + } + } +}; +``` + +#### 修复问题三:房间排序问题 +**问题现象:** 选择宿舍楼名称后,房间名称按照sort序号进行排序返回,但因为每层房间的sort都从0开始编码,导致返回数据把每个楼层的第一个房间先返回。 + +**解决方案:** 修改后端`XgDormRoomServiceImpl.java`中的`buildQueryWrapper`方法,调整排序逻辑为先按楼层再按sort排序。 + +```java +// 修改前 +lqw.orderByAsc(XgDormRoom::getSort); // 第一优先级:按排序号升序 +lqw.orderByAsc(XgDormRoom::getFloorNum); // 第二优先级:按楼层升序 + +// 修改后 +lqw.orderByAsc(XgDormRoom::getFloorNum); // 第一优先级:按楼层升序 +lqw.orderByAsc(XgDormRoom::getSort); // 第二优先级:按排序号升序 +``` + +#### 修复效果 +1. **问题一**:新增时个人扣分直接显示0,用户体验更好 +2. **问题二**:宿舍扣分与个人扣分完全同步,包括0分的情况 +3. **问题三**:房间列表按楼层顺序显示,每层内部按sort排序,符合用户期望 + +#### 涉及文件 +- `web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckDialog.vue` +- `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java` \ No newline at end of file diff --git a/docs/开发文档/API/宿舍楼树形结构API.md b/docs/开发文档/API/宿舍楼树形结构API.md index 3a2384a1..de43ee96 100644 --- a/docs/开发文档/API/宿舍楼树形结构API.md +++ b/docs/开发文档/API/宿舍楼树形结构API.md @@ -2,11 +2,13 @@ ## 接口说明 -本文档描述了获取宿舍楼树形结构数据的API接口,该接口返回校区-宿舍区-宿舍楼的三级树形结构,其中校区和宿舍区节点不可选,只有宿舍楼节点可以选择。 +本文档描述了获取宿舍楼树形结构数据的API接口。**重要变更**:根据业务需求调整,主要接口已从 "校区-宿舍区-宿舍楼" 三级结构改为 "校区-宿舍楼" 二级结构,隐藏宿舍区层级,简化用户操作。 ## 接口定义 -### 获取宿舍楼树形结构 +### 🔄 主要接口(二级结构) + +#### 1. 获取宿舍楼树形结构(推荐使用) **请求URL**:`/sslxx/dormBuilding/buildingTree` @@ -14,6 +16,8 @@ **权限标识**:`sslxx:dormBuilding:list` +**结构说明**:校区-宿舍楼二级结构,校区不可选,只有宿舍楼可以选择 + **请求参数**:无 **响应示例**: @@ -30,24 +34,16 @@ "disabled": true, "children": [ { - "id": 101, - "label": "东区宿舍区", - "nodeType": "area", - "disabled": true, - "children": [ - { - "id": 1001, - "label": "1号楼", - "nodeType": "building", - "disabled": false - }, - { - "id": 1002, - "label": "2号楼", - "nodeType": "building", - "disabled": false - } - ] + "id": 1001, + "label": "1号楼", + "nodeType": "building", + "disabled": false + }, + { + "id": 1002, + "label": "2号楼", + "nodeType": "building", + "disabled": false } ] }, @@ -58,22 +54,92 @@ "disabled": true, "children": [ { - "id": 201, - "label": "西区宿舍区", - "nodeType": "area", + "id": 2001, + "label": "3号楼", + "nodeType": "building", + "disabled": false + }, + { + "id": 2002, + "label": "4号楼", + "nodeType": "building", + "disabled": false + } + ] + } + ] +} +``` + +#### 2. 获取宿舍楼树形结构(基于权限) + +**请求URL**:`/xgss/dormBuildingTree/list` + +**请求方式**:`GET` + +**权限标识**:`xgss:dormBuildingTree:list` + +**结构说明**:校区-宿舍楼二级结构,根据当前用户权限过滤 + +**请求参数**: + +| 参数名 | 类型 | 必填 | 描述 | +|-----------|--------|------|----------------| +| campusId | Long | 否 | 校区ID过滤条件 | +| buildingName | String | 否 | 宿舍楼名称过滤条件 | + +**响应格式**:与上述接口相同 + +#### 3. 获取所有宿舍楼树形结构(不考虑权限) + +**请求URL**:`/xgss/dormBuildingTree/listAll` + +**请求方式**:`GET` + +**权限标识**:`xgss:dormBuildingTree:list` + +**结构说明**:校区-宿舍楼二级结构,返回所有数据,不进行权限过滤 + +**响应格式**:与上述接口相同 + +### 📋 特殊用途接口(三级结构) + +#### 4. 获取完整宿舍楼树形结构(包含宿舍区) + +**请求URL**:`/xgss/dormBuildingTree/fullList` + +**请求方式**:`GET` + +**权限标识**:`xgss:dormBuildingTree:list` + +**结构说明**:校区-宿舍区-宿舍楼三级结构,仅用于特殊需求 + +**响应示例**: + +```json +{ + "code": 200, + "msg": "操作成功", + "data": [ + { + "id": 1, + "label": "东校区", + "type": "campus", + "disabled": true, + "children": [ + { + "id": 101, + "label": "东区宿舍区", + "type": "area", "disabled": true, "children": [ { - "id": 2001, - "label": "3号楼", - "nodeType": "building", - "disabled": false - }, - { - "id": 2002, - "label": "4号楼", - "nodeType": "building", - "disabled": false + "id": 1001, + "label": "1号楼", + "type": "building", + "disabled": false, + "buildingFloors": 6, + "buildingType": "1" } ] } @@ -85,7 +151,7 @@ ## 数据结构 -### BuildingTreeSelectVo +### BuildingTreeSelectVo(二级结构) 树形结构节点对象: @@ -93,7 +159,7 @@ |------------|-------------------|----------------------------------------| | id | Long | 节点ID | | label | String | 节点名称 | -| nodeType | String | 节点类型:campus(校区)、area(宿舍区)、building(宿舍楼) | +| nodeType | String | 节点类型:campus(校区)、building(宿舍楼) | | disabled | Boolean | 是否禁用选择 | | children | List | 子节点列表 | | createBy | Long | 创建者 | @@ -101,6 +167,20 @@ | updateBy | Long | 更新者 | | updateTime | Date | 更新时间 | +### DormBuildingTreeVo(完整结构) + +完整三级结构节点对象: + +| 字段名 | 类型 | 描述 | +|-----------------|-------------------|----------------------------------------| +| id | Long | 节点ID | +| label | String | 节点名称 | +| type | String | 节点类型:campus(校区)、area(宿舍区)、building(宿舍楼) | +| disabled | Boolean | 是否禁用选择 | +| children | List | 子节点列表 | +| buildingFloors | Integer | 宿舍楼层数(仅宿舍楼节点) | +| buildingType | String | 宿舍楼类型(仅宿舍楼节点) | + ## 使用方法 ### 前端使用示例 @@ -114,7 +194,7 @@ import type { BuildingTreeSelectVo } from '@/api/sslxx/dormBuilding/types'; // 在组件的setup函数中 const treeData = ref([]); -// 加载宿舍楼树形数据 +// 加载宿舍楼树形数据(推荐使用二级结构) const loadBuildingTree = async () => { try { const res = await getBuildingTree(); @@ -155,20 +235,34 @@ loadBuildingTree(); ## 特殊说明 -1. 接口返回的树形结构中,校区和宿舍区节点已设置 `disabled=true`,前端无需额外处理即可实现只能选择宿舍楼节点的效果 -2. 使用 `check-strictly=true` 可以确保树形选择器的选中状态不会级联影响父子节点 -3. 该接口已支持管理员分配宿舍楼管理范围的业务需求 +### ⚠️ 重要变更说明 + +1. **主要接口结构调整**:为简化用户操作,去除宿舍区概念,主要接口已改为二级结构 +2. **向后兼容**:保留了 `fullList` 接口供需要完整三级结构的特殊场景使用 +3. **业务影响**:前端界面将不再显示宿舍区层级,用户直接在校区下选择宿舍楼 + +### 📋 使用建议 + +1. **推荐使用二级结构接口**:适用于绝大多数业务场景 +2. **校区节点设置为不可选**:确保用户只能选择宿舍楼 +3. **使用权限过滤接口**:根据用户权限显示相应的宿舍楼范围 ## 相关API接口 - [宿舍管理员授权API](./宿舍管理员授权API.md) +- [宿舍床位分配管理API](./宿舍床位分配管理API.md) ## 数据库关系 - `xg_dorm_building` 表通过 `campus_id` 字段关联 `xg_dorm_campus` 表 -- `xg_dorm_building` 表通过 `area_id` 字段关联 `xg_dorm_area` 表 +- `xg_dorm_building` 表通过 `area_id` 字段关联 `xg_dorm_area` 表(数据库保留,UI隐藏) - `xg_dorm_area` 表通过 `campus_id` 字段关联 `xg_dorm_campus` 表 +## 版本历史 + +- **2025-01-29**: 接口结构重大调整,主要接口从三级改为二级结构 +- **2025-07-20**: 初始版本,三级结构 + ## 最后更新时间 -2025-07-20 \ No newline at end of file +2025-01-29 \ No newline at end of file diff --git a/docs/设计文档/database/hzdyjs_xg0820.sql b/docs/设计文档/database/hzdyjs_xg0820.sql index 052f60d3..f8fa8b63 100644 --- a/docs/设计文档/database/hzdyjs_xg0820.sql +++ b/docs/设计文档/database/hzdyjs_xg0820.sql @@ -4376,7 +4376,7 @@ CREATE TABLE `xg_dorm_hygiene_check` ( `area_name` varchar(100) DEFAULT NULL COMMENT '宿舍区名称', `campus_id` bigint(20) DEFAULT NULL COMMENT '校区ID,关联xg_dorm_campus.id', `campus_name` varchar(100) DEFAULT NULL COMMENT '校区名称', - `total_score` decimal(5,2) DEFAULT NULL COMMENT '宿舍总分(0-100分)', + `total_score` decimal(5,2) DEFAULT NULL COMMENT '宿舍扣分(0-100分)', `hygiene_grade` char(1) DEFAULT NULL COMMENT '卫生等级(A优秀 B良好 C合格 D不合格)', `attachment_ids` text COMMENT '附件OSS ID列表,逗号分隔', `check_summary` text COMMENT '检查总结', @@ -4422,7 +4422,7 @@ CREATE TABLE `xg_dorm_hygiene_student_score` ( `college_name` varchar(100) DEFAULT NULL COMMENT '学院名称', `bed_id` bigint(20) DEFAULT NULL COMMENT '床位ID,关联xg_dorm_bed.id', `bed_code` varchar(50) DEFAULT NULL COMMENT '床位编号', - `student_score` decimal(5,2) DEFAULT NULL COMMENT '学生个人得分(0-100分)', + `student_score` decimal(5,2) DEFAULT NULL COMMENT '学生个人扣分(0-100分)', `attendance_status` char(1) DEFAULT '1' COMMENT '出勤状态(1在寝 2外出 3请假 4其他)', `student_grade` char(1) DEFAULT NULL COMMENT '学生评级(A优秀 B良好 C合格 D不合格)', `is_responsible_person` tinyint(1) DEFAULT '0' COMMENT '是否为寝室长(0否 1是)', diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckDialog.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckDialog.vue index 9a8d40f6..e03d4962 100644 --- a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckDialog.vue +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckDialog.vue @@ -1,7 +1,7 @@ + + From 582031ac141b875340793a8af70c2ba04f963c0b Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sun, 7 Sep 2025 00:34:23 +0800 Subject: [PATCH 15/29] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xg/controller/XgDormRoomController.java | 29 ++ .../xg/service/IXgDormRoomService.java | 10 + .../service/impl/XgDormRoomServiceImpl.java | 280 ++++++++++++++++++ .../dormHygieneCheck/index.vue | 4 +- web/src/views/xg/xgss/xsssfpejxy/index.vue | 101 ++++++- 5 files changed, 420 insertions(+), 4 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormRoomController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormRoomController.java index a6a8337d..78437195 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormRoomController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormRoomController.java @@ -1,6 +1,7 @@ package org.dromara.xg.controller; import java.util.List; +import java.util.HashMap; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; @@ -21,6 +22,7 @@ import org.dromara.xg.domain.vo.XgDormRoomVo; import org.dromara.xg.domain.bo.XgDormRoomBo; import org.dromara.xg.service.IXgDormRoomService; import org.dromara.common.mybatis.core.page.TableDataInfo; +import java.util.Map; /** * 宿舍房间信息 @@ -77,6 +79,33 @@ public class XgDormRoomController extends BaseController { ExcelUtil.exportExcel(list, "宿舍房间信息", XgDormRoomVo.class, response); } + /** + * 导出住宿清单(宿舍卡片式布局) + */ + @SaCheckPermission("xgss:dormRoom:export") + @Log(title = "住宿清单", businessType = BusinessType.EXPORT) + @PostMapping("/exportAccommodationList") + public void exportAccommodationList( + @RequestParam(required = false) Long buildingId, + @RequestParam(required = false) String buildingName, + @RequestParam(required = false) String buildingType, + @RequestParam(required = false) String accommodationData, + HttpServletResponse response) { + + // 构建参数Map + Map params = new HashMap<>(); + if (buildingId != null) + params.put("buildingId", buildingId); + if (buildingName != null) + params.put("buildingName", buildingName); + if (buildingType != null) + params.put("buildingType", buildingType); + if (accommodationData != null) + params.put("accommodationData", accommodationData); + + xgDormRoomService.exportAccommodationList(params, response); + } + /** * 获取宿舍房间信息详细信息 * diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormRoomService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormRoomService.java index f5b8cd41..4e64135f 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormRoomService.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormRoomService.java @@ -7,6 +7,8 @@ import org.dromara.common.mybatis.core.page.PageQuery; import java.util.Collection; import java.util.List; +import java.util.Map; +import jakarta.servlet.http.HttpServletResponse; /** * 宿舍房间信息Service接口 @@ -84,4 +86,12 @@ public interface IXgDormRoomService { * @return 是否删除成功 */ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 导出住宿清单(宿舍卡片式布局) + * + * @param params 导出参数 + * @param response HTTP响应对象 + */ + void exportAccommodationList(Map params, HttpServletResponse response); } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java index e254bb53..36502a5e 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java @@ -26,6 +26,24 @@ import java.util.Map; import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.util.CellRangeAddress; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dromara.xg.domain.XgDormBed; +import org.dromara.xg.mapper.XgDormBedMapper; +import java.io.IOException; +import java.io.OutputStream; /** * 宿舍房间信息Service业务层处理 @@ -41,6 +59,7 @@ public class XgDormRoomServiceImpl implements IXgDormRoomService { private final XgDormRoomMapper baseMapper; private final XgDormBedStudentMapper xgDormBedStudentMapper; private final XgSysDeptMapper xgSysDeptMapper; + private final XgDormBedMapper xgDormBedMapper; /** * 查询宿舍房间信息 @@ -312,4 +331,265 @@ public class XgDormRoomServiceImpl implements IXgDormRoomService { return Set.of(); } } + + /** + * 导出住宿清单(宿舍卡片式布局) + * + * @param params 导出参数 + * @param response HTTP响应对象 + */ + @Override + public void exportAccommodationList(Map params, HttpServletResponse response) { + try { + // 解析参数 + Long buildingId = Long.valueOf(params.get("buildingId").toString()); + String buildingName = params.get("buildingName").toString(); + String buildingType = params.get("buildingType").toString(); + String buildingTypeText = "1".equals(buildingType) ? "男生楼" : "女生楼"; + + // 解析JSON字符串 + String accommodationDataJson = params.get("accommodationData").toString(); + ObjectMapper objectMapper = new ObjectMapper(); + @SuppressWarnings("unchecked") + Map accommodationData = objectMapper.readValue(accommodationDataJson, Map.class); + + @SuppressWarnings("unchecked") + Map buildingInfo = (Map) accommodationData.get("buildingInfo"); + + @SuppressWarnings("unchecked") + List> floorDataList = (List>) accommodationData.get("floorData"); + + // 创建Excel工作簿 + try (XSSFWorkbook workbook = new XSSFWorkbook()) { + XSSFSheet sheet = workbook.createSheet(buildingName + "住宿清单"); + + // 创建样式 + CellStyle titleStyle = createTitleStyle(workbook); + CellStyle headerStyle = createHeaderStyle(workbook); + CellStyle dataStyle = createDataStyle(workbook); + CellStyle roomNameStyle = createRoomNameStyle(workbook); + + int currentRow = 0; + + // 添加标题 + XSSFRow titleRow = sheet.createRow(currentRow++); + XSSFCell titleCell = titleRow.createCell(0); + titleCell.setCellValue(buildingName + " " + buildingTypeText + " 住宿清单"); + titleCell.setCellStyle(titleStyle); + sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 19)); // 合并20列 + currentRow++; // 空行 + + // 处理每个楼层 + for (Map floorData : floorDataList) { + Integer floorNum = (Integer) floorData.get("floorNum"); + @SuppressWarnings("unchecked") + List> rooms = (List>) floorData.get("rooms"); + + if (rooms == null || rooms.isEmpty()) { + continue; + } + + // 添加楼层标题 + XSSFRow floorRow = sheet.createRow(currentRow++); + XSSFCell floorCell = floorRow.createCell(0); + floorCell.setCellValue("第" + floorNum + "层"); + floorCell.setCellStyle(headerStyle); + sheet.addMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, 0, 19)); + currentRow++; // 空行 + + // 按4个宿舍一行进行分组处理 + for (int i = 0; i < rooms.size(); i += 4) { + int endIndex = Math.min(i + 4, rooms.size()); + List> roomGroup = rooms.subList(i, endIndex); + + // 处理当前组的宿舍 + currentRow = processRoomGroup(sheet, roomGroup, currentRow, roomNameStyle, headerStyle, + dataStyle); + currentRow += 2; // 组间空行 + } + } + + // 设置列宽 (4个宿舍,每个宿舍4列,宿舍间3个空列,共19列) + for (int i = 0; i < 20; i++) { + sheet.setColumnWidth(i, 15 * 256); // 15个字符宽度 + } + + // 设置响应头 + String fileName = buildingName + "_住宿清单_" + System.currentTimeMillis() + ".xlsx"; + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", + "attachment; filename=\"" + new String(fileName.getBytes("UTF-8"), "ISO-8859-1") + "\""); + + // 输出Excel文件 + try (OutputStream outputStream = response.getOutputStream()) { + workbook.write(outputStream); + outputStream.flush(); + } + } + + } catch (Exception e) { + log.error("导出住宿清单失败", e); + throw new RuntimeException("导出住宿清单失败", e); + } + } + + /** + * 处理一组宿舍(最多4个) + */ + private int processRoomGroup(XSSFSheet sheet, List> roomGroup, int startRow, + CellStyle roomNameStyle, CellStyle headerStyle, CellStyle dataStyle) { + int currentRow = startRow; + + // 宿舍名称行 + XSSFRow roomNameRow = sheet.createRow(currentRow++); + for (int i = 0; i < roomGroup.size(); i++) { + Map room = roomGroup.get(i); + String roomName = (String) room.get("roomName"); + + // 每个宿舍占4列,宿舍之间增加1列间隔 (i * 5) + int startCol = i * 5; + + // 为整个合并区域的所有单元格创建并设置样式 + for (int col = startCol; col <= startCol + 3; col++) { + XSSFCell cell = roomNameRow.createCell(col); + cell.setCellStyle(roomNameStyle); + if (col == startCol) { + cell.setCellValue(roomName); // 只在第一个单元格设置值 + } + } + + sheet.addMergedRegion(new CellRangeAddress(currentRow - 1, currentRow - 1, startCol, startCol + 3)); + } + + // 表头行 + XSSFRow headerRow = sheet.createRow(currentRow++); + String[] headers = { "床位", "姓名", "班级", "备注" }; + for (int i = 0; i < roomGroup.size(); i++) { + for (int j = 0; j < headers.length; j++) { + // 每个宿舍占4列,宿舍之间增加1列间隔 (i * 5) + XSSFCell headerCell = headerRow.createCell(i * 5 + j); + headerCell.setCellValue(headers[j]); + headerCell.setCellStyle(headerStyle); + } + } + + // 计算最大床位数 + int maxBeds = roomGroup.stream() + .mapToInt(room -> { + @SuppressWarnings("unchecked") + List> beds = (List>) room.get("beds"); + return beds != null ? beds.size() : 0; + }) + .max() + .orElse(0); + + // 床位数据行 + for (int bedIndex = 0; bedIndex < maxBeds; bedIndex++) { + XSSFRow dataRow = sheet.createRow(currentRow++); + + for (int roomIndex = 0; roomIndex < roomGroup.size(); roomIndex++) { + Map room = roomGroup.get(roomIndex); + @SuppressWarnings("unchecked") + List> beds = (List>) room.get("beds"); + + if (beds != null && bedIndex < beds.size()) { + Map bed = beds.get(bedIndex); + + // 每个宿舍占4列,宿舍之间增加1列间隔 (roomIndex * 5) + // 床位 + XSSFCell bedNameCell = dataRow.createCell(roomIndex * 5); + bedNameCell.setCellValue((String) bed.get("bedName")); + bedNameCell.setCellStyle(dataStyle); + + // 姓名 + XSSFCell studentNameCell = dataRow.createCell(roomIndex * 5 + 1); + studentNameCell.setCellValue((String) bed.get("studentName")); + studentNameCell.setCellStyle(dataStyle); + + // 班级 + XSSFCell classNameCell = dataRow.createCell(roomIndex * 5 + 2); + classNameCell.setCellValue((String) bed.get("className")); + classNameCell.setCellStyle(dataStyle); + + // 备注 + XSSFCell remarkCell = dataRow.createCell(roomIndex * 5 + 3); + remarkCell.setCellValue((String) bed.get("bedRemark")); + remarkCell.setCellStyle(dataStyle); + } else { + // 空床位 + for (int j = 0; j < 4; j++) { + XSSFCell emptyCell = dataRow.createCell(roomIndex * 5 + j); + emptyCell.setCellValue(""); + emptyCell.setCellStyle(dataStyle); + } + } + } + } + + return currentRow; + } + + /** + * 创建标题样式 + */ + private CellStyle createTitleStyle(XSSFWorkbook workbook) { + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setBold(true); + font.setFontHeightInPoints((short) 16); + style.setFont(font); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + return style; + } + + /** + * 创建表头样式 + */ + private CellStyle createHeaderStyle(XSSFWorkbook workbook) { + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setBold(true); + font.setFontHeightInPoints((short) 12); + style.setFont(font); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + return style; + } + + /** + * 创建数据样式 + */ + private CellStyle createDataStyle(XSSFWorkbook workbook) { + CellStyle style = workbook.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + return style; + } + + /** + * 创建宿舍名称样式 + */ + private CellStyle createRoomNameStyle(XSSFWorkbook workbook) { + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setBold(true); + font.setFontHeightInPoints((short) 14); + style.setFont(font); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + return style; + } } diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue index 40950620..fe402d14 100644 --- a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue @@ -76,11 +76,11 @@ >修改 - + diff --git a/web/src/views/xg/xgss/xsssfpejxy/index.vue b/web/src/views/xg/xgss/xsssfpejxy/index.vue index 521d752a..3e7a3430 100644 --- a/web/src/views/xg/xgss/xsssfpejxy/index.vue +++ b/web/src/views/xg/xgss/xsssfpejxy/index.vue @@ -135,6 +135,7 @@ 分配给二级学院 批量收回宿舍 批量退宿床位 + 导出住宿清单 import { ref, computed, reactive, onMounted, onUnmounted, nextTick, watch, getCurrentInstance } from 'vue'; -import { InfoFilled, House, Avatar, UserFilled } from '@element-plus/icons-vue'; +import { InfoFilled, House, Avatar, UserFilled, Download } from '@element-plus/icons-vue'; import type { DormTreeNode, BedInfo } from '@/api/xg/xgss/xsssfjgl/types'; import type { DormRoomVO } from '@/api/xg/xgss/dormRoom/types'; import type { DormBedDetailVO } from '@/api/xg/xgss/dormBed/types'; @@ -376,7 +377,7 @@ import BedDialog from './components/BedDialog.vue'; import CollegeAllocationDialog from './components/CollegeAllocationDialog.vue'; import { batchRecallDormBeds, classTeacherStayawaySelectedBeds } from '@/api/xg/xgss/dormBedStudent/dormBedStudent'; -// const { proxy } = getCurrentInstance(); +const { proxy } = getCurrentInstance() as any; // 组件状态 const dormTreeRef = ref(); @@ -775,6 +776,102 @@ const handleBatchStayaway = async () => { } }; +/** 导出住宿清单 */ +const handleExportAccommodationList = async () => { + if (!selectedBuilding.value) { + ElMessage.warning('请先选择宿舍楼'); + return; + } + + if (!roomList.value || roomList.value.length === 0) { + ElMessage.warning('当前宿舍楼没有房间数据'); + return; + } + + try { + // 创建Excel工作簿数据 + const accommodationData = generateAccommodationExcelData(); + + // 使用proxy.download方法导出Excel + const filename = `${selectedBuilding.value.label}_住宿清单_${new Date().getTime()}.xlsx`; + + proxy?.download( + 'xgss/dormRoom/exportAccommodationList', + { + buildingId: selectedBuilding.value.id, + buildingName: selectedBuilding.value.label, + buildingType: selectedBuilding.value.buildingType, + accommodationData: JSON.stringify(accommodationData) // 转换为JSON字符串 + }, + filename + ); + + ElMessage.success('导出任务已提交'); + } catch (error) { + console.error('导出住宿清单失败:', error); + ElMessage.error('导出失败,请重试'); + } +}; + +/** 生成住宿清单Excel数据 */ +const generateAccommodationExcelData = () => { + // 按楼层分组房间数据 + const floorGroups = new Map(); + + roomList.value.forEach((room) => { + const floorNum = room.floorNum || 1; + if (!floorGroups.has(floorNum)) { + floorGroups.set(floorNum, []); + } + + // 为每个房间准备数据,包括床位信息 + const roomData = { + roomId: room.id, + roomName: room.roomName, + floorNum: room.floorNum, + roomType: room.roomType, + roomStatus: room.roomStatus, + remark: room.remark || '', + beds: room.children + ? room.children.map((bed: any) => ({ + bedId: bed.id, + bedName: bed.bedName, + bedType: bed.bedType, + bedStatus: bed.bedStatus, + studentNo: bed.studentNo || '', + studentName: bed.studentName || '', + collegeName: bed.collegeName || '', + majorName: bed.majorName || '', + className: bed.className || '', + studentGender: bed.studentGender, + bedRemark: bed.bedRemark || '' + })) + : [] + }; + + floorGroups.get(floorNum)!.push(roomData); + }); + + // 按楼层排序 + const sortedFloors = Array.from(floorGroups.keys()).sort((a, b) => a - b); + + // 构建Excel数据结构 + const excelData = { + buildingInfo: { + buildingId: selectedBuilding.value?.id, + buildingName: selectedBuilding.value?.label, + buildingType: selectedBuilding.value?.buildingType, + buildingTypeText: selectedBuilding.value?.buildingType === '1' ? '男生楼' : '女生楼' + }, + floorData: sortedFloors.map((floorNum) => ({ + floorNum, + rooms: floorGroups.get(floorNum) || [] + })) + }; + + return excelData; +}; + /** 全部展开 */ const expandAll = async () => { if (expandAllLoading.value || selectionLoading.value || !roomList.value?.length) return; From cf21360ac14dadf86cf995a47f4dd374de8e57dc Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sun, 7 Sep 2025 07:43:10 +0800 Subject: [PATCH 16/29] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/XskhDisciplineRecordMapper.java | 84 ++++++++++--------- .../impl/XskhDisciplineRecordServiceImpl.java | 21 +++++ .../mapper/XskhDisciplineRecordMapper.xml | 9 ++ web/src/views/xskh/disciplineType/index.vue | 6 +- 4 files changed, 79 insertions(+), 41 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhDisciplineRecordMapper.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhDisciplineRecordMapper.java index fe285aaa..3557020d 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhDisciplineRecordMapper.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhDisciplineRecordMapper.java @@ -19,46 +19,54 @@ import java.util.Map; */ public interface XskhDisciplineRecordMapper extends BaseMapperPlus { - /** - * 查询学生选项列表(用于下拉框) - * - * @param keyword 搜索关键词(学号或姓名) - * @return 学生选项列表 - */ - List selectStudentOptions(String keyword); + /** + * 查询学生选项列表(用于下拉框) + * + * @param keyword 搜索关键词(学号或姓名) + * @return 学生选项列表 + */ + List selectStudentOptions(String keyword); - /** - * 查询班主任班级的违纪处分记录列表(班主任专用) - * - * @param page 分页参数 - * @param bo 查询条件 - * @param classTeacherId 班主任ID - * @return 分页查询结果 - */ - Page selectClassTeacherVoPage(@Param("page") Page page, - @Param("bo") XskhDisciplineRecordBo bo, - @Param("classTeacherId") Long classTeacherId); + /** + * 查询班主任班级的违纪处分记录列表(班主任专用) + * + * @param page 分页参数 + * @param bo 查询条件 + * @param classTeacherId 班主任ID + * @return 分页查询结果 + */ + Page selectClassTeacherVoPage(@Param("page") Page page, + @Param("bo") XskhDisciplineRecordBo bo, + @Param("classTeacherId") Long classTeacherId); - /** - * 获取指标关联的违纪处分类型和奖励类型 - * - * @param indicatorId 指标ID - * @return 包含disciplineTypeIds和rewardTypeIds的Map - */ - Map selectIndicatorTypes(@Param("indicatorId") Long indicatorId); + /** + * 获取指标关联的违纪处分类型和奖励类型 + * + * @param indicatorId 指标ID + * @return 包含disciplineTypeIds和rewardTypeIds的Map + */ + Map selectIndicatorTypes(@Param("indicatorId") Long indicatorId); - /** - * 获取学生在指定时间范围内的违纪处分记录 - * - * @param studentId 学生ID - * @param disciplineTypeIds 违纪处分类型ID列表 - * @param startTime 开始时间 - * @param endTime 结束时间 - * @return 违纪处分记录列表 - */ - List selectStudentDisciplineRecords(@Param("studentId") Long studentId, - @Param("disciplineTypeIds") List disciplineTypeIds, - @Param("startTime") String startTime, - @Param("endTime") String endTime); + /** + * 获取学生在指定时间范围内的违纪处分记录 + * + * @param studentId 学生ID + * @param disciplineTypeIds 违纪处分类型ID列表 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 违纪处分记录列表 + */ + List selectStudentDisciplineRecords(@Param("studentId") Long studentId, + @Param("disciplineTypeIds") List disciplineTypeIds, + @Param("startTime") String startTime, + @Param("endTime") String endTime); + + /** + * 根据违纪记录ID逻辑删除宿舍卫生检查违纪关联记录 + * + * @param disciplineRecordId 违纪记录ID + * @return 删除的记录数量 + */ + int deleteHygieneCheckViolationByDisciplineRecordId(@Param("disciplineRecordId") Long disciplineRecordId); } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineRecordServiceImpl.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineRecordServiceImpl.java index d7cba428..9f12aaf5 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineRecordServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineRecordServiceImpl.java @@ -17,6 +17,8 @@ import org.dromara.xskh.domain.vo.XskhStudentOptionVo; import org.dromara.xskh.mapper.XskhDisciplineRecordMapper; import org.dromara.xskh.service.IXskhDisciplineRecordService; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import lombok.extern.slf4j.Slf4j; import java.util.Collection; import java.util.List; @@ -30,6 +32,7 @@ import java.util.stream.Collectors; * @author ruoyi * @date 2025-01-03 */ +@Slf4j @RequiredArgsConstructor @Service public class XskhDisciplineRecordServiceImpl implements IXskhDisciplineRecordService { @@ -132,10 +135,28 @@ public class XskhDisciplineRecordServiceImpl implements IXskhDisciplineRecordSer * 批量删除学生违纪处分记录 */ @Override + @Transactional(rollbackFor = Exception.class) public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if (isValid) { // TODO 做一些业务上的校验,判断是否需要校验 } + + // 在删除违纪记录前,先删除相关的宿舍卫生检查违纪关联记录 + for (Long id : ids) { + try { + // 查询违纪记录,判断是否为宿舍卫生检查来源 + XskhDisciplineRecordVo disciplineRecord = baseMapper.selectVoById(id); + if (disciplineRecord != null && "1".equals(disciplineRecord.getViolationSource())) { + // 删除宿舍卫生检查违纪关联记录 + int deletedCount = baseMapper.deleteHygieneCheckViolationByDisciplineRecordId(id); + log.info("删除违纪记录ID: {} 对应的宿舍卫生检查违纪关联记录,删除数量: {}", id, deletedCount); + } + } catch (Exception e) { + log.error("删除违纪记录ID: {} 对应的宿舍卫生检查违纪关联记录失败: {}", id, e.getMessage(), e); + throw new RuntimeException("删除宿舍卫生检查违纪关联记录失败", e); + } + } + return baseMapper.deleteBatchIds(ids) > 0; } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/XskhDisciplineRecordMapper.xml b/backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/XskhDisciplineRecordMapper.xml index f22a2fb5..9713a923 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/XskhDisciplineRecordMapper.xml +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/XskhDisciplineRecordMapper.xml @@ -125,4 +125,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by create_time desc + + + update xg_hygiene_check_violation + set deleted = 1, + update_time = now() + where discipline_record_id = #{disciplineRecordId} + and deleted = 0 + + diff --git a/web/src/views/xskh/disciplineType/index.vue b/web/src/views/xskh/disciplineType/index.vue index bd6f9dd8..c89841bd 100644 --- a/web/src/views/xskh/disciplineType/index.vue +++ b/web/src/views/xskh/disciplineType/index.vue @@ -184,9 +184,9 @@ -
- 录入违纪记录时,扣分必须在此范围内 -
+ From f6257774844d5e129086071ce5b748374745db14 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sun, 7 Sep 2025 08:38:04 +0800 Subject: [PATCH 17/29] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xskh/domain/XskhDisciplineType.java | 10 +++ .../dromara/xskh/domain/XskhRewardType.java | 10 +++ .../xskh/domain/bo/XskhDisciplineTypeBo.java | 11 +++ .../xskh/domain/bo/XskhRewardTypeBo.java | 11 +++ .../xskh/domain/vo/XskhDisciplineTypeVo.java | 13 ++++ .../xskh/domain/vo/XskhRewardTypeVo.java | 16 ++++- .../学生考核管理/学生考核管理数据库模型.sql | 4 ++ .../disciplineType/disciplineType/types.ts | 20 ++++++ .../api/xskh/rewardType/rewardType/types.ts | 30 ++++++++ web/src/views/xskh/disciplineType/index.vue | 63 +++++++++++++++-- web/src/views/xskh/rewardType/index.vue | 69 ++++++++++++++++++- 11 files changed, 247 insertions(+), 10 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhDisciplineType.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhDisciplineType.java index c27e10dd..610a4a9b 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhDisciplineType.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhDisciplineType.java @@ -57,6 +57,16 @@ public class XskhDisciplineType extends TenantEntity { */ private java.math.BigDecimal maxScore; + /** + * 是否均化(0-否 1-是) + */ + private String isEqualized; + + /** + * 均化阈值 + */ + private java.math.BigDecimal equalizedThreshold; + /** * 类型说明 */ diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhRewardType.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhRewardType.java index 94eb5c16..bc521373 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhRewardType.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhRewardType.java @@ -42,6 +42,16 @@ public class XskhRewardType extends TenantEntity { */ private String description; + /** + * 是否均化(0-否 1-是) + */ + private String isEqualized; + + /** + * 均化阈值 + */ + private java.math.BigDecimal equalizedThreshold; + /** * 状态(0-正常 1-停用) */ diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhDisciplineTypeBo.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhDisciplineTypeBo.java index 5c862cef..c8190fa5 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhDisciplineTypeBo.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhDisciplineTypeBo.java @@ -67,6 +67,17 @@ public class XskhDisciplineTypeBo extends BaseEntity { @DecimalMax(value = "100.00", message = "最高扣分不能大于100", groups = { AddGroup.class, EditGroup.class }) private java.math.BigDecimal maxScore; + /** + * 是否均化(0-否 1-是) + */ + private String isEqualized; + + /** + * 均化阈值 + */ + @DecimalMin(value = "0.00", message = "均化阈值不能小于0", groups = { AddGroup.class, EditGroup.class }) + private java.math.BigDecimal equalizedThreshold; + /** * 类型说明 */ diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhRewardTypeBo.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhRewardTypeBo.java index 134ec50b..27bdfb66 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhRewardTypeBo.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhRewardTypeBo.java @@ -43,6 +43,17 @@ public class XskhRewardTypeBo extends BaseEntity { */ private String description; + /** + * 是否均化(0-否 1-是) + */ + private String isEqualized; + + /** + * 均化阈值 + */ + @DecimalMin(value = "0.00", message = "均化阈值不能小于0", groups = { AddGroup.class, EditGroup.class }) + private java.math.BigDecimal equalizedThreshold; + /** * 状态(0-正常 1-停用) */ diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhDisciplineTypeVo.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhDisciplineTypeVo.java index a3bb97cb..3d9fb5f9 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhDisciplineTypeVo.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhDisciplineTypeVo.java @@ -70,6 +70,19 @@ public class XskhDisciplineTypeVo implements Serializable { @ExcelProperty(value = "最高扣分") private java.math.BigDecimal maxScore; + /** + * 是否均化(0-否 1-是) + */ + @ExcelProperty(value = "是否均化", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_yes_no") + private String isEqualized; + + /** + * 均化阈值 + */ + @ExcelProperty(value = "均化阈值") + private java.math.BigDecimal equalizedThreshold; + /** * 类型说明 */ diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhRewardTypeVo.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhRewardTypeVo.java index fd17fdbd..d644a9ab 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhRewardTypeVo.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhRewardTypeVo.java @@ -12,8 +12,6 @@ import java.io.Serial; import java.io.Serializable; import java.util.Date; - - /** * 学生奖励类型视图对象 xskh_reward_type * @@ -52,6 +50,19 @@ public class XskhRewardTypeVo implements Serializable { @ExcelProperty(value = "类型说明") private String description; + /** + * 是否均化(0-否 1-是) + */ + @ExcelProperty(value = "是否均化", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_yes_no") + private String isEqualized; + + /** + * 均化阈值 + */ + @ExcelProperty(value = "均化阈值") + private java.math.BigDecimal equalizedThreshold; + /** * 状态(0-正常 1-停用) */ @@ -71,5 +82,4 @@ public class XskhRewardTypeVo implements Serializable { @ExcelProperty(value = "删除标志(0-未删除 1-已删除)") private Long deleted; - } diff --git a/docs/设计文档/database/学生考核管理/学生考核管理数据库模型.sql b/docs/设计文档/database/学生考核管理/学生考核管理数据库模型.sql index dc094e82..17018f26 100644 --- a/docs/设计文档/database/学生考核管理/学生考核管理数据库模型.sql +++ b/docs/设计文档/database/学生考核管理/学生考核管理数据库模型.sql @@ -15,6 +15,8 @@ CREATE TABLE `xskh_discipline_type` ( `area_type` char(1) DEFAULT '1' COMMENT '区域类型(1-宿舍区违纪 2-教学区违纪)' , `min_score` decimal(5,2) DEFAULT 0.00 COMMENT '最低扣分', `max_score` decimal(5,2) DEFAULT 100.00 COMMENT '最高扣分', + `is_equalized` char(1) DEFAULT '0' COMMENT '是否均化(0-否 1-是)', + `equalized_threshold` decimal(10,2) DEFAULT NULL COMMENT '均化阈值', `status` char(1) DEFAULT '0' COMMENT '状态(0-正常 1-停用)', `sort_order` int(4) DEFAULT 0 COMMENT '排序号', `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', @@ -134,6 +136,8 @@ CREATE TABLE `xskh_reward_type` ( `type_code` varchar(50) NOT NULL COMMENT '类型编码', `type_name` varchar(100) NOT NULL COMMENT '类型名称', `description` varchar(500) DEFAULT NULL COMMENT '类型说明', + `is_equalized` char(1) DEFAULT '0' COMMENT '是否均化(0-否 1-是)', + `equalized_threshold` decimal(10,2) DEFAULT NULL COMMENT '均化阈值', `status` char(1) DEFAULT '0' COMMENT '状态(0-正常 1-停用)', `sort_order` int(4) DEFAULT 0 COMMENT '排序号', `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', diff --git a/web/src/api/xskh/disciplineType/disciplineType/types.ts b/web/src/api/xskh/disciplineType/disciplineType/types.ts index da4e7af2..be6edc8f 100644 --- a/web/src/api/xskh/disciplineType/disciplineType/types.ts +++ b/web/src/api/xskh/disciplineType/disciplineType/types.ts @@ -34,6 +34,16 @@ export interface DisciplineTypeVO { */ maxScore: number; + /** + * 是否均化(0-否 1-是) + */ + isEqualized: string; + + /** + * 均化阈值 + */ + equalizedThreshold: number; + /** * 类型说明 */ @@ -91,6 +101,16 @@ export interface DisciplineTypeForm extends BaseEntity { */ maxScore?: number; + /** + * 是否均化(0-否 1-是) + */ + isEqualized?: string; + + /** + * 均化阈值 + */ + equalizedThreshold?: number; + /** * 类型说明 */ diff --git a/web/src/api/xskh/rewardType/rewardType/types.ts b/web/src/api/xskh/rewardType/rewardType/types.ts index cab8b0d8..28fbabf1 100644 --- a/web/src/api/xskh/rewardType/rewardType/types.ts +++ b/web/src/api/xskh/rewardType/rewardType/types.ts @@ -19,6 +19,16 @@ export interface RewardTypeVO { */ description: string; + /** + * 是否均化(0-否 1-是) + */ + isEqualized: string; + + /** + * 均化阈值 + */ + equalizedThreshold: number; + /** * 状态(0-正常 1-停用) */ @@ -56,6 +66,16 @@ export interface RewardTypeForm extends BaseEntity { */ description?: string; + /** + * 是否均化(0-否 1-是) + */ + isEqualized?: string; + + /** + * 均化阈值 + */ + equalizedThreshold?: number; + /** * 状态(0-正常 1-停用) */ @@ -88,6 +108,16 @@ export interface RewardTypeQuery extends PageQuery { */ description?: string; + /** + * 是否均化(0-否 1-是) + */ + isEqualized?: string; + + /** + * 均化阈值 + */ + equalizedThreshold?: number; + /** * 状态(0-正常 1-停用) */ diff --git a/web/src/views/xskh/disciplineType/index.vue b/web/src/views/xskh/disciplineType/index.vue index c89841bd..300f291a 100644 --- a/web/src/views/xskh/disciplineType/index.vue +++ b/web/src/views/xskh/disciplineType/index.vue @@ -3,7 +3,7 @@
- + @@ -113,6 +113,17 @@ + + + + + + + + diff --git a/web/src/views/cw/projectApproval/index.vue b/web/src/views/cw/projectApproval/index.vue new file mode 100644 index 00000000..855244d1 --- /dev/null +++ b/web/src/views/cw/projectApproval/index.vue @@ -0,0 +1,1217 @@ + + + + + diff --git a/web/src/views/cw/projectApproval/pending.vue b/web/src/views/cw/projectApproval/pending.vue new file mode 100644 index 00000000..271a6626 --- /dev/null +++ b/web/src/views/cw/projectApproval/pending.vue @@ -0,0 +1,433 @@ + + + + + diff --git a/web/src/views/cw/projectAuditor/BUG_FIX_REPORT.md b/web/src/views/cw/projectAuditor/BUG_FIX_REPORT.md new file mode 100644 index 00000000..3c76eca5 --- /dev/null +++ b/web/src/views/cw/projectAuditor/BUG_FIX_REPORT.md @@ -0,0 +1,150 @@ +# 项目审批人批量添加功能Bug修复报告 + +## 🐛 问题描述 +用户反馈:批量添加审批人时,选择用户后点击提交,页面提示添加成功,但后台数据库中没有新增数据。 + +## 🔍 问题分析 + +### 根本原因 +1. **前端使用模拟API函数**:`index.vue` 中第354-378行定义了模拟的API函数,而不是真实的API调用 +2. **缺少正确的API导入**:没有导入真实的 `batchAddProjectAuditor` 等API函数 +3. **数据格式不匹配**:前端传递的数据格式与后端期望格式存在差异 + +### 问题代码 +```javascript +// 错误:使用模拟函数 +const batchAddProjectAuditor = (data: any) => { + return Promise.resolve() // 直接返回成功,不实际调用后端 +} +``` + +## 🔧 修复方案 + +### 1. 导入真实API函数 +```javascript +import { + listProjectAuditor, + getProjectAuditor, + updateProjectAuditor, + delProjectAuditor, + batchAddProjectAuditor +} from '@/api/cw/projectAuditor' +import type { CwProjectAuditorVO, CwProjectAuditorForm, CwProjectAuditorQuery, CwProjectAuditorBatchBo } from '@/api/cw/types' +``` + +### 2. 修复数据类型定义 +```typescript +// 修正数据类型 +const auditorList = ref([]) +const queryParams = ref({...}) +const editDialog = reactive({ + form: {} as CwProjectAuditorForm +}) +``` + +### 3. 修复批量添加数据格式 +```javascript +const confirmBatchAdd = async () => { + const userIds = batchDialog.selectedUsers.map((user) => user.userId as number) + const data: CwProjectAuditorBatchBo = { + userIds: userIds, + remark: '批量添加' + } + + const response = await batchAddProjectAuditor(data) + // ... 处理响应 +} +``` + +### 4. 修复后端数据解析 +```javascript +const response = await listProjectAuditor(queryParams.value) +// 后端返回的是TableDataInfo格式 +auditorList.value = response.data?.rows || [] +total.value = response.data?.total || 0 +``` + +## ✅ 修复内容清单 + +### 前端修复 +- [x] 移除模拟API函数,导入真实API +- [x] 修复TypeScript类型定义 +- [x] 修复批量添加数据格式 +- [x] 修复后端响应数据解析 +- [x] 改进错误处理和用户反馈 +- [x] 修复按钮事件类型问题 + +### 后端验证 +- [x] 确认后端API接口正常工作 +- [x] 确认数据库表结构正确 +- [x] 确认权限控制配置 + +## 🧪 测试验证 + +### 测试用例 +1. **批量添加功能测试** + - 选择部门 + - 选择多个用户 + - 点击确认添加 + - 验证数据库是否成功插入数据 + +2. **查询列表功能测试** + - 验证API调用正常 + - 验证数据格式解析正确 + - 验证分页功能 + +3. **错误处理测试** + - 测试网络错误情况 + - 测试用户已存在情况 + - 测试权限不足情况 + +### 测试脚本 +提供了 `test-api.js` 文件用于功能验证测试。 + +## 📋 技术规范遵循 + +### 符合项目规范 +- ✅ **前端开发规范**:符合Vue 3.5.13 + TypeScript + Element Plus架构 +- ✅ **API接口规范**:使用统一的request工具和错误处理 +- ✅ **UI设计规范**:继承后勤管理模块的样式风格和颜色主题 +- ✅ **数据库规范**:使用雪花ID、逻辑删除、多租户等标准字段 +- ✅ **权限控制**:使用@SaCheckPermission注解控制接口权限 + +### 代码质量 +- ✅ **类型安全**:完整的TypeScript类型定义 +- ✅ **错误处理**:完善的异常捕获和用户提示 +- ✅ **用户体验**:优化的加载状态和反馈信息 +- ✅ **响应式设计**:保持左右布局在不同屏幕下的表现 + +## 🚀 部署说明 + +### 验证步骤 +1. 确保后端服务正常运行 +2. 检查数据库表 `cw_project_auditor` 是否存在 +3. 验证用户权限配置 `cw:projectAuditor:add` +4. 测试前端页面功能 + +### 预期结果 +- 批量添加功能正常工作 +- 数据成功写入数据库 +- 页面显示准确的成功/失败提示 +- 列表数据正确刷新 + +## 📝 修复日志 + +| 时间 | 修复内容 | 影响范围 | +|------|----------|----------| +| 2025-01-03 | 修复API调用问题 | 批量添加功能 | +| 2025-01-03 | 修复数据格式问题 | 前后端数据传递 | +| 2025-01-03 | 修复类型定义问题 | TypeScript类型安全 | +| 2025-01-03 | 修复响应解析问题 | 列表查询功能 | + +## 🎯 总结 + +本次修复解决了批量添加审批人功能的根本问题,确保: +1. **功能正常**:批量添加能够真实地向后端发送请求并保存数据 +2. **类型安全**:完整的TypeScript类型定义和检查 +3. **用户体验**:准确的成功/失败提示和数据刷新 +4. **代码质量**:符合项目开发规范和最佳实践 + +修复后的功能完全符合需求分析文档的要求,并遵循项目的前后端开发规范。 diff --git a/web/src/views/cw/projectAuditor/README.md b/web/src/views/cw/projectAuditor/README.md new file mode 100644 index 00000000..072083d1 --- /dev/null +++ b/web/src/views/cw/projectAuditor/README.md @@ -0,0 +1,58 @@ +# 项目审批人配置模块 + +## 功能概述 + +项目审批人配置模块用于管理项目申报审批流程中的审批人员池,支持从系统用户中选择并配置审批人员,为自定义审批流程提供人员支持。 + +## 主要功能 + +### 1. 审批人列表管理 +- 查询审批人列表(支持分页) +- 按用户姓名、部门、状态等条件筛选 +- 显示审批人基本信息(姓名、部门、联系方式等) +- 支持审批人状态管理(正常/停用) + +### 2. 批量添加审批人 +- 通过部门树选择部门 +- 展示部门下的可用用户列表 +- 支持多选用户批量添加为审批人 +- 自动过滤已存在的审批人 + +### 3. 审批人信息维护 +- 修改审批人联系信息 +- 更新审批人状态 +- 添加备注信息 +- 删除审批人配置 + +## 文件结构 + +``` +web/src/views/cw/projectAuditor/ +├── index.vue # 主页面组件 +└── README.md # 功能说明文档 +``` + +## API接口 + +使用的主要API接口: +- `listProjectAuditor` - 查询审批人列表 +- `getProjectAuditor` - 获取审批人详情 +- `updateProjectAuditor` - 更新审批人信息 +- `delProjectAuditor` - 删除审批人 +- `batchAddProjectAuditor` - 批量添加审批人 +- `getDeptUsers` - 获取部门用户列表 + +## 权限控制 + +页面功能对应的权限标识: +- `cw:projectAuditor:query` - 查询权限 +- `cw:projectAuditor:add` - 新增权限 +- `cw:projectAuditor:edit` - 编辑权限 +- `cw:projectAuditor:remove` - 删除权限 + +## 技术实现 + +- 基于Vue 3 Composition API +- 使用Element Plus UI组件库 +- 集成项目标准的权限控制和国际化 +- 遵循项目统一的代码规范和样式风格 diff --git a/web/src/views/cw/projectAuditor/debug.vue b/web/src/views/cw/projectAuditor/debug.vue new file mode 100644 index 00000000..2af39840 --- /dev/null +++ b/web/src/views/cw/projectAuditor/debug.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/web/src/views/cw/projectAuditor/index.vue b/web/src/views/cw/projectAuditor/index.vue new file mode 100644 index 00000000..ffd000c7 --- /dev/null +++ b/web/src/views/cw/projectAuditor/index.vue @@ -0,0 +1,952 @@ + + + + + diff --git a/web/src/views/cw/projectAuditor/test.vue b/web/src/views/cw/projectAuditor/test.vue new file mode 100644 index 00000000..e6b62ed1 --- /dev/null +++ b/web/src/views/cw/projectAuditor/test.vue @@ -0,0 +1,140 @@ + + + + + From 48da578546f0b68e9723667c19c9191e283f888d Mon Sep 17 00:00:00 2001 From: wuzhen <52326163@qq.com> Date: Sun, 7 Sep 2025 18:17:50 +0800 Subject: [PATCH 19/29] wuzhen update --- .../cw/mapper/CwProjectAuditorMapper.java | 6 + .../impl/CwProjectAuditorServiceImpl.java | 7 +- .../mapper/CwProjectAuditorMapper.xml | 21 + web/src/api/bzrkh/assessmentApproval/index.ts | 8 +- web/src/api/bzrkh/assessmentApproval/types.ts | 15 +- web/src/api/cw/projectApproval.ts | 1 + web/src/api/cw/projectAuditor.ts | 30 + web/src/api/stgl/activityteaminfo/index.ts | 2 +- web/src/api/stgl/activityteaminfo/types.ts | 2 +- web/src/api/stgl/stclass/index.ts | 2 +- web/src/api/stgl/stclass/types.ts | 2 +- .../assessmentApplication.ts | 9 +- .../assessmentApproval/assessmentApproval.ts | 2 +- web/src/api/xskh/assessmentIndicator/types.ts | 2 +- .../assessmentIndicatorType.ts | 9 +- .../api/xskh/assessmentIndicatorType/types.ts | 2 +- .../api/xskh/assessmentPlan/assessmentPlan.ts | 2 +- web/src/api/xskh/assessmentPlan/types.ts | 2 +- .../assessmentSchedule/assessmentSchedule.ts | 2 +- web/src/api/xskh/assessmentSchedule/types.ts | 2 +- web/src/components/AdvisorManager/index.vue | 10 +- web/src/components/ClubSelector/index.vue | 19 +- .../assessmentApproval/approvalDialog.vue | 19 +- .../bzrkh/assessmentApproval/viewDialog.vue | 94 ++-- .../bzrkh/assessmentIndicatorType/index.vue | 40 +- web/src/views/bzrkh/assessmentPlan/index.vue | 4 +- .../views/bzrkh/assessmentScheme/index.vue | 4 +- .../views/bzrkh/teacherAssessment/index.vue | 46 +- .../bzrkh/teacherAssessment/submitDialog.vue | 56 +- .../bzrkh/teacherAssessment/viewDialog.vue | 140 +++-- web/src/views/cw/README.md | 25 +- web/src/views/cw/application/form.vue | 2 +- web/src/views/cw/application/index.vue | 172 ++++-- web/src/views/cw/approvalRecord/index.vue | 14 +- web/src/views/cw/leaderApproval/index.vue | 4 +- web/src/views/cw/projectApproval/README.md | 19 +- .../components/ApprovalProcessSelector.vue | 2 +- web/src/views/cw/projectApproval/index.vue | 4 +- web/src/views/cw/projectApproval/pending.vue | 46 +- .../views/cw/projectAuditor/BUG_FIX_REPORT.md | 48 +- web/src/views/cw/projectAuditor/README.md | 5 + web/src/views/cw/projectAuditor/debug.vue | 8 +- web/src/views/cw/projectAuditor/index.vue | 343 ++++++----- web/src/views/cw/projectAuditor/test.vue | 32 +- .../views/jcbt/poverty/Application/index.vue | 10 +- web/src/views/jcbt/poverty/review/index.vue | 9 +- .../jcbt/scholarship/application/index.vue | 25 +- .../views/jcbt/scholarship/batch/index.vue | 52 +- .../views/jcbt/scholarship/quota/index.vue | 145 ++--- .../views/jcbt/scholarship/review/index.vue | 2 +- web/src/views/jcbt/scholarship/type/index.vue | 144 +++-- web/src/views/rs/index.vue | 22 +- web/src/views/rs/overseas/approval.vue | 15 +- web/src/views/rs/overseas/index.vue | 18 +- web/src/views/rs/overseasApproval/index.vue | 12 +- web/src/views/rs/recruitment-portal.vue | 46 +- web/src/views/rs/training/index.vue | 531 +++++++++--------- web/src/views/rs/trainingApproval/index.vue | 22 +- .../components/ActivityDetailView.vue | 63 ++- .../components/ActivityMembersDialog.vue | 44 +- .../components/ActivityRatingView.vue | 40 +- .../components/ActivityRegistrationView.vue | 78 ++- .../activity/components/PendingMembersTab.vue | 138 +++-- .../components/RegisteredMembersTab.vue | 142 ++--- web/src/views/stgl/activity/hall/index.vue | 10 +- web/src/views/stgl/activity/index.vue | 242 ++++---- .../club/info/components/JoinedMembersTab.vue | 11 +- .../components/MemberManagementDialog.vue | 63 ++- .../info/components/PendingMembersTab.vue | 106 ++-- web/src/views/stgl/club/info/index.vue | 37 +- web/src/views/stgl/my/activity/index.vue | 178 +++--- web/src/views/stgl/my/record/index.vue | 11 +- web/src/views/stgl/stclass/index.vue | 14 +- .../team/components/JoinedMembersTab.vue | 75 ++- .../components/MemberManagementDialog.vue | 63 ++- .../team/components/PendingMembersTab.vue | 91 +-- web/src/views/stgl/volunteer/team/index.vue | 2 +- .../xskh/assessmentIndicatorType/types.ts | 2 +- web/src/views/zsjy/enrollment/index.vue | 16 +- web/src/views/zsjy/graduateInfo/index.vue | 8 +- web/src/views/zsjy/internship/daily/index.vue | 16 +- .../views/zsjy/internship/msgmanage/index.vue | 26 +- .../views/zsjy/internship/pmonthly/index.vue | 49 +- web/src/views/zsjy/internship/visit/index.vue | 40 +- web/src/views/zsjy/jobPosition/index.vue | 18 +- .../views/zsjy/recruitmentPoster/index.vue | 30 +- 86 files changed, 2101 insertions(+), 1849 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwProjectAuditorMapper.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwProjectAuditorMapper.java index 38c97582..277706ec 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwProjectAuditorMapper.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwProjectAuditorMapper.java @@ -1,6 +1,7 @@ package org.dromara.cw.mapper; import org.dromara.cw.domain.CwProjectAuditor; +import org.dromara.cw.domain.UserWithDeptInfo; import org.dromara.cw.domain.vo.CwProjectAuditorVo; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; import org.apache.ibatis.annotations.Param; @@ -25,4 +26,9 @@ public interface CwProjectAuditorMapper extends BaseMapperPlus selectAuditorStatistics(); + /** + * 查询完整的用户信息(包含部门信息)用于批量添加 + */ + List selectUsersWithDeptInfo(@Param("userIds") List userIds); + } diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectAuditorServiceImpl.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectAuditorServiceImpl.java index 40e31fa6..42c6e73d 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectAuditorServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectAuditorServiceImpl.java @@ -14,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional; import org.dromara.cw.domain.bo.CwProjectAuditorBo; import org.dromara.cw.domain.vo.CwProjectAuditorVo; import org.dromara.cw.domain.CwProjectAuditor; +import org.dromara.cw.domain.UserWithDeptInfo; import org.dromara.cw.mapper.CwProjectAuditorMapper; import org.dromara.cw.service.ICwProjectAuditorService; import org.dromara.system.service.ISysUserService; @@ -150,8 +151,8 @@ public class CwProjectAuditorServiceImpl implements ICwProjectAuditorService { return false; } - // 1. 获取用户信息 - List users = sysUserService.selectUserByIds(userIds, null); + // 1. 获取完整的用户信息(包含部门信息) + List users = baseMapper.selectUsersWithDeptInfo(userIds); if (users.isEmpty()) { return false; } @@ -163,7 +164,7 @@ public class CwProjectAuditorServiceImpl implements ICwProjectAuditorService { // 3. 构建新增审核人列表 List auditors = new ArrayList<>(); - for (SysUserVo user : users) { + for (UserWithDeptInfo user : users) { if (!existingUserIds.contains(user.getUserId())) { CwProjectAuditor auditor = new CwProjectAuditor(); auditor.setUserId(user.getUserId()); diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwProjectAuditorMapper.xml b/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwProjectAuditorMapper.xml index 781cc3ad..fb8efb03 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwProjectAuditorMapper.xml +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwProjectAuditorMapper.xml @@ -111,4 +111,25 @@ and deleted = 0 + + + diff --git a/web/src/api/bzrkh/assessmentApproval/index.ts b/web/src/api/bzrkh/assessmentApproval/index.ts index 133d9224..fb6a0087 100644 --- a/web/src/api/bzrkh/assessmentApproval/index.ts +++ b/web/src/api/bzrkh/assessmentApproval/index.ts @@ -1,6 +1,12 @@ import request from '@/utils/request'; import { AxiosPromise } from 'axios'; -import { AssessmentApprovalQuery, AssessmentApprovalVO, AssessmentApprovalDetailVO, AssessmentApprovalSubmitBO, EvaluationLevelStatsVO } from './types'; +import { + AssessmentApprovalQuery, + AssessmentApprovalVO, + AssessmentApprovalDetailVO, + AssessmentApprovalSubmitBO, + EvaluationLevelStatsVO +} from './types'; /** * 查询班主任考核审核列表 diff --git a/web/src/api/bzrkh/assessmentApproval/types.ts b/web/src/api/bzrkh/assessmentApproval/types.ts index ce8c6dbe..8d06ba56 100644 --- a/web/src/api/bzrkh/assessmentApproval/types.ts +++ b/web/src/api/bzrkh/assessmentApproval/types.ts @@ -224,11 +224,12 @@ export interface AssessmentApprovalSpecialWorkSubmitVO { * 评价等级统计数据 */ export interface EvaluationLevelStatsVO { - totalTeachers: number; // 总班主任数量 + totalTeachers: number; // 总班主任数量 scoreRankings: ScoreRankingVO[]; // 分数排名列表 - evaluationLevelRules: { // 评价等级规则 - aLevelMaxPercentage: number; // A等最大占比(如20%) - passScore: number; // 及格分数(如60分) + evaluationLevelRules: { + // 评价等级规则 + aLevelMaxPercentage: number; // A等最大占比(如20%) + passScore: number; // 及格分数(如60分) }; } @@ -236,8 +237,8 @@ export interface EvaluationLevelStatsVO { * 分数排名VO */ export interface ScoreRankingVO { - score: number; // 考核得分 - ranking: number; // 排名(从1开始) - percentage: number; // 百分比排名(前X%) + score: number; // 考核得分 + ranking: number; // 排名(从1开始) + percentage: number; // 百分比排名(前X%) evaluatedUserId?: string | number; // 被评价用户ID(可选,用于标识当前用户) } diff --git a/web/src/api/cw/projectApproval.ts b/web/src/api/cw/projectApproval.ts index 4e948529..e83a4cec 100644 --- a/web/src/api/cw/projectApproval.ts +++ b/web/src/api/cw/projectApproval.ts @@ -61,6 +61,7 @@ export interface CwApprovalRecordVO { */ export interface CwCustomApprovalRequest { applicationId: string | number; + applicationNo: string; auditorIds: Array; processName: string; submitterId: string | number; diff --git a/web/src/api/cw/projectAuditor.ts b/web/src/api/cw/projectAuditor.ts index 69fa3fc2..9f4a1027 100644 --- a/web/src/api/cw/projectAuditor.ts +++ b/web/src/api/cw/projectAuditor.ts @@ -63,6 +63,22 @@ export interface BatchAddRequest { remark?: string; } +/** + * 批量添加审核人详细请求对象(包含完整用户信息) + */ +export interface BatchAddDetailRequest { + users: Array<{ + userId: string | number; + userName: string; + nickName: string; + deptId: string | number; + deptName: string; + phoneNumber: string; // 注意:这里使用驼峰命名,确保与数据库字段映射 + email: string; + }>; + remark?: string; +} + /** * 状态更新请求对象 */ @@ -149,6 +165,20 @@ export function batchAddProjectAuditor(data: BatchAddRequest): AxiosPromise { +// return request({ +// url: '/cw/projectAuditor/batchAddWithDetails', +// method: 'post', +// data: data +// }); +// } + /** * 获取可用审核人列表 */ diff --git a/web/src/api/stgl/activityteaminfo/index.ts b/web/src/api/stgl/activityteaminfo/index.ts index 8cd32b24..f2046618 100644 --- a/web/src/api/stgl/activityteaminfo/index.ts +++ b/web/src/api/stgl/activityteaminfo/index.ts @@ -59,4 +59,4 @@ export function getTeamsByActivityId(activityId: string | number) { url: '/stgl/activityteaminfo/activity/' + activityId, method: 'get' }); -} \ No newline at end of file +} diff --git a/web/src/api/stgl/activityteaminfo/types.ts b/web/src/api/stgl/activityteaminfo/types.ts index 39dad5a4..1f0a8445 100644 --- a/web/src/api/stgl/activityteaminfo/types.ts +++ b/web/src/api/stgl/activityteaminfo/types.ts @@ -238,4 +238,4 @@ export interface StglActivityTeamInfoVo { * 更新时间 */ updateTime?: string; -} \ No newline at end of file +} diff --git a/web/src/api/stgl/stclass/index.ts b/web/src/api/stgl/stclass/index.ts index 6dd6a53f..aad3ff60 100644 --- a/web/src/api/stgl/stclass/index.ts +++ b/web/src/api/stgl/stclass/index.ts @@ -61,4 +61,4 @@ export function exportStglClass(query: StglClassQuery) { method: 'get', params: query }); -} \ No newline at end of file +} diff --git a/web/src/api/stgl/stclass/types.ts b/web/src/api/stgl/stclass/types.ts index b075ad4a..68ef621a 100644 --- a/web/src/api/stgl/stclass/types.ts +++ b/web/src/api/stgl/stclass/types.ts @@ -299,4 +299,4 @@ export interface StglClassDetailVO { * 年级 */ grade: string; -} \ No newline at end of file +} diff --git a/web/src/api/xskh/assessmentApplication/assessmentApplication.ts b/web/src/api/xskh/assessmentApplication/assessmentApplication.ts index 07a88751..88d997ca 100644 --- a/web/src/api/xskh/assessmentApplication/assessmentApplication.ts +++ b/web/src/api/xskh/assessmentApplication/assessmentApplication.ts @@ -1,5 +1,10 @@ import request from '@/utils/request'; -import { XskhAssessmentApplicationApprovalQuery, XskhAssessmentApplicationApprovalBo, XskhAssessmentApplicationApprovalVo, AvailableScheduleOption } from './types'; +import { + XskhAssessmentApplicationApprovalQuery, + XskhAssessmentApplicationApprovalBo, + XskhAssessmentApplicationApprovalVo, + AvailableScheduleOption +} from './types'; // 查询学生考核申请列表 export function listAssessmentApplication(query: XskhAssessmentApplicationApprovalQuery) { @@ -91,4 +96,4 @@ export function getIndicatorsByScheduleId(scheduleId: string | number) { url: `/assessment/application/indicators/${scheduleId}`, method: 'get' }); -} \ No newline at end of file +} diff --git a/web/src/api/xskh/assessmentApproval/assessmentApproval.ts b/web/src/api/xskh/assessmentApproval/assessmentApproval.ts index d3d25f68..46ed6b03 100644 --- a/web/src/api/xskh/assessmentApproval/assessmentApproval.ts +++ b/web/src/api/xskh/assessmentApproval/assessmentApproval.ts @@ -71,4 +71,4 @@ export function getAvailableAssessmentSchedules() { url: '/assessment/approval/available', method: 'get' }); -} \ No newline at end of file +} diff --git a/web/src/api/xskh/assessmentIndicator/types.ts b/web/src/api/xskh/assessmentIndicator/types.ts index 8a3c894c..b2c0fb9d 100644 --- a/web/src/api/xskh/assessmentIndicator/types.ts +++ b/web/src/api/xskh/assessmentIndicator/types.ts @@ -138,4 +138,4 @@ export interface RewardTypeOption { typeCode: string; /** 类型名称 */ typeName: string; -} \ No newline at end of file +} diff --git a/web/src/api/xskh/assessmentIndicatorType/assessmentIndicatorType.ts b/web/src/api/xskh/assessmentIndicatorType/assessmentIndicatorType.ts index 4e1ccf5d..9065fcfd 100644 --- a/web/src/api/xskh/assessmentIndicatorType/assessmentIndicatorType.ts +++ b/web/src/api/xskh/assessmentIndicatorType/assessmentIndicatorType.ts @@ -1,7 +1,12 @@ import request from '@/utils/request'; import { AxiosPromise } from 'axios'; import { TableDataInfo, R } from '@/api/types'; -import { XskhAssessmentIndicatorTypeVo, XskhAssessmentIndicatorTypeBo, XskhAssessmentIndicatorTypeQuery, XskhAssessmentIndicatorTypeForm } from './types'; +import { + XskhAssessmentIndicatorTypeVo, + XskhAssessmentIndicatorTypeBo, + XskhAssessmentIndicatorTypeQuery, + XskhAssessmentIndicatorTypeForm +} from './types'; /** * 查询学生考核指标类型列表 @@ -75,4 +80,4 @@ export function getEnabledAssessmentIndicatorTypes(): AxiosPromise> { url: '/assessment/plan/enabled', method: 'get' }); -} \ No newline at end of file +} diff --git a/web/src/api/xskh/assessmentSchedule/types.ts b/web/src/api/xskh/assessmentSchedule/types.ts index 73212fde..49ac3b92 100644 --- a/web/src/api/xskh/assessmentSchedule/types.ts +++ b/web/src/api/xskh/assessmentSchedule/types.ts @@ -142,4 +142,4 @@ export interface AssessmentPlanOption { planName: string; /** 方案说明 */ planDescription: string; -} \ No newline at end of file +} diff --git a/web/src/components/AdvisorManager/index.vue b/web/src/components/AdvisorManager/index.vue index 62105d79..64ab3eb2 100644 --- a/web/src/components/AdvisorManager/index.vue +++ b/web/src/components/AdvisorManager/index.vue @@ -15,9 +15,9 @@ - @@ -119,7 +119,7 @@ const removeAdvisor = (index: number) => { // 监听teacher选择变化,自动填充相关信息 const handleTeacherChange = (advisor: AdvisorVO, teacher: TeacherVO | null) => { console.log('handleTeacherChange called with teacher:', teacher); - + if (teacher) { // 自动填充老师信息 advisor.name = teacher.nickName || ''; @@ -127,7 +127,7 @@ const handleTeacherChange = (advisor: AdvisorVO, teacher: TeacherVO | null) => { advisor.dept = teacher.deptName || ''; advisor.email = teacher.email || '暂无邮箱'; advisor.userId = teacher.userId?.toString() || ''; - + console.log('Updated advisor:', { name: advisor.name, phone: advisor.phone, diff --git a/web/src/components/ClubSelector/index.vue b/web/src/components/ClubSelector/index.vue index 4ee98f48..b635f3c7 100644 --- a/web/src/components/ClubSelector/index.vue +++ b/web/src/components/ClubSelector/index.vue @@ -14,7 +14,12 @@ value-key="uniqueKey" style="width: 100%" > - +
{{ option.relatedClubName }} {{ getTypeLabel(option.relatedType) }} @@ -98,9 +103,13 @@ const selectedOption = ref(null); // 监听 modelValue 变化 import { watch } from 'vue'; -watch(() => props.modelValue, (newValue) => { - console.log('ClubSelector - modelValue changed:', newValue); -}, { immediate: true }); +watch( + () => props.modelValue, + (newValue) => { + console.log('ClubSelector - modelValue changed:', newValue); + }, + { immediate: true } +); // 格式化选项标签 const formatOptionLabel = (option: ClubOption): string => { @@ -172,7 +181,7 @@ const loadOptions = async () => { uniqueKey: `${item.relatedType}-${item.relatedClubId}` })); options.value = [...clubOptions.value, ...volunteerOptions.value]; - + console.log('ClubSelector - 加载选项数据完成:', { clubCount: clubOptions.value.length, volunteerCount: volunteerOptions.value.length, diff --git a/web/src/views/bzrkh/assessmentApproval/approvalDialog.vue b/web/src/views/bzrkh/assessmentApproval/approvalDialog.vue index 8a5b93c9..e75ea8d7 100644 --- a/web/src/views/bzrkh/assessmentApproval/approvalDialog.vue +++ b/web/src/views/bzrkh/assessmentApproval/approvalDialog.vue @@ -267,11 +267,11 @@ - @@ -457,7 +457,7 @@ const loadApprovalDetail = async () => { if (approvalDetail.value?.routineWorkIndicators && Array.isArray(approvalDetail.value.routineWorkIndicators)) { routineWorkScores.value = approvalDetail.value.routineWorkIndicators.map((item) => { const mappedItem = { - ...item, + ...item, // 确保数值字段不为空 selfScore: Number(item.selfScore) || 0, superiorScore: Number(item.superiorScore) || Number(item.selfScore) || 0, // 默认使用自评分数 @@ -517,7 +517,7 @@ const loadApprovalDetail = async () => { if (approvalDetail.value?.specialWorkIndicators && Array.isArray(approvalDetail.value.specialWorkIndicators)) { specialWorkScores.value = approvalDetail.value.specialWorkIndicators.map((item) => { const mappedItem = { - ...item, + ...item, // 自评分数字段处理 - 兼容不同字段名,确保数值类型 score: Number(item.score || item.selfScore) || 0, selfScore: Number(item.selfScore || item.score) || 0, @@ -557,18 +557,17 @@ const loadApprovalDetail = async () => { approvalForm.auditComment = ''; console.log('=== 审批详情数据加载完成 ==='); - + // 验证数据完整性 if (routineWorkScores.value.length === 0) { console.warn('⚠️ 警告:班主任常规工作数据为空,可能存在数据问题'); proxy?.$modal.msgWarning('班主任常规工作数据为空,请检查数据是否正确'); } - + if (specialWorkScores.value.length === 0) { console.warn('⚠️ 警告:特色工作数据为空,可能存在数据问题'); proxy?.$modal.msgWarning('特色工作数据为空,请检查数据是否正确'); } - } catch (error) { console.error('加载审批详情失败:', error); proxy?.$modal.msgError('加载审批详情失败'); diff --git a/web/src/views/bzrkh/assessmentApproval/viewDialog.vue b/web/src/views/bzrkh/assessmentApproval/viewDialog.vue index 2fba8e33..d519e764 100644 --- a/web/src/views/bzrkh/assessmentApproval/viewDialog.vue +++ b/web/src/views/bzrkh/assessmentApproval/viewDialog.vue @@ -241,15 +241,13 @@ -
+
@@ -259,9 +257,7 @@
- - {{ calculateMonthlyScore().toFixed(2) }}分 - + {{ calculateMonthlyScore().toFixed(2) }}分 {{ parseTime(currentRow?.createTime, '{y}-{m}-{d} {h}:{i}:{s}') || '-' }} @@ -310,9 +306,10 @@ const getEvaluationLevelOptions = (): DictDataOption[] => { console.log('✅ 使用数据库字典数据:', bmzr_evaluation_level.value); return bmzr_evaluation_level.value; } - + // 字典数据未加载或为空时的处理 - if (visible.value) { // 只在弹窗打开时输出警告 + if (visible.value) { + // 只在弹窗打开时输出警告 console.warn('⚠️ 字典数据为空,使用备用数据,当前状态:', { 弹窗是否打开: visible.value, 字典对象存在: !!bmzr_evaluation_level.value, @@ -321,7 +318,7 @@ const getEvaluationLevelOptions = (): DictDataOption[] => { 字典数据内容: bmzr_evaluation_level.value }); } - + return fallbackEvaluationLevelOptions.value; }; @@ -363,10 +360,10 @@ const dialogTitle = computed(() => { const isDictDataMissing = computed(() => { // 如果还没有尝试加载字典数据,不显示警告 if (!visible.value) return false; - + // 如果字典数据已经加载成功,不显示警告 if (isDictLoaded.value) return false; - + // 只有在弹窗打开且字典数据为空时才显示警告 return !bmzr_evaluation_level.value || bmzr_evaluation_level.value.length === 0; }); @@ -434,7 +431,7 @@ const calculateEvaluationLevel = () => { console.log('💯 考核得分:', score); console.log('📈 是否有统计数据:', !!evaluationStats.value); console.log('👤 当前用户ID:', currentRow.value?.evaluatedUserId); - + if (evaluationStats.value) { console.log('📊 统计数据详情:', { 总教师数: evaluationStats.value.totalTeachers, @@ -443,15 +440,17 @@ const calculateEvaluationLevel = () => { 及格分数: evaluationStats.value.evaluationLevelRules?.passScore }); } - + let level = ''; let calculationMethod = ''; - + // 如果有统计数据且用户ID存在,使用严格的占比规则 - if (evaluationStats.value && - evaluationStats.value.totalTeachers > 0 && - evaluationStats.value.scoreRankings.length > 0 && - currentRow.value?.evaluatedUserId) { + if ( + evaluationStats.value && + evaluationStats.value.totalTeachers > 0 && + evaluationStats.value.scoreRankings.length > 0 && + currentRow.value?.evaluatedUserId + ) { level = calculateEvaluationLevelByStats(score, currentRow.value.evaluatedUserId); calculationMethod = '📈 严格占比规则评级'; } else { @@ -464,17 +463,17 @@ const calculateEvaluationLevel = () => { level = 'B'; // B等(合格):60-85分 } calculationMethod = '🔢 简化分数评级'; - + // 输出使用简化评级的原因 const reasons = []; if (!evaluationStats.value) reasons.push('统计数据为空'); if (evaluationStats.value?.totalTeachers === 0) reasons.push('总教师数为0'); if (evaluationStats.value?.scoreRankings?.length === 0) reasons.push('排名数据为空'); if (!currentRow.value?.evaluatedUserId) reasons.push('用户ID缺失'); - + console.warn('⚠️ 使用简化评级的原因:', reasons.join('、')); } - + console.log('🎯 计算方法:', calculationMethod); console.log('📋 最终评价等级:', level); console.log('=== 📊 评价等级计算调试结束 ==='); @@ -486,32 +485,30 @@ const calculateEvaluationLevelByStats = (score: number, evaluatedUserId: string const stats = evaluationStats.value; if (!stats) { console.warn('统计数据不可用,使用简单评级'); - return score < 60 ? 'C' : (score >= 85 ? 'A' : 'B'); + return score < 60 ? 'C' : score >= 85 ? 'A' : 'B'; } - + const { totalTeachers, scoreRankings, evaluationLevelRules } = stats; const { aLevelMaxPercentage, passScore } = evaluationLevelRules; - + // C等:不及格 if (score < passScore) { return 'C'; } - + // 查找当前用户的排名 - const currentRanking = scoreRankings.find(item => - item.evaluatedUserId === evaluatedUserId || item.score === score - ); - + const currentRanking = scoreRankings.find((item) => item.evaluatedUserId === evaluatedUserId || item.score === score); + if (!currentRanking) { console.warn('找不到当前用户的排名信息,使用简单评级'); - return score < 60 ? 'C' : (score >= 85 ? 'A' : 'B'); + return score < 60 ? 'C' : score >= 85 ? 'A' : 'B'; } - + // A等:及格且排名在前20%内 if (score >= passScore && currentRanking.percentage <= aLevelMaxPercentage) { return 'A'; } - + // B等:其余及格的 return 'B'; }; @@ -525,28 +522,28 @@ const open = async (row: AssessmentApprovalVO) => { currentRow.value = row; visible.value = true; - + // 检查字典数据加载状态 console.log('🔍 打开弹窗时字典数据状态:', { 字典对象存在: !!bmzr_evaluation_level.value, 字典数据长度: bmzr_evaluation_level.value?.length || 0, 字典数据内容: bmzr_evaluation_level.value }); - + // 等待字典数据加载完成(最多等待2秒) let waitCount = 0; while ((!bmzr_evaluation_level.value || bmzr_evaluation_level.value.length === 0) && waitCount < 20) { - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 100)); waitCount++; } - + if (bmzr_evaluation_level.value && bmzr_evaluation_level.value.length > 0) { console.log('✅ 字典数据加载完成:', bmzr_evaluation_level.value); isDictLoaded.value = true; } else { console.warn('⚠️ 字典数据加载超时,使用备用数据'); } - + await loadViewDetail(); }; @@ -577,11 +574,15 @@ const loadViewDetail = async () => { if (statsRes.status === 'fulfilled') { evaluationStats.value = statsRes.value.data; console.log('✅ 评价等级统计数据加载成功:', evaluationStats.value); - + // 检查统计数据的完整性 if (!evaluationStats.value.totalTeachers || evaluationStats.value.scoreRankings.length === 0) { - console.warn('⚠️ 统计数据不完整,总教师数:', evaluationStats.value.totalTeachers, - '排名数据条数:', evaluationStats.value.scoreRankings.length); + console.warn( + '⚠️ 统计数据不完整,总教师数:', + evaluationStats.value.totalTeachers, + '排名数据条数:', + evaluationStats.value.scoreRankings.length + ); proxy?.$modal.msgWarning('统计数据不完整,可能影响等级评定准确性'); } } else { @@ -696,18 +697,17 @@ const loadViewDetail = async () => { approvalData.auditComment = ''; console.log('=== 查看详情数据加载完成 ==='); - + // 验证数据完整性 if (routineWorkScores.value.length === 0) { console.warn('⚠️ 警告:班主任常规工作数据为空,可能存在数据问题'); proxy?.$modal.msgWarning('班主任常规工作数据为空,请检查数据是否正确'); } - + if (specialWorkScores.value.length === 0) { console.warn('⚠️ 警告:特色工作数据为空,可能存在数据问题'); proxy?.$modal.msgWarning('特色工作数据为空,请检查数据是否正确'); } - } catch (error) { console.error('加载查看详情失败:', error); proxy?.$modal.msgError('加载查看详情失败'); @@ -724,8 +724,8 @@ const handleClose = () => { routineWorkScores.value = []; specialWorkScores.value = []; evaluationStats.value = undefined; - isDictLoaded.value = false; // 重置字典加载状态 - + isDictLoaded.value = false; // 重置字典加载状态 + Object.assign(managementEffectivenessData, { classDeduction: 0, classDeductionHalf: 0, @@ -802,7 +802,7 @@ defineExpose({ max-height: 150px; overflow-y: auto; } - + .el-table__row { height: auto; } diff --git a/web/src/views/bzrkh/assessmentIndicatorType/index.vue b/web/src/views/bzrkh/assessmentIndicatorType/index.vue index 01bf2e01..43f12139 100644 --- a/web/src/views/bzrkh/assessmentIndicatorType/index.vue +++ b/web/src/views/bzrkh/assessmentIndicatorType/index.vue @@ -7,12 +7,22 @@ - + - + @@ -194,7 +204,7 @@ const handleQuery = () => { pageNum: queryParams.value.pageNum, pageSize: queryParams.value.pageSize }); - + queryParams.value.pageNum = 1; getList(); }; @@ -202,24 +212,26 @@ const handleQuery = () => { /** 查询班主任考核指标类型列表 */ const getList = async () => { loading.value = true; - + // 构建查询参数,正确处理空字符串和undefined/null值 const queryData = { pageNum: queryParams.value.pageNum, pageSize: queryParams.value.pageSize, // 修复:正确处理空字符串,只过滤undefined和null - ...(queryParams.value.indicatorTypeCode !== undefined && queryParams.value.indicatorTypeCode !== null && { indicatorTypeCode: queryParams.value.indicatorTypeCode }), - ...(queryParams.value.indicatorTypeName !== undefined && queryParams.value.indicatorTypeName !== null && { indicatorTypeName: queryParams.value.indicatorTypeName }), + ...(queryParams.value.indicatorTypeCode !== undefined && + queryParams.value.indicatorTypeCode !== null && { indicatorTypeCode: queryParams.value.indicatorTypeCode }), + ...(queryParams.value.indicatorTypeName !== undefined && + queryParams.value.indicatorTypeName !== null && { indicatorTypeName: queryParams.value.indicatorTypeName }), ...(queryParams.value.status !== undefined && queryParams.value.status !== null && { status: queryParams.value.status }) }; - + console.log('📋 发送查询请求,参数:', queryData); console.log('🔍 原始查询参数:', { indicatorTypeCode: `"${queryParams.value.indicatorTypeCode}"`, indicatorTypeName: `"${queryParams.value.indicatorTypeName}"`, status: `"${queryParams.value.status}"` }); - + try { const res = await listAssessmentIndicatorType(queryData); console.log('📊 查询结果:', { @@ -227,7 +239,7 @@ const getList = async () => { dataCount: res.rows?.length, firstItem: res.rows?.[0] }); - + assessmentIndicatorTypeList.value = res.rows; total.value = res.total; } catch (error) { @@ -253,23 +265,23 @@ const reset = () => { /** 重置按钮操作 */ const resetQuery = () => { console.log('🔄 重置查询参数'); - + // 重置表单字段 queryFormRef.value?.resetFields(); - + // 手动重置查询参数对象,确保清空所有搜索条件 queryParams.value.indicatorTypeCode = undefined; queryParams.value.indicatorTypeName = undefined; queryParams.value.status = undefined; queryParams.value.pageNum = 1; - + console.log('🔄 重置后的查询参数:', { indicatorTypeCode: queryParams.value.indicatorTypeCode, indicatorTypeName: queryParams.value.indicatorTypeName, status: queryParams.value.status, pageNum: queryParams.value.pageNum }); - + // 重新查询 getList(); }; @@ -349,7 +361,7 @@ onMounted(() => { pageNum: queryParams.value.pageNum, pageSize: queryParams.value.pageSize }); - + getList(); }); diff --git a/web/src/views/bzrkh/assessmentPlan/index.vue b/web/src/views/bzrkh/assessmentPlan/index.vue index 1e1f55d1..7eee5bb4 100644 --- a/web/src/views/bzrkh/assessmentPlan/index.vue +++ b/web/src/views/bzrkh/assessmentPlan/index.vue @@ -454,7 +454,7 @@ const submitForm = () => { /** 删除按钮操作 */ const handleDelete = async (row?: AssessmentPlanVO) => { const _ids = row?.id || ids.value; - + let confirmMessage = ''; if (row) { const semesterText = row.semester === '1' ? '上学期' : '下学期'; @@ -462,7 +462,7 @@ const handleDelete = async (row?: AssessmentPlanVO) => { } else { confirmMessage = `是否确认删除选中的${ids.value.length}条考核计划?`; } - + await proxy?.$modal.confirm(confirmMessage).then(async () => { await delAssessmentPlan(_ids).then(() => { getList(); diff --git a/web/src/views/bzrkh/assessmentScheme/index.vue b/web/src/views/bzrkh/assessmentScheme/index.vue index 4a025634..54492165 100644 --- a/web/src/views/bzrkh/assessmentScheme/index.vue +++ b/web/src/views/bzrkh/assessmentScheme/index.vue @@ -382,14 +382,14 @@ const submitForm = () => { /** 删除按钮操作 */ const handleDelete = async (row?: BmzrAssessmentSchemeVO) => { const _ids = row?.id || ids.value; - + let confirmMessage = ''; if (row) { confirmMessage = `是否确认删除考核方案"${row.schemeName}"(${row.academicYear}学年第${row.semester}学期)?`; } else { confirmMessage = `是否确认删除选中的${ids.value.length}条考核方案?`; } - + try { await proxy?.$modal.confirm(confirmMessage); await delBmzrAssessmentScheme(_ids); diff --git a/web/src/views/bzrkh/teacherAssessment/index.vue b/web/src/views/bzrkh/teacherAssessment/index.vue index 491eef44..b350f94f 100644 --- a/web/src/views/bzrkh/teacherAssessment/index.vue +++ b/web/src/views/bzrkh/teacherAssessment/index.vue @@ -95,14 +95,14 @@ @@ -155,7 +155,7 @@ - +
@@ -241,13 +241,13 @@ const getTeacherClasses = async () => { queryParams.value.planName = undefined; // 确保搜索条件被重置 queryParams.value.pageNum = 1; queryParams.value.classId = activeTab.value; - + console.log('🔄 初始化完成:', { activeTab: activeTab.value, classId: queryParams.value.classId, planName: queryParams.value.planName }); - + // 等待activeTab设置完成后再获取列表 await nextTick(); await getList(); @@ -270,9 +270,9 @@ const getList = async () => { teacherId: userStore.userId, // 确保包含teacherId planStatus: '1' // 确保包含planStatus }; - + console.log('📋 查询参数:', queryData); - + const res = await listTeacherAssessmentPlans(queryData); console.log('📊 查询结果:', { total: res.total, @@ -280,7 +280,7 @@ const getList = async () => { firstItemStatus: res.rows?.[0]?.auditStatus, firstItemSubmitted: res.rows?.[0]?.submitted }); - + assessmentPlanList.value = res.rows || []; total.value = res.total || 0; } catch (error) { @@ -298,18 +298,18 @@ const handleTabChange = async (tabName: string) => { activeTab: activeTab.value, oldPlanName: queryParams.value.planName }); - + // 重置分页和搜索条件,设置新的班级ID queryParams.value.pageNum = 1; queryParams.value.planName = undefined; // 重置搜索条件,避免标签页间共享 queryParams.value.classId = tabName; - + console.log('🔄 参数设置完成:', { classId: queryParams.value.classId, activeTab: activeTab.value, planName: queryParams.value.planName }); - + // 等待参数设置完成后再查询 await nextTick(); getList(); @@ -332,20 +332,20 @@ const handleQuery = () => { /** 重置按钮操作 */ const resetQuery = () => { console.log('🔄 开始重置查询参数'); - + try { // 保存当前班级ID和计划状态(这些不应该被重置) const currentClassId = queryParams.value.classId; const currentPlanStatus = queryParams.value.planStatus; - + // 手动重置查询参数到初始状态 queryParams.value.planName = undefined; queryParams.value.pageNum = 1; - + // 恢复不应该被重置的参数 queryParams.value.classId = currentClassId; queryParams.value.planStatus = currentPlanStatus; - + console.log('🔄 重置后的查询参数:', { planName: queryParams.value.planName, pageNum: queryParams.value.pageNum, @@ -353,7 +353,7 @@ const resetQuery = () => { planStatus: queryParams.value.planStatus, teacherId: userStore.userId }); - + // 等待DOM更新完成后再刷新数据 nextTick(() => { console.log('🔄 开始强制刷新数据'); @@ -367,8 +367,6 @@ const resetQuery = () => { } }; - - /** 排序触发事件 */ const sortChange = (column: any) => { const { prop, order } = column; diff --git a/web/src/views/bzrkh/teacherAssessment/submitDialog.vue b/web/src/views/bzrkh/teacherAssessment/submitDialog.vue index 06656520..a84009e9 100644 --- a/web/src/views/bzrkh/teacherAssessment/submitDialog.vue +++ b/web/src/views/bzrkh/teacherAssessment/submitDialog.vue @@ -185,7 +185,7 @@ 特色工作(加20分)
- + @@ -203,7 +203,15 @@ - + @@ -224,7 +232,15 @@
- +
@@ -245,7 +261,15 @@
- +
@@ -266,15 +290,23 @@ - + - - + +
@@ -316,7 +348,9 @@ @@ -525,7 +559,7 @@ const calculateMonthlyScore = () => { // 检查是否有实际评分数据(避免空白状态显示50分) const hasAnyScore = routineWorkTotal > 0 || deductionHalf > 0 || specialWorkTotal > 0; - + if (!hasAnyScore || totalCoefficient === 0) { return 0; // 没有评分数据时显示0 } @@ -534,7 +568,7 @@ const calculateMonthlyScore = () => { const classDeductionMaxScore = 50; // 修正后的考核得分公式 = 总系数(K)* ((班主任常规工作自评分之和 + 班级扣分项满分值) - 按班级扣分50%计的分值 + 特色工作总分值) - return totalCoefficient * ((routineWorkTotal + classDeductionMaxScore) - deductionHalf + specialWorkTotal); + return totalCoefficient * (routineWorkTotal + classDeductionMaxScore - deductionHalf + specialWorkTotal); }; /** 打开弹窗 */ @@ -935,7 +969,7 @@ defineExpose({ font-weight: 400; text-align: center; } - + /* 覆盖small尺寸的字体大小,与特色工作输入框保持一致 */ .el-input-number--small.score-input .el-input-number__input { font-size: 14px !important; diff --git a/web/src/views/bzrkh/teacherAssessment/viewDialog.vue b/web/src/views/bzrkh/teacherAssessment/viewDialog.vue index 8c91e595..267a3e6b 100644 --- a/web/src/views/bzrkh/teacherAssessment/viewDialog.vue +++ b/web/src/views/bzrkh/teacherAssessment/viewDialog.vue @@ -55,9 +55,7 @@ {{ getCurrentClassDetail()?.totalStudentCount || 0 }}人 - {{ - formatNumber(getCurrentCoefficient()?.studentCountCoefficient, 3) - }} + {{ formatNumber(getCurrentCoefficient()?.studentCountCoefficient, 3) }}
@@ -70,9 +68,7 @@ {{ getCurrentClassDetail()?.boardingStudentCount || 0 }}人 - {{ - formatNumber(getCurrentCoefficient()?.boardingCoefficient, 3) - }} + {{ formatNumber(getCurrentCoefficient()?.boardingCoefficient, 3) }} @@ -165,10 +161,10 @@ - @@ -176,12 +172,12 @@ - + - + @@ -280,17 +276,17 @@ - + - + - + @@ -307,15 +303,13 @@ -
+
@@ -325,16 +319,14 @@
- - {{ calculateMonthlyScore().toFixed(2) }}分 - + {{ calculateMonthlyScore().toFixed(2) }}分 {{ parseTime(approvalDetail?.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') || '-' }} - + @@ -434,11 +426,7 @@ import { InfoFilled, WarningFilled } from '@element-plus/icons-vue'; import { getAssessmentSubmitDetail, getClassStudentRecords } from '@/api/bzrkh/teacherAssessment'; import { getAssessmentApprovalDetail, getEvaluationLevelStats } from '@/api/bzrkh/assessmentApproval'; -import { - AssessmentDetailVO, - AssessmentIndicatorVO, - ClassStudentRecordsVO -} from '@/api/bzrkh/teacherAssessment/types'; +import { AssessmentDetailVO, AssessmentIndicatorVO, ClassStudentRecordsVO } from '@/api/bzrkh/teacherAssessment/types'; import { AssessmentApprovalDetailVO, AssessmentApprovalIndicatorVO, @@ -471,9 +459,10 @@ const getEvaluationLevelOptions = (): DictDataOption[] => { console.log('✅ 使用数据库字典数据:', bmzr_evaluation_level.value); return bmzr_evaluation_level.value; } - + // 字典数据未加载或为空时的处理 - if (visible.value) { // 只在弹窗打开时输出警告 + if (visible.value) { + // 只在弹窗打开时输出警告 console.warn('⚠️ 字典数据为空,使用备用数据,当前状态:', { 弹窗是否打开: visible.value, 字典对象存在: !!bmzr_evaluation_level.value, @@ -482,7 +471,7 @@ const getEvaluationLevelOptions = (): DictDataOption[] => { 字典数据内容: bmzr_evaluation_level.value }); } - + return fallbackEvaluationLevelOptions.value; }; @@ -511,10 +500,10 @@ const isDictLoaded = ref(false); const isDictDataMissing = computed(() => { // 如果还没有尝试加载字典数据,不显示警告 if (!visible.value) return false; - + // 如果字典数据已经加载成功,不显示警告 if (isDictLoaded.value) return false; - + // 只有在弹窗打开且字典数据为空时才显示警告 return !bmzr_evaluation_level.value || bmzr_evaluation_level.value.length === 0; }); @@ -556,16 +545,16 @@ const specialWorkForm = reactive({ totalScore: 0 }); - // 考核得分表单(只读) - const monthlyScoreForm = reactive({ - overallComment: '' - }); +// 考核得分表单(只读) +const monthlyScoreForm = reactive({ + overallComment: '' +}); - // 审批模式下的考核得分数据 - const approvalMonthlyScoreData = reactive({ - selfOverallComment: '', - superiorOverallComment: '' - }); +// 审批模式下的考核得分数据 +const approvalMonthlyScoreData = reactive({ + selfOverallComment: '', + superiorOverallComment: '' +}); // 学生违纪记录和奖励记录弹窗相关 const studentRecordsVisible = ref(false); @@ -666,7 +655,7 @@ const calculateMonthlyScore = () => { // 检查是否有实际评分数据(避免空白状态显示50分) const hasAnyScore = routineWorkTotal > 0 || deductionHalf > 0 || specialWorkTotal > 0; - + if (!hasAnyScore || totalCoefficient === 0) { return 0; // 没有评分数据时显示0 } @@ -675,7 +664,7 @@ const calculateMonthlyScore = () => { const classDeductionMaxScore = 50; // 修正后的考核得分公式 = 总系数(K)* ((班主任常规工作自评分之和 + 班级扣分项满分值) - 按班级扣分50%计的分值 + 特色工作总分值) - return totalCoefficient * ((routineWorkTotal + classDeductionMaxScore) - deductionHalf + specialWorkTotal); + return totalCoefficient * (routineWorkTotal + classDeductionMaxScore - deductionHalf + specialWorkTotal); } }; @@ -685,7 +674,7 @@ const calculateEvaluationLevel = () => { console.log('=== 📊 评价等级计算调试 ==='); console.log('💯 考核得分:', score); console.log('📈 是否有统计数据:', !!evaluationStats.value); - + if (evaluationStats.value) { console.log('📊 统计数据详情:', { 总教师数: evaluationStats.value.totalTeachers, @@ -694,14 +683,12 @@ const calculateEvaluationLevel = () => { 及格分数: evaluationStats.value.evaluationLevelRules?.passScore }); } - + let level = ''; let calculationMethod = ''; - + // 如果有统计数据,使用严格的占比规则 - if (evaluationStats.value && - evaluationStats.value.totalTeachers > 0 && - evaluationStats.value.scoreRankings.length > 0) { + if (evaluationStats.value && evaluationStats.value.totalTeachers > 0 && evaluationStats.value.scoreRankings.length > 0) { level = calculateEvaluationLevelByStats(score); calculationMethod = '📈 严格占比规则评级'; } else { @@ -714,16 +701,16 @@ const calculateEvaluationLevel = () => { level = 'B'; // B等(合格):60-85分 } calculationMethod = '🔢 简化分数评级'; - + // 输出使用简化评级的原因 const reasons = []; if (!evaluationStats.value) reasons.push('统计数据为空'); if (evaluationStats.value?.totalTeachers === 0) reasons.push('总教师数为0'); if (evaluationStats.value?.scoreRankings?.length === 0) reasons.push('排名数据为空'); - + console.warn('⚠️ 使用简化评级的原因:', reasons.join('、')); } - + console.log('🎯 计算方法:', calculationMethod); console.log('📋 最终评价等级:', level); console.log('=== 📊 评价等级计算调试结束 ==='); @@ -735,30 +722,30 @@ const calculateEvaluationLevelByStats = (score: number) => { const stats = evaluationStats.value; if (!stats) { console.warn('统计数据不可用,使用简单评级'); - return score < 60 ? 'C' : (score >= 85 ? 'A' : 'B'); + return score < 60 ? 'C' : score >= 85 ? 'A' : 'B'; } - + const { totalTeachers, scoreRankings, evaluationLevelRules } = stats; const { aLevelMaxPercentage, passScore } = evaluationLevelRules; - + // C等:不及格 if (score < passScore) { return 'C'; } - + // 查找当前分数的排名 - const currentRanking = scoreRankings.find(item => item.score === score); - + const currentRanking = scoreRankings.find((item) => item.score === score); + if (!currentRanking) { console.warn('找不到当前分数的排名信息,使用简单评级'); - return score < 60 ? 'C' : (score >= 85 ? 'A' : 'B'); + return score < 60 ? 'C' : score >= 85 ? 'A' : 'B'; } - + // A等:及格且排名在前20%内 if (score >= passScore && currentRanking.percentage <= aLevelMaxPercentage) { return 'A'; } - + // B等:其余及格的 return 'B'; }; @@ -783,7 +770,7 @@ const open = async (auditStatus?: string) => { routineWorkScores.value = []; approvalRoutineWorkScores.value = []; approvalSpecialWorkScores.value = []; - + Object.assign(managementEffectivenessForm, { classDeduction: 0, classDeductionHalf: 0, @@ -816,7 +803,7 @@ const open = async (auditStatus?: string) => { }); visible.value = true; - + // 检查字典数据加载状态 console.log('🔍 打开弹窗时字典数据状态:', { 字典对象存在: !!bmzr_evaluation_level.value, @@ -824,21 +811,21 @@ const open = async (auditStatus?: string) => { 字典加载状态: isDictLoaded.value, 字典数据内容: bmzr_evaluation_level.value }); - + // 等待字典数据加载完成(最多等待2秒) let waitCount = 0; while ((!bmzr_evaluation_level.value || bmzr_evaluation_level.value.length === 0) && waitCount < 20) { - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 100)); waitCount++; } - + if (bmzr_evaluation_level.value && bmzr_evaluation_level.value.length > 0) { console.log('✅ 字典数据加载完成:', bmzr_evaluation_level.value); isDictLoaded.value = true; } else { console.warn('⚠️ 字典数据加载超时,使用备用数据'); } - + // 根据显示模式加载不同的数据 if (viewMode.value === 'approval') { await loadApprovalDetail(); @@ -854,7 +841,7 @@ const loadApprovalDetail = async () => { // 从userStore获取当前用户ID作为被评价用户ID const userStore = useUserStore(); const evaluatedUserId = userStore.userId; - + if (!evaluatedUserId) { proxy?.$modal.msgError('获取用户信息失败'); return; @@ -925,7 +912,6 @@ const loadApprovalDetail = async () => { } console.log('=== 审批详情数据加载完成 ==='); - } catch (error) { console.error('加载审批详情失败:', error); proxy?.$modal.msgError('加载审批详情失败,请检查网络连接或刷新页面重试'); @@ -981,7 +967,7 @@ const loadAssessmentDetail = async () => { break; } }); - + // 计算特色工作总分 specialWorkForm.totalScore = (specialWorkForm.score1 || 0) + (specialWorkForm.score2 || 0) + (specialWorkForm.score3 || 0) + (specialWorkForm.score4 || 0); @@ -1033,8 +1019,8 @@ const handleClose = () => { approvalRoutineWorkScores.value = []; approvalSpecialWorkScores.value = []; evaluationStats.value = undefined; - isDictLoaded.value = false; // 重置字典加载状态 - + isDictLoaded.value = false; // 重置字典加载状态 + Object.assign(managementEffectivenessForm, { classDeduction: 0, classDeductionHalf: 0, @@ -1128,13 +1114,13 @@ defineExpose({ .total-score { font-size: 16px; font-weight: 600; - color: #409EFF; + color: #409eff; } .monthly-score { font-size: 18px; font-weight: 600; - color: #67C23A; + color: #67c23a; } /* 表格样式 */ @@ -1183,7 +1169,7 @@ defineExpose({ max-height: 150px; overflow-y: auto; } - + .el-table__row { height: auto; } @@ -1280,4 +1266,4 @@ defineExpose({ padding: 16px; } } - \ No newline at end of file + diff --git a/web/src/views/cw/README.md b/web/src/views/cw/README.md index 47f3cb14..e46fe239 100644 --- a/web/src/views/cw/README.md +++ b/web/src/views/cw/README.md @@ -33,6 +33,7 @@ web/src/api/cw/ ## 🚀 核心功能 ### 1. 项目审批人配置管理 (`projectAuditor/`) + - **审批人池管理**: 从系统用户中选择并配置审批人员 - **批量添加**: 通过部门树选择用户,支持批量添加审批人 - **跨部门选择**: 支持在不同部门间选择用户,选择状态会自动保持 @@ -40,23 +41,27 @@ web/src/api/cw/ - **权限控制**: 基于RBAC的细粒度权限控制 ### 2. 项目审批流程管理 (`projectApproval/`) + - **流程监控**: 查看所有审批流程状态和进度(待实现) - **流程详情**: 查看审批记录和时间轴展示(待实现) - **流程终止**: 管理员可终止进行中的审批流程(待实现) ### 3. 我的待审批 (`projectApproval/pending.vue`) + - **待审批列表**: 显示当前用户需要审批的项目 - **项目详情**: 查看项目完整信息和审批历史 - **审批操作**: 支持通过/拒绝,必填审批意见 - **批量审批**: 支持批量通过或拒绝多个项目(待实现) ### 4. 审批流程选择组件 (`components/ApprovalProcessSelector.vue`) + - **自定义流程配置**: 支持用户从审批人池中选择审核人 - **可视化流程预览**: 直观展示审批顺序和流程走向 - **审核人管理**: 支持添加、删除、排序审核人 - **流程验证**: 验证审批流程的合理性和完整性 ### 5. 项目申报集成 (`application/form.vue` - 已修改) + - **流程选择对话框**: 在提交时弹出审批流程选择界面 - **数据保存**: 先保存申报数据,再配置审批流程 - **流程启动**: 自动启动配置的审批流程 @@ -65,6 +70,7 @@ web/src/api/cw/ ## 📋 实现特色 ### 🔧 技术特色 + 1. **Vue 3.5.13 Composition API**: 使用最新的组合式API,代码结构清晰 2. **TypeScript支持**: 完整的类型定义,提供良好的开发体验 3. **Element Plus UI**: 统一的UI组件库,符合后勤管理模块设计规范 @@ -72,6 +78,7 @@ web/src/api/cw/ 5. **组件化开发**: 高度复用的组件设计,易于维护和扩展 ### 🎨 用户体验特色 + 1. **跨部门选择**: 审批人选择支持在多个部门间切换,保持选择状态 2. **可视化流程**: 审批流程以图形化方式展示,直观易懂 3. **实时验证**: 表单验证和流程验证,及时提供反馈 @@ -79,6 +86,7 @@ web/src/api/cw/ 5. **状态恢复**: 切换部门后自动恢复之前的选择状态 ### 🚀 性能特色 + 1. **按需加载**: 组件和API按需加载,优化性能 2. **智能缓存**: 合理的数据缓存策略,减少重复请求 3. **批量操作**: 支持批量添加、删除等操作,提升效率 @@ -87,16 +95,19 @@ web/src/api/cw/ ## 🔄 核心流程 ### 审批人配置流程 + ``` 管理员登录 → 进入审批人配置 → 选择部门 → 批量选择用户 → 确认添加 → 配置完成 ``` ### 项目申报审批流程 + ``` 填写申报信息 → 点击提交 → 选择审批流程 → 配置审核人 → 确认提交 → 流程启动 ``` ### 审批执行流程 + ``` 审核人收到通知 → 查看待审批列表 → 查看项目详情 → 执行审批操作 → 填写意见 → 提交审批 ``` @@ -104,16 +115,19 @@ web/src/api/cw/ ## 🛠️ 后端支持 ### 数据库设计 + 1. **cw_project_auditor**: 项目审核人配置表 2. **cw_project_approval_process**: 项目审批流程表 3. **cw_approval_record**: 审批记录表(已扩展支持自定义流程) ### API接口 + 1. **审核人配置**: 16个标准API接口(CRUD + 业务功能 + 辅助功能) 2. **审批流程**: 10个核心API接口(流程管理 + 审批执行) 3. **权限控制**: 基于Sa-Token的权限管理 ### 数据安全 + 1. **多租户隔离**: 严格的数据隔离机制 2. **权限控制**: 细粒度的功能权限控制 3. **操作日志**: 完整的操作记录和审计 @@ -121,18 +135,21 @@ web/src/api/cw/ ## 📊 技术规范 ### 前端规范 + - **组件命名**: PascalCase,清晰的语义化命名 - **文件组织**: 按功能模块组织,层次分明 - **样式管理**: Scoped CSS,避免样式冲突 - **状态管理**: Reactive响应式状态,简洁高效 ### 后端规范 + - **接口设计**: RESTful风格,统一响应格式 - **数据验证**: Bean Validation参数校验 - **异常处理**: 统一的异常处理机制 - **事务管理**: 合理的事务边界控制 ### 代码质量 + - **TypeScript**: 完整的类型检查 - **ESLint**: 代码风格检查 - **Prettier**: 代码格式化 @@ -141,18 +158,21 @@ web/src/api/cw/ ## 🎯 后续优化 ### 功能扩展 + 1. **审批流程管理页面**: 完整的流程监控和管理界面 2. **批量审批功能**: 支持批量处理多个项目 3. **审批历史查询**: 完整的审批记录查询功能 4. **移动端支持**: 基于uni-app的移动端审批应用 ### 性能优化 + 1. **虚拟滚动**: 大数据量列表的性能优化 2. **缓存策略**: 更智能的数据缓存机制 3. **懒加载**: 按需加载组件和数据 4. **CDN集成**: 静态资源CDN加速 ### 用户体验 + 1. **快捷操作**: 键盘快捷键支持 2. **拖拽排序**: 审核人拖拽排序功能 3. **消息通知**: 实时消息推送 @@ -161,18 +181,21 @@ web/src/api/cw/ ## 🚀 部署说明 ### 开发环境 + 1. 确保Node.js 20.15.x+,npm 10.7.x+ 2. 安装依赖:`npm install` 3. 启动开发服务器:`npm run dev` 4. 访问:http://localhost ### 生产环境 + 1. 构建项目:`npm run build` 2. 部署到Nginx或其他Web服务器 3. 配置反向代理到后端API 4. 确保数据库脚本已执行 ### 数据库部署 + 1. 执行数据库脚本:`docs/数据库修复脚本.sql` 2. 配置权限菜单和角色权限 3. 创建测试用户和审核人数据 @@ -187,4 +210,4 @@ web/src/api/cw/ **📝 维护日期**: 2025-01-03 **📁 文件**: 完整的前后端实现 + 数据库脚本 + API文档 -**🔄 状态**: 核心功能完成,待进一步测试和优化 \ No newline at end of file +**🔄 状态**: 核心功能完成,待进一步测试和优化 diff --git a/web/src/views/cw/application/form.vue b/web/src/views/cw/application/form.vue index 10f46c4b..9639b93d 100644 --- a/web/src/views/cw/application/form.vue +++ b/web/src/views/cw/application/form.vue @@ -1158,7 +1158,7 @@ const confirmApprovalSubmit = async () => { // 构建审批流程数据 const approvalData = { applicationId: form.id, - applicationNo: form.applicationNo, // 新增:申报编号 + applicationNo: form.applicationNo, // 新增:申报编号 auditorIds: processConfig.auditorIds, processName: processConfig.processName || `${form.projectName}审批流程`, submitterId: userStore.userId, diff --git a/web/src/views/cw/application/index.vue b/web/src/views/cw/application/index.vue index 97cf69fe..8e09d93b 100644 --- a/web/src/views/cw/application/index.vue +++ b/web/src/views/cw/application/index.vue @@ -122,10 +122,14 @@ 修改 - 删除 + 删除 - 提交审批 + 提交审批 导出 @@ -135,7 +139,15 @@
- + @@ -169,31 +181,13 @@ - + - + - +
@@ -234,6 +228,20 @@
+ + + + + +
@@ -247,8 +255,11 @@ import { submitApplication, getApplicationStatistics } from '@/api/cw/projectApplication'; +import { startCustomApprovalProcess, type CwCustomApprovalRequest } from '@/api/cw/projectApproval'; +import { getInfo } from '@/api/login'; import ApplicationForm from './form.vue'; import ApplicationSummary from './summary.vue'; +import ApprovalProcessSelector from '../projectApproval/components/ApprovalProcessSelector.vue'; import Pagination from '@/components/Pagination/index.vue'; export default { @@ -256,6 +267,7 @@ export default { components: { ApplicationForm, ApplicationSummary, + ApprovalProcessSelector, Pagination }, data() { @@ -300,7 +312,14 @@ export default { totalCount: 0, approvedCount: 0, pendingCount: 0, - draftCount: 0 + draftCount: 0, + // 审批流程选择对话框 + approvalProcessDialog: { + visible: false, + applicationId: null, + applicationNo: null, + selectedAuditors: [] + } }; }, created() { @@ -476,21 +495,92 @@ export default { }, /** 提交审批 */ handleSubmit(row) { - const applications = row?.applicationNo - ? [row.applicationNo] - : this.applicationList.filter((item) => this.ids.includes(item.id)).map((item) => item.applicationNo); + if (row) { + // 单个提交 + this.approvalProcessDialog.applicationId = row.id; + this.approvalProcessDialog.applicationNo = row.applicationNo; + this.approvalProcessDialog.visible = true; + this.approvalProcessDialog.selectedAuditors = []; + } else { + // 批量提交 - 暂不支持 + this.$modal.msgWarning('批量提交审批功能暂未开放,请单个提交'); + } + }, + /** 确认提交审批 */ + async confirmSubmitApproval() { + try { + const processSelector = this.$refs.approvalProcessSelectorRef; + if (!processSelector) { + this.$modal.msgError('审批流程选择组件未加载'); + return; + } - this.$modal - .confirm('是否确认提交申报编号为"' + applications.join('", "') + '"的数据项?') - .then(() => { - return submitApplication(row?.id || this.ids); - }) - .then(() => { - this.getList(); - this.getStatistics(); // 提交后更新统计 - this.$modal.msgSuccess('提交成功'); - }) - .catch(() => {}); + // 验证审批流程 + const validation = processSelector.validateProcess(); + if (!validation.valid) { + this.$modal.msgError(validation.message); + return; + } + + // 获取当前用户信息 + let userInfo = null; + try { + const userResponse = await getInfo(); + if (userResponse.code === 200 && userResponse.data && userResponse.data.user) { + userInfo = userResponse.data.user; + } + } catch (userError) { + console.error('获取用户信息失败:', userError); + this.$modal.msgError('获取用户信息失败,请重新登录'); + return; + } + + if (!userInfo || !userInfo.userId) { + this.$modal.msgError('用户信息不完整,请重新登录'); + return; + } + + // 获取审批流程配置 + const processConfig = processSelector.getProcessConfig(); + + // 构建请求数据 + const approvalData: CwCustomApprovalRequest = { + applicationId: this.approvalProcessDialog.applicationId, + applicationNo: this.approvalProcessDialog.applicationNo, + processName: processConfig.processName, + auditorIds: processConfig.auditorIds, + submitterId: userInfo.userId, + submitterName: userInfo.nickName || userInfo.userName + }; + + console.log('启动审批流程数据:', approvalData); + + // 启动审批流程 + const result = await startCustomApprovalProcess(approvalData); + console.log('启动审批流程结果:', result); + + // 关闭对话框 + this.approvalProcessDialog.visible = false; + this.approvalProcessDialog.applicationId = null; + this.approvalProcessDialog.applicationNo = null; + this.approvalProcessDialog.selectedAuditors = []; + + // 刷新列表和统计 + this.getList(); + this.getStatistics(); + + this.$modal.msgSuccess('审批流程启动成功'); + } catch (error) { + console.error('启动审批流程失败:', error); + this.$modal.msgError('启动审批流程失败:' + (error.message || '未知错误')); + } + }, + /** 取消提交审批 */ + cancelSubmitApproval() { + this.approvalProcessDialog.visible = false; + this.approvalProcessDialog.applicationId = null; + this.approvalProcessDialog.applicationNo = null; + this.approvalProcessDialog.selectedAuditors = []; }, /** 撤回申请 */ handleWithdraw(row) { @@ -642,7 +732,9 @@ export default { /* 卡片样式 */ :deep(.el-card) { border: 1px solid #e4e7ed; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); + box-shadow: + 0 2px 4px rgba(0, 0, 0, 0.12), + 0 0 6px rgba(0, 0, 0, 0.04); } /* 表格卡片头部样式 */ diff --git a/web/src/views/cw/approvalRecord/index.vue b/web/src/views/cw/approvalRecord/index.vue index 73f218c6..4b1b6ad7 100644 --- a/web/src/views/cw/approvalRecord/index.vue +++ b/web/src/views/cw/approvalRecord/index.vue @@ -98,7 +98,15 @@
- +
@@ -377,7 +385,9 @@ export default { .table-card { margin-top: 0; border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); + box-shadow: + 0 2px 4px rgba(0, 0, 0, 0.12), + 0 0 6px rgba(0, 0, 0, 0.04); } .table-card :deep(.el-card__body) { diff --git a/web/src/views/cw/leaderApproval/index.vue b/web/src/views/cw/leaderApproval/index.vue index 2aa754a3..96e1632f 100644 --- a/web/src/views/cw/leaderApproval/index.vue +++ b/web/src/views/cw/leaderApproval/index.vue @@ -157,8 +157,8 @@ - 通过 - 退回申请人 + 通过 + 退回申请人 diff --git a/web/src/views/cw/projectApproval/README.md b/web/src/views/cw/projectApproval/README.md index d8adf7e3..4e81dfa4 100644 --- a/web/src/views/cw/projectApproval/README.md +++ b/web/src/views/cw/projectApproval/README.md @@ -7,23 +7,27 @@ ## 主要功能 ### 1. 审批流程管理 + - 查询审批流程列表(支持分页) - 按申报编号、流程状态、提交人等条件筛选 - 显示审批进度和流程状态 - 支持终止正在进行的审批流程 ### 2. 待我审批 + - 查看分配给当前用户的待审批任务 - 执行审批操作(通过/打回) - 填写审批意见 - 查看申报详情和审批记录 ### 3. 我的已审批 + - 查看当前用户已完成的审批记录 - 显示审批动作和意见 - 统计审批效率和处理时长 ### 4. 审批记录跟踪 + - 完整的审批时间线展示 - 每个审批节点的详细信息 - 审批意见和处理结果记录 @@ -39,21 +43,25 @@ web/src/views/cw/projectApproval/ ## 功能特性 ### 1. 多标签页设计 + - **审批流程管理**: 查看所有审批流程的整体情况 - **待我审批**: 专注于当前用户需要处理的审批任务 - **我的已审批**: 查看历史审批记录和统计 ### 2. 统计卡片 + - 总流程数、等待开始、进行中、已完成等状态统计 - 平均处理时长、待我审批数量等关键指标 - 本月完成数量、已终止流程等趋势数据 ### 3. 审批操作 + - 支持批量审批和单个审批 - 必填审批意见,确保审批质量 - 实时更新审批状态和流程进度 ### 4. 流程可视化 + - 进度条显示审批进度 - 时间线展示审批历史 - 状态标签清晰标识各阶段 @@ -61,6 +69,7 @@ web/src/views/cw/projectApproval/ ## API接口 使用的主要API接口: + - `listProjectApproval` - 查询审批流程列表 - `listPendingApprovals` - 查询待我审批列表 - `listProcessedApprovals` - 查询我的已审批列表 @@ -72,6 +81,7 @@ web/src/views/cw/projectApproval/ ## 权限控制 页面功能对应的权限标识: + - `cw:projectApproval:list` - 查询权限 - `cw:projectApproval:add` - 启动流程权限 - `cw:projectApproval:approve` - 审批权限 @@ -81,18 +91,21 @@ web/src/views/cw/projectApproval/ ## 状态说明 ### 流程状态 + - `waiting` - 等待开始 -- `running` - 进行中 +- `running` - 进行中 - `completed` - 已完成 - `terminated` - 已终止 ### 节点状态 + - `waiting` - 等待审批 - `approved` - 已通过 - `rejected` - 已拒绝 - `pending` - 待处理 ### 审批动作 + - `approve` - 审批通过 - `reject` - 打回申请 @@ -114,6 +127,7 @@ web/src/views/cw/projectApproval/ ## 使用说明 ### 审批人员操作流程 + 1. 登录系统,进入项目审批管理页面 2. 点击"待我审批"标签页查看待处理任务 3. 点击具体记录查看申报详情 @@ -121,6 +135,7 @@ web/src/views/cw/projectApproval/ 5. 提交审批结果,系统自动推进流程 ### 管理员操作流程 + 1. 在"审批流程管理"标签页查看所有流程状态 2. 可以终止异常或不需要的审批流程 3. 通过统计卡片监控审批效率和积压情况 @@ -131,4 +146,4 @@ web/src/views/cw/projectApproval/ 1. 审批意见为必填项,且至少5个字符 2. 审批操作不可撤销,请谨慎操作 3. 终止流程会影响项目申报状态,需谨慎使用 -4. 导出功能需要相应权限,请联系管理员配置 \ No newline at end of file +4. 导出功能需要相应权限,请联系管理员配置 diff --git a/web/src/views/cw/projectApproval/components/ApprovalProcessSelector.vue b/web/src/views/cw/projectApproval/components/ApprovalProcessSelector.vue index 2a740a7a..fecbbf7c 100644 --- a/web/src/views/cw/projectApproval/components/ApprovalProcessSelector.vue +++ b/web/src/views/cw/projectApproval/components/ApprovalProcessSelector.vue @@ -2,7 +2,7 @@
- 自定义审批流程 + 自定义审批流程 diff --git a/web/src/views/cw/projectApproval/index.vue b/web/src/views/cw/projectApproval/index.vue index 855244d1..4cb86591 100644 --- a/web/src/views/cw/projectApproval/index.vue +++ b/web/src/views/cw/projectApproval/index.vue @@ -307,11 +307,11 @@ - + 审批通过 - + 打回申请 diff --git a/web/src/views/cw/projectApproval/pending.vue b/web/src/views/cw/projectApproval/pending.vue index 271a6626..5e484174 100644 --- a/web/src/views/cw/projectApproval/pending.vue +++ b/web/src/views/cw/projectApproval/pending.vue @@ -21,11 +21,7 @@ - + @@ -64,13 +60,7 @@ - +
@@ -87,25 +77,18 @@ - + 审批通过 - + 审批拒绝 - +
@@ -113,27 +96,15 @@ - +
- +
@@ -181,15 +192,15 @@ :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" - @update:page="(val) => queryParams.pageNum = val" - @update:limit="(val) => queryParams.pageSize = val" + @update:page="(val) => (queryParams.pageNum = val)" + @update:limit="(val) => (queryParams.pageSize = val)" @pagination="getList" />
- +
@@ -221,24 +232,42 @@ - - - - - + + + + + + + + @@ -261,7 +290,7 @@ @close="removeSelectedUser(user)" class="selected-user-tag" > - {{ user.nickName }}({{ user.userName }}) + {{ user.nickName }}({{ user.userName }}) - {{ user.deptName || '未知部门' }}
@@ -271,12 +300,7 @@ - +