zlwu hace 1 año
padre
commit
a884f695ea

+ 24 - 4
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormViolationController.java

@@ -19,6 +19,8 @@ import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.xg.domain.vo.XgDormViolationVo;
 import org.dromara.xg.domain.vo.XgDormViolationVo;
 import org.dromara.xg.domain.bo.XgDormViolationBo;
 import org.dromara.xg.domain.bo.XgDormViolationBo;
+import org.dromara.xg.domain.vo.StudentOptionVo;
+import org.dromara.xg.domain.bo.StudentOptionBo;
 import org.dromara.xg.service.IXgDormViolationService;
 import org.dromara.xg.service.IXgDormViolationService;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 
 
@@ -63,8 +65,7 @@ public class XgDormViolationController extends BaseController {
      */
      */
     @SaCheckPermission("dormViolation:dormViolation:query")
     @SaCheckPermission("dormViolation:dormViolation:query")
     @GetMapping("/{id}")
     @GetMapping("/{id}")
-    public R<XgDormViolationVo> getInfo(@NotNull(message = "主键不能为空")
-                                     @PathVariable Long id) {
+    public R<XgDormViolationVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
         return R.ok(xgDormViolationService.queryById(id));
         return R.ok(xgDormViolationService.queryById(id));
     }
     }
 
 
@@ -98,8 +99,27 @@ public class XgDormViolationController extends BaseController {
     @SaCheckPermission("dormViolation:dormViolation:remove")
     @SaCheckPermission("dormViolation:dormViolation:remove")
     @Log(title = "宿舍违纪记录", businessType = BusinessType.DELETE)
     @Log(title = "宿舍违纪记录", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
     @DeleteMapping("/{ids}")
-    public R<Void> remove(@NotEmpty(message = "主键不能为空")
-                          @PathVariable Long[] ids) {
+    public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
         return toAjax(xgDormViolationService.deleteWithValidByIds(List.of(ids), true));
         return toAjax(xgDormViolationService.deleteWithValidByIds(List.of(ids), true));
     }
     }
+
+    /**
+     * 获取学生选择器选项列表(支持按学号、姓名检索)
+     */
+    @SaCheckPermission("dormViolation:dormViolation:list")
+    @GetMapping("/studentOptions")
+    public TableDataInfo<StudentOptionVo> getStudentOptions(StudentOptionBo bo, PageQuery pageQuery) {
+        return xgDormViolationService.getStudentOptions(bo, pageQuery);
+    }
+
+    /**
+     * 根据学生ID获取学生详细信息
+     *
+     * @param studentId 学生ID
+     */
+    @SaCheckPermission("dormViolation:dormViolation:list")
+    @GetMapping("/student/{studentId}")
+    public R<StudentOptionVo> getStudentById(@NotNull(message = "学生ID不能为空") @PathVariable Long studentId) {
+        return R.ok(xgDormViolationService.getStudentById(studentId));
+    }
 }
 }

+ 21 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormViolationMapper.java

@@ -2,7 +2,12 @@ package org.dromara.xg.mapper;
 
 
 import org.dromara.xg.domain.XgDormViolation;
 import org.dromara.xg.domain.XgDormViolation;
 import org.dromara.xg.domain.vo.XgDormViolationVo;
 import org.dromara.xg.domain.vo.XgDormViolationVo;
+import org.dromara.xg.domain.vo.StudentOptionVo;
+import org.dromara.xg.domain.bo.StudentOptionBo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
 
 
 /**
 /**
  * 宿舍违纪记录Mapper接口
  * 宿舍违纪记录Mapper接口
@@ -12,4 +17,20 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
  */
  */
 public interface XgDormViolationMapper extends BaseMapperPlus<XgDormViolation, XgDormViolationVo> {
 public interface XgDormViolationMapper extends BaseMapperPlus<XgDormViolation, XgDormViolationVo> {
 
 
+    /**
+     * 查询学生选择器选项列表(支持按学号、姓名检索)
+     *
+     * @param page 分页参数
+     * @param bo   查询条件
+     * @return 学生选项列表
+     */
+    Page<StudentOptionVo> selectStudentOptions(IPage<?> page, @Param("bo") StudentOptionBo bo);
+
+    /**
+     * 根据学生ID获取学生详细信息
+     *
+     * @param studentId 学生ID
+     * @return 学生详细信息
+     */
+    StudentOptionVo selectStudentById(@Param("studentId") Long studentId);
 }
 }

+ 19 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormViolationService.java

@@ -2,6 +2,8 @@ package org.dromara.xg.service;
 
 
 import org.dromara.xg.domain.vo.XgDormViolationVo;
 import org.dromara.xg.domain.vo.XgDormViolationVo;
 import org.dromara.xg.domain.bo.XgDormViolationBo;
 import org.dromara.xg.domain.bo.XgDormViolationBo;
+import org.dromara.xg.domain.vo.StudentOptionVo;
+import org.dromara.xg.domain.bo.StudentOptionBo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.PageQuery;
 
 
@@ -65,4 +67,21 @@ public interface IXgDormViolationService {
      * @return 是否删除成功
      * @return 是否删除成功
      */
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 获取学生选择器选项列表(支持按学号、姓名检索)
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 学生选项分页列表
+     */
+    TableDataInfo<StudentOptionVo> getStudentOptions(StudentOptionBo bo, PageQuery pageQuery);
+
+    /**
+     * 根据学生ID获取学生详细信息
+     *
+     * @param studentId 学生ID
+     * @return 学生详细信息
+     */
+    StudentOptionVo getStudentById(Long studentId);
 }
 }

+ 39 - 9
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormViolationServiceImpl.java

@@ -12,6 +12,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.dromara.xg.domain.bo.XgDormViolationBo;
 import org.dromara.xg.domain.bo.XgDormViolationBo;
 import org.dromara.xg.domain.vo.XgDormViolationVo;
 import org.dromara.xg.domain.vo.XgDormViolationVo;
+import org.dromara.xg.domain.bo.StudentOptionBo;
+import org.dromara.xg.domain.vo.StudentOptionVo;
 import org.dromara.xg.domain.XgDormViolation;
 import org.dromara.xg.domain.XgDormViolation;
 import org.dromara.xg.mapper.XgDormViolationMapper;
 import org.dromara.xg.mapper.XgDormViolationMapper;
 import org.dromara.xg.service.IXgDormViolationService;
 import org.dromara.xg.service.IXgDormViolationService;
@@ -40,7 +42,7 @@ public class XgDormViolationServiceImpl implements IXgDormViolationService {
      * @return 宿舍违纪记录
      * @return 宿舍违纪记录
      */
      */
     @Override
     @Override
-    public XgDormViolationVo queryById(Long id){
+    public XgDormViolationVo queryById(Long id) {
         return baseMapper.selectVoById(id);
         return baseMapper.selectVoById(id);
     }
     }
 
 
@@ -94,11 +96,15 @@ public class XgDormViolationServiceImpl implements IXgDormViolationService {
         lqw.eq(bo.getBedId() != null, XgDormViolation::getBedId, bo.getBedId());
         lqw.eq(bo.getBedId() != null, XgDormViolation::getBedId, bo.getBedId());
         lqw.like(StringUtils.isNotBlank(bo.getBedName()), XgDormViolation::getBedName, bo.getBedName());
         lqw.like(StringUtils.isNotBlank(bo.getBedName()), XgDormViolation::getBedName, bo.getBedName());
         lqw.eq(StringUtils.isNotBlank(bo.getViolationType()), XgDormViolation::getViolationType, bo.getViolationType());
         lqw.eq(StringUtils.isNotBlank(bo.getViolationType()), XgDormViolation::getViolationType, bo.getViolationType());
-        lqw.eq(StringUtils.isNotBlank(bo.getViolationLevel()), XgDormViolation::getViolationLevel, bo.getViolationLevel());
+        lqw.eq(StringUtils.isNotBlank(bo.getViolationLevel()), XgDormViolation::getViolationLevel,
+                bo.getViolationLevel());
         lqw.eq(bo.getViolationTime() != null, XgDormViolation::getViolationTime, bo.getViolationTime());
         lqw.eq(bo.getViolationTime() != null, XgDormViolation::getViolationTime, bo.getViolationTime());
-        lqw.like(StringUtils.isNotBlank(bo.getViolationDescription()), XgDormViolation::getViolationDescription, bo.getViolationDescription());
-        lqw.like(StringUtils.isNotBlank(bo.getInvolvedStudents()), XgDormViolation::getInvolvedStudents, bo.getInvolvedStudents());
-        lqw.like(StringUtils.isNotBlank(bo.getAttachmentOssIds()), XgDormViolation::getAttachmentOssIds, bo.getAttachmentOssIds());
+        lqw.like(StringUtils.isNotBlank(bo.getViolationDescription()), XgDormViolation::getViolationDescription,
+                bo.getViolationDescription());
+        lqw.like(StringUtils.isNotBlank(bo.getInvolvedStudents()), XgDormViolation::getInvolvedStudents,
+                bo.getInvolvedStudents());
+        lqw.like(StringUtils.isNotBlank(bo.getAttachmentOssIds()), XgDormViolation::getAttachmentOssIds,
+                bo.getAttachmentOssIds());
         lqw.eq(StringUtils.isNotBlank(bo.getDeleted()), XgDormViolation::getDeleted, bo.getDeleted());
         lqw.eq(StringUtils.isNotBlank(bo.getDeleted()), XgDormViolation::getDeleted, bo.getDeleted());
         return lqw;
         return lqw;
     }
     }
@@ -136,8 +142,8 @@ public class XgDormViolationServiceImpl implements IXgDormViolationService {
     /**
     /**
      * 保存前的数据校验
      * 保存前的数据校验
      */
      */
-    private void validEntityBeforeSave(XgDormViolation entity){
-        //TODO 做一些数据校验,如唯一约束
+    private void validEntityBeforeSave(XgDormViolation entity) {
+        // TODO 做一些数据校验,如唯一约束
     }
     }
 
 
     /**
     /**
@@ -149,9 +155,33 @@ public class XgDormViolationServiceImpl implements IXgDormViolationService {
      */
      */
     @Override
     @Override
     public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
     public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
-        if(isValid){
-            //TODO 做一些业务上的校验,判断是否需要校验
+        if (isValid) {
+            // TODO 做一些业务上的校验,判断是否需要校验
         }
         }
         return baseMapper.deleteByIds(ids) > 0;
         return baseMapper.deleteByIds(ids) > 0;
     }
     }
+
+    /**
+     * 获取学生选择器选项列表(支持按学号、姓名检索)
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 学生选项分页列表
+     */
+    @Override
+    public TableDataInfo<StudentOptionVo> getStudentOptions(StudentOptionBo bo, PageQuery pageQuery) {
+        Page<StudentOptionVo> result = baseMapper.selectStudentOptions(pageQuery.build(), bo);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 根据学生ID获取学生详细信息
+     *
+     * @param studentId 学生ID
+     * @return 学生详细信息
+     */
+    @Override
+    public StudentOptionVo getStudentById(Long studentId) {
+        return baseMapper.selectStudentById(studentId);
+    }
 }
 }

+ 114 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/dormViolation/XgDormViolationMapper.xml

@@ -4,4 +4,118 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="org.dromara.xg.mapper.XgDormViolationMapper">
 <mapper namespace="org.dromara.xg.mapper.XgDormViolationMapper">
 
 
+    <!-- 学生选项查询结果映射 -->
+    <resultMap id="StudentOptionResult" type="org.dromara.xg.domain.vo.StudentOptionVo">
+        <id property="id" column="id"/>
+        <result property="studentNo" column="student_no"/>
+        <result property="name" column="name"/>
+        <result property="gender" column="gender"/>
+        <result property="collegeId" column="college_id"/>
+        <result property="collegeName" column="college_name"/>
+        <result property="majorId" column="major_id"/>
+        <result property="majorName" column="major_name"/>
+        <result property="classId" column="class_id"/>
+        <result property="className" column="class_name"/>
+        <result property="phone" column="phone"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+        <association property="dormInfo" javaType="org.dromara.xg.domain.vo.StudentOptionVo$DormInfo">
+            <result property="campusId" column="campus_id"/>
+            <result property="campusName" column="campus_name"/>
+            <result property="areaId" column="area_id"/>
+            <result property="areaName" column="area_name"/>
+            <result property="buildingId" column="building_id"/>
+            <result property="buildingName" column="building_name"/>
+            <result property="roomId" column="room_id"/>
+            <result property="roomName" column="room_name"/>
+            <result property="bedId" column="bed_id"/>
+            <result property="bedName" column="bed_name"/>
+        </association>
+    </resultMap>
+
+    <!-- 查询学生选择器选项列表 -->
+    <select id="selectStudentOptions" resultMap="StudentOptionResult">
+        SELECT
+            s.id,
+            s.student_no,
+            s.name,
+            s.gender,
+            s.college_id,
+            s.college_name,
+            s.major_id,
+            s.major_name,
+            s.class_id,
+            s.class_name,
+            s.phone,
+            s.create_by,
+            s.create_time,
+            s.update_by,
+            s.update_time,
+            COALESCE(dc.id, NULL) as campus_id,
+            COALESCE(dc.campus_name, NULL) as campus_name,
+            COALESCE(da.id, NULL) as area_id,
+            COALESCE(da.area_name, NULL) as area_name,
+            COALESCE(db.id, NULL) as building_id,
+            COALESCE(db.building_name, NULL) as building_name,
+            COALESCE(dr.id, NULL) as room_id,
+            COALESCE(dr.room_name, NULL) as room_name,
+            COALESCE(bed.id, NULL) as bed_id,
+            COALESCE(bed.bed_name, NULL) as bed_name
+        FROM xg_student_msg s
+        LEFT JOIN xg_dorm_bed_student dbs ON s.id = dbs.student_id AND dbs.deleted = 0
+        LEFT JOIN xg_dorm_bed bed ON dbs.bed_id = bed.id AND bed.deleted = 0
+        LEFT JOIN xg_dorm_room dr ON bed.room_id = dr.id AND dr.deleted = 0
+        LEFT JOIN xg_dorm_building db ON dr.building_id = db.id AND db.deleted = 0
+        LEFT JOIN xg_dorm_area da ON db.area_id = da.id AND da.deleted = 0
+        LEFT JOIN xg_dorm_campus dc ON da.campus_id = dc.id AND dc.deleted = 0
+        WHERE s.deleted = 0
+        AND s.student_status = '1'
+        <if test="bo.keyword != null and bo.keyword != ''">
+            AND (s.student_no LIKE CONCAT('%', #{bo.keyword}, '%')
+            OR s.name LIKE CONCAT('%', #{bo.keyword}, '%'))
+        </if>
+        ORDER BY s.student_no ASC
+    </select>
+
+    <!-- 根据学生ID获取学生详细信息 -->
+    <select id="selectStudentById" resultMap="StudentOptionResult">
+        SELECT
+            s.id,
+            s.student_no,
+            s.name,
+            s.gender,
+            s.college_id,
+            s.college_name,
+            s.major_id,
+            s.major_name,
+            s.class_id,
+            s.class_name,
+            s.phone,
+            s.create_by,
+            s.create_time,
+            s.update_by,
+            s.update_time,
+            COALESCE(dc.id, NULL) as campus_id,
+            COALESCE(dc.campus_name, NULL) as campus_name,
+            COALESCE(da.id, NULL) as area_id,
+            COALESCE(da.area_name, NULL) as area_name,
+            COALESCE(db.id, NULL) as building_id,
+            COALESCE(db.building_name, NULL) as building_name,
+            COALESCE(dr.id, NULL) as room_id,
+            COALESCE(dr.room_name, NULL) as room_name,
+            COALESCE(bed.id, NULL) as bed_id,
+            COALESCE(bed.bed_name, NULL) as bed_name
+        FROM xg_student_msg s
+        LEFT JOIN xg_dorm_bed_student dbs ON s.id = dbs.student_id AND dbs.deleted = 0
+        LEFT JOIN xg_dorm_bed bed ON dbs.bed_id = bed.id AND bed.deleted = 0
+        LEFT JOIN xg_dorm_room dr ON bed.room_id = dr.id AND dr.deleted = 0
+        LEFT JOIN xg_dorm_building db ON dr.building_id = db.id AND db.deleted = 0
+        LEFT JOIN xg_dorm_area da ON db.area_id = da.id AND da.deleted = 0
+        LEFT JOIN xg_dorm_campus dc ON da.campus_id = dc.id AND dc.deleted = 0
+        WHERE s.deleted = 0
+        AND s.id = #{studentId}
+    </select>
+
 </mapper>
 </mapper>

+ 16 - 2
web/src/api/xg/xgss/dormViolation/dormViolation/index.ts

@@ -78,12 +78,14 @@ export const delDormViolation = (id: string | number | Array<string | number>) =
  * @param query 查询参数
  * @param query 查询参数
  * @returns 学生选项列表
  * @returns 学生选项列表
  */
  */
-export const getStudentOptions = (query?: StudentOptionQuery): AxiosPromise<{
+export const getStudentOptions = (
+  query?: StudentOptionQuery
+): AxiosPromise<{
   total: number;
   total: number;
   rows: StudentOptionVO[];
   rows: StudentOptionVO[];
 }> => {
 }> => {
   return request({
   return request({
-    url: '/holidayStay/holidayStay/studentOptions',
+    url: '/dormViolation/dormViolation/studentOptions',
     method: 'get',
     method: 'get',
     params: query
     params: query
   });
   });
@@ -151,3 +153,15 @@ export const getBedOptions = (roomId?: string | number): AxiosPromise<any[]> =>
     params: roomId ? { roomId } : undefined
     params: roomId ? { roomId } : undefined
   });
   });
 };
 };
+
+/**
+ * 根据学生ID获取学生详细信息
+ * @param studentId 学生ID
+ * @returns 学生详细信息
+ */
+export const getStudentById = (studentId: string | number): AxiosPromise<StudentOptionVO> => {
+  return request({
+    url: `/dormViolation/dormViolation/student/${studentId}`,
+    method: 'get'
+  });
+};

+ 2 - 2
web/src/api/xg/xgss/dormViolation/dormViolation/types.ts

@@ -263,9 +263,9 @@ export interface DormViolationForm extends BaseEntity {
   violationDescription?: string;
   violationDescription?: string;
 
 
   /**
   /**
-   * 涉及其他学生id(逗号分隔)
+   * 涉及其他学生id(逗号分隔)或数组
    */
    */
-  involvedStudents?: string;
+  involvedStudents?: string | (string | number)[];
 
 
   /**
   /**
    * 附件OSS ID列表(逗号分隔)
    * 附件OSS ID列表(逗号分隔)

+ 606 - 84
web/src/views/xg/xgss/dormViolation/dormViolation/index.vue

@@ -106,7 +106,7 @@
               >修改</el-button
               >修改</el-button
             >
             >
           </el-col>
           </el-col>
-          <el-col :span="1.5">
+          <!-- <el-col :span="1.5">
             <el-button
             <el-button
               type="danger"
               type="danger"
               plain
               plain
@@ -116,7 +116,7 @@
               v-hasPermi="['dormViolation:dormViolation:remove']"
               v-hasPermi="['dormViolation:dormViolation:remove']"
               >删除</el-button
               >删除</el-button
             >
             >
-          </el-col>
+          </el-col> -->
           <el-col :span="1.5">
           <el-col :span="1.5">
             <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['dormViolation:dormViolation:export']">导出</el-button>
             <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['dormViolation:dormViolation:export']">导出</el-button>
           </el-col>
           </el-col>
@@ -126,47 +126,47 @@
 
 
       <el-table v-loading="loading" border :data="dormViolationList" @selection-change="handleSelectionChange">
       <el-table v-loading="loading" border :data="dormViolationList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="违纪记录ID" align="center" prop="id" v-if="true" />
-        <el-table-column label="学生ID" align="center" prop="studentId" />
+        <!-- <el-table-column label="违纪记录ID" align="center" prop="id" v-if="true" />
+        <el-table-column label="学生ID" align="center" prop="studentId" /> -->
         <el-table-column label="学生姓名" align="center" prop="studentName" />
         <el-table-column label="学生姓名" align="center" prop="studentName" />
         <el-table-column label="学号" align="center" prop="studentNo" />
         <el-table-column label="学号" align="center" prop="studentNo" />
-        <el-table-column label="学院ID" align="center" prop="collegeId" />
+        <!-- <el-table-column label="学院ID" align="center" prop="collegeId" /> -->
         <el-table-column label="学院名称" align="center" prop="collegeName" />
         <el-table-column label="学院名称" align="center" prop="collegeName" />
-        <el-table-column label="专业ID" align="center" prop="majorId" />
+        <!-- <el-table-column label="专业ID" align="center" prop="majorId" /> -->
         <el-table-column label="专业名称" align="center" prop="majorName" />
         <el-table-column label="专业名称" align="center" prop="majorName" />
-        <el-table-column label="班级ID" align="center" prop="classId" />
+        <!-- <el-table-column label="班级ID" align="center" prop="classId" /> -->
         <el-table-column label="班级名称" align="center" prop="className" />
         <el-table-column label="班级名称" align="center" prop="className" />
-        <el-table-column label="校区ID" align="center" prop="campusId" />
+        <!-- <el-table-column label="校区ID" align="center" prop="campusId" /> -->
         <el-table-column label="校区名称" align="center" prop="campusName" />
         <el-table-column label="校区名称" align="center" prop="campusName" />
-        <el-table-column label="宿舍区域ID" align="center" prop="areaId" />
+        <!-- <el-table-column label="宿舍区域ID" align="center" prop="areaId" /> -->
         <el-table-column label="宿舍区域名称" align="center" prop="areaName" />
         <el-table-column label="宿舍区域名称" align="center" prop="areaName" />
-        <el-table-column label="宿舍楼ID" align="center" prop="buildingId" />
+        <!-- <el-table-column label="宿舍楼ID" align="center" prop="buildingId" /> -->
         <el-table-column label="宿舍楼名称" align="center" prop="buildingName" />
         <el-table-column label="宿舍楼名称" align="center" prop="buildingName" />
-        <el-table-column label="宿舍房间ID" align="center" prop="roomId" />
+        <!-- <el-table-column label="宿舍房间ID" align="center" prop="roomId" /> -->
         <el-table-column label="房间名称" align="center" prop="roomName" />
         <el-table-column label="房间名称" align="center" prop="roomName" />
-        <el-table-column label="床位ID" align="center" prop="bedId" />
+        <!-- <el-table-column label="床位ID" align="center" prop="bedId" /> -->
         <el-table-column label="床位名称" align="center" prop="bedName" />
         <el-table-column label="床位名称" align="center" prop="bedName" />
         <el-table-column label="违纪类型" align="center" prop="violationType">
         <el-table-column label="违纪类型" align="center" prop="violationType">
           <template #default="scope">
           <template #default="scope">
             <dict-tag :options="dorm_violation_type" :value="scope.row.violationType" />
             <dict-tag :options="dorm_violation_type" :value="scope.row.violationType" />
           </template>
           </template>
         </el-table-column>
         </el-table-column>
-        <el-table-column label="违纪等级:1轻微 2一般 3严重 4特别严重" align="center" prop="violationLevel">
+        <el-table-column label="违纪等级" align="center" prop="violationLevel">
           <template #default="scope">
           <template #default="scope">
             <dict-tag :options="dorm_violation_level" :value="scope.row.violationLevel" />
             <dict-tag :options="dorm_violation_level" :value="scope.row.violationLevel" />
           </template>
           </template>
         </el-table-column>
         </el-table-column>
         <el-table-column label="违纪发生时间" align="center" prop="violationTime" width="180">
         <el-table-column label="违纪发生时间" align="center" prop="violationTime" width="180">
           <template #default="scope">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.violationTime, '{y}-{m}-{d}') }}</span>
+            <span>{{ parseTime(scope.row.violationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
           </template>
           </template>
         </el-table-column>
         </el-table-column>
         <el-table-column label="违纪行为描述" align="center" prop="violationDescription" />
         <el-table-column label="违纪行为描述" align="center" prop="violationDescription" />
-        <el-table-column label="涉及其他学生id" align="center" prop="involvedStudents" />
-        <el-table-column label="附件OSS ID列表" align="center" prop="attachmentOssIds" />
-        <el-table-column label="删除标志" align="center" prop="deleted" />
+        <!-- <el-table-column label="涉及其他学生id" align="center" prop="involvedStudents" /> -->
+        <!-- <el-table-column label="附件OSS ID列表" align="center" prop="attachmentOssIds" /> -->
+        <!-- <el-table-column label="删除标志" align="center" prop="deleted" /> -->
         <el-table-column label="备注" align="center" prop="remark" />
         <el-table-column label="备注" align="center" prop="remark" />
-        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="120">
           <template #default="scope">
           <template #default="scope">
             <el-tooltip content="修改" placement="top">
             <el-tooltip content="修改" placement="top">
               <el-button
               <el-button
@@ -180,7 +180,7 @@
             <el-tooltip content="删除" placement="top">
             <el-tooltip content="删除" placement="top">
               <el-button
               <el-button
                 link
                 link
-                type="primary"
+                type="danger"
                 icon="Delete"
                 icon="Delete"
                 @click="handleDelete(scope.row)"
                 @click="handleDelete(scope.row)"
                 v-hasPermi="['dormViolation:dormViolation:remove']"
                 v-hasPermi="['dormViolation:dormViolation:remove']"
@@ -377,13 +377,89 @@
               />
               />
             </el-form-item>
             </el-form-item>
           </el-col>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="涉及其他学生" prop="involvedStudents">
-              <el-input v-model="form.involvedStudents" placeholder="请输入涉及其他学生ID" />
-            </el-form-item>
-          </el-col>
         </el-row>
         </el-row>
 
 
+        <!-- 涉及其他学生 - 单独一行 -->
+        <el-form-item label="涉及其他学生" prop="involvedStudents">
+          <el-select
+            v-model="form.involvedStudents"
+            placeholder="请选择涉及的其他学生"
+            filterable
+            remote
+            :remote-method="remoteSearchInvolvedStudents"
+            :loading="involvedStudentLoading"
+            clearable
+            multiple
+            collapse-tags
+            collapse-tags-tooltip
+            style="width: 100%"
+            @visible-change="handleInvolvedStudentSelectVisibleChange"
+            @change="handleInvolvedStudentsChange"
+            popper-class="involved-student-select-dropdown"
+          >
+            <el-option
+              v-for="student in involvedStudentOptions.filter((s) => s.id !== form.studentId)"
+              :key="student.id"
+              :label="`${student.name} (${student.studentNo})`"
+              :value="student.id"
+            >
+              <div class="student-option">
+                <div class="student-main">
+                  <span class="student-name">{{ student.name }}</span>
+                  <span class="student-no">{{ student.studentNo }}</span>
+                  <span class="student-gender">{{ student.gender === '0' ? '男' : '女' }}</span>
+                </div>
+                <div class="student-info" v-if="student.collegeName || student.majorName || student.className">
+                  <span v-if="student.collegeName && student.collegeName.trim()" class="info-item">{{ student.collegeName }}</span>
+                  <span v-if="student.majorName && student.majorName.trim()" class="info-item">{{ student.majorName }}</span>
+                  <span v-if="student.className && student.className.trim()" class="info-item">{{ student.className }}</span>
+                </div>
+              </div>
+            </el-option>
+          </el-select>
+          <div class="form-tip">
+            <el-icon><InfoFilled /></el-icon>
+            <span>支持多选,可搜索学号或姓名,最多选择10名学生</span>
+          </div>
+
+          <!-- 涉及其他学生信息展示表格 -->
+          <div v-if="selectedInvolvedStudents.length > 0" class="involved-students-table-container">
+            <div class="table-header">
+              <span class="table-title">已选择的涉及学生信息</span>
+              <span class="table-count">(共 {{ selectedInvolvedStudents.length }} 人)</span>
+            </div>
+            <el-table :data="selectedInvolvedStudents" size="small" border stripe class="involved-students-table" max-height="300">
+              <el-table-column label="序号" type="index" width="50" align="center" />
+              <el-table-column label="姓名" prop="name" width="120" align="center" />
+              <el-table-column label="学号" prop="studentNo" width="120" align="center" />
+              <el-table-column label="性别" prop="gender" width="60" align="center">
+                <template #default="scope">
+                  <el-tag :type="scope.row.gender === '0' ? 'primary' : 'danger'" size="small">
+                    {{ scope.row.gender === '0' ? '男' : '女' }}
+                  </el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="学院" prop="collegeName" min-width="120" align="center" show-overflow-tooltip />
+              <el-table-column label="专业" prop="majorName" min-width="120" align="center" show-overflow-tooltip />
+              <el-table-column label="班级" prop="className" min-width="120" align="center" show-overflow-tooltip />
+              <!-- <el-table-column label="宿舍信息" min-width="150" align="center" show-overflow-tooltip>
+                <template #default="scope">
+                  <div v-if="scope.row.dormInfo" class="dorm-info">
+                    <div>{{ scope.row.dormInfo.campusName }}</div>
+                    <div>{{ scope.row.dormInfo.buildingName }} - {{ scope.row.dormInfo.roomName }}</div>
+                  </div>
+                  <span v-else class="no-dorm">暂无宿舍信息</span>
+                </template>
+              </el-table-column> -->
+              <el-table-column label="操作" width="80" align="center" fixed="right">
+                <template #default="scope">
+                  <el-button type="danger" size="small" link @click="removeInvolvedStudent(scope.row.id)"> 移除 </el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+        </el-form-item>
+
         <!-- 完整宽度的字段 -->
         <!-- 完整宽度的字段 -->
         <el-form-item label="违纪行为描述" prop="violationDescription">
         <el-form-item label="违纪行为描述" prop="violationDescription">
           <el-input v-model="form.violationDescription" type="textarea" placeholder="请输入违纪行为描述" :rows="3" />
           <el-input v-model="form.violationDescription" type="textarea" placeholder="请输入违纪行为描述" :rows="3" />
@@ -483,7 +559,7 @@ import { ref, reactive, toRefs, onMounted, getCurrentInstance, ComponentInternal
 import ElMessage from 'element-plus/es/components/message/index';
 import ElMessage from 'element-plus/es/components/message/index';
 import ElMessageBox from 'element-plus/es/components/message-box/index';
 import ElMessageBox from 'element-plus/es/components/message-box/index';
 import ElLoading from 'element-plus/es/components/loading/index';
 import ElLoading from 'element-plus/es/components/loading/index';
-import { UploadFilled, Document, Loading, Download, Delete } from '@element-plus/icons-vue';
+import { UploadFilled, Document, Loading, Download, Delete, InfoFilled } from '@element-plus/icons-vue';
 import { getToken } from '@/utils/auth';
 import { getToken } from '@/utils/auth';
 import ImagePreview from '@/components/ImagePreview/index.vue';
 import ImagePreview from '@/components/ImagePreview/index.vue';
 import request from '@/utils/request';
 import request from '@/utils/request';
@@ -494,6 +570,7 @@ import {
   addDormViolation,
   addDormViolation,
   updateDormViolation,
   updateDormViolation,
   getStudentOptions,
   getStudentOptions,
+  getStudentById,
   getCampusOptions,
   getCampusOptions,
   getAreaOptions,
   getAreaOptions,
   getBuildingOptions,
   getBuildingOptions,
@@ -530,6 +607,76 @@ const studentOptions = ref<StudentOptionVO[]>([]);
 const studentLoading = ref(false);
 const studentLoading = ref(false);
 const studentSelectVisible = ref(false);
 const studentSelectVisible = ref(false);
 
 
+// 涉及其他学生选择器相关状态
+const involvedStudentOptions = ref<StudentOptionVO[]>([]);
+const involvedStudentLoading = ref(false);
+const involvedStudentSelectVisible = ref(false);
+
+// 存储已选择的涉及学生完整信息
+const selectedInvolvedStudentsMap = ref<Map<string | number, StudentOptionVO>>(new Map());
+
+// 计算属性:获取已选择的涉及学生详细信息
+const selectedInvolvedStudents = computed(() => {
+  if (!form.value.involvedStudents || !Array.isArray(form.value.involvedStudents)) {
+    return [];
+  }
+
+  console.log('计算涉及学生信息 - 当前ID列表:', form.value.involvedStudents);
+  console.log('当前Map大小:', selectedInvolvedStudentsMap.value.size);
+  console.log('当前涉及学生选项数量:', involvedStudentOptions.value.length);
+
+  const result = form.value.involvedStudents
+    .map((id) => {
+      console.log(`处理学生ID: ${id}`);
+
+      // 优先从已保存的完整信息中获取(包括预加载的占位符)
+      const savedStudent = selectedInvolvedStudentsMap.value.get(id);
+      if (savedStudent) {
+        console.log(`从Map中找到学生: ${savedStudent.name}`);
+        return savedStudent;
+      }
+
+      // 从涉及学生选项中查找详细信息
+      const student = involvedStudentOptions.value.find((s) => s.id === id);
+      if (student) {
+        console.log(`从涉及学生选项中找到: ${student.name}`);
+        // 保存到Map中,避免后续丢失
+        selectedInvolvedStudentsMap.value.set(id, student);
+        return student;
+      }
+
+      // 如果涉及学生选项中没有,从主学生选项中查找
+      const mainStudent = studentOptions.value.find((s) => s.id === id);
+      if (mainStudent) {
+        console.log(`从主学生选项中找到: ${mainStudent.name}`);
+        // 保存到Map中,避免后续丢失
+        selectedInvolvedStudentsMap.value.set(id, mainStudent);
+        return mainStudent;
+      }
+
+      console.log(`未找到学生 ${id} 的详细信息,创建临时占位符`);
+      // 如果都没有找到,创建一个临时的占位符信息
+      const tempPlaceholder = {
+        id: id,
+        name: `学生${id.toString().slice(-4)}`, // 使用ID后4位作为显示名称
+        studentNo: id.toString(),
+        gender: '',
+        collegeName: '信息加载中...',
+        majorName: '信息加载中...',
+        className: '信息加载中...',
+        dormInfo: null
+      };
+
+      // 将临时占位符也保存到Map中,避免重复创建
+      selectedInvolvedStudentsMap.value.set(id, tempPlaceholder);
+      return tempPlaceholder;
+    })
+    .filter((student) => student); // 过滤掉无效数据
+
+  console.log('最终涉及学生列表:', result);
+  return result;
+});
+
 // 宿舍级联选择器相关状态
 // 宿舍级联选择器相关状态
 const campusOptions = ref<CampusOptionVO[]>([]);
 const campusOptions = ref<CampusOptionVO[]>([]);
 const areaOptions = ref<AreaOptionVO[]>([]);
 const areaOptions = ref<AreaOptionVO[]>([]);
@@ -538,6 +685,25 @@ const roomOptions = ref<RoomOptionVO[]>([]);
 const bedOptions = ref<BedOptionVO[]>([]);
 const bedOptions = ref<BedOptionVO[]>([]);
 const dormLoading = ref(false);
 const dormLoading = ref(false);
 
 
+// 缓存机制,避免重复请求
+const dormCache = ref<Map<string, any[]>>(new Map());
+const cacheExpireTime = 5 * 60 * 1000; // 5分钟缓存过期
+const cacheTimestamps = ref<Map<string, number>>(new Map());
+
+// 缓存工具函数
+const getCachedData = (key: string) => {
+  const timestamp = cacheTimestamps.value.get(key);
+  if (timestamp && Date.now() - timestamp < cacheExpireTime) {
+    return dormCache.value.get(key);
+  }
+  return null;
+};
+
+const setCachedData = (key: string, data: any[]) => {
+  dormCache.value.set(key, data);
+  cacheTimestamps.value.set(key, Date.now());
+};
+
 // 附件上传相关状态
 // 附件上传相关状态
 const attachmentList = ref<any[]>([]);
 const attachmentList = ref<any[]>([]);
 
 
@@ -574,7 +740,7 @@ const initFormData: DormViolationForm = {
   violationLevel: undefined,
   violationLevel: undefined,
   violationTime: undefined,
   violationTime: undefined,
   violationDescription: undefined,
   violationDescription: undefined,
-  involvedStudents: undefined,
+  involvedStudents: [] as (string | number)[], // 初始化为空数组
   attachmentOssIds: undefined,
   attachmentOssIds: undefined,
   deleted: undefined,
   deleted: undefined,
   remark: undefined
   remark: undefined
@@ -624,7 +790,19 @@ const data = reactive<PageData<DormViolationForm, DormViolationQuery>>({
     violationType: [{ required: true, message: '违纪类型不能为空', trigger: 'change' }],
     violationType: [{ required: true, message: '违纪类型不能为空', trigger: 'change' }],
     violationLevel: [{ required: true, message: '违纪等级:1轻微 2一般 3严重 4特别严重不能为空', trigger: 'change' }],
     violationLevel: [{ required: true, message: '违纪等级:1轻微 2一般 3严重 4特别严重不能为空', trigger: 'change' }],
     violationTime: [{ required: true, message: '违纪发生时间不能为空', trigger: 'blur' }],
     violationTime: [{ required: true, message: '违纪发生时间不能为空', trigger: 'blur' }],
-    violationDescription: [{ required: true, message: '违纪行为描述不能为空', trigger: 'blur' }]
+    violationDescription: [{ required: true, message: '违纪行为描述不能为空', trigger: 'blur' }],
+    involvedStudents: [
+      {
+        validator: (rule: any, value: any, callback: any) => {
+          if (Array.isArray(value) && value.length > 10) {
+            callback(new Error('最多只能选择10名学生'));
+          } else {
+            callback();
+          }
+        },
+        trigger: 'change'
+      }
+    ]
   }
   }
 });
 });
 
 
@@ -676,6 +854,9 @@ const reset = () => {
 
 
   // 清空附件列表
   // 清空附件列表
   attachmentList.value = [];
   attachmentList.value = [];
+
+  // 清空已选择的涉及学生信息Map
+  selectedInvolvedStudentsMap.value.clear();
 };
 };
 
 
 /** 搜索按钮操作 */
 /** 搜索按钮操作 */
@@ -706,19 +887,67 @@ const handleAdd = () => {
 
 
 /** 修改按钮操作 */
 /** 修改按钮操作 */
 const handleUpdate = async (row?: DormViolationVO) => {
 const handleUpdate = async (row?: DormViolationVO) => {
-  reset();
-  const _id = row?.id || ids.value[0];
-  const res = await getDormViolation(_id);
-  Object.assign(form.value, res.data);
+  // 先显示弹窗,提升用户体验
+  dialog.visible = true;
+  dialog.title = '修改宿舍违纪记录';
 
 
-  // 根据已有数据级联加载宿舍选项
-  await loadCascadeOptionsForUpdate(form.value);
+  // 显示加载状态
+  const loadingInstance = ElLoading.service({
+    target: '.el-dialog__body',
+    text: '正在加载数据...',
+    background: 'rgba(255, 255, 255, 0.8)'
+  });
 
 
-  // 处理附件数据
-  await handleAttachmentData();
+  try {
+    reset();
+    const _id = row?.id || ids.value[0];
 
 
-  dialog.visible = true;
-  dialog.title = '修改宿舍违纪记录';
+    // 并行执行数据加载,提升性能
+    const [violationRes, campusRes] = await Promise.all([
+      getDormViolation(_id),
+      getCampusOptions() // 预加载校区选项
+    ]);
+
+    Object.assign(form.value, violationRes.data);
+    campusOptions.value = campusRes.rows || [];
+
+    console.log('编辑违纪记录,使用原始数据:', violationRes.data);
+
+    // 处理涉及其他学生数据 - 将逗号分隔的字符串转换为数组
+    if (form.value.involvedStudents && typeof form.value.involvedStudents === 'string') {
+      form.value.involvedStudents = form.value.involvedStudents
+        .split(',')
+        .map((id) => id.trim())
+        .filter((id) => id) as (string | number)[];
+
+      console.log('编辑时的涉及学生ID列表:', form.value.involvedStudents);
+    }
+
+    // 并行执行其他数据加载任务
+    const loadTasks = [];
+
+    // 如果有涉及学生,异步加载学生信息
+    if (form.value.involvedStudents && form.value.involvedStudents.length > 0) {
+      loadTasks.push(
+        Promise.all([loadInvolvedStudentsInfo(form.value.involvedStudents), loadInvolvedStudentsForSelect(form.value.involvedStudents)])
+      );
+    }
+
+    // 异步加载宿舍级联选项
+    loadTasks.push(loadCascadeOptionsForUpdate(form.value));
+
+    // 异步处理附件数据
+    loadTasks.push(handleAttachmentData());
+
+    // 等待所有异步任务完成
+    await Promise.all(loadTasks);
+  } catch (error) {
+    console.error('加载编辑数据失败:', error);
+    ElMessage.error('加载数据失败,请重试');
+    dialog.visible = false;
+  } finally {
+    loadingInstance.close();
+  }
 };
 };
 
 
 /** 提交按钮 */
 /** 提交按钮 */
@@ -733,6 +962,11 @@ const submitForm = () => {
       // 将附件ID添加到表单数据中,处理成逗号分隔的字符串
       // 将附件ID添加到表单数据中,处理成逗号分隔的字符串
       form.value.attachmentOssIds = attachmentOssIds.join(',');
       form.value.attachmentOssIds = attachmentOssIds.join(',');
 
 
+      // 处理涉及其他学生数据 - 将数组转换为逗号分隔的字符串
+      if (Array.isArray(form.value.involvedStudents)) {
+        form.value.involvedStudents = form.value.involvedStudents.join(',');
+      }
+
       if (form.value.id) {
       if (form.value.id) {
         await updateDormViolation(form.value).finally(() => (buttonLoading.value = false));
         await updateDormViolation(form.value).finally(() => (buttonLoading.value = false));
       } else {
       } else {
@@ -776,7 +1010,8 @@ const remoteSearchStudents = async (query: string) => {
     };
     };
     const res = await getStudentOptions(params);
     const res = await getStudentOptions(params);
     console.log('学生搜索响应:', res);
     console.log('学生搜索响应:', res);
-    studentOptions.value = res.rows || [];
+    // 修复:正确处理API返回的数据结构
+    studentOptions.value = res.data?.rows || res.rows || [];
 
 
     // 调试:检查第一个学生的数据
     // 调试:检查第一个学生的数据
     if (studentOptions.value.length > 0) {
     if (studentOptions.value.length > 0) {
@@ -818,7 +1053,8 @@ const loadInitialStudents = async () => {
     };
     };
     const res = await getStudentOptions(params);
     const res = await getStudentOptions(params);
     console.log('初始学生数据响应:', res);
     console.log('初始学生数据响应:', res);
-    studentOptions.value = res.rows || [];
+    // 修复:正确处理API返回的数据结构
+    studentOptions.value = res.data?.rows || res.rows || [];
 
 
     // 调试:检查第一个学生的数据
     // 调试:检查第一个学生的数据
     if (studentOptions.value.length > 0) {
     if (studentOptions.value.length > 0) {
@@ -839,6 +1075,171 @@ const loadInitialStudents = async () => {
   }
   }
 };
 };
 
 
+/** 远程搜索涉及其他学生 */
+const remoteSearchInvolvedStudents = async (query: string) => {
+  involvedStudentLoading.value = true;
+  try {
+    const params: StudentOptionQuery = {
+      keyword: query.trim(),
+      pageNum: 1,
+      pageSize: 20
+    };
+    const res = await getStudentOptions(params);
+    console.log('涉及学生搜索响应:', res);
+    // 修复:正确处理API返回的数据结构
+    involvedStudentOptions.value = res.data?.rows || res.rows || [];
+  } catch (error) {
+    console.error('搜索涉及学生失败:', error);
+    involvedStudentOptions.value = [];
+  } finally {
+    involvedStudentLoading.value = false;
+  }
+};
+
+/** 涉及其他学生选择器可见性变化处理 */
+const handleInvolvedStudentSelectVisibleChange = async (visible: boolean) => {
+  involvedStudentSelectVisible.value = visible;
+
+  // 当下拉框打开且没有数据时,加载初始数据
+  if (visible && involvedStudentOptions.value.length === 0) {
+    await loadInitialInvolvedStudents();
+  }
+};
+
+/** 加载初始涉及其他学生数据 */
+const loadInitialInvolvedStudents = async () => {
+  involvedStudentLoading.value = true;
+  try {
+    const params: StudentOptionQuery = {
+      keyword: '', // 空关键词获取初始数据
+      pageNum: 1,
+      pageSize: 20
+    };
+    const res = await getStudentOptions(params);
+    console.log('初始涉及学生数据响应:', res);
+    // 修复:正确处理API返回的数据结构
+    involvedStudentOptions.value = res.data?.rows || res.rows || [];
+  } catch (error) {
+    console.error('加载初始涉及学生数据失败:', error);
+    involvedStudentOptions.value = [];
+  } finally {
+    involvedStudentLoading.value = false;
+  }
+};
+
+/** 预加载涉及学生的完整信息 */
+const loadInvolvedStudentsInfo = async (studentIds: (string | number)[]) => {
+  // 批量获取学生信息,减少接口调用次数
+  try {
+    const studentPromises = studentIds.map(async (studentId) => {
+      try {
+        const studentRes = await getStudentById(studentId);
+        const studentInfo = studentRes.data;
+        selectedInvolvedStudentsMap.value.set(studentId, studentInfo);
+        console.log(`预加载学生 ${studentId} 信息:`, studentInfo);
+        return { studentId, success: true, data: studentInfo };
+      } catch (error) {
+        console.error(`预加载学生 ${studentId} 信息失败:`, error);
+        // 创建临时占位符
+        const tempPlaceholder = {
+          id: studentId,
+          name: `学生${studentId.toString().slice(-4)}`,
+          studentNo: studentId.toString(),
+          gender: '',
+          collegeName: '信息加载失败',
+          majorName: '信息加载失败',
+          className: '信息加载失败',
+          dormInfo: null
+        };
+        selectedInvolvedStudentsMap.value.set(studentId, tempPlaceholder);
+        return { studentId, success: false, data: tempPlaceholder };
+      }
+    });
+
+    // 并行执行所有学生信息获取
+    await Promise.all(studentPromises);
+  } catch (error) {
+    console.error('批量加载涉及学生信息失败:', error);
+  }
+};
+
+/** 为选择框加载涉及学生信息 */
+const loadInvolvedStudentsForSelect = async (studentIds: (string | number)[]) => {
+  try {
+    // 获取涉及学生的完整信息
+    const studentsInfo: StudentOptionVO[] = [];
+
+    for (const studentId of studentIds) {
+      try {
+        const studentRes = await getStudentById(studentId);
+        const studentInfo = studentRes.data;
+        studentsInfo.push(studentInfo);
+        console.log(`为选择框加载学生 ${studentId} 信息:`, studentInfo);
+      } catch (error) {
+        console.error(`为选择框加载学生 ${studentId} 信息失败:`, error);
+        // 创建临时占位符
+        const tempPlaceholder = {
+          id: studentId,
+          name: `学生${studentId.toString().slice(-4)}`,
+          studentNo: studentId.toString(),
+          gender: '',
+          collegeName: '信息加载失败',
+          majorName: '信息加载失败',
+          className: '信息加载失败',
+          dormInfo: null
+        };
+        studentsInfo.push(tempPlaceholder);
+      }
+    }
+
+    // 将学生信息添加到涉及学生选项中,确保选择框能正确显示
+    involvedStudentOptions.value = [...studentsInfo];
+    console.log('涉及学生选择框选项已更新:', involvedStudentOptions.value);
+  } catch (error) {
+    console.error('为选择框加载涉及学生信息失败:', error);
+  }
+};
+
+/** 涉及其他学生选择变化处理 */
+const handleInvolvedStudentsChange = (selectedIds: (string | number)[]) => {
+  // 确保不会选择主学生
+  if (form.value.studentId && selectedIds.includes(form.value.studentId)) {
+    ElMessage.warning('不能选择主学生作为涉及其他学生');
+    // 移除主学生ID
+    form.value.involvedStudents = selectedIds.filter((id) => id !== form.value.studentId) as (string | number)[];
+  } else {
+    // 直接更新选择的学生列表
+    form.value.involvedStudents = selectedIds as (string | number)[];
+  }
+
+  // 确保新选择的学生信息被保存
+  selectedIds.forEach((id) => {
+    if (!selectedInvolvedStudentsMap.value.has(id)) {
+      // 从当前选项中查找并保存学生信息
+      const student = involvedStudentOptions.value.find((s) => s.id === id);
+      if (student) {
+        selectedInvolvedStudentsMap.value.set(id, student);
+      } else {
+        // 从主学生选项中查找
+        const mainStudent = studentOptions.value.find((s) => s.id === id);
+        if (mainStudent) {
+          selectedInvolvedStudentsMap.value.set(id, mainStudent);
+        }
+      }
+    }
+  });
+};
+
+/** 移除涉及学生 */
+const removeInvolvedStudent = (studentId: string | number) => {
+  if (Array.isArray(form.value.involvedStudents)) {
+    form.value.involvedStudents = form.value.involvedStudents.filter((id) => id !== studentId) as (string | number)[];
+    // 从Map中也移除该学生信息
+    selectedInvolvedStudentsMap.value.delete(studentId);
+    ElMessage.success('已移除该学生');
+  }
+};
+
 /** 学生选择处理 */
 /** 学生选择处理 */
 const handleStudentChange = async (studentId: string | number) => {
 const handleStudentChange = async (studentId: string | number) => {
   if (!studentId) {
   if (!studentId) {
@@ -868,6 +1269,11 @@ const handleStudentChange = async (studentId: string | number) => {
     return;
     return;
   }
   }
 
 
+  // 如果主学生ID在涉及其他学生列表中,则移除
+  if (Array.isArray(form.value.involvedStudents)) {
+    form.value.involvedStudents = form.value.involvedStudents.filter((id) => id !== studentId) as (string | number)[];
+  }
+
   const selectedStudent = studentOptions.value.find((student) => student.id === studentId);
   const selectedStudent = studentOptions.value.find((student) => student.id === studentId);
   if (selectedStudent) {
   if (selectedStudent) {
     // 填充学生基本信息
     // 填充学生基本信息
@@ -989,9 +1395,19 @@ const handleCampusChange = async (campusId: string | number) => {
 /** 加载宿舍区选项 */
 /** 加载宿舍区选项 */
 const loadAreaOptions = async (campusId: string | number) => {
 const loadAreaOptions = async (campusId: string | number) => {
   try {
   try {
+    const cacheKey = `area_${campusId}`;
+    const cachedData = getCachedData(cacheKey);
+
+    if (cachedData) {
+      areaOptions.value = cachedData;
+      return;
+    }
+
     dormLoading.value = true;
     dormLoading.value = true;
     const res = await getAreaOptions(campusId);
     const res = await getAreaOptions(campusId);
-    areaOptions.value = res.rows || [];
+    const data = res.rows || [];
+    areaOptions.value = data;
+    setCachedData(cacheKey, data);
   } catch (error) {
   } catch (error) {
     console.error('加载宿舍区选项失败:', error);
     console.error('加载宿舍区选项失败:', error);
     areaOptions.value = [];
     areaOptions.value = [];
@@ -1141,25 +1557,33 @@ const handleBedChange = (bedId: string | number) => {
 /** 修改时级联加载宿舍选项数据 */
 /** 修改时级联加载宿舍选项数据 */
 const loadCascadeOptionsForUpdate = async (formData: DormViolationForm) => {
 const loadCascadeOptionsForUpdate = async (formData: DormViolationForm) => {
   try {
   try {
+    // 并行加载所有需要的宿舍选项,提升性能
+    const loadTasks = [];
+
     // 如果有校区ID,加载宿舍区选项
     // 如果有校区ID,加载宿舍区选项
     if (formData.campusId) {
     if (formData.campusId) {
-      await loadAreaOptions(formData.campusId);
+      loadTasks.push(loadAreaOptions(formData.campusId));
 
 
       // 如果有宿舍区ID,加载宿舍楼选项
       // 如果有宿舍区ID,加载宿舍楼选项
       if (formData.areaId) {
       if (formData.areaId) {
-        await loadBuildingOptions(formData.areaId);
+        loadTasks.push(loadBuildingOptions(formData.areaId));
 
 
         // 如果有宿舍楼ID,加载房间选项
         // 如果有宿舍楼ID,加载房间选项
         if (formData.buildingId) {
         if (formData.buildingId) {
-          await loadRoomOptions(formData.buildingId);
+          loadTasks.push(loadRoomOptions(formData.buildingId));
 
 
           // 如果有房间ID,加载床位选项
           // 如果有房间ID,加载床位选项
           if (formData.roomId) {
           if (formData.roomId) {
-            await loadBedOptions(formData.roomId);
+            loadTasks.push(loadBedOptions(formData.roomId));
           }
           }
         }
         }
       }
       }
     }
     }
+
+    // 并行执行所有加载任务
+    if (loadTasks.length > 0) {
+      await Promise.all(loadTasks);
+    }
   } catch (error) {
   } catch (error) {
     console.error('修改时级联加载宿舍选项失败:', error);
     console.error('修改时级联加载宿舍选项失败:', error);
   }
   }
@@ -1364,56 +1788,63 @@ const handleDownloadAttachment = async (file: any) => {
 // 处理附件数据
 // 处理附件数据
 const handleAttachmentData = async () => {
 const handleAttachmentData = async () => {
   try {
   try {
-    // 如果有附件ID,则获取附件信息
-    if (form.value.attachmentOssIds && form.value.attachmentOssIds.toString().trim()) {
-      console.log('开始处理附件数据:', form.value.attachmentOssIds);
+    // 如果没有附件ID,直接返回,避免不必要的请求
+    if (!form.value.attachmentOssIds || !form.value.attachmentOssIds.toString().trim()) {
+      console.log('没有附件数据,清空附件列表');
+      attachmentList.value = [];
+      return;
+    }
 
 
-      // 将逗号分隔的字符串转换为数组
-      const ossIdsArray = form.value.attachmentOssIds
-        .toString()
-        .split(',')
-        .map((id) => id.trim())
-        .filter((id) => id);
+    console.log('开始处理附件数据:', form.value.attachmentOssIds);
 
 
-      // 调用接口获取附件信息
-      const response = await request({
-        url: `/resource/oss/listByIds/${ossIdsArray.join(',')}`,
-        method: 'get'
-      });
+    // 将逗号分隔的字符串转换为数组
+    const ossIdsArray = form.value.attachmentOssIds
+      .toString()
+      .split(',')
+      .map((id) => id.trim())
+      .filter((id) => id);
 
 
-      if (response.code === 200 && response.data) {
-        console.log('获取到的附件信息:', response.data);
-
-        // 将附件信息转换为上传组件需要的格式
-        const attachmentFiles = response.data.map((item: any, index: number) => ({
-          uid: `existing_${index}_${Date.now()}`,
-          name: item.originalName || `附件${index + 1}`,
-          url: item.url,
-          ossId: item.ossId,
-          status: 'success', // 标记为已上传成功
-          response: {
-            code: 200,
-            data: {
-              url: item.url,
-              ossId: item.ossId
-            }
+    // 如果附件ID数组为空,直接返回
+    if (ossIdsArray.length === 0) {
+      attachmentList.value = [];
+      return;
+    }
+
+    // 调用接口获取附件信息
+    const response = await request({
+      url: `/resource/oss/listByIds/${ossIdsArray.join(',')}`,
+      method: 'get'
+    });
+
+    if (response.code === 200 && response.data) {
+      console.log('获取到的附件信息:', response.data);
+
+      // 将附件信息转换为上传组件需要的格式
+      const attachmentFiles = response.data.map((item: any, index: number) => ({
+        uid: `existing_${index}_${Date.now()}`,
+        name: item.originalName || `附件${index + 1}`,
+        url: item.url,
+        ossId: item.ossId,
+        status: 'success', // 标记为已上传成功
+        response: {
+          code: 200,
+          data: {
+            url: item.url,
+            ossId: item.ossId
           }
           }
-        }));
+        }
+      }));
 
 
-        // 设置附件列表
-        attachmentList.value = attachmentFiles;
-        console.log('设置附件列表成功:', attachmentList.value);
-      } else {
-        console.warn('获取附件信息失败:', response.msg);
-        attachmentList.value = [];
-      }
+      // 设置附件列表
+      attachmentList.value = attachmentFiles;
+      console.log('设置附件列表成功:', attachmentList.value);
     } else {
     } else {
-      console.log('没有附件数据,清空附件列表');
+      console.warn('获取附件信息失败:', response.msg);
       attachmentList.value = [];
       attachmentList.value = [];
     }
     }
   } catch (error) {
   } catch (error) {
     console.error('处理附件数据失败:', error);
     console.error('处理附件数据失败:', error);
-    ElMessage.error('获取附件信息失败,请稍后重试');
+    // 不显示错误提示,避免影响用户体验
     attachmentList.value = [];
     attachmentList.value = [];
   }
   }
 };
 };
@@ -1483,6 +1914,27 @@ onMounted(() => {
   padding: 4px 12px !important;
   padding: 4px 12px !important;
 }
 }
 
 
+:global(.involved-student-select-dropdown .el-select-dropdown__item) {
+  height: auto !important;
+  min-height: 32px !important;
+  padding: 4px 12px !important;
+}
+
+/* 表单提示样式 */
+.form-tip {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  margin-top: 4px;
+  font-size: 12px;
+  color: #909399;
+}
+
+.form-tip .el-icon {
+  font-size: 14px;
+  color: #409eff;
+}
+
 /* 附件上传容器样式 */
 /* 附件上传容器样式 */
 .attachment-upload-container {
 .attachment-upload-container {
   width: 100%;
   width: 100%;
@@ -1692,4 +2144,74 @@ onMounted(() => {
   cursor: not-allowed;
   cursor: not-allowed;
   background: #f5f5f5;
   background: #f5f5f5;
 }
 }
+
+/* 涉及其他学生表格样式 */
+.involved-students-table-container {
+  margin-top: 12px;
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  background: #fafafa;
+  overflow: hidden;
+}
+
+.table-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 8px 12px;
+  background: #f5f7fa;
+  border-bottom: 1px solid #e4e7ed;
+}
+
+.table-title {
+  font-size: 13px;
+  font-weight: 500;
+  color: #303133;
+}
+
+.table-count {
+  font-size: 12px;
+  color: #909399;
+}
+
+.involved-students-table {
+  background: #fff;
+}
+
+.involved-students-table :deep(.el-table__header) {
+  background: #f5f7fa;
+}
+
+.involved-students-table :deep(.el-table__header th) {
+  background: #f5f7fa;
+  color: #606266;
+  font-weight: 500;
+  font-size: 12px;
+}
+
+.involved-students-table :deep(.el-table__body td) {
+  font-size: 12px;
+  padding: 6px 0;
+}
+
+.dorm-info {
+  font-size: 11px;
+  line-height: 1.3;
+}
+
+.dorm-info div:first-child {
+  color: #303133;
+  font-weight: 500;
+}
+
+.dorm-info div:last-child {
+  color: #606266;
+  margin-top: 2px;
+}
+
+.no-dorm {
+  color: #c0c4cc;
+  font-size: 11px;
+  font-style: italic;
+}
 </style>
 </style>