Pārlūkot izejas kodu

feat(monthlyAssess): 优化月度考核指标选择与展示逻辑

1. 新增分类树关联指标查询API,替换原指标下拉选择为级联选择器
2. 重构指标数据展示与表单绑定逻辑,修复字符串比较引号问题
3. 新增考核月份切换时自动加载已有考核数据的功能
4. 简化详情链接默认显示文本,移除冗余的空值判断
5. 调整表单提交与数据加载的时序逻辑,优化用户操作体验
huoyi 1 mēnesi atpakaļ
vecāks
revīzija
8447052d00

+ 8 - 0
src/api/system/classificationAssessIndicator.js

@@ -46,3 +46,11 @@ export function queryAssessCategoryTree(query) {
46 46
         params: query
47 47
     })
48 48
 }
49
+//获取分类树及关联指标(支持模糊查询)
50
+export function queryAssessCategoryTreeAndIndicator(data) {
51
+    return request({
52
+        url: '/system/performanceIndicatorCategory/getCategoryTreeWithIndicatorList',
53
+        method: 'post',
54
+        data
55
+    })
56
+}

+ 174 - 19
src/views/performanceManage/monthlyAssess/index.vue

@@ -188,7 +188,7 @@
188 188
           <el-col :span="8">
189 189
             <el-form-item label="考核月份" prop="assessmentMonth" label-width="100px">
190 190
               <el-date-picker v-model="nonCadreForm.assessmentMonth" type="month" placeholder="请选择考核月份"
191
-                value-format="YYYY-MM" style="width: 100%" />
191
+                value-format="YYYY-MM" style="width: 100%" @change="handleAssessmentMonthChange" />
192 192
             </el-form-item>
193 193
           </el-col>
194 194
           <el-col :span="8">
@@ -265,7 +265,7 @@
265 265
             <div class="indicator-group-title">{{ group.title }}</div>
266 266
             <div v-for="(item, itemIndex) in group.items" :key="itemIndex" class="indicator-item">
267 267
               <div class="indicator-name">{{ item.indicatorName }}</div>
268
-              <div class="indicator-value" v-if="item.categoryNameOne!='红线指标'">{{ item.score }}/次</div>
268
+              <div class="indicator-value" v-if="item.categoryNameOne != '红线指标'">{{ item.score }}/次</div>
269 269
               <div class="indicator-count">{{ item.occurCount }}次</div>
270 270
               <div class="indicator-total">{{ item.scoreResult }}</div>
271 271
               <div class="indicator-actions">
@@ -289,7 +289,7 @@
289 289
               <el-form-item label="红线指标依据">
290 290
                 <span class="detail-link"
291 291
                   @click="showDetailModal('红线指标依据', formatAccordList(nonCadreForm.redLineIndexAccordList))">
292
-                  {{ formatAccordList(nonCadreForm.redLineIndexAccordList) || '查看详情' }}
292
+                  查看详情
293 293
                 </span>
294 294
               </el-form-item>
295 295
             </el-col>
@@ -305,7 +305,7 @@
305 305
               <el-form-item label="核心指标依据">
306 306
                 <span class="detail-link"
307 307
                   @click="showDetailModal('核心指标依据', formatAccordList(nonCadreForm.coreIndexAccordList))">
308
-                  {{ formatAccordList(nonCadreForm.coreIndexAccordList) || '查看详情' }}
308
+                  查看详情
309 309
                 </span>
310 310
               </el-form-item>
311 311
             </el-col>
@@ -321,7 +321,7 @@
321 321
               <el-form-item label="其他指标中的安全指标(仅含SOC/站品控检查扣分)依据">
322 322
                 <span class="detail-link"
323 323
                   @click="showDetailModal('其他指标中的安全指标依据', formatAccordList(nonCadreForm.otherIndexSafetyScoreWithSocStationQcAccordList))">
324
-                  {{ formatAccordList(nonCadreForm.otherIndexSafetyScoreWithSocStationQcAccordList) || '查看详情' }}
324
+                  查看详情
325 325
                 </span>
326 326
               </el-form-item>
327 327
             </el-col>
@@ -337,7 +337,7 @@
337 337
               <el-form-item label="其他指标中的非安全指标依据">
338 338
                 <span class="detail-link"
339 339
                   @click="showDetailModal('其他指标中的非安全指标依据', formatAccordList(nonCadreForm.otherIndexNonSafetyAccordList))">
340
-                  {{ formatAccordList(nonCadreForm.otherIndexNonSafetyAccordList) || '查看详情' }}
340
+                  查看详情
341 341
                 </span>
342 342
               </el-form-item>
343 343
             </el-col>
@@ -353,7 +353,7 @@
353 353
               <el-form-item label="SOC/站品控检查的涉及核心、安全指标扣分依据">
354 354
                 <span class="detail-link"
355 355
                   @click="showDetailModal('SOC/站品控检查的涉及核心、安全指标扣分依据', formatAccordList(nonCadreForm.socStationQcInvolvedCoreSafetyAccordList))">
356
-                  {{ formatAccordList(nonCadreForm.socStationQcInvolvedCoreSafetyAccordList) || '查看详情' }}
356
+                  查看详情
357 357
                 </span>
358 358
               </el-form-item>
359 359
             </el-col>
@@ -390,7 +390,7 @@
390 390
               <el-form-item label="奖励明细">
391 391
                 <span class="detail-link"
392 392
                   @click="showDetailModal('奖励明细', formatAccordList(nonCadreForm.rewardAccordList))">
393
-                  {{ formatAccordList(nonCadreForm.rewardAccordList) || '查看详情' }}
393
+                  查看详情
394 394
                 </span>
395 395
               </el-form-item>
396 396
             </el-col>
@@ -398,7 +398,7 @@
398 398
               <el-form-item label="惩罚明细">
399 399
                 <span class="detail-link"
400 400
                   @click="showDetailModal('惩罚明细', formatAccordList(nonCadreForm.punishmentAccordList))">
401
-                  {{ formatAccordList(nonCadreForm.punishmentAccordList) || '查看详情' }}
401
+                  查看详情
402 402
                 </span>
403 403
               </el-form-item>
404 404
             </el-col>
@@ -483,16 +483,14 @@
483 483
       <el-form label-width="150px" class="indicator-form" :model="indicatorDialog.form" :rules="indicatorDialog.rules">
484 484
         <el-form-item label="指标名称" prop="indicatorId" required>
485 485
 
486
-          <el-select v-model="indicatorDialog.form.indicatorId" placeholder="搜索指标名称" filterable remote reserve-keyword
487
-            :remote-method="searchIndicators" :loading="indicatorDialog.loading" style="flex: 1;"
488
-            @change="onIndicatorNameChange">
489
-            <el-option v-for="item in indicatorDialog.indicatorOptions" :key="item.id" :label="item.name"
490
-              :value="item.id" />
491
-          </el-select>
486
+          <el-cascader v-model="indicatorDialog.form.indicatorId" placeholder="搜索指标名称" filterable clearable
487
+            :options="indicatorDialog.cascaderOptions" :props="indicatorDialog.cascaderProps" style="flex: 1;"
488
+            @change="onIndicatorCascaderChange">
489
+          </el-cascader>
492 490
 
493 491
         </el-form-item>
494 492
 
495
-        <el-form-item label="分值/单位" v-if="indicatorDialog.form.categoryNameOne!='红线指标'">
493
+        <el-form-item label="分值/单位" v-if="indicatorDialog.form.categoryNameOne != '红线指标'">
496 494
           <div style="display: flex; align-items: center; gap: 10px;">
497 495
             <span style="font-size: 24px; font-weight: bold;">{{ indicatorDialog.form.score }}/次</span>
498 496
 
@@ -568,13 +566,14 @@
568 566
 </template>
569 567
 
570 568
 <script setup>
571
-import { ref, reactive, onMounted, getCurrentInstance, watch } from 'vue'
569
+import { ref, reactive, onMounted, getCurrentInstance, watch, nextTick } from 'vue'
572 570
 import { ElMessage, ElMessageBox } from 'element-plus'
573 571
 
574 572
 // API导入(需要根据实际API路径调整)
575 573
 import { listCadreAssessment, generateCadreAssessment, listNonCadreAssessment, addNonCadreAssessment, updateNonCadreAssessment, deleteNonCadreAssessment, exportNonCadreAssessment, generateNonCadreAssessment, getNonCadreAssessment } from '@/api/performance/monthlyAssess.js'
576 574
 import { selectUserLeaderListByCondition, listUserPerformance } from '@/api/system/user.js'
577 575
 import { listIndicator } from '@/api/system/classificationAssess.js'
576
+import { queryAssessCategoryTreeAndIndicator } from '@/api/system/classificationAssessIndicator.js'
578 577
 
579 578
 const { proxy } = getCurrentInstance()
580 579
 const { post, work_area, employment_type, assessment_team, base_performance_indicator_qc_dept_type } = proxy.useDict('post', 'work_area', 'employment_type', 'assessment_team', 'base_performance_indicator_qc_dept_type')
@@ -711,6 +710,14 @@ const indicatorDialog = reactive({
711 710
   itemIndex: null,
712 711
   loading: false,
713 712
   indicatorOptions: [],
713
+  cascaderOptions: [],
714
+  cascaderProps: {
715
+    value: 'id',
716
+    label: 'name',
717
+    children: 'indicatorList',
718
+    checkStrictly: false,
719
+    emitPath: false
720
+  },
714 721
   rules: {
715 722
     indicatorId: [
716 723
       { required: true, message: '请选择指标名称', trigger: 'change' }
@@ -751,6 +758,82 @@ async function searchIndicators(query) {
751 758
   }
752 759
 }
753 760
 
761
+async function loadIndicatorCascaderOptions() {
762
+  try {
763
+    const res = await queryAssessCategoryTreeAndIndicator({})
764
+    const treeData = res.data || []
765
+    indicatorDialog.cascaderOptions = transformIndicatorTree(treeData)
766
+  } catch (error) {
767
+    console.error('加载指标树失败:', error)
768
+    indicatorDialog.cascaderOptions = []
769
+  }
770
+}
771
+
772
+function transformIndicatorTree(treeData) {
773
+  return treeData.map(node => {
774
+    const transformed = {
775
+      id: node.id,
776
+      name: node.name,
777
+      indicatorList: []
778
+    }
779
+
780
+    if (node.children && node.children.length > 0) {
781
+      transformed.indicatorList = node.children.map(child => {
782
+        const transformedChild = {
783
+          id: child.id,
784
+          name: child.name,
785
+          indicatorList: []
786
+        }
787
+
788
+        if (child.indicatorList && child.indicatorList.length > 0) {
789
+          transformedChild.indicatorList = child.indicatorList.map(indicator => ({
790
+            ...indicator, name: indicator.name, id: indicator.code, indicatorId: indicator.id, indicatorName: indicator.name, occurCount: 1, personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList: []
791
+          }))
792
+        }
793
+
794
+        return transformedChild
795
+      })
796
+    }
797
+
798
+    if (node.indicatorList && node.indicatorList.length > 0) {
799
+      transformed.indicatorList = node.indicatorList.map(indicator => ({
800
+        ...indicator, name: indicator.name, id: indicator.code, indicatorId: indicator.id, indicatorName: indicator.name, occurCount: 1, personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList: []
801
+      }))
802
+    }
803
+
804
+    return transformed
805
+  })
806
+}
807
+
808
+function onIndicatorCascaderChange(value) {
809
+  const selected = findIndicatorById(indicatorDialog.cascaderOptions, value)
810
+  if (selected) {
811
+    indicatorDialog.form = {
812
+      ...selected,
813
+      name: selected.name,
814
+      id: selected.code,
815
+      indicatorId: selected.id,
816
+      indicatorName: selected.name,
817
+      occurCount: 1,
818
+      personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList: []
819
+    }
820
+    updateTotal()
821
+  }
822
+}
823
+
824
+function findIndicatorById(tree, targetId) {
825
+  for (const node of tree) {
826
+    if (node.id === targetId) {
827
+      return node
828
+    }
829
+    if (node.indicatorList && node.indicatorList.length > 0) {
830
+      const found = findIndicatorById(node.indicatorList, targetId)
831
+      if (found) return found
832
+    }
833
+  }
834
+  return null
835
+}
836
+
754 837
 
755 838
 
756 839
 // 弹窗配置
@@ -849,6 +932,7 @@ async function handleUserChange(userId) {
849 932
     nonCadreForm.deputySupervisorName = ''
850 933
     nonCadreForm.deputyManagerId = ''
851 934
     nonCadreForm.deputyManagerName = ''
935
+    checkAndLoadExistingAssessment()
852 936
     return
853 937
   }
854 938
 
@@ -876,6 +960,72 @@ async function handleUserChange(userId) {
876 960
     nonCadreForm.deputyManagerId = ''
877 961
     nonCadreForm.deputyManagerName = ''
878 962
   }
963
+
964
+  checkAndLoadExistingAssessment()
965
+}
966
+
967
+async function handleAssessmentMonthChange() {
968
+  checkAndLoadExistingAssessment()
969
+}
970
+
971
+async function checkAndLoadExistingAssessment() {
972
+  if (!nonCadreForm.userId || !nonCadreForm.assessmentMonth) {
973
+    return
974
+  }
975
+
976
+  try {
977
+    const assessmentMonth = nonCadreForm.assessmentMonth.replace('-', '')
978
+    const res = await listNonCadreAssessment({
979
+      userId: nonCadreForm.userId,
980
+      assessmentMonth: assessmentMonth
981
+    })
982
+
983
+    const rows = res.rows || []
984
+    if (rows.length > 0) {
985
+      const assessmentId = rows[0].id
986
+      await loadAssessmentDetail(assessmentId)
987
+    }
988
+  } catch (error) {
989
+    console.error('检查已有考核数据失败:', error)
990
+  }
991
+}
992
+
993
+async function loadAssessmentDetail(id) {
994
+  try {
995
+    const res = await getNonCadreAssessment(id)
996
+    const detailList = res.data.personnelMonthlyAssessmentIndicatorDetailList || []
997
+    const indicatorGroupsMap = {}
998
+    detailList.forEach(item => {
999
+      const categoryKey = item.categoryCodeOne || item.categoryNameOne || '未分类'
1000
+      if (!indicatorGroupsMap[categoryKey]) {
1001
+        indicatorGroupsMap[categoryKey] = {
1002
+          title: item.categoryNameOne || '未分类',
1003
+          items: []
1004
+        }
1005
+      }
1006
+      indicatorGroupsMap[categoryKey].items.push({
1007
+        ...item
1008
+      })
1009
+    })
1010
+    const indicatorGroups = Object.values(indicatorGroupsMap)
1011
+
1012
+    Object.keys(res.data).forEach(key => {
1013
+      if (key === 'assessmentMonth' && res.data[key]) {
1014
+        const val = res.data[key]
1015
+        if (typeof val === 'string' && val.length === 6 && /^\d+$/.test(val)) {
1016
+          nonCadreForm[key] = val.substring(0, 4) + '-' + val.substring(4, 6)
1017
+        } else {
1018
+          nonCadreForm[key] = val
1019
+        }
1020
+      } else if (key !== 'personnelMonthlyAssessmentIndicatorDetailList') {
1021
+        nonCadreForm[key] = res.data[key]
1022
+      }
1023
+    })
1024
+    nonCadreForm.indicatorGroups = indicatorGroups
1025
+  } catch (error) {
1026
+    console.error('获取考核详情失败:', error)
1027
+    ElMessage.error('获取考核详情失败')
1028
+  }
879 1029
 }
880 1030
 
881 1031
 // 获取数据列表
@@ -1030,7 +1180,7 @@ const submitForm = async () => {
1030 1180
       if (submitData.assessmentMonth) {
1031 1181
         submitData.assessmentMonth = submitData.assessmentMonth.replace('-', '')
1032 1182
       }
1033
-      
1183
+
1034 1184
       if (submitData.indicatorGroups && submitData.indicatorGroups.length > 0) {
1035 1185
         submitData.personnelMonthlyAssessmentIndicatorDetailList = submitData.indicatorGroups.flatMap(group =>
1036 1186
           group.items.map(item => ({
@@ -1148,16 +1298,21 @@ const addIndicator = () => {
1148 1298
     amount: 0,
1149 1299
     personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList: []
1150 1300
   }
1301
+  loadIndicatorCascaderOptions()
1151 1302
 }
1152 1303
 
1153 1304
 // 编辑指标 - 打开编辑模态框
1154
-const editIndicator = (groupIndex, itemIndex) => {
1305
+const editIndicator = async (groupIndex, itemIndex) => {
1155 1306
   const item = nonCadreForm.indicatorGroups[groupIndex].items[itemIndex]
1156 1307
   indicatorDialog.visible = true
1157 1308
   indicatorDialog.title = '编辑扣分指标'
1158 1309
   indicatorDialog.mode = 'edit'
1159 1310
   indicatorDialog.groupIndex = groupIndex
1160 1311
   indicatorDialog.itemIndex = itemIndex
1312
+
1313
+  await loadIndicatorCascaderOptions()
1314
+  await nextTick()
1315
+
1161 1316
   indicatorDialog.form = { ...item }
1162 1317
   if (!indicatorDialog.form.personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList) {
1163 1318
     indicatorDialog.form.personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList = []