Ver Fonte

Merge branch 'master' of http://47.99.164.66:3000/hzdyjs-xg/all-project

李旭明 há 1 ano atrás
pai
commit
aca0a8636a
19 ficheiros alterados com 745 adições e 215 exclusões
  1. 14 1
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhDisciplineRecordController.java
  2. 12 0
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhRewardRecordController.java
  3. 12 0
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhDisciplineRecordMapper.java
  4. 12 0
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhRewardRecordMapper.java
  5. 10 0
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhDisciplineRecordService.java
  6. 10 0
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhRewardRecordService.java
  7. 19 0
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineRecordServiceImpl.java
  8. 21 0
      backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhRewardRecordServiceImpl.java
  9. 17 0
      backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/XskhDisciplineRecordMapper.xml
  10. 17 0
      backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhRewardRecordMapper.xml
  11. 1 0
      docs/开发文档/API/API文档总览.md
  12. 8 4
      web/src/api/xskh/assessmentIndicator/types.ts
  13. 360 114
      web/src/views/xskh/assessmentApplication/AssessmentApplicationForm.vue
  14. 9 8
      web/src/views/xskh/assessmentApplication/IndicatorDataDialog.vue
  15. 118 21
      web/src/views/xskh/assessmentApproval/AssessmentApprovalForm.vue
  16. 92 61
      web/src/views/xskh/assessmentIndicator/AssessmentIndicatorForm.vue
  17. 1 1
      web/src/views/xskh/assessmentIndicator/index.vue
  18. 10 5
      web/src/views/xskh/assessmentPlan/AssessmentPlanForm.vue
  19. 2 0
      web/src/views/xskh/assessmentSchedule/index.vue

+ 14 - 1
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhDisciplineRecordController.java

@@ -151,6 +151,19 @@ public class XskhDisciplineRecordController extends BaseController {
             @RequestParam String disciplineTypeIds,
             @RequestParam String startTime,
             @RequestParam String endTime) {
-        return R.ok(xskhDisciplineRecordService.getStudentDisciplineRecords(studentId, disciplineTypeIds, startTime, endTime));
+        return R.ok(xskhDisciplineRecordService.getStudentDisciplineRecords(studentId, disciplineTypeIds, startTime,
+                endTime));
+    }
+
+    /**
+     * 获取指定违纪类型在指定时间范围内所有学生的最高总扣分
+     */
+    @SaCheckPermission("disciplineRecord:disciplineRecord:query")
+    @GetMapping("/getMaxScoreByTypeAndPeriod/{typeId}")
+    public R<java.math.BigDecimal> getMaxScoreByTypeAndPeriod(
+            @NotNull(message = "违纪类型ID不能为空") @PathVariable Long typeId,
+            @RequestParam String startTime,
+            @RequestParam String endTime) {
+        return R.ok(xskhDisciplineRecordService.getMaxScoreByTypeAndPeriod(typeId, startTime, endTime));
     }
 }

+ 12 - 0
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhRewardRecordController.java

@@ -143,4 +143,16 @@ public class XskhRewardRecordController extends BaseController {
             @RequestParam String endTime) {
         return R.ok(xskhRewardRecordService.getStudentRewardRecords(studentId, rewardTypeIds, startTime, endTime));
     }
+
+    /**
+     * 获取指定奖励类型在指定时间范围内所有学生的最高总加分
+     */
+    @SaCheckPermission("rewardRecord:rewardRecord:query")
+    @GetMapping("/getMaxScoreByTypeAndPeriod/{typeId}")
+    public R<java.math.BigDecimal> getMaxScoreByTypeAndPeriod(
+            @NotNull(message = "奖励类型ID不能为空") @PathVariable Long typeId,
+            @RequestParam String startTime,
+            @RequestParam String endTime) {
+        return R.ok(xskhRewardRecordService.getMaxScoreByTypeAndPeriod(typeId, startTime, endTime));
+    }
 }

+ 12 - 0
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhDisciplineRecordMapper.java

@@ -69,4 +69,16 @@ public interface XskhDisciplineRecordMapper extends BaseMapperPlus<XskhDisciplin
          */
         int deleteHygieneCheckViolationByDisciplineRecordId(@Param("disciplineRecordId") Long disciplineRecordId);
 
+        /**
+         * 获取指定违纪类型在指定时间范围内所有学生的最高总扣分
+         *
+         * @param typeId    违纪类型ID
+         * @param startTime 开始时间
+         * @param endTime   结束时间
+         * @return 最高总扣分
+         */
+        java.math.BigDecimal selectMaxScoreByTypeAndPeriod(@Param("typeId") Long typeId,
+                @Param("startTime") String startTime,
+                @Param("endTime") String endTime);
+
 }

+ 12 - 0
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/mapper/XskhRewardRecordMapper.java

@@ -51,4 +51,16 @@ public interface XskhRewardRecordMapper extends BaseMapperPlus<XskhRewardRecord,
             @Param("rewardTypeIds") List<Long> rewardTypeIds,
             @Param("startTime") String startTime,
             @Param("endTime") String endTime);
+
+    /**
+     * 获取指定奖励类型在指定时间范围内所有学生的最高总加分
+     *
+     * @param typeId    奖励类型ID
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     * @return 最高总加分
+     */
+    java.math.BigDecimal selectMaxScoreByTypeAndPeriod(@Param("typeId") Long typeId,
+            @Param("startTime") String startTime,
+            @Param("endTime") String endTime);
 }

+ 10 - 0
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhDisciplineRecordService.java

@@ -91,4 +91,14 @@ public interface IXskhDisciplineRecordService {
      */
     List<XskhDisciplineRecordVo> getStudentDisciplineRecords(Long studentId, String disciplineTypeIds, String startTime,
             String endTime);
+
+    /**
+     * 获取指定违纪类型在指定时间范围内所有学生的最高总扣分
+     *
+     * @param typeId    违纪类型ID
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     * @return 最高总扣分
+     */
+    java.math.BigDecimal getMaxScoreByTypeAndPeriod(Long typeId, String startTime, String endTime);
 }

+ 10 - 0
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/IXskhRewardRecordService.java

@@ -104,4 +104,14 @@ public interface IXskhRewardRecordService {
      */
     List<XskhRewardRecordVo> getStudentRewardRecords(Long studentId, String rewardTypeIds, String startTime,
             String endTime);
+
+    /**
+     * 获取指定奖励类型在指定时间范围内所有学生的最高总加分
+     *
+     * @param typeId    奖励类型ID
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     * @return 最高总加分
+     */
+    java.math.BigDecimal getMaxScoreByTypeAndPeriod(Long typeId, String startTime, String endTime);
 }

+ 19 - 0
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineRecordServiceImpl.java

@@ -267,4 +267,23 @@ public class XskhDisciplineRecordServiceImpl implements IXskhDisciplineRecordSer
 
         return baseMapper.selectStudentDisciplineRecords(studentId, typeIds, startTime, endTime);
     }
+
+    /**
+     * 获取指定违纪类型在指定时间范围内所有学生的最高总扣分
+     */
+    @Override
+    public java.math.BigDecimal getMaxScoreByTypeAndPeriod(Long typeId, String startTime, String endTime) {
+        log.info("获取违纪类型 {} 在时间范围 {} - {} 内的最高总扣分", typeId, startTime, endTime);
+
+        java.math.BigDecimal maxScore = baseMapper.selectMaxScoreByTypeAndPeriod(typeId, startTime, endTime);
+
+        // 如果查询结果为null,返回0
+        if (maxScore == null) {
+            log.info("违纪类型 {} 在指定时间范围内无数据,返回0", typeId);
+            return java.math.BigDecimal.ZERO;
+        }
+
+        log.info("违纪类型 {} 的最高总扣分为: {}", typeId, maxScore);
+        return maxScore;
+    }
 }

+ 21 - 0
backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhRewardRecordServiceImpl.java

@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Arrays;
 import java.util.stream.Collectors;
 import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
 
 /**
  * 学生奖励记录Service业务层处理
@@ -31,6 +32,7 @@ import java.util.Map;
  * @author ruoyi
  * @date 2025-01-03
  */
+@Slf4j
 @RequiredArgsConstructor
 @Service
 public class XskhRewardRecordServiceImpl implements IXskhRewardRecordService {
@@ -249,4 +251,23 @@ public class XskhRewardRecordServiceImpl implements IXskhRewardRecordService {
 
         return baseMapper.selectStudentRewardRecords(studentId, typeIds, startTime, endTime);
     }
+
+    /**
+     * 获取指定奖励类型在指定时间范围内所有学生的最高总加分
+     */
+    @Override
+    public java.math.BigDecimal getMaxScoreByTypeAndPeriod(Long typeId, String startTime, String endTime) {
+        log.info("获取奖励类型 {} 在时间范围 {} - {} 内的最高总加分", typeId, startTime, endTime);
+
+        java.math.BigDecimal maxScore = baseMapper.selectMaxScoreByTypeAndPeriod(typeId, startTime, endTime);
+
+        // 如果查询结果为null,返回0
+        if (maxScore == null) {
+            log.info("奖励类型 {} 在指定时间范围内无数据,返回0", typeId);
+            return java.math.BigDecimal.ZERO;
+        }
+
+        log.info("奖励类型 {} 的最高总加分为: {}", typeId, maxScore);
+        return maxScore;
+    }
 }

+ 17 - 0
backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/XskhDisciplineRecordMapper.xml

@@ -134,4 +134,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           and deleted = 0
     </update>
 
+    <!-- 获取指定违纪类型在指定时间范围内所有学生的最高总扣分 -->
+    <select id="selectMaxScoreByTypeAndPeriod" resultType="java.math.BigDecimal">
+        <![CDATA[
+        SELECT MAX(student_total_score) as max_score
+        FROM (
+            SELECT student_id, SUM(score_deduction) as student_total_score
+            FROM xskh_discipline_record
+            WHERE discipline_type_id = #{typeId}
+              AND discipline_date >= #{startTime}
+              AND discipline_date <= #{endTime}
+              AND status = '1'
+              AND deleted = 0
+            GROUP BY student_id
+        ) student_scores
+        ]]>
+    </select>
+
 </mapper>

+ 17 - 0
backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhRewardRecordMapper.xml

@@ -151,4 +151,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         order by create_time desc
     </select>
 
+    <!-- 获取指定奖励类型在指定时间范围内所有学生的最高总加分 -->
+    <select id="selectMaxScoreByTypeAndPeriod" resultType="java.math.BigDecimal">
+        <![CDATA[
+        SELECT MAX(student_total_score) as max_score
+        FROM (
+            SELECT student_id, SUM(score_addition) as student_total_score
+            FROM xskh_reward_record
+            WHERE reward_type_id = #{typeId}
+              AND reward_date >= #{startTime}
+              AND reward_date <= #{endTime}
+              AND status = '1'
+              AND deleted = 0
+            GROUP BY student_id
+        ) student_scores
+        ]]>
+    </select>
+
 </mapper>

+ 1 - 0
docs/开发文档/API/API文档总览.md

@@ -20,6 +20,7 @@
 - [学生管理员授权关系API](./学生管理员授权关系API.md)
 - [学生销假管理API](./学生销假管理API.md)
 - [假期留宿管理API](./假期留宿管理API.md) - **重要修复:单人留宿计算逻辑**(2025-01-03)
+- [均化处理最高分值API](./均化处理最高分值API.md) - **新增**:支持考核申请均化处理功能(2025-01-03)
 
 ### 1. 用户认证登录
 

+ 8 - 4
web/src/api/xskh/assessmentIndicator/types.ts

@@ -80,6 +80,10 @@ export interface XskhAssessmentIndicatorVo extends BaseEntity {
   disciplineTypeIds?: string[];
   /** 关联的奖励类型ID列表 */
   rewardTypeIds?: string[];
+  /** 关联的违纪处分类型ID(单个值,用于前端表单) */
+  disciplineTypeId?: string | number;
+  /** 关联的奖励类型ID(单个值,用于前端表单) */
+  rewardTypeId?: string | number;
   /** 创建者 */
   createBy?: string | number;
   /** 创建时间 */
@@ -110,10 +114,10 @@ export interface XskhAssessmentIndicatorForm extends BaseEntity {
   status?: string;
   /** 排序号 */
   sortOrder?: number;
-  /** 关联的违纪处分类型ID列表 */
-  disciplineTypeIds?: string[];
-  /** 关联的奖励类型ID列表 */
-  rewardTypeIds?: string[];
+  /** 关联的违纪处分类型ID */
+  disciplineTypeId?: string | number;
+  /** 关联的奖励类型ID */
+  rewardTypeId?: string | number;
 }
 
 /**

+ 360 - 114
web/src/views/xskh/assessmentApplication/AssessmentApplicationForm.vue

@@ -65,7 +65,7 @@
             <el-card shadow="never">
               <template #header>
                 <div class="card-header">
-                  <span>指标模块 </span>
+                  <span>指标模块(基础分:100分)</span>
                 </div>
               </template>
               <el-table :data="form.selfScoreList" border style="width: 100%">
@@ -85,34 +85,25 @@
                     </div>
                   </template>
                 </el-table-column>
-                <el-table-column label="指标编码" prop="indicatorCode" min-width="100" />
+                <!-- <el-table-column label="指标编码" prop="indicatorCode" min-width="100" /> -->
                 <el-table-column label="指标说明" prop="indicatorDescription" min-width="150" show-overflow-tooltip />
-                <el-table-column label="最高分值" prop="planMaxScore" min-width="80" />
+                <!-- <el-table-column label="最高分值" prop="planMaxScore" min-width="80" /> -->
                 <el-table-column label="自评得分" width="120">
                   <template #header>
                     <div class="column-header">
                       <span>自评得分</span>
-                      <el-tooltip content="自评得分不能超过最高分值,且不能为负数" placement="top">
+                      <el-tooltip
+                        content="新算分规则:基础分100分,先扣除违纪处分,再加上奖励加分和附加分。自评得分为系统自动计算,不可手动编辑"
+                        placement="top"
+                      >
                         <el-icon class="header-icon"><QuestionFilled /></el-icon>
                       </el-tooltip>
                     </div>
                   </template>
                   <template #default="scope">
-                    <div v-if="isViewMode" class="score-display">
-                      {{ scope.row.selfScore || '-' }}
+                    <div class="score-display auto-calculated">
+                      {{ scope.row.selfScore || '0' }}
                     </div>
-                    <el-input
-                      v-else
-                      v-model="scope.row.selfScore"
-                      type="number"
-                      :min="0"
-                      :max="scope.row.planMaxScore"
-                      :step="0.1"
-                      @input="(value) => handleSelfScoreInput(value, scope.row)"
-                      @blur="(event) => handleSelfScoreBlur(event, scope.row)"
-                      :class="{ 'score-input-warning': isScoreExceeded(scope.row) }"
-                      style="width: 100%"
-                    />
                   </template>
                 </el-table-column>
                 <el-table-column label="自评备注" min-width="120">
@@ -401,6 +392,7 @@ import {
   getApplicationByStudentAndSchedule,
   getIndicatorsByScheduleId
 } from '@/api/xskh/assessmentApplication/assessmentApplication';
+// 注意:暂时使用request直接调用API,避免导入路径问题
 
 // 类型定义
 interface XskhAssessmentApplicationApprovalForm {
@@ -446,6 +438,9 @@ interface XskhAssessmentSelfScoreForm {
   selfRemark?: string;
   teacherScore?: number;
   teacherRemark?: string;
+  // 新增字段:用于新算分规则
+  deductionScore?: number; // 扣分
+  additionScore?: number; // 加分
 }
 
 interface XskhAssessmentBonusApprovalForm {
@@ -600,6 +595,210 @@ const getCurrentUserStudentInfo = async () => {
   }
 };
 
+/** 计算单个指标的自评得分(新算分规则) */
+const calculateIndicatorSelfScore = async (indicatorId: string | number): Promise<{ deduction: number; addition: number }> => {
+  try {
+    // 获取指标关联的违纪处分类型和奖励类型
+    const indicatorTypesResponse = await request({
+      url: `/disciplineRecord/disciplineRecord/getIndicatorTypes/${indicatorId}`,
+      method: 'get'
+    });
+
+    if (indicatorTypesResponse.code !== 200 || !indicatorTypesResponse.data) {
+      console.warn(`获取指标 ${indicatorId} 的类型信息失败`);
+      return { deduction: 0, addition: 0 };
+    }
+
+    // 修复字段名映射:后端返回下划线格式,前端使用驼峰格式
+    const disciplineTypeIds = indicatorTypesResponse.data.discipline_type_ids || indicatorTypesResponse.data.disciplineTypeIds;
+    const rewardTypeIds = indicatorTypesResponse.data.reward_type_ids || indicatorTypesResponse.data.rewardTypeIds;
+
+    // 并行获取违纪处分记录和奖励记录
+    const [disciplineRecords, rewardRecords] = await Promise.all([getDisciplineRecords(disciplineTypeIds), getRewardRecords(rewardTypeIds)]);
+
+    // 计算违纪扣分(正数,表示扣分金额)
+    let disciplineDeduction = 0;
+    if (disciplineRecords && disciplineRecords.length > 0) {
+      disciplineDeduction = disciplineRecords.reduce((sum: number, record: any) => {
+        const deduction = parseFloat(String(record.scoreDeduction || 0)) || 0;
+        return sum + deduction;
+      }, 0);
+
+      // 检查违纪类型是否需要均化处理
+      if (disciplineTypeIds) {
+        disciplineDeduction = await applyEqualizationIfNeeded(disciplineTypeIds, disciplineDeduction, 'discipline');
+      }
+    }
+
+    // 计算奖励加分(正数,表示加分金额)
+    let rewardAddition = 0;
+    if (rewardRecords && rewardRecords.length > 0) {
+      rewardAddition = rewardRecords.reduce((sum: number, record: any) => {
+        const addition = parseFloat(String(record.scoreAddition || 0)) || 0;
+        return sum + addition;
+      }, 0);
+
+      // 检查奖励类型是否需要均化处理
+      if (rewardTypeIds) {
+        rewardAddition = await applyEqualizationIfNeeded(rewardTypeIds, rewardAddition, 'reward');
+      }
+    }
+
+    console.log(
+      `指标 ${indicatorId} 自评得分计算完成: 扣分=${disciplineDeduction}, 加分=${rewardAddition} (违纪扣分: ${disciplineRecords?.length || 0}条, 奖励加分: ${rewardRecords?.length || 0}条)`
+    );
+
+    return {
+      deduction: Math.round(disciplineDeduction * 10) / 10,
+      addition: Math.round(rewardAddition * 10) / 10
+    };
+  } catch (error) {
+    console.error(`计算指标 ${indicatorId} 自评得分失败:`, error);
+    return { deduction: 0, addition: 0 };
+  }
+};
+
+/** 获取学生违纪处分记录 */
+const getDisciplineRecords = async (disciplineTypeIds: string): Promise<any[]> => {
+  if (!disciplineTypeIds || !form.value.studentId) {
+    return [];
+  }
+
+  try {
+    const response = await request({
+      url: '/disciplineRecord/disciplineRecord/getStudentDisciplineRecords',
+      method: 'get',
+      params: {
+        studentId: form.value.studentId,
+        disciplineTypeIds: disciplineTypeIds,
+        startTime: form.value.dataStartTime,
+        endTime: form.value.dataEndTime,
+        status: 1 // 只查询状态为1的记录
+      }
+    });
+
+    if (response.code === 200) {
+      return response.data || [];
+    } else {
+      console.warn('获取违纪处分记录失败:', response.msg);
+      return [];
+    }
+  } catch (error) {
+    console.error('获取违纪处分记录失败:', error);
+    return [];
+  }
+};
+
+/** 获取学生奖励记录 */
+const getRewardRecords = async (rewardTypeIds: string): Promise<any[]> => {
+  if (!rewardTypeIds || !form.value.studentId) {
+    return [];
+  }
+
+  try {
+    const response = await request({
+      url: '/rewardRecord/rewardRecord/getStudentRewardRecords',
+      method: 'get',
+      params: {
+        studentId: form.value.studentId,
+        rewardTypeIds: rewardTypeIds,
+        startTime: form.value.dataStartTime,
+        endTime: form.value.dataEndTime,
+        status: 1 // 只查询状态为1的记录
+      }
+    });
+
+    if (response.code === 200) {
+      return response.data || [];
+    } else {
+      console.warn('获取奖励记录失败:', response.msg);
+      return [];
+    }
+  } catch (error) {
+    console.error('获取奖励记录失败:', error);
+    return [];
+  }
+};
+
+/** 应用均化处理(如果需要) */
+const applyEqualizationIfNeeded = async (typeIds: string, studentScore: number, scoreType: 'discipline' | 'reward'): Promise<number> => {
+  if (!typeIds || studentScore === 0) {
+    return studentScore;
+  }
+
+  try {
+    // 将逗号分隔的类型ID字符串转换为数组
+    const typeIdArray = typeIds
+      .split(',')
+      .map((id) => id.trim())
+      .filter((id) => id);
+
+    console.log('typeIdArray:', typeIdArray);
+
+    // 遍历每个类型,检查是否需要均化
+    for (const typeId of typeIdArray) {
+      console.log('typeId:', typeId);
+
+      // 获取类型的均化设置
+      const typeDetailsResponse = await request({
+        url: scoreType === 'discipline' ? `/disciplineType/disciplineType/${typeId}` : `/rewardType/rewardType/${typeId}`,
+        method: 'get'
+      });
+
+      console.log('typeDetailsResponse:', typeDetailsResponse);
+
+      if (typeDetailsResponse.code === 200 && typeDetailsResponse.data) {
+        const typeInfo = typeDetailsResponse.data;
+
+        // 检查是否启用均化 (isEqualized: '1' 表示启用)
+        const isEqualized = typeInfo.isEqualized === '1';
+        const equalizedThreshold = parseFloat(String(typeInfo.equalizedThreshold || 0)) || 0;
+
+        if (isEqualized && equalizedThreshold > 0) {
+          console.log(`类型 ${typeId} 启用了均化处理,均化阈值: ${equalizedThreshold}`);
+
+          // 调用专门的接口获取统计周期内该类型下所有学生的最高总分值
+          const maxScoreResponse = await request({
+            url:
+              scoreType === 'discipline'
+                ? `/disciplineRecord/disciplineRecord/getMaxScoreByTypeAndPeriod/${typeId}`
+                : `/rewardRecord/rewardRecord/getMaxScoreByTypeAndPeriod/${typeId}`,
+            method: 'get',
+            params: {
+              startTime: form.value.dataStartTime,
+              endTime: form.value.dataEndTime
+            }
+          });
+
+          if (maxScoreResponse.code === 200 && maxScoreResponse.data !== null && maxScoreResponse.data !== undefined) {
+            const maxScore = parseFloat(String(maxScoreResponse.data)) || 0;
+
+            console.log(`类型 ${typeId} 统计周期内学生最高总分值: ${maxScore}, 当前学生得分: ${studentScore}, 均化阈值: ${equalizedThreshold}`);
+
+            // 如果最高分值超过了均化阈值,应用均化公式
+            if (maxScore > equalizedThreshold) {
+              const equalizedScore = (studentScore * equalizedThreshold) / maxScore;
+              console.log(`应用均化公式: ${studentScore} * ${equalizedThreshold} / ${maxScore} = ${equalizedScore}`);
+              return Math.round(equalizedScore * 10) / 10; // 保留一位小数
+            } else {
+              console.log(`最高分值 ${maxScore} 未超过均化阈值 ${equalizedThreshold},不需要均化处理`);
+            }
+          } else {
+            console.warn(`获取类型 ${typeId} 的最高分值失败:`, maxScoreResponse.msg);
+          }
+        }
+      }
+    }
+
+    // 如果没有需要均化的类型,返回原始分值
+    return studentScore;
+  } catch (error) {
+    console.error(`均化处理失败 (类型: ${scoreType}, typeIds: ${typeIds}):`, error);
+    // 均化失败时返回原始分值,不影响正常计算
+    return studentScore;
+  }
+};
+
 /** 根据考核计划ID获取关联的指标数据 */
 const loadIndicatorsByScheduleId = async (scheduleId: string | number) => {
   try {
@@ -607,7 +806,7 @@ const loadIndicatorsByScheduleId = async (scheduleId: string | number) => {
 
     if (response.code === 200 && response.data) {
       // 将指标数据转换为自评得分列表格式
-      form.value.selfScoreList = response.data.map((item: any) => ({
+      const indicatorList = response.data.map((item: any) => ({
         id: undefined,
         applicationId: undefined,
         indicatorId: item.indicatorId,
@@ -618,12 +817,19 @@ const loadIndicatorsByScheduleId = async (scheduleId: string | number) => {
         maxScore: item.maxScore, // 修改:使用maxScore字段名,匹配后端期望
         planMaxScore: item.maxScore, // 保留planMaxScore用于前端显示
         sortOrder: item.sortOrder,
-        selfScore: 0,
+        selfScore: 0, // 初始化为0,稍后计算
         selfRemark: '',
         teacherScore: 0,
         teacherRemark: ''
       }));
 
+      form.value.selfScoreList = indicatorList;
+
+      // 自动计算每个指标的自评得分
+      if (form.value.studentId && form.value.dataStartTime && form.value.dataEndTime) {
+        await calculateAllIndicatorScores();
+      }
+
       console.log('获取到考核计划关联的指标数据:', response.data);
     } else {
       ElMessage.warning(response.msg || '获取指标数据失败');
@@ -638,115 +844,133 @@ const loadIndicatorsByScheduleId = async (scheduleId: string | number) => {
   }
 };
 
-/** 计算总得分 */
-const calculateTotalScore = () => {
-  // 计算自评得分
-  let selfScore = 0;
-  if (form.value.selfScoreList && form.value.selfScoreList.length > 0) {
-    selfScore += form.value.selfScoreList.reduce((sum, item) => {
-      const score = parseFloat(String(item.selfScore || 0)) || 0;
-      return sum + score;
-    }, 0);
+/** 计算所有指标的自评得分(新算分规则) */
+const calculateAllIndicatorScores = async () => {
+  if (!form.value.selfScoreList || form.value.selfScoreList.length === 0) {
+    return;
   }
 
-  if (form.value.bonusApprovalList && form.value.bonusApprovalList.length > 0) {
-    selfScore += form.value.bonusApprovalList.reduce((sum, item) => {
-      const score = parseFloat(String(item.bonusScore || 0)) || 0;
-      return sum + score;
-    }, 0);
+  console.log('开始计算所有指标的自评得分(新算分规则)...');
+
+  // 并行计算所有指标的得分
+  const scorePromises = form.value.selfScoreList.map(async (indicator) => {
+    const { deduction, addition } = await calculateIndicatorSelfScore(indicator.indicatorId);
+
+    // 设置扣分和加分
+    indicator.deductionScore = deduction;
+    indicator.additionScore = addition;
+
+    // 为了兼容现有显示,selfScore设置为净得分(加分-扣分)
+    indicator.selfScore = addition - deduction;
+
+    return {
+      indicatorId: indicator.indicatorId,
+      deduction,
+      addition,
+      netScore: indicator.selfScore
+    };
+  });
+
+  try {
+    const results = await Promise.all(scorePromises);
+    console.log('所有指标自评得分计算完成(新算分规则):', results);
+
+    // 重新计算总得分
+    calculateTotalScore();
+  } catch (error) {
+    console.error('计算指标自评得分失败:', error);
+    ElMessage.error('计算指标自评得分失败,请稍后重试');
   }
+};
 
-  form.value.selfScore = parseFloat(selfScore.toFixed(1));
+/** 计算总得分(新算分规则) */
+const calculateTotalScore = () => {
+  // 第1步:基础分100分 - 指标模块的扣分,最小为0
+  const baseScore = 100;
+  let totalIndicatorDeduction = 0;
+  let totalIndicatorAddition = 0;
 
-  // 计算班主任评分
-  let teacherScore = 0;
+  // 计算指标模块的总扣分和总加分
   if (form.value.selfScoreList && form.value.selfScoreList.length > 0) {
-    teacherScore += form.value.selfScoreList.reduce((sum, item) => {
-      const score = parseFloat(String(item.teacherScore || 0)) || 0;
-      return sum + score;
-    }, 0);
+    form.value.selfScoreList.forEach((item) => {
+      const deduction = parseFloat(String(item.deductionScore || 0)) || 0;
+      const addition = parseFloat(String(item.additionScore || 0)) || 0;
+      totalIndicatorDeduction += deduction;
+      totalIndicatorAddition += addition;
+    });
+  }
+
+  // 第1步:基础分 - 扣分,结果不能小于0
+  let step1Score = baseScore - totalIndicatorDeduction;
+  if (step1Score < 0) {
+    step1Score = 0;
   }
 
+  // 第2步:加上指标模块的加分
+  const step2Score = step1Score + totalIndicatorAddition;
+
+  // 第3步:加上附加分模块的分值(可正可负)
+  let totalBonusScore = 0;
   if (form.value.bonusApprovalList && form.value.bonusApprovalList.length > 0) {
-    teacherScore += form.value.bonusApprovalList.reduce((sum, item) => {
-      const score = parseFloat(String(item.approvalScore || 0)) || 0;
+    totalBonusScore = form.value.bonusApprovalList.reduce((sum, item) => {
+      const score = parseFloat(String(item.bonusScore || 0)) || 0;
       return sum + score;
     }, 0);
   }
 
-  form.value.teacherScore = parseFloat(teacherScore.toFixed(1));
+  // 最终自评得分
+  const finalSelfScore = step2Score + totalBonusScore;
+  form.value.selfScore = parseFloat(finalSelfScore.toFixed(1));
 
-  // 总得分等于班主任评分
-  form.value.totalScore = parseFloat(teacherScore.toFixed(1));
-};
+  console.log(
+    `自评得分计算详情: 基础分=${baseScore}, 指标扣分=${totalIndicatorDeduction}, 第1步结果=${step1Score}, 指标加分=${totalIndicatorAddition}, 第2步结果=${step2Score}, 附加分=${totalBonusScore}, 最终得分=${form.value.selfScore}`
+  );
 
-/** 处理自评得分输入验证 */
-const handleSelfScoreInput = (value: string | number, row: XskhAssessmentSelfScoreForm) => {
-  const numValue = parseFloat(String(value)) || 0;
-  const maxScore = parseFloat(String(row.planMaxScore)) || 0;
+  // 班主任评分采用相同逻辑,但使用班主任的评分数据
+  let teacherTotalIndicatorDeduction = 0;
+  let teacherTotalIndicatorAddition = 0;
 
-  // 如果输入值超过最高分值,自动限制为最高分值
-  if (numValue > maxScore) {
-    row.selfScore = maxScore;
-    ElMessage.warning(`自评得分不能超过最高分值 ${maxScore}`);
-  } else {
-    row.selfScore = numValue;
+  // 计算班主任对指标模块的评分
+  if (form.value.selfScoreList && form.value.selfScoreList.length > 0) {
+    form.value.selfScoreList.forEach((item) => {
+      const teacherScore = parseFloat(String(item.teacherScore || 0)) || 0;
+      // 班主任评分可能是综合评分,这里需要根据实际情况调整
+      if (teacherScore < 0) {
+        teacherTotalIndicatorDeduction += Math.abs(teacherScore);
+      } else {
+        teacherTotalIndicatorAddition += teacherScore;
+      }
+    });
   }
 
-  // 重新计算总得分
-  calculateTotalScore();
-};
-
-/** 处理自评得分失焦验证 */
-const handleSelfScoreBlur = (event: Event, row: XskhAssessmentSelfScoreForm) => {
-  const target = event.target as HTMLInputElement;
-  const value = parseFloat(target.value) || 0;
-  const maxScore = parseFloat(String(row.planMaxScore)) || 0;
-
-  // 失焦时再次验证,确保不超过最高分值
-  if (value > maxScore) {
-    row.selfScore = maxScore;
-    target.value = maxScore.toString();
-    ElMessage.warning(`自评得分已自动调整为最高分值 ${maxScore}`);
+  // 第1步:基础分 - 班主任评定的扣分
+  let teacherStep1Score = baseScore - teacherTotalIndicatorDeduction;
+  if (teacherStep1Score < 0) {
+    teacherStep1Score = 0;
   }
 
-  // 确保值为非负数
-  if (value < 0) {
-    row.selfScore = 0;
-    target.value = '0';
-  }
-};
+  // 第2步:加上班主任评定的加分
+  const teacherStep2Score = teacherStep1Score + teacherTotalIndicatorAddition;
 
-/** 验证所有自评得分是否超过最高分值 */
-const validateSelfScores = (): boolean => {
-  if (!form.value.selfScoreList || form.value.selfScoreList.length === 0) {
-    return true;
+  // 第3步:加上附加分模块的审核得分
+  let totalApprovalScore = 0;
+  if (form.value.bonusApprovalList && form.value.bonusApprovalList.length > 0) {
+    totalApprovalScore = form.value.bonusApprovalList.reduce((sum, item) => {
+      const score = parseFloat(String(item.approvalScore || 0)) || 0;
+      return sum + score;
+    }, 0);
   }
 
-  for (let i = 0; i < form.value.selfScoreList.length; i++) {
-    const item = form.value.selfScoreList[i];
-    const selfScore = parseFloat(String(item.selfScore || 0)) || 0;
-    const maxScore = parseFloat(String(item.planMaxScore || 0)) || 0;
-
-    if (selfScore > maxScore) {
-      ElMessage.error(`第${i + 1}项指标"${item.indicatorName}"的自评得分(${selfScore})不能超过最高分值(${maxScore})`);
-      return false;
-    }
+  // 最终班主任评分
+  const finalTeacherScore = teacherStep2Score + totalApprovalScore;
+  form.value.teacherScore = parseFloat(finalTeacherScore.toFixed(1));
 
-    if (selfScore < 0) {
-      ElMessage.error(`第${i + 1}项指标"${item.indicatorName}"的自评得分不能为负数`);
-      return false;
-    }
-  }
-
-  return true;
-};
+  // 总得分等于班主任评分
+  form.value.totalScore = parseFloat(finalTeacherScore.toFixed(1));
 
-/** 检查单个指标的自评得分是否超过最高分值 */
-const isScoreExceeded = (row: XskhAssessmentSelfScoreForm): boolean => {
-  const selfScore = parseFloat(String(row.selfScore || 0)) || 0;
-  const maxScore = parseFloat(String(row.planMaxScore || 0)) || 0;
-  return selfScore > maxScore;
+  console.log(
+    `班主任评分计算详情: 基础分=${baseScore}, 指标扣分=${teacherTotalIndicatorDeduction}, 第1步结果=${teacherStep1Score}, 指标加分=${teacherTotalIndicatorAddition}, 第2步结果=${teacherStep2Score}, 附加审核分=${totalApprovalScore}, 最终得分=${form.value.teacherScore}`
+  );
 };
 
 /** 添加附加分项目 */
@@ -1096,6 +1320,11 @@ const openDialog = async (id?: string | number, title?: string, schedule?: Asses
             teacherScore: item.teacherScore || 0,
             teacherRemark: item.teacherRemark || ''
           }));
+
+          // 重新计算自评得分(确保数据是最新的)
+          if (form.value.studentId && form.value.dataStartTime && form.value.dataEndTime) {
+            await calculateAllIndicatorScores();
+          }
         }
 
         // 确保附加分列表正确回显
@@ -1164,6 +1393,11 @@ const openDialog = async (id?: string | number, title?: string, schedule?: Asses
               teacherScore: item.teacherScore || 0,
               teacherRemark: item.teacherRemark || ''
             }));
+
+            // 重新计算自评得分(确保数据是最新的)
+            if (form.value.studentId && form.value.dataStartTime && form.value.dataEndTime) {
+              await calculateAllIndicatorScores();
+            }
           }
 
           // 确保附加分列表正确回显
@@ -1258,11 +1492,6 @@ const safeConvertId = (id: string | number | undefined): string | undefined => {
 const handleSave = async () => {
   assessmentApplicationFormRef.value?.validate(async (valid) => {
     if (valid) {
-      // 验证自评得分是否超过最高分值
-      if (!validateSelfScores()) {
-        return;
-      }
-
       try {
         // 处理附件数据,将fileList中的ossId合并为evidenceFile字段
         if (form.value.bonusApprovalList && form.value.bonusApprovalList.length > 0) {
@@ -1310,11 +1539,6 @@ const handleSave = async () => {
 const handleSubmit = async () => {
   assessmentApplicationFormRef.value?.validate(async (valid) => {
     if (valid) {
-      // 验证自评得分是否超过最高分值
-      if (!validateSelfScores()) {
-        return;
-      }
-
       try {
         // 处理附件数据,将fileList中的ossId合并为evidenceFile字段
         if (form.value.bonusApprovalList && form.value.bonusApprovalList.length > 0) {
@@ -1764,6 +1988,28 @@ defineExpose({
   justify-content: center;
 }
 
+/* 自动计算得分样式 */
+.score-display.auto-calculated {
+  /* background: #f0f9ff; */
+  /* order: 1px solid #38bdf8; */
+  /* color: #0369a1; */
+  font-weight: 600;
+  position: relative;
+}
+
+.score-display.auto-calculated::after {
+  /* content: '自动计算'; */
+  position: absolute;
+  top: -8px;
+  right: -8px;
+  background: #38bdf8;
+  color: white;
+  font-size: 10px;
+  padding: 1px 4px;
+  border-radius: 2px;
+  font-weight: normal;
+}
+
 /* 备注展示样式 */
 .remark-display {
   padding: 8px 12px;

+ 9 - 8
web/src/views/xskh/assessmentApplication/IndicatorDataDialog.vue

@@ -1,15 +1,15 @@
 <template>
   <el-dialog :title="`${indicatorName} - 学生数据详情`" v-model="dialogVisible" width="1000px" append-to-body destroy-on-close>
     <div v-loading="loading" element-loading-text="正在加载数据...">
-      <!-- 违纪处分记录区域 -->
-      <el-card class="mb-4" shadow="never">
+      <!-- 违纪处分记录区域 - 只有数据时才显示 -->
+      <el-card class="mb-4" shadow="never" v-if="disciplineRecords.length > 0">
         <template #header>
           <div class="card-header">
             <span>违纪处分记录</span>
             <el-tag type="danger" size="small">{{ disciplineRecords.length }} 条记录</el-tag>
           </div>
         </template>
-        <el-table :data="disciplineRecords" border style="width: 100%" v-if="disciplineRecords.length > 0">
+        <el-table :data="disciplineRecords" border style="width: 100%">
           <el-table-column label="违纪日期" prop="disciplineDate" width="120">
             <template #default="scope">
               {{ formatDate(scope.row.disciplineDate) }}
@@ -24,18 +24,17 @@
             </template>
           </el-table-column>
         </el-table>
-        <el-empty v-else description="暂无违纪处分记录" />
       </el-card>
 
-      <!-- 奖励记录区域 -->
-      <el-card shadow="never">
+      <!-- 奖励记录区域 - 只有数据时才显示 -->
+      <el-card shadow="never" v-if="rewardRecords.length > 0">
         <template #header>
           <div class="card-header">
             <span>奖励记录</span>
             <el-tag type="success" size="small">{{ rewardRecords.length }} 条记录</el-tag>
           </div>
         </template>
-        <el-table :data="rewardRecords" border style="width: 100%" v-if="rewardRecords.length > 0">
+        <el-table :data="rewardRecords" border style="width: 100%">
           <el-table-column label="奖励日期" prop="rewardDate" width="120">
             <template #default="scope">
               {{ formatDate(scope.row.rewardDate) }}
@@ -49,8 +48,10 @@
             </template>
           </el-table-column>
         </el-table>
-        <el-empty v-else description="暂无奖励记录" />
       </el-card>
+
+      <!-- 当没有任何数据时显示 -->
+      <el-empty v-if="disciplineRecords.length === 0 && rewardRecords.length === 0 && !loading" description="暂无相关数据记录" />
     </div>
   </el-dialog>
 </template>

+ 118 - 21
web/src/views/xskh/assessmentApproval/AssessmentApprovalForm.vue

@@ -67,10 +67,10 @@
               <el-card shadow="never">
                 <template #header>
                   <div class="card-header">
-                    <span>指标模块</span>
+                    <span>指标模块(基础分:100分)</span>
                   </div>
                 </template>
-                <el-table :data="form.selfScoreList" border style="width: 100%">
+                <el-table :data="sortedSelfScoreList" border style="width: 100%">
                   <el-table-column label="指标名称" prop="indicatorName" min-width="120">
                     <template #default="scope">
                       <div class="indicator-name-cell">
@@ -484,46 +484,130 @@ const isViewMode = computed(() => {
   return dialog.title === '查看考核申请' || form.value.status === '6';
 });
 
-/** 计算总得分 */
+// 计算排序后的指标列表
+const sortedSelfScoreList = computed(() => {
+  if (!form.value.selfScoreList || form.value.selfScoreList.length === 0) {
+    return [];
+  }
+
+  // 创建副本进行排序,避免修改原数据
+  const list = [...form.value.selfScoreList];
+
+  return list.sort((a, b) => {
+    const scoreA = parseFloat(String(a.selfScore || 0)) || 0;
+    const scoreB = parseFloat(String(b.selfScore || 0)) || 0;
+
+    // 如果A不为0,B为0,A排前面
+    if (scoreA !== 0 && scoreB === 0) return -1;
+    // 如果A为0,B不为0,B排前面
+    if (scoreA === 0 && scoreB !== 0) return 1;
+    // 如果都不为0,按分值倒序排列(高分在前)
+    if (scoreA !== 0 && scoreB !== 0) return scoreB - scoreA;
+    // 如果都为0,保持原顺序
+    return 0;
+  });
+});
+
+/** 计算总得分(新算分规则) */
 const calculateTotalScore = () => {
-  // 计算自评得分
-  let selfScore = 0;
+  // 第1步:基础分100分 - 指标模块的扣分,最小为0
+  const baseScore = 100;
+  let totalIndicatorDeduction = 0;
+  let totalIndicatorAddition = 0;
+
+  // 计算指标模块的总扣分和总加分
   if (form.value.selfScoreList && form.value.selfScoreList.length > 0) {
-    selfScore += form.value.selfScoreList.reduce((sum, item) => {
-      const score = parseFloat(String(item.selfScore || 0)) || 0;
-      return sum + score;
-    }, 0);
+    form.value.selfScoreList.forEach((item) => {
+      // 兼容性处理:如果存在 deductionScore 和 additionScore 字段,使用新算分规则
+      const itemWithNewFields = item as any; // 类型断言以访问可能的新字段
+      if (itemWithNewFields.deductionScore !== undefined || itemWithNewFields.additionScore !== undefined) {
+        const deduction = parseFloat(String(itemWithNewFields.deductionScore || 0)) || 0;
+        const addition = parseFloat(String(itemWithNewFields.additionScore || 0)) || 0;
+        totalIndicatorDeduction += deduction;
+        totalIndicatorAddition += addition;
+      } else {
+        // 兼容性处理:如果没有新字段,从 selfScore 推算(假设都是加分)
+        const score = parseFloat(String(item.selfScore || 0)) || 0;
+        if (score >= 0) {
+          totalIndicatorAddition += score;
+        } else {
+          totalIndicatorDeduction += Math.abs(score);
+        }
+      }
+    });
   }
 
+  // 第1步:基础分 - 扣分,结果不能小于0
+  let step1Score = baseScore - totalIndicatorDeduction;
+  if (step1Score < 0) {
+    step1Score = 0;
+  }
+
+  // 第2步:加上指标模块的加分
+  const step2Score = step1Score + totalIndicatorAddition;
+
+  // 第3步:加上附加分模块的分值(可正可负)
+  let totalBonusScore = 0;
   if (form.value.bonusApprovalList && form.value.bonusApprovalList.length > 0) {
-    selfScore += form.value.bonusApprovalList.reduce((sum, item) => {
+    totalBonusScore = form.value.bonusApprovalList.reduce((sum, item) => {
       const score = parseFloat(String(item.bonusScore || 0)) || 0;
       return sum + score;
     }, 0);
   }
 
-  form.value.selfScore = parseFloat(selfScore.toFixed(1));
+  // 最终自评得分
+  const finalSelfScore = step2Score + totalBonusScore;
+  form.value.selfScore = parseFloat(finalSelfScore.toFixed(1));
+
+  console.log(
+    `自评得分计算详情: 基础分=${baseScore}, 指标扣分=${totalIndicatorDeduction}, 第1步结果=${step1Score}, 指标加分=${totalIndicatorAddition}, 第2步结果=${step2Score}, 附加分=${totalBonusScore}, 最终得分=${form.value.selfScore}`
+  );
 
-  // 计算班主任评分
-  let teacherScore = 0;
+  // 班主任评分采用相同逻辑,但使用班主任的评分数据
+  let teacherTotalIndicatorDeduction = 0;
+  let teacherTotalIndicatorAddition = 0;
+
+  // 计算班主任对指标模块的评分
   if (form.value.selfScoreList && form.value.selfScoreList.length > 0) {
-    teacherScore += form.value.selfScoreList.reduce((sum, item) => {
-      const score = parseFloat(String(item.teacherScore || 0)) || 0;
-      return sum + score;
-    }, 0);
+    form.value.selfScoreList.forEach((item) => {
+      const teacherScore = parseFloat(String(item.teacherScore || 0)) || 0;
+      // 班主任评分:负数视为扣分,正数视为加分
+      if (teacherScore < 0) {
+        teacherTotalIndicatorDeduction += Math.abs(teacherScore);
+      } else {
+        teacherTotalIndicatorAddition += teacherScore;
+      }
+    });
+  }
+
+  // 第1步:基础分 - 班主任评定的扣分
+  let teacherStep1Score = baseScore - teacherTotalIndicatorDeduction;
+  if (teacherStep1Score < 0) {
+    teacherStep1Score = 0;
   }
 
+  // 第2步:加上班主任评定的加分
+  const teacherStep2Score = teacherStep1Score + teacherTotalIndicatorAddition;
+
+  // 第3步:加上附加分模块的审核得分
+  let totalApprovalScore = 0;
   if (form.value.bonusApprovalList && form.value.bonusApprovalList.length > 0) {
-    teacherScore += form.value.bonusApprovalList.reduce((sum, item) => {
+    totalApprovalScore = form.value.bonusApprovalList.reduce((sum, item) => {
       const score = parseFloat(String(item.approvalScore || 0)) || 0;
       return sum + score;
     }, 0);
   }
 
-  form.value.teacherScore = parseFloat(teacherScore.toFixed(1));
+  // 最终班主任评分
+  const finalTeacherScore = teacherStep2Score + totalApprovalScore;
+  form.value.teacherScore = parseFloat(finalTeacherScore.toFixed(1));
 
   // 总得分等于班主任评分
-  form.value.totalScore = parseFloat(teacherScore.toFixed(1));
+  form.value.totalScore = parseFloat(finalTeacherScore.toFixed(1));
+
+  console.log(
+    `班主任评分计算详情: 基础分=${baseScore}, 指标扣分=${teacherTotalIndicatorDeduction}, 第1步结果=${teacherStep1Score}, 指标加分=${teacherTotalIndicatorAddition}, 第2步结果=${teacherStep2Score}, 附加审核分=${totalApprovalScore}, 最终得分=${form.value.teacherScore}`
+  );
 };
 
 /** 获取状态标签类型 */
@@ -1465,11 +1549,24 @@ defineExpose({
 .indicator-name-cell {
   display: flex;
   align-items: center;
-  gap: 8px;
+  gap: 6px;
+}
+
+.indicator-name-cell span {
+  flex: 1;
+  font-size: inherit;
 }
 
 .indicator-data-btn {
   flex-shrink: 0;
   margin-left: 4px;
+  transform: scale(0.7);
+  opacity: 0.7;
+}
+
+.indicator-data-btn:hover {
+  transform: scale(0.8);
+  opacity: 1;
+  transition: all 0.2s ease;
 }
 </style>

+ 92 - 61
web/src/views/xskh/assessmentIndicator/AssessmentIndicatorForm.vue

@@ -26,7 +26,8 @@
             </el-select>
           </el-form-item>
         </el-col>
-        <el-col :span="12">
+        <!-- 隐藏最高分值字段 -->
+        <el-col :span="12" v-show="false">
           <el-form-item label="最高分值" prop="maxScore">
             <el-input-number
               v-model="form.maxScore"
@@ -68,52 +69,46 @@
         </el-col>
       </el-row>
 
-      <!-- 关联的违纪处分类型和奖励类型 -->
+      <!-- 关联的违纪处分类型和奖励类型(改为单选且互斥) -->
       <el-row :gutter="20">
         <el-col :span="12">
-          <el-form-item label="违纪处分类型" prop="disciplineTypeIds">
+          <el-form-item label="违纪处分类型" prop="disciplineTypeId">
             <el-select
-              v-model="form.disciplineTypeIds"
+              v-model="form.disciplineTypeId"
               placeholder="请选择违纪处分类型"
-              multiple
               filterable
               clearable
               style="width: 100%"
               :disabled="dialog.title === '查看'"
-              class="fixed-height-select"
-              collapse-tags
-              collapse-tags-tooltip
+              @change="onDisciplineTypeChange"
             >
               <el-option v-for="type in disciplineTypeOptions" :key="type.id" :label="`${type.typeCode} - ${type.typeName}`" :value="type.id" />
             </el-select>
           </el-form-item>
           <!-- 显示选择的违纪处分类型 -->
-          <div v-if="selectedDisciplineTypes.length > 0" class="selected-items">
+          <div v-if="selectedDisciplineType" class="selected-items">
             <div class="selected-title">已选择的违纪处分类型:</div>
-            <div v-for="type in selectedDisciplineTypes" :key="type.id" class="selected-item">{{ type.typeCode }} - {{ type.typeName }}</div>
+            <div class="selected-item">{{ selectedDisciplineType.typeCode }} - {{ selectedDisciplineType.typeName }}</div>
           </div>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="奖励类型" prop="rewardTypeIds">
+          <el-form-item label="奖励类型" prop="rewardTypeId">
             <el-select
-              v-model="form.rewardTypeIds"
+              v-model="form.rewardTypeId"
               placeholder="请选择奖励类型"
-              multiple
               filterable
               clearable
               style="width: 100%"
               :disabled="dialog.title === '查看'"
-              class="fixed-height-select"
-              collapse-tags
-              collapse-tags-tooltip
+              @change="onRewardTypeChange"
             >
               <el-option v-for="type in rewardTypeOptions" :key="type.id" :label="`${type.typeCode} - ${type.typeName}`" :value="type.id" />
             </el-select>
           </el-form-item>
           <!-- 显示选择的奖励类型 -->
-          <div v-if="selectedRewardTypes.length > 0" class="selected-items">
+          <div v-if="selectedRewardType" class="selected-items">
             <div class="selected-title">已选择的奖励类型:</div>
-            <div v-for="type in selectedRewardTypes" :key="type.id" class="selected-item">{{ type.typeCode }} - {{ type.typeName }}</div>
+            <div class="selected-item">{{ selectedRewardType.typeCode }} - {{ selectedRewardType.typeName }}</div>
           </div>
         </el-col>
       </el-row>
@@ -158,11 +153,11 @@ const initFormData: XskhAssessmentIndicatorForm = {
   indicatorName: undefined,
   description: undefined,
   indicatorTypeId: undefined,
-  maxScore: undefined,
+  maxScore: 100, // 默认值设为100
   status: '0',
   sortOrder: 0,
-  disciplineTypeIds: [],
-  rewardTypeIds: []
+  disciplineTypeId: undefined, // 改为单个值
+  rewardTypeId: undefined // 改为单个值
 };
 
 const data = reactive({
@@ -181,22 +176,38 @@ const rules = reactive({
     { min: 1, max: 100, message: '指标名称长度必须介于 1 和 100 之间', trigger: 'blur' }
   ],
   indicatorTypeId: [{ required: true, message: '指标类型不能为空', trigger: 'change' }],
-  maxScore: [{ required: true, message: '最高分值不能为空', trigger: 'blur' }],
+  // maxScore: [{ required: true, message: '最高分值不能为空', trigger: 'blur' }],
   status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
   sortOrder: [{ required: true, message: '排序号不能为空', trigger: 'blur' }],
   description: [{ max: 500, message: '指标说明长度不能超过 500 个字符', trigger: 'blur' }]
 } as any);
 
-// 计算属性:已选择的违纪处分类型
-const selectedDisciplineTypes = computed(() => {
-  return disciplineTypeOptions.value.filter((type) => form.value.disciplineTypeIds?.includes(type.id.toString()));
+// 计算属性:已选择的违纪处分类型(改为单个)
+const selectedDisciplineType = computed(() => {
+  return disciplineTypeOptions.value.find((type) => type.id.toString() === form.value.disciplineTypeId?.toString());
 });
 
-// 计算属性:已选择的奖励类型
-const selectedRewardTypes = computed(() => {
-  return rewardTypeOptions.value.filter((type) => form.value.rewardTypeIds?.includes(type.id.toString()));
+// 计算属性:已选择的奖励类型(改为单个)
+const selectedRewardType = computed(() => {
+  return rewardTypeOptions.value.find((type) => type.id.toString() === form.value.rewardTypeId?.toString());
 });
 
+// 违纪处分类型选择变化时的处理(实现互斥逻辑)
+const onDisciplineTypeChange = (value: any) => {
+  if (value) {
+    // 如果选择了违纪处分类型,清空奖励类型
+    form.value.rewardTypeId = undefined;
+  }
+};
+
+// 奖励类型选择变化时的处理(实现互斥逻辑)
+const onRewardTypeChange = (value: any) => {
+  if (value) {
+    // 如果选择了奖励类型,清空违纪处分类型
+    form.value.disciplineTypeId = undefined;
+  }
+};
+
 /** 获取指标类型选项 */
 const getIndicatorTypeOptions = async () => {
   try {
@@ -270,7 +281,41 @@ const openDialog = async (id?: string | number, title?: string) => {
   if (id) {
     try {
       const res = await getAssessmentIndicator(id);
-      Object.assign(form.value, res.data);
+      console.log('API响应数据:', res);
+      console.log('res.data:', res.data);
+
+      // 处理后端返回的数据,将数组格式转换为单个值格式
+      const responseData = res.data;
+      console.log('处理后的数据:', responseData);
+
+      // 创建表单数据对象,只包含表单需要的字段
+      const formData: any = {
+        id: responseData.id,
+        indicatorCode: responseData.indicatorCode,
+        indicatorName: responseData.indicatorName,
+        description: responseData.description,
+        indicatorTypeId: responseData.indicatorTypeId,
+        maxScore: responseData.maxScore,
+        status: responseData.status,
+        sortOrder: responseData.sortOrder
+      };
+
+      // 转换违纪处分类型ID(取第一个,因为前端现在只支持单选)
+      if (responseData.disciplineTypeIds && responseData.disciplineTypeIds.length > 0) {
+        formData.disciplineTypeId = responseData.disciplineTypeIds[0];
+      }
+
+      // 转换奖励类型ID(取第一个,因为前端现在只支持单选)
+      if (responseData.rewardTypeIds && responseData.rewardTypeIds.length > 0) {
+        formData.rewardTypeId = responseData.rewardTypeIds[0];
+      }
+
+      console.log('表单数据:', formData);
+      console.log('赋值前的form.value:', form.value);
+
+      Object.assign(form.value, formData);
+
+      console.log('赋值后的form.value:', form.value);
     } catch (error) {
       console.error('获取数据失败:', error);
     }
@@ -282,11 +327,27 @@ const submitForm = async () => {
   assessmentIndicatorFormRef.value?.validate(async (valid) => {
     if (valid) {
       try {
+        // 按照XskhAssessmentIndicatorForm接口定义,只提取需要的字段
+        const submitData: any = {
+          indicatorCode: form.value.indicatorCode,
+          indicatorName: form.value.indicatorName,
+          description: form.value.description,
+          indicatorTypeId: form.value.indicatorTypeId,
+          maxScore: Number(form.value.maxScore) || 100,
+          status: form.value.status,
+          sortOrder: form.value.sortOrder,
+          // 将单个ID转换为数组格式以兼容后端
+          disciplineTypeIds: form.value.disciplineTypeId ? [form.value.disciplineTypeId] : [],
+          rewardTypeIds: form.value.rewardTypeId ? [form.value.rewardTypeId] : []
+        };
+
+        // 如果是编辑,添加ID字段
         if (form.value.id) {
-          await updateAssessmentIndicator(form.value);
+          submitData.id = form.value.id;
+          await updateAssessmentIndicator(submitData);
           proxy?.$modal.msgSuccess('修改成功');
         } else {
-          await addAssessmentIndicator(form.value);
+          await addAssessmentIndicator(submitData);
           proxy?.$modal.msgSuccess('新增成功');
         }
         dialog.visible = false;
@@ -331,34 +392,4 @@ defineExpose({
   color: #409eff;
   font-size: 14px;
 }
-
-/* 固定多选框高度,防止动态变化 */
-.fixed-height-select :deep(.el-select__tags) {
-  max-height: 32px;
-  overflow: hidden;
-}
-
-.fixed-height-select :deep(.el-select__tags-text) {
-  max-width: 120px;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-
-.fixed-height-select :deep(.el-select__collapse-tags) {
-  max-width: 100%;
-}
-
-/* 确保多选框输入框高度固定 */
-.fixed-height-select :deep(.el-input__wrapper) {
-  min-height: 32px;
-  max-height: 32px;
-  overflow: hidden;
-}
-
-/* 多选框下拉选项的最大高度 */
-.fixed-height-select :deep(.el-select-dropdown__list) {
-  max-height: 200px;
-  overflow-y: auto;
-}
 </style>

+ 1 - 1
web/src/views/xskh/assessmentIndicator/index.vue

@@ -62,7 +62,7 @@
         <el-table-column label="指标名称" align="center" prop="indicatorName" min-width="140" show-overflow-tooltip />
         <el-table-column label="指标类型" align="center" prop="indicatorTypeName" min-width="120" show-overflow-tooltip />
         <el-table-column label="指标说明" align="center" prop="description" min-width="200" show-overflow-tooltip />
-        <el-table-column label="最高分值" align="center" prop="maxScore" min-width="100">
+        <el-table-column label="最高分值" align="center" prop="maxScore" min-width="100" v-if="false">
           <template #default="scope">
             <span>{{ scope.row.maxScore ? parseFloat(scope.row.maxScore).toFixed(2) : '0.00' }}</span>
           </template>

+ 10 - 5
web/src/views/xskh/assessmentPlan/AssessmentPlanForm.vue

@@ -69,12 +69,12 @@
               <el-input v-model="scope.row.indicatorDescription" readonly />
             </template>
           </el-table-column>
-          <el-table-column label="指标默认最高分值" min-width="150">
+          <el-table-column label="指标默认最高分值" min-width="150" v-if="false">
             <template #default="scope">
               <el-input v-model="scope.row.indicatorMaxScore" readonly />
             </template>
           </el-table-column>
-          <el-table-column label="该方案中该指标最高分值" min-width="180">
+          <el-table-column label="该方案中该指标最高分值" min-width="180" v-if="false">
             <template #default="scope">
               <el-input-number
                 v-model="scope.row.maxScore"
@@ -252,8 +252,8 @@ const openDialog = async (id?: string | number, title?: string) => {
   dialog.visible = true;
   dialog.title = title || '新增';
 
-  // 重置表单
-  Object.assign(form.value, initFormData);
+  // 重置表单数据
+  form.value = { ...initFormData };
   indicatorList.value = [];
 
   // 获取选项数据
@@ -304,8 +304,13 @@ const submitForm = async () => {
       }
 
       try {
+        // 准备提交数据,排除自动填充字段
         const submitData = {
-          ...form.value,
+          id: form.value.id,
+          planCode: form.value.planCode,
+          planName: form.value.planName,
+          description: form.value.description,
+          status: form.value.status,
           indicatorList: indicatorList.value
         };
 

+ 2 - 0
web/src/views/xskh/assessmentSchedule/index.vue

@@ -182,6 +182,8 @@ const data = reactive<PageData<AssessmentScheduleFormType, XskhAssessmentSchedul
     academicYear: undefined,
     semester: undefined,
     status: undefined,
+    orderByColumn: 'createTime',
+    isAsc: 'desc',
     params: {}
   }
 });