浏览代码

更新新绩效系统

manywhy 3 周之前
父节点
当前提交
a071429a2e
共有 100 个文件被更改,包括 20483 次插入3098 次删除
  1. 7 1
      package.json
  2. 二进制
      src/assets/excel.jpg
  3. 二进制
      src/assets/indicators_template.xlsx
  4. 二进制
      src/assets/step/add-indicator-tips.png
  5. 二进制
      src/assets/step/choose-employee-tips.png
  6. 二进制
      src/assets/step/choose-indicator-tips.png
  7. 二进制
      src/assets/step/choose-process-tips.png
  8. 二进制
      src/assets/step/edit-data-tips.png
  9. 二进制
      src/assets/step/publish-btn-tips.png
  10. 二进制
      src/assets/step/step.png
  11. 二进制
      src/assets/step/step1.png
  12. 二进制
      src/assets/step/templateDetail/calcMethods.png
  13. 二进制
      src/assets/step/templateDetail/cc.png
  14. 二进制
      src/assets/step/templateDetail/resultInput.png
  15. 二进制
      src/assets/step/templateDetail/review.png
  16. 二进制
      src/assets/step/templateDetail/score.png
  17. 二进制
      src/assets/step/templateDetail/scoreEachOther.png
  18. 二进制
      src/assets/step/templateDetail/selfScore.png
  19. 二进制
      src/assets/step/templateDetail/setting-flow-tips.png
  20. 二进制
      src/assets/step/templateDetail/targetConfirm.png
  21. 二进制
      src/assets/step/workbench/more-target-confirm.png
  22. 二进制
      src/assets/step/workbench/result-input.png
  23. 二进制
      src/assets/step/workbench/review-details.png
  24. 二进制
      src/assets/step/workbench/review.png
  25. 二进制
      src/assets/step/workbench/score.png
  26. 二进制
      src/assets/step/workbench/self-score.png
  27. 二进制
      src/assets/step/workbench/target-confirm.png
  28. 11 7
      src/components/UserImage.vue
  29. 4 4
      src/home.vue
  30. 1 1
      src/index.vue
  31. 20 1
      src/main.js
  32. 591 0
      src/newPerformance/components/AnnualStatement.vue
  33. 205 182
      src/newPerformance/components/ExamineContrast.vue
  34. 248 44
      src/newPerformance/components/ExamineRecord.vue
  35. 5 7
      src/newPerformance/components/ExamineRecord/LeftExamineRecord.vue
  36. 142 0
      src/newPerformance/components/ExamineRecord/PieChart1.vue
  37. 63 59
      src/newPerformance/components/ExamineRecord/RightEamineComp/EditScoreList.vue
  38. 685 0
      src/newPerformance/components/ExamineRecord/RightExamineRecord copy.vue
  39. 304 175
      src/newPerformance/components/ExamineRecord/RightExamineRecord.vue
  40. 640 0
      src/newPerformance/components/IndicatorSetting.vue
  41. 347 0
      src/newPerformance/components/IndicatorSetting/PerCcSelector.vue
  42. 362 0
      src/newPerformance/components/IndicatorSetting/PerCcSelectorOnly.vue
  43. 316 0
      src/newPerformance/components/IndicatorSetting/PerEmployeeSelector.vue
  44. 316 0
      src/newPerformance/components/IndicatorSetting/PerResultInputSelector.vue
  45. 327 0
      src/newPerformance/components/IndicatorSetting/PerResultInputSelectorOnly.vue
  46. 364 0
      src/newPerformance/components/IndicatorSetting/PerReviewsSelector.vue
  47. 380 0
      src/newPerformance/components/IndicatorSetting/PerReviewsSelectorOnly.vue
  48. 93 0
      src/newPerformance/components/IndicatorSetting/PerScoreEachOtherOnly.vue
  49. 95 0
      src/newPerformance/components/IndicatorSetting/PerScoreSelfOnly.vue
  50. 366 0
      src/newPerformance/components/IndicatorSetting/PerScoresSelector.vue
  51. 382 0
      src/newPerformance/components/IndicatorSetting/PerScoresSelectorOnly.vue
  52. 364 0
      src/newPerformance/components/IndicatorSetting/PerTargetConfirmSelector.vue
  53. 389 0
      src/newPerformance/components/IndicatorSetting/PerTargetConfirmSelectorOnly.vue
  54. 627 0
      src/newPerformance/components/IndicatorSetting/PublishComp.vue
  55. 928 0
      src/newPerformance/components/IndicatorSetting/TemplateDetails.vue
  56. 736 0
      src/newPerformance/components/IndicatorSetting/TemplateMixedPublish.vue
  57. 1072 0
      src/newPerformance/components/IndicatorSetting/UploadPublish.vue
  58. 1015 0
      src/newPerformance/components/IndicatorSetting/UploadPublish2 copy.vue
  59. 1064 0
      src/newPerformance/components/IndicatorSetting/UploadPublish2.vue
  60. 212 0
      src/newPerformance/components/IndicatorStore.vue
  61. 263 0
      src/newPerformance/components/LevelSetting copy.vue
  62. 226 0
      src/newPerformance/components/LevelSetting.vue
  63. 514 230
      src/newPerformance/components/MyPerformance.vue
  64. 23 16
      src/newPerformance/components/MyPerformance/SelectExamine.vue
  65. 194 0
      src/newPerformance/components/NormalDistribution.vue
  66. 214 117
      src/newPerformance/components/OrganizationExamine.vue
  67. 4 3
      src/newPerformance/components/PerInterview.vue
  68. 163 289
      src/newPerformance/components/PerReviewDetail.vue
  69. 84 0
      src/newPerformance/components/PerformSetting.vue
  70. 225 0
      src/newPerformance/components/PerformSetting/LevelSetting.vue
  71. 272 0
      src/newPerformance/components/PerformanceCoaching.vue
  72. 2 3
      src/newPerformance/components/PerformanceInterview.vue
  73. 304 210
      src/newPerformance/components/PersonalExamine.vue
  74. 126 95
      src/newPerformance/components/PersonalExamine/ExamineDetails copy.vue
  75. 267 162
      src/newPerformance/components/PersonalExamine/ExamineDetails.vue
  76. 855 0
      src/newPerformance/components/ProcessTracking.vue
  77. 210 0
      src/newPerformance/components/PublicComp/OrganizationSelect.vue
  78. 124 54
      src/newPerformance/components/PublicComp/ShowHanderDialog.vue
  79. 623 0
      src/newPerformance/components/PublicComp/ShowHanderDialog2 copy.vue
  80. 149 55
      src/newPerformance/components/PublicComp/ShowHanderDialog2.vue
  81. 1136 0
      src/newPerformance/components/ResultAnalysis.vue
  82. 25 1
      src/newPerformance/components/RoleSetting.vue
  83. 84 25
      src/newPerformance/components/TemplateDetails/BatchHandleNode.vue
  84. 21 15
      src/newPerformance/components/TemplateDetails/CC.vue
  85. 54 24
      src/newPerformance/components/TemplateDetails/CateDetails.vue
  86. 91 0
      src/newPerformance/components/TemplateDetails/EditDescription.vue
  87. 12 3
      src/newPerformance/components/TemplateDetails/FormulaComp.vue
  88. 3 2
      src/newPerformance/components/TemplateDetails/HandleNode.vue
  89. 58 63
      src/newPerformance/components/TemplateDetails/InterviewFlow.vue
  90. 9 9
      src/newPerformance/components/TemplateDetails/PublishComp.vue
  91. 50 14
      src/newPerformance/components/TemplateDetails/ResultInput.vue
  92. 51 14
      src/newPerformance/components/TemplateDetails/Reviews.vue
  93. 12 20
      src/newPerformance/components/TemplateDetails/ScoreEachOther.vue
  94. 52 12
      src/newPerformance/components/TemplateDetails/Scores.vue
  95. 52 12
      src/newPerformance/components/TemplateDetails/TargetConfirms.vue
  96. 19 9
      src/newPerformance/components/TemplateMixedPublish.vue
  97. 213 38
      src/newPerformance/components/TemplateSelector.vue
  98. 237 628
      src/newPerformance/components/UploadPublish.vue
  99. 394 379
      src/newPerformance/components/Workbench.vue
  100. 387 115
      src/newPerformance/components/Workbench/EditNode.vue

+ 7 - 1
package.json

@@ -47,9 +47,11 @@
     "connect": "^3.7.0",
     "date-fns": "^4.1.0",
     "dhtmlx-gantt": "^8.0.6",
+    "driver.js": "^0.9.8",
     "echarts": "^4.9.0",
     "element-ui": "^2.14.1",
     "i": "^0.3.7",
+    "intro.js": "^7.2.0",
     "js-cookie": "^3.0.5",
     "jsencrypt": "^3.2.2",
     "moment": "^2.29.4",
@@ -66,9 +68,12 @@
     "swiper": "^5.4.5",
     "tiptap": "^1.32.2",
     "uglify-js": "^3.19.3",
+    "v-viewer": "^1.7.4",
+    "viewerjs": "^1.11.7",
     "vue": "^2.7.15",
     "vue-baidu-map": "^0.21.20",
     "vue-calendar-component": "^2.8.2",
+    "vue-clickaway": "^2.2.2",
     "vue-count-to": "^1.0.13",
     "vue-json-excel": "^0.3.0",
     "vue-photo-preview": "^1.0.9",
@@ -79,6 +84,7 @@
     "vue-tree-chart": "^1.2.9",
     "vue-tree-color": "^2.3.3",
     "vue2-org-tree": "^1.3.6",
+    "vuedraggable": "^2.24.3",
     "vuex": "^3.6.2",
     "ws": "^8.18.1",
     "xlsx": "^0.17.5",
@@ -107,7 +113,7 @@
     "eslint-friendly-formatter": "4.0.1",
     "eslint-loader": "2.0.0",
     "eslint-plugin-html": "4.0.5",
-    "file-loader": "1.1.11",
+    "file-loader": "^1.1.11",
     "friendly-errors-webpack-plugin": "1.7.0",
     "hash-sum": "^1.0.2",
     "html-webpack-plugin": "4.0.0-alpha",

二进制
src/assets/excel.jpg


二进制
src/assets/indicators_template.xlsx


二进制
src/assets/step/add-indicator-tips.png


二进制
src/assets/step/choose-employee-tips.png


二进制
src/assets/step/choose-indicator-tips.png


二进制
src/assets/step/choose-process-tips.png


二进制
src/assets/step/edit-data-tips.png


二进制
src/assets/step/publish-btn-tips.png


二进制
src/assets/step/step.png


二进制
src/assets/step/step1.png


二进制
src/assets/step/templateDetail/calcMethods.png


二进制
src/assets/step/templateDetail/cc.png


二进制
src/assets/step/templateDetail/resultInput.png


二进制
src/assets/step/templateDetail/review.png


二进制
src/assets/step/templateDetail/score.png


二进制
src/assets/step/templateDetail/scoreEachOther.png


二进制
src/assets/step/templateDetail/selfScore.png


二进制
src/assets/step/templateDetail/setting-flow-tips.png


二进制
src/assets/step/templateDetail/targetConfirm.png


二进制
src/assets/step/workbench/more-target-confirm.png


二进制
src/assets/step/workbench/result-input.png


二进制
src/assets/step/workbench/review-details.png


二进制
src/assets/step/workbench/review.png


二进制
src/assets/step/workbench/score.png


二进制
src/assets/step/workbench/self-score.png


二进制
src/assets/step/workbench/target-confirm.png


+ 11 - 7
src/components/UserImage.vue

@@ -75,16 +75,20 @@ export default {
   // 方法
   methods: {
     set_info() {
-      let info = this.$getEmployeeMapItem(this.id) || { name: '', img_url: '', id: 0 };
-      if (this.img_url != '') {
-        info.img_url = this.img_url;
+      if (this.id) {
+        let info = this.id && this.$getEmployeeMapItem(this.id) || { name: '', img_url: '', id: 0 };
+        if (this.img_url != '') {
+          info.img_url = this.img_url;
+        }
+        this.info = info;
       }
-      this.info = info;
+      
     },
     // 加载
     name_no() {
-      if(this.id){
-        let info = this.$getEmployeeMapItem(this.id) || { name: '', img_url: '', id: 0 };
+      if (this.id) {
+        console.log(this.id)
+        let info = this.id && this.$getEmployeeMapItem(Number(this.id)) || { name: '', img_url: '', id: 0 };
         if(info.id){
           let pattern = new RegExp('^[\u4E00-\u9FA5]+');
           this.imgUrl=info.img_url;
@@ -120,7 +124,7 @@ export default {
   },
   // 组件挂载完成
   mounted() {
-    this.name_no();
+    // this.name_no();
   }
 };
 </script>

+ 4 - 4
src/home.vue

@@ -6,17 +6,17 @@
 
           <div style="background-color: #fff;padding:20px;margin-bottom: 10px;" class="br-5 ">
 
-            <div class="flex-box-ce" v-if="user_info">
+            <div class="flex-box-ce" v-if="user_info && user_info.id">
                 <div style="margin-right: 10px;">
-                  <userImage :id="user_info.id" :img_url="user_info.img_url" :user_name="user_info.name" width="50px" height="50px"></userImage>
+                  <userImage v-if="user_info && user_info.id" :id="user_info.id" :img_url="user_info.img_url" :user_name="user_info.name" width="50px" height="50px"></userImage>
                 </div>
                 <div class="flex-1">
                   <div class="flex-box-ce" style="margin-bottom: 6px;font-size: 14px;color: #666;font-weight: 700;">
-                    <div style="font-size: 16px;font-weight: 700;padding-right:15px;" class="black">你好,{{ user_info.name }}</div>
+                    <div style="font-size: 16px;font-weight: 700;padding-right:15px;" class="black">你好,{{ user_info.name }} </div>
                   </div>
                   <div class="fontColorC font-flex-word" style="max-width: 180px;">{{returnDeptName()}}</div>
                 </div>
-                <template v-if="user_info.post_info.length>0">
+                <template v-if="user_info.post_info.length > 0">
                   <div class="flex-box-v flex-center-center" v-for="(item,index) in user_info.post_info" :key="index" style="border-radius: 3px;margin-left: 10px;padding: 10px;background-color: #f0f4fa;">
                     <i style="font-size: 18px;" class="el-icon-s-custom fontColorC"></i>
                     <span class="fontColorB">{{item.name}}</span>

+ 1 - 1
src/index.vue

@@ -21,7 +21,7 @@
           <div style="margin-left: 20px;">
             <el-popover v-if="isAdministrator" placement="bottom"  width="364"  trigger="hover">
               <SystemMessage></SystemMessage>
-              <div slot="reference" class="count_max">{{ site_info.user_count_max }}用户</div>
+              <div slot="reference" class="count_max">{{ site_info.user_count_max }}用户 </div>
             </el-popover>
           </div>
           <div class="flex-1"></div>

+ 20 - 1
src/main.js

@@ -96,7 +96,7 @@ Vue.prototype.$onFilePreView = onFilePreView
 Vue.prototype.$action = 'https://integralsys.oss-cn-shenzhen.aliyuncs.com'
 Vue.prototype.$acceptImg = 'image/jpeg,image/png'
 Vue.prototype.$acceptFile = 'application/pdf,application/vnd.ms-excel,application/msword,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
-Vue.prototype.$acceptImgFile='image/jpeg,image/png,application/pdf,application/vnd.ms-excel,application/msword,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+Vue.prototype.$acceptImgFile ='image/jpeg,image/png,application/pdf,application/vnd.ms-excel,application/msword,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
 Vue.prototype.$xtoken={ 'X-Token': getToken() };
 Vue.directive('dragscroll', function (el) {
   el.onmousedown = function (ev) {
@@ -136,6 +136,13 @@ Object.keys(filters).forEach(key => {
 })
 
 
+Vue.directive('focus', {
+  inserted: function (el) {
+    el.querySelector('input').focus();
+  }
+});
+
+
 import tableMove from "@/utils/tableMove";
 
 Vue.use(tableMove)
@@ -154,6 +161,18 @@ Vue.prototype.$bus = bus
 
 Vue.config.productionTip = false
 
+import Viewer from 'v-viewer';
+
+import 'viewerjs/dist/viewer.css';
+
+Vue.use(Viewer, {
+  defaultOptions: {
+    zIndex: 9999, // 设置图片预览组件的层级,确保能在其他组件之上
+  },
+});
+
+
+
 new Vue({
   el: '#app',
   router,

+ 591 - 0
src/newPerformance/components/AnnualStatement.vue

@@ -0,0 +1,591 @@
+<template>
+    <div class="contrast-container">
+        <el-button class="export-btn" type="primary" @click="exportToExcel('mytable', '考核对比')">导出报表</el-button>
+        <el-tabs v-model="activeName" style="width: 100%; height: 100%; ">
+            <el-tab-pane label="月度" name="4" style="width: 100%; height: 100%; display: flex; flex-direction: column;">
+                <div class="block">
+                    <el-date-picker v-model="params.year" type="year" placeholder="选择年" value-format="yyyy">
+                    </el-date-picker>
+
+                    <div class="dept_wdiv flex-box-ce" style="margin: 0 0 0 10px;">
+                        <div class="dept_inp" @click="show_dept_selector = true">
+                            <span v-if="deptVisibleName != ''">{{ deptVisibleName }}</span>
+                            <span v-else style="color: #b9b9b9;">选择部门</span>
+                        </div>
+                        <i class="el-icon-arrow-down"></i>
+                    </div>
+
+                    <div class="dept_wdiv flex-box-ce" style="margin: 0 10px;">
+                        <div class="dept_inp" @click="showEmployeeSearch = true">
+                            <span v-if="employeeVisibleName != ''">{{ employeeVisibleName }}</span>
+                            <span v-else style="color: #b9b9b9;">选择员工</span>
+                        </div>
+                        <i class="el-icon-arrow-down"></i>
+                    </div>
+
+                    <el-select style="margin-right: 10px;" v-model="filters.level" placeholder="选择等级"
+                        @change="changeLevel">
+                        <el-option v-for="item in gradeLevels" :key="item.name" :label="item.name" :value="item.name">
+                        </el-option>
+                    </el-select>
+
+
+                    <el-button @click="reset">重置</el-button>
+                </div>
+
+                <div class="table-box">
+                    <el-table id="mytable" :data="filteredData" style="width: 100%; height: auto;" border stripe
+                        :header-cell-style="{ background: '#f5f7fa' }" v-loading="loading" size="small">
+                        <el-table-column prop="employeeName" label="姓名" align="center">
+                        </el-table-column>
+                        <el-table-column prop="departments" label="部门" align="center" width="200">
+                            <template slot-scope="scope">
+                                <el-tooltip class="item" effect="dark" placement="top">
+                                    <div slot="content" style="max-width: 300px; ">
+                                        {{ scope.row.departments | formatDeptName }}
+                                    </div>
+                                    <div class="oneLine">
+                                        {{ scope.row.departments | formatDeptName }}
+                                    </div>
+                                </el-tooltip>
+                            </template>
+                        </el-table-column>
+                        <el-table-column v-for="(item, index) in tableHeader" :key="index" :label="item" align="center">
+                            <template slot-scope="scope">
+                                <div>{{ scope.row.scores[index] }}</div>
+                                <div>{{ scope.row.levelNames[index] }}</div>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+                </div>
+
+
+            </el-tab-pane>
+
+
+            <el-tab-pane label="季度" name="3" style="width: 100%; height: 100%; display: flex; flex-direction: column;">
+                <div class="block">
+                    <el-date-picker v-model="params.year" type="year" placeholder="选择年" value-format="yyyy">
+                    </el-date-picker>
+                    <div class="dept_wdiv flex-box-ce" style="margin: 0 0 0 10px;">
+                        <div class="dept_inp" @click="show_dept_selector = true">
+                            <span v-if="deptVisibleName != ''">{{ deptVisibleName }}</span>
+                            <span v-else style="color: #b9b9b9;">选择部门</span>
+                        </div>
+                        <i class="el-icon-arrow-down"></i>
+                    </div>
+
+                    <div class="dept_wdiv flex-box-ce" style="margin: 0 10px;">
+                        <div class="dept_inp" @click="showEmployeeSearch = true">
+                            <span v-if="employeeVisibleName != ''">{{ employeeVisibleName }}</span>
+                            <span v-else style="color: #b9b9b9;">选择员工</span>
+                        </div>
+                        <i class="el-icon-arrow-down"></i>
+                    </div>
+
+                    <el-select style="margin-right: 10px;" v-model="filters.level" placeholder="选择等级"
+                        @change="changeLevel">
+                        <el-option v-for="item in gradeLevels" :key="item.name" :label="item.name" :value="item.name">
+                        </el-option>
+                    </el-select>
+
+                    <el-button @click="reset">重置</el-button>
+
+                </div>
+
+                <el-table id="mytable" :data="filteredData" style="width: 100%; " border stripe
+                    :header-cell-style="{ background: '#f5f7fa' }" v-loading="loading" size="small">
+                    <el-table-column prop="employeeName" label="姓名" align="center">
+                    </el-table-column>
+                    <el-table-column prop="departments" label="部门" align="center" width="200">
+                        <template slot-scope="scope">
+                            <el-tooltip class="item" effect="dark" placement="top">
+                                <div slot="content" style="max-width: 300px; ">
+                                    {{ scope.row.departments | formatDeptName }}
+                                </div>
+                                <div class="oneLine">
+                                    {{ scope.row.departments | formatDeptName }}
+                                </div>
+                            </el-tooltip>
+                        </template>
+                    </el-table-column>
+                    <el-table-column v-for="(item, index) in tableHeader" :key="index" :label="item" align="center">
+                        <template slot-scope="scope">
+                            <div>{{ scope.row.scores[index] }}</div>
+                            <div>{{ scope.row.levelNames[index] }}</div>
+                        </template>
+                    </el-table-column>
+                </el-table>
+
+            </el-tab-pane>
+
+            <el-tab-pane label="半年度" name="2" style="width: 100%; height: 100%; display: flex; flex-direction: column;">
+                <div class="block">
+                    <el-date-picker v-model="params.year" type="year" placeholder="选择年" value-format="yyyy">
+                    </el-date-picker>
+                    <div class="dept_wdiv flex-box-ce" style="margin: 0 0 0 10px;">
+                        <div class="dept_inp" @click="show_dept_selector = true">
+                            <span v-if="deptVisibleName != ''">{{ deptVisibleName }}</span>
+                            <span v-else style="color: #b9b9b9;">选择部门</span>
+                        </div>
+                        <i class="el-icon-arrow-down"></i>
+                    </div>
+
+                    <div class="dept_wdiv flex-box-ce" style="margin: 0 10px;">
+                        <div class="dept_inp" @click="showEmployeeSearch = true">
+                            <span v-if="employeeVisibleName != ''">{{ employeeVisibleName }}</span>
+                            <span v-else style="color: #b9b9b9;">选择员工</span>
+                        </div>
+                        <i class="el-icon-arrow-down"></i>
+                    </div>
+
+                    <el-select style="margin-right: 10px;" v-model="filters.level" placeholder="选择等级"
+                        @change="changeLevel">
+                        <el-option v-for="item in gradeLevels" :key="item.name" :label="item.name" :value="item.name">
+                        </el-option>
+                    </el-select>
+
+                    <el-button @click="reset">重置</el-button>
+
+                </div>
+
+                <el-table id="mytable" :data="filteredData" style="width: 100%; " border stripe
+                    :header-cell-style="{ background: '#f5f7fa' }" v-loading="loading" size="small">
+                    <el-table-column prop="employeeName" label="姓名" align="center">
+                    </el-table-column>
+                    <el-table-column prop="departments" label="部门" align="center">
+                        <template slot-scope="scope">
+                            <el-tooltip class="item" effect="dark" placement="top">
+                                <div slot="content" style="max-width: 300px; ">
+                                    {{ scope.row.departments | formatDeptName }}
+                                </div>
+                                <div class="oneLine">
+                                    {{ scope.row.departments | formatDeptName }}
+                                </div>
+                            </el-tooltip>
+                        </template>
+                    </el-table-column>
+                    <el-table-column v-for="(item, index) in tableHeader" :key="index" :label="item" align="center">
+                        <template slot-scope="scope">
+                            <div>{{ scope.row.scores[index] }}</div>
+                            <div>{{ scope.row.levelNames[index] }}</div>
+                        </template>
+                    </el-table-column>
+                </el-table>
+
+            </el-tab-pane>
+
+            <el-tab-pane label="年度" name="1" style="width: 100%; height: 100%; display: flex; flex-direction: column;">
+                <div class="block">
+
+                    <div class="dept_wdiv flex-box-ce" style="margin: 0 0 0 10px;">
+                        <div class="dept_inp" @click="show_dept_selector = true">
+                            <span v-if="deptVisibleName != ''">{{ deptVisibleName }}</span>
+                            <span v-else style="color: #b9b9b9;">选择部门</span>
+                        </div>
+                        <i class="el-icon-arrow-down"></i>
+                    </div>
+
+                    <div class="dept_wdiv flex-box-ce" style="margin: 0 10px;">
+                        <div class="dept_inp" @click="showEmployeeSearch = true">
+                            <span v-if="employeeVisibleName != ''">{{ employeeVisibleName }}</span>
+                            <span v-else style="color: #b9b9b9;">选择员工</span>
+                        </div>
+                        <i class="el-icon-arrow-down"></i>
+                    </div>
+
+                    <el-select style="margin-right: 10px;" v-model="filters.level" placeholder="选择等级"
+                        @change="changeLevel">
+                        <el-option v-for="item in gradeLevels" :key="item.name" :label="item.name" :value="item.name">
+                        </el-option>
+                    </el-select>
+
+                    <el-button @click="reset">重置</el-button>
+
+                </div>
+
+                <el-table id="mytable" :data="filteredData" style="width: 100%; " border stripe
+                    :header-cell-style="{ background: '#f5f7fa' }" v-loading="loading" size="small">
+                    <el-table-column prop="employeeName" label="姓名" align="center">
+                    </el-table-column>
+                    <el-table-column prop="departments" label="部门" align="center" width="300">
+                        <template slot-scope="scope">
+                            <el-tooltip class="item" effect="dark" placement="top">
+                                <div slot="content" style="max-width: 300px; ">
+                                    {{ scope.row.departments | formatDeptName }}
+                                </div>
+                                <div class="oneLine">
+                                    {{ scope.row.departments | formatDeptName }}
+                                </div>
+                            </el-tooltip>
+                        </template>
+                    </el-table-column>
+                    <el-table-column v-for="(item, index) in tableHeader" :key="index" :label="item" align="center">
+                        <template slot-scope="scope">
+                            <div>{{ scope.row.scores[index] }}</div>
+                            <div>{{ scope.row.levelNames[index] }}</div>
+                        </template>
+                    </el-table-column>
+                </el-table>
+
+            </el-tab-pane>
+
+
+        </el-tabs>
+
+        <div style="height: 50px;"></div>
+
+        <!-- 部门选择 -->
+        <EmployeeSelector :title="'选择部门'" :isChecKedAll="false" :can_select_employee="false" :can_select_dept="true"
+            :dept_children="false" :selected="dept_selected" :visible.sync="show_dept_selector"
+            @confirm="dept_confirm" />
+
+        <EmployeeSelector :multi="true" :is_filtration_creator="false" :selected="employeeSelectedObj"
+            :isChecKedAll="false" :visible.sync="showEmployeeSearch" @confirm="onEmployeeConfirm" />
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+import cloneDeep from 'lodash.clonedeep';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 部门选择
+import FileSaver from "file-saver";
+import XLSX from "xlsx";
+
+export default {
+    components: {
+        EmployeeSelector
+    },
+    data() {
+        return {
+            employees: this.$getEmployeeMap(), // 员工列表
+            activeName: "4", // activeName 和 周期类型对应 周期种类 1-年度 2-半年度 3-季度 4-月度
+            loading: false,
+            treeData: {},
+            deptList: [], // 部门列表 - 树形结构
+            dept_list: [], // 部门列表
+            tableHeader: [],
+            tableData: [],
+            filteredData: [],
+            userList: [],
+            // 筛选条件
+            filters: {
+                dept_ids: [], // 选择的部门列表
+                employee_ids: [], // 选择的员工列表
+                level: ''
+            },
+            params: {
+                cycleType: "",
+                year: "2025",
+            },
+            gradeLevels: [],
+            show_dept_selector: false,
+            deptVisibleName: '',
+            dept_selected: { dept: [], employee: [] },
+            showEmployeeSearch: false,
+            employeeVisibleName: '',
+            
+            employeeSelectedObj: {
+                employee: [],
+                dept: []
+            },
+        }
+    },
+
+
+    watch: {
+        activeName(val) {
+            this.params.cycleType = val
+            this.params.year = '2025'
+            this.deptVisibleName = '';
+            this.dept_selected = { dept: [], employee: [] };
+            this.employeeSelectedObj = { dept: [], employee: [] };
+            this.employeeVisibleName = '';
+            this.filters = {
+                dept_ids: [], // 选择的部门列表
+                employee_ids: [], // 选择的员工列表
+            }
+            this.tableData = [];
+            this.filteredData = [];
+            this.getRecords();
+        },
+        'params.year'(val) {
+            this.getRecords();
+        }
+    },
+
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        },
+        formatDeptName(val) {
+            let str = '';
+            if (val && val.length > 0) {
+                val.forEach(dept => {
+                    str += dept.name +","
+                })
+                str = str.substr(0, str.length - 1)
+            } else {
+                str = "--"
+            }
+            return str
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+    },
+    created() {
+        this.getAllSet();
+        this.getRecords();
+    },
+    methods: {
+
+        exportToExcel(tableId, fileName) {
+            // exportToExcel(tableId, fileName)
+            this.downloadLoading = true
+            const xlsxParam = { raw: true };
+            const wb = XLSX.utils.table_to_book(document.getElementById(tableId), xlsxParam);
+
+            // 遍历工作表中的所有单元格,处理换行
+            const ws = wb.Sheets[wb.SheetNames[0]]; // 工作簿1
+            for (const cell in ws) {
+                if (ws[cell].v && typeof ws[cell].v === 'string') {
+                    ws[cell].v = ws[cell].v.replace(/\n/g, '\n');
+                    ws[cell].s = {
+                        alignment: {
+                            wrapText: true // 启用自动换行
+                        }
+                    };
+                }
+            }
+
+            const wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: true, type: 'array' });
+            try {
+                FileSaver.saveAs(new Blob([wbout], { type: 'application/octet-stream' }), fileName + '.xlsx');
+            } catch (e) {
+                console.error(e, wbout);
+            }
+            this.downloadLoading = false;
+        },
+
+        // 获取全局等级设置
+        async getAllSet() {
+            let res = await this.$axiosUser('get', 'api/pro/per/user/base_config')
+            let data = res.data.data;
+            let levels = data.level_scope.levels;
+            let gradeLevels = [];
+            let max = 0;//最大值
+            if (levels && levels.length > 0) {
+                levels.forEach((item, index) => {
+                    var obj;
+                    if (index == 0) {
+                        obj = { name: item.name, max: Number(item.value), min: 0 };
+                    } else {
+                        obj = { name: item.name, max: Number(item.value), min: max };//当不是第一个等级时,最小值为上一个的最大值
+                    }
+                    max = item.value;
+                    gradeLevels.push(obj);
+                })
+                this.gradeLevels = gradeLevels;
+            }
+        },
+
+        reset() {
+            this.params.cycleType = this.activeName
+            this.deptVisibleName = '';
+            this.dept_selected = { dept: [], employee: [] };
+            this.employeeSelectedObj = { dept: [], employee: [] };
+            this.employeeVisibleName = '';
+            this.filters = {
+                dept_ids: [], // 选择的部门列表
+                employee_ids: [], // 选择的员工列表
+                level: ''
+            }
+            this.applyFilters();
+        },
+
+        // 按参数获取对比数据
+        getRecords() {
+            this.loading = true;
+            let params = {
+                year: this.params.year
+            }
+            let url = `/performance/statistics/cycle/employees/${this.user_info.site_id}/${this.activeName}`
+            this.$axiosUser("get", url, params).then(res => {
+                this.loading = false;
+                let { data: { data: { titles , users}, code } } = res;
+                this.tableHeader = titles;
+                this.tableData = users;
+                this.filteredData = users;
+            })
+        },
+
+        // 应用筛选条件
+        applyFilters() {
+            this.filteredData = this.tableData.filter((item) => {
+                const levelMatch = this.filters.level.length === 0 || item.levelNames.some(level => this.filters.level === level);
+                const employeeMatch = this.filters.employee_ids.length === 0 || this.filters.employee_ids.some(employeeId => employeeId == item.employeeId);
+                const departmentMatch = this.filters.dept_ids.length === 0 || item.departments.some(dept => this.filters.dept_ids.includes(dept.id));
+                return levelMatch && employeeMatch && departmentMatch;
+            });
+        },
+        
+        //部门选择
+        dept_confirm(data) {
+            this.dept_selected = { dept: [], employee: [] };
+            this.deptVisibleName = '';
+            let deptList_id = [];
+            if (data.dept !== null && data.dept.length != 0) {
+                this.dept_selected = data;
+                data.dept.forEach((element, index) => {
+                    deptList_id.push(element.dept_id);
+                    this.deptVisibleName += element.dept_name;
+                    if (data.dept.length - index > 1) {
+                        this.deptVisibleName += ',';
+                    }
+                });
+            }
+            this.filters.dept_ids = deptList_id
+            this.applyFilters()
+        },
+
+        // 员工选择
+        onEmployeeConfirm(data) {
+            this.employeeSelectedObj = { dept: [], employee: [] };
+            this.employeeVisibleName = '';
+            let employee_ids = [];
+            if (data.employee !== null && data.employee.length != 0) {
+                this.employeeSelectedObj = data;
+                data.employee.forEach((element, index) => {
+                    employee_ids.push(element.id);
+                    this.employeeVisibleName += element.name;
+                    if (data.employee.length - index > 1) {
+                        this.employeeVisibleName += ',';
+                    }
+                });
+            }
+            this.filters.employee_ids = employee_ids
+            this.applyFilters()
+        },
+
+        // 等级选择
+        changeLevel(v) {
+            this.filters.level = v;
+            this.applyFilters()
+        }
+
+    },
+
+}
+
+</script>
+
+
+
+<style scoped="scoped" lang="scss">
+
+.el-tabs__content, .el-tab-pane {
+    width: 100% !important;
+    height: 100% !important;
+}
+
+.export-btn {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    z-index: 99;
+    &:hover {
+        cursor: hand !important;
+    }
+}
+
+.contrast-container {
+    width: 100%;
+    height: 100%;
+    padding: 10px;
+    background-color: #fff;
+    position: relative;
+    overflow: hidden;
+    box-sizing: border-box;
+    
+    
+
+    .el-tabs {
+        width: 100%;
+        height: 100%;
+        display: flex;
+        flex-direction: column;
+
+        
+        
+    }
+        .block {
+            margin: 10px 0;
+            display: flex;
+            .dept_wdiv {
+                width: 240px;
+                position: relative;
+                .dept_inp {
+                    width: 240px;
+                    z-index: 9;
+                    height: 36px;
+                    line-height: 36px;
+                    border: 1px solid #e0e0e0;
+                    border-radius: 3px;
+                    font-size: 12px;
+                    padding: 0 10px;
+                    overflow: hidden;
+                    white-space: nowrap;
+                    text-overflow: ellipsis;
+                    cursor: pointer;
+                    color: #676767;
+                }
+        
+                i {
+                    position: absolute;
+                    top: 0;
+                    right: 0;
+                    top: 12px;
+                    right: 10px;
+                    font-size: 14px;
+                    color: #b5b5b5;
+                }
+            }
+        }
+
+
+        .table-box {
+            width: 100%;
+            flex: 1;
+            overflow-y: auto;
+
+            /* 设置滚动条的宽度和背景色 */
+            &::-webkit-scrollbar {
+                width: 6px;
+                height: 6px;
+                background-color: #f9f9f9;
+            }
+
+            /* 设置滚动条滑块的样式 */
+            &::-webkit-scrollbar-thumb {
+                border-radius: 6px;
+                background-color: #c1c1c1;
+            }
+
+            /* 设置滚动条滑块hover样式 */
+            &::-webkit-scrollbar-thumb:hover {
+                background-color: #a8a8a8;
+            }
+
+            /* 设置滚动条轨道的样式 */
+            &::-webkit-scrollbar-track {
+                box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+                border-radius: 6px;
+                background: #ededed;
+            }
+        }
+
+}
+</style>

+ 205 - 182
src/newPerformance/components/ExamineContrast.vue

@@ -1,9 +1,9 @@
 <template>
     <div class="contrast-container scroll-bar" style="padding-top: 10px;">
 
-        <div class="flex-box-ce title-box" style="" v-if="selectExamineList && selectExamineList.length > 0">
+        <!-- <div class="flex-box-ce title-box" style="" v-if="selectExamineList && selectExamineList.length > 0">
             <div class="title">已选择的考核列表:</div>
-            <el-button size="mini" @click="dialogVisible = true">查看更多</el-button>
+            <el-button size="mini" @click="openChooseExamineItem()">查看更多</el-button>
         </div>
 
 
@@ -15,22 +15,16 @@
                     {{ item.title }}
                 </el-tag>
             </template>
-        </div>
-
-        <div v-else class="flex-box-ce" style="padding: 0 20px;">
-            <el-button size="mini" @click="dialogVisible = true" style="margin-left: auto;">查看更多</el-button>
-        </div>
+        </div> -->
 
-        <el-tabs v-model="activeName" @tab-click="handleClick"
-            style="width: 100%; padding: 0 20px; box-sizing: border-box;">
-            <el-tab-pane label="表格" name="0">
-                <el-table v-if="userList && userList.length > 0" ref="tableRef" custom-class="openAnimAbcd" id="mytable"
+        <div class="flex-box-ce" style="padding: 0 20px;">
+            <el-button size="mini" @click="openChooseExamineItem()" style="margin-left: auto;">查看更多</el-button>
                     :data="userList" style="width: 100%; " border stripe :header-cell-style="{ background: '#f5f7fa' }"
-                    v-loading="loading">
+                    v-loading="loading" :height="500">
                     <el-table-column prop="employeeName" label="姓名" align="center">
                     </el-table-column>
                     <el-table-column :label="item.title" v-for="(item, index) in selectExamineList"
-                        :key="item.reviewPackageId" align="center">
+                        :key="item.reviewPackageId" align="center" :render-header="(h, obj) => renderHeader(h, obj)">
                         <template slot-scope="scope">
                             <div v-for="user in item.users">
                                 <span v-if="user.employeeId == scope.row.employeeId">{{ user.score }}</span>
@@ -41,13 +35,13 @@
             </el-tab-pane>
             <el-tab-pane label="地图" name="1" style="width: 100%;">
                 <div v-if="treeData && JSON.stringify(treeData) !== '{}'" class="flex-box-ce scroll-bar"
-                    style="width: 100%; justify-content: center; overflow-x: auto;">
-                    <vue2-org-tree :data="treeData" style="width: 100%;" />
+                    style="width: 100%; align-items: center; justify-content: center; overflow-x: auto;">
+                    <vue2-org-tree :data="treeData" style="margin: 0 auto !important;" />
                 </div>
             </el-tab-pane>
         </el-tabs>
 
-        <el-dialog title="请选择需要对比的考核列表" center :visible.sync="dialogVisible" width="700px"
+        <el-dialog title="请选择需要对比的考核列表" center :visible.sync="dialogVisible" width="600px"
             :before-close="dialogBeforeClose">
             <div>
                 <div class="search-box">
@@ -73,38 +67,30 @@
                     <el-button plain round size="mini" style="margin-left: 10px;" @click="reset">重 置</el-button>
                 </div>
                 <div class="package-list">
-                    <el-transfer v-model="transferValue" :data="transferData" style="margin: 20px auto;"></el-transfer>
-                    <!-- <div class="template-list scroll-bar">
-                        <template v-if="examineList && examineList.length > 0">
-                            <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll"
-                                @change="handleCheckAllChange" style="margin-bottom: 10px;">全选</el-checkbox>
-                            <el-checkbox-group v-model="selectExamineIds" size="small" @change="changeSelectExamineIds">
-                                <template v-for="item in examineList">
-                                    <el-checkbox :key="item.reviewPackageId" :label="item.reviewPackageId" border>
-                                        {{ item.title }}
-                                    </el-checkbox>
-                                </template>
-                            </el-checkbox-group>
-                        </template>
-                        <template v-else>
-                            <div class="flex-box-ce" style="justify-content: center;">
-                                <noData content="暂无数据" imgW="120px" imgH="80px"></noData>
+
+                    <div class="template-list-box">
+                        <el-checkbox label="全选" v-model="isAllCheck" style="margin-bottom: 10px;"></el-checkbox>
+                        <div class="template-list scroll-bar">
+                            <div class="template-item" :class="item.isCheck ? 'active' : ''" v-for="item in examineList"
+                                :key="item.reviewPackageId" @click="chooseTemplate(item)">
+                                {{ item.title }}
                             </div>
-                        </template>
+                        </div>
                     </div>
-                    <div class="choose-template scroll-bar">
+
+                    <div class="choose-template-box">
                         <el-button plain round size="mini" @click="clear">清 空</el-button>
-                        <div class="line"></div>
-                        <template v-for="(item, index) in chooseExamineList">
-                            <div class="flex-box-ce choose-template-item" style="justify-content: space-between;">
-                                <div>{{ item.title }}</div>
-                                <i class="el-icon-close" @click="deleteItem(index)"></i>
-                            </div>
-                        </template>
-                        <div ref="placeholder" style="height: 50px;"></div>
-                    </div> -->
+                        <div class="choose-template scroll-bar">
+                            <template v-for="(item, index) in chooseExamineList">
+                                <div v-if="item.isCheck" class="flex-box-ce choose-template-item"
+                                    style="justify-content: space-between;">
+                                    {{ item.title }}<i class="el-icon-close" @click="deleteItem(item, index)"></i>
+                                </div>
+                            </template>
+                            <div ref="placeholder" style="height: 50px;"></div>
+                        </div>
+                    </div>
                 </div>
-
             </div>
             <div slot="footer">
                 <el-button @click="dialogVisible = false">取 消</el-button>
@@ -119,6 +105,7 @@
 <script>
 import { mapGetters } from 'vuex';
 import moment from 'moment';
+import cloneDeep from 'lodash.clonedeep';
 
 export default {
     data() {
@@ -155,8 +142,6 @@ export default {
                 endDate: '',
                 status: -1, // 不传默认返回全部 0-未完成 1-已完成
             },
-            transferData: [],
-            transferValue: [],
             pickerOptions: {
                 shortcuts: [{
                     text: '最近一周',
@@ -189,16 +174,22 @@ export default {
             tableHeader: [],
             tableData: [],
             userList: [],
-            selectExamineIds: [],
-            selectExamineList: [],
-            chooseExamineList: []
+            chooseExamineList: [],
+            selectExamineList: [], // 显示选择的数据
+            isAllCheck: false
+        }
+    },
+    watch: {
+        isAllCheck(v) {
+            if (v) {
+                this.examineList.forEach(item => item.isCheck = true)
+                this.chooseExamineList = this.examineList
+            } else {
+                this.examineList.forEach(item => item.isCheck = false)
+                this.chooseExamineList = []
+            }
         }
     },
-    // watch: {
-    //     chooseExamineList(v) {
-    //         this.selectExamineList = this.chooseExamineList
-    //     }
-    // },
     filters: {
         formatDate(val) {
             if (val) return moment(val).format('YYYY-MM-DD')
@@ -207,136 +198,135 @@ export default {
     },
     computed: {
         ...mapGetters(['user_info']),
-        // selectExamineList() {
-        //     return this.examineList.filter(item => this.selectExamineIds.includes(item.reviewPackageId))
-        // }
     },
     created() {
         this.getRecords();
         this.dialogVisible = true;
     },
     methods: {
-        deleteItem(index) {
-            let chooseIndex = this.selectExamineIds.findIndex(select => select == this.chooseExamineList[index].reviewPackageId)
-            this.selectExamineIds.splice(chooseIndex, 1)
-            this.chooseExamineList.splice(index, 1)
-        },
 
-        changeSelectExamineIds(v) {
-            console.log(this.selectExamineIds)
-            if (v && v.length > 0) {
-                this.examineList.forEach(examine => {
-                    this.selectExamineIds.forEach(val => {
-                        if (val == examine.reviewPackageId) this.chooseExamineList.push(examine)
-                    })
-                })
-                this.chooseExamineList = Array.from(new Set(this.chooseExamineList.map(JSON.stringify))).map(JSON.parse);
-            } else {
-                // this.selectExamineIds = []
-            }
+        // 自定义表头
+        renderHeader(h, { column, $index }) {
+            let that = this;
+            return h(
+                'div', {
+                style: { display: "flex", alignItems: "center", justifyContent: "center" }
+            }, [
+                h('el-button', {
+                    props: {
+                        icon: 'el-icon-delete',
+                        size: 'mini'
+                    },
+                    style: 'padding: 5px',
+                    on: {
+                        click: function () {
+                            that.clickButton({ column, $index });
+                        }
+                    }
+                }, column.label)
+            ])
+        },
 
+        clickButton(obj) {
+            let { column, $index } = obj;
+            this.handleTagDelete($index - 1);
         },
-        handleCheckAllChange(v) {
 
-            if (v) {
-                this.examineList.forEach(examine => {
-                    this.selectExamineIds.push(examine.reviewPackageId)
-                })
-                this.examineList.forEach(examine => {
-                    this.selectExamineIds.forEach(val => {
-                        if (val == examine.reviewPackageId) this.chooseExamineList.push(examine)
-                    })
-                })
-                this.isIndeterminate = true;
-                this.checkAll = true;
-                this.$nextTick(() => {
-                    if (this.$refs.placeholder) this.$refs.placeholder.scrollIntoView(false);
-                })
-            } else {
-                this.selectExamineIds = [];
-                this.isIndeterminate = false;
-                this.checkAll = false;
-                if (this.examineList && this.examineList.length > 0) {
-                    let examineIds = this.examineList.map(item => item.reviewPackageId)
-                    this.chooseExamineList = this.chooseExamineList.filter(choose => !examineIds.includes(choose.reviewPackageId))
+        openChooseExamineItem() {
+            this.chooseExamineList = this.selectExamineList;
+            this.examineList.forEach(item => {
+                if (this.chooseExamineList && this.chooseExamineList.length > 0) {
+                    let flag = this.chooseExamineList.find(chooseExamine => chooseExamine.reviewPackageId == item.reviewPackageId)
+                    if (flag) {
+                        item.isCheck = true
+                    } else {
+                        item.isCheck = false
+                    }
                 }
-
-            }
+            })
+            this.dialogVisible = true
         },
 
-        chooseExamine(item) {
-            if (this.selectExamineIds.includes(item.reviewPackageId)) {
-                let index = this.selectExamineIds.findIndex(item => item.reviewPackageId);
-                this.selectExamineIds.splice(index, 1)
-            } else {
-                this.selectExamineIds.push(item.reviewPackageId);
-            }
-            if (this.selectExamineIds.length === this.examineList.length) {
-                this.isIndeterminate = true;
-                this.checkAll = true;
+        chooseTemplate(item) {
+            item.isCheck = !item.isCheck
+            let flag = this.chooseExamineList.find(chooseExamine => chooseExamine.reviewPackageId == item.reviewPackageId)
+            if (item.isCheck) {
+
+                if (!flag) this.chooseExamineList.push(item)
             } else {
-                this.isIndeterminate = false;
-                this.checkAll = false;
+                if (flag) {
+                    let index = this.chooseExamineList.findIndex(chooseExamine => chooseExamine.reviewPackageId == item.reviewPackageId)
+                    this.chooseExamineList.splice(index, 1)
+                } 
             }
-            if (this.activeName == '0') this.initTableData()
-            if (this.activeName == '1') this.initTreeData()
+            
+            
+            this.chooseExamineList = Array.from(new Set(this.chooseExamineList.map(JSON.stringify))).map(JSON.parse);
+        },
+
+        deleteItem(item, index) {
+            this.examineList.forEach(examine => {
+                
+                if (examine.reviewPackageId == item.reviewPackageId) {
+                    examine.isCheck = false
+                }
+                    
+            })
+            this.chooseExamineList.splice(index, 1)
         },
 
+        
+
         handleTagDelete(index) {
             // 最后一个不能删除
             if (this.selectExamineList && this.selectExamineList.length > 1)
                 this.selectExamineList.splice(index, 1)
+
         },
         getRecords() {
             this.loading = true;
+
             if (this.status === '-1') this.params.status = '' // 不传默认返回全部
             else this.params.status = this.status
-            if (this.cycleType == '-1') this.params = { ...this.params }
-            else this.params = { ...this.params, cycleType: this.cycleType }
+            if (this.cycleType == '-1') {
+                if (this.params.hasOwnProperty('cycleType')) {
+                    delete this.params.cycleType
+                }
+            } else {
+                this.params = { ...this.params, cycleType: this.cycleType }
+            }
             this.$axiosUser("get", `/performance/statistics/packages/${this.user_info.site_id}`, this.params).then(res => {
                 this.loading = false;
                 let { data: { data: { list, total }, code } } = res;
                 this.examineList = list;
-                this.transferData = this.examineList.map(item => ({
-                    key: item.reviewPackageId,
-                    label: item.title
-                }))
-                if (this.examineList && this.examineList.length > 0) {
-                    this.examineList.map(item => {
-                        this.chooseExamineList.forEach(choose => {
-                            if (item.reviewPackageId == choose.reviewPackageId)
-                                this.selectExamineIds.push(item.reviewPackageId)
-                        })
-                    })
-                }
-
+                this.examineList.forEach(item => {
+                    if (this.chooseExamineList && this.chooseExamineList.length > 0) {
+                        let flag = this.chooseExamineList.find(chooseExamine => chooseExamine.reviewPackageId == item.reviewPackageId)
+                        if (flag) {
+                            item.isCheck = true
+                        } else {
+                            item.isCheck = false
+                        }
+                    }
+                })
                 this.treeData = {
                     id: 0,
                     label: "考核对比",
                     children: []
                 }
-                // this.selectExamineIds = [];
             })
         },
+        
         initTableData() {
             let userList = [];
-            let selectExamineList = [];
-
-            this.examineList && this.examineList.forEach(item => {
-                this.transferValue.forEach(selectExamineId => {
-                    if (item.reviewPackageId == selectExamineId) {
-                        selectExamineList.push(item)
-                    }
-                })
-
-            })
+            let selectExamineList = this.chooseExamineList;
 
             selectExamineList.forEach(item => {
                 let { reviewPackageId, title, users } = item;
                 if (users && users.length > 0) {
                     users.forEach(user => {
-                        let { employeeName, employeeId } = user
-                        userList.push({ employeeName, employeeId })
+                        let { employeeName, employeeId, score } = user
+                        userList.push({ employeeName, employeeId, score: score ? score : 0 })
                     })
                 }
             })
@@ -365,10 +355,20 @@ export default {
                 children: this.selectExamineList
             }
         },
+
         handleClick(tab, event) {
-            if (this.activeName == '0') this.initTableData()
-            if (this.activeName == '1') this.initTreeData()
+            if (this.activeName == '0') { 
+                if (this.selectExamineList && this.selectExamineList.length > 0) {
+                    this.initTableData()
+                }
+            } 
+            if (this.activeName == '1') {
+                if (this.selectExamineList && this.selectExamineList.length > 0) {
+                    this.initTreeData()
+                }
+            } 
         },
+
         // 日期选择时间
         changeDate(v) {
             if (v && v.length > 0) {
@@ -376,11 +376,10 @@ export default {
                 this.params.endDate = v[1] || ''
                 this.getRecords();
             }
-
         },
         //选择周期
         changeCircle(v) {
-            console.log(v);
+            this.cycleType = v;
             this.getRecords();
         },
         // 选择状态
@@ -402,14 +401,23 @@ export default {
 
         // 清空选择的考核列表
         clear() {
-            this.selectExamineIds = [];
+            this.chooseExamineList.forEach(chooseExamine => {
+                this.examineList.forEach(examine => {
+                    if (chooseExamine.reviewPackageId == examine.reviewPackageId) {
+                        examine.isCheck = false
+                    }
+                })
+            })
+            this.isAllCheck = false;
             this.chooseExamineList = [];
         },
+
         dialogBeforeClose() {
             this.dialogVisible = false;
         },
+
         confirm() {
-            this.selectExamineList = this.examineList.filter(item => this.transferValue.indexOf(item.reviewPackageId) >= 0);
+            this.selectExamineList = cloneDeep(this.chooseExamineList);
             if (this.activeName == '0') this.initTableData()
             if (this.activeName == '1') this.initTreeData()
             this.dialogVisible = false;
@@ -423,6 +431,11 @@ export default {
 
 
 <style scoped="scoped" lang="scss">
+
+.org-tree {
+    margin: 0 auto !important;
+}
+
 .contrast-container {
     width: 100%;
     height: 100%;
@@ -457,57 +470,67 @@ export default {
         }
     }
 
+
     .package-list {
         width: 100%;
         height: 400px;
+        display: flex;
         margin-top: 10px;
         border: 1px solid #f7f7f7;
-
-        .template-list {
+        .template-list-box {
             width: 50%;
-            height: 400px;
-            overflow-y: auto;
-            display: flex;
-            flex-direction: column;
-            border-right: 1px solid #f7f7f7;
             padding: 10px;
             box-sizing: border-box;
-
-            .el-checkbox-group {
-                width: 100% !important;
-                height: 100% !important;
-
-                .el-checkbox {
-                    margin: 0;
-                    width: 200px;
-                    margin-bottom: 10px;
+            border-right: 1px solid #f7f7f7;
+            .template-list {
+                width: 100%;
+                height: 350px;
+                overflow-y: auto;
+                display: flex;
+                flex-direction: column;
+                .template-item {
+                    width: 100%;
+                    height: 30px;
+                    line-height: 30px;
+                    border: 1px solid #f1f1f1;
+                    margin-bottom: 5px;
+                    border-radius: 6px;
+                    padding-left: 10px;
+                }
+        
+                .active {
+                    border: 1px solid #409eff;
+                    color: #409eff;
                 }
-
             }
         }
 
-        .choose-template {
+        .choose-template-box {
             width: 50%;
-            height: 400px;
-            overflow-y: auto;
             padding: 10px;
             box-sizing: border-box;
-
-            .line {
-                width: 90%;
-                height: 1px;
-                background-color: #f7f7f7;
-                margin: 10px auto;
-            }
-
-            &-item {
-                width: 90%;
-                height: 30px;
-                line-height: 30px;
-                padding-left: 10px;
-                border-bottom: 1px solid #f7f7f7;
-                margin: 0 auto 10px auto;
-                box-sizing: border-box;
+            .choose-template {
+                width: 100%;
+                height: 350px;
+                overflow-y: auto;
+        
+        
+                .line {
+                    width: 90%;
+                    height: 1px;
+                    background-color: #f7f7f7;
+                    margin: 10px auto;
+                }
+        
+                &-item {
+                    width: 90%;
+                    height: 30px;
+                    line-height: 30px;
+                    padding-left: 10px;
+                    border-bottom: 1px solid #f7f7f7;
+                    margin: 0 auto 10px auto;
+                    box-sizing: border-box;
+                }
             }
         }
     }

+ 248 - 44
src/newPerformance/components/ExamineRecord.vue

@@ -1,91 +1,295 @@
 <template>
     <div class="record-container">
-        <!-- 左边 考核列表组件 -->
-        <LeftExamineRecord @selectRow="handleSeleRow" />
-        <!-- 右边 考核明细组件 -->
-        <RightExamineRecord v-if="JSON.stringify(detailInfo) !== '{}'" :detailInfo="detailInfo" :cateList="cateList" />
+
+        <ResultAnalysis  />
+        <!-- 水平线 -->
+        <!-- <div v-if="detailInfo" class="line"></div>
+        <div v-if="detailInfo" v-loading="loading" class="main-content">
+            <ResultAnalysis v-if="reviewPackageId" :detailInfo="detailInfo" />
+        </div>
+
+        <div v-else class="flex-box-v flex-center-center no-data-box" style="">
+            <img src="static/images/invite_new_company.png" style="" />
+            <div class="fontColorB">暂无考核数据</div>
+        </div> -->
     </div>
 </template>
 
 
 <script>
+let that;
 import { mapGetters } from 'vuex';
-import RightExamineRecord from "./ExamineRecord/RightExamineRecord" // 考核列表组件
-import LeftExamineRecord from "./ExamineRecord/LeftExamineRecord" // 考核列表组件
+import moment from 'moment';
+import ResultAnalysis from "./ResultAnalysis" // 考核详情组件
+
 export default {
     components: {
-        RightExamineRecord,
-        LeftExamineRecord
+        ResultAnalysis
     },
     data() {
         return {
-            detailInfo: {},
-            cateList: [], // 考核分类
+            reviewPackageId: 0,
+            detailInfo: null,
+            year: '2025',
+            headValue: [],
+            options: [{ value: '0', label: '未定义', leaf: false, children: [] },
+                { value: '1', label: '年度', leaf: false, children: [] },
+                { value: '2', label: '半年度', leaf: false, children: [] },
+                { value: '3', label: '季度', leaf: false, children: [] },
+                { value: '4', label: '月度', leaf: false, children: [] },
+            ], //
+            props: {
+                value: 'value',
+                label: 'label',
+                children: 'children',
+                lazy: true,
+                lazyLoad(node, resolve) {
+                    that.getAssessTree(node, resolve);
+                }
+            },
+            total: 0,
+            loading: false,
+            tableData: [],
+            deptList: [], // 部门列表 - 树形结构
+            dept_list: [], // 部门列表
+            selected_dept_ids: [], // 选择的部门列表
+            placeholder: "",
+            chooseChildren: [],
+            noDataText: '暂无数据'
         }
     },
+    watch: {
+        year(val) {
+            this.getRecords()
+        },
+        headValue(val) {
+            if (this.chooseChildren && this.chooseChildren.length > 0) {
+                let obj = this.chooseChildren.find(child => child.value == this.headValue[1])
+                if (obj) { 
+                    let value = ''
+                    if (this.headValue[0] && this.headValue[0] == '0') value = "未定义 / "
+                    if (this.headValue[0] && this.headValue[0] == '1') value = "年度 / "
+                    if (this.headValue[0] && this.headValue[0] == '2') value = "半年度 / "
+                    if (this.headValue[0] && this.headValue[0] == '3') value = "季度 / "
+                    if (this.headValue[0] && this.headValue[0] == '4') value = "月度 / "
+                    this.placeholder = obj.label || ''
+                } 
+            }
+            this.getResultAnalyze()
+        },
+    },
+
     created() {
-        this.getCateList()
+        // that = this;
+        // this.getRecords('4'); // 优先获取当月最新考核数据 递归周期类型,获取考核数据,优先按月,季,半年度,年度来调用 
     },
+    
     computed: {
         ...mapGetters(['user_info']),
     },
+
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        },
+        filterProgress(list) {
+            if (list && list.length > 0) {
+                let sum = 0;
+                list.forEach(item => {
+                    if (item.status == 1) sum += 1 // 完成的个数
+                })
+                return Number(sum / list.length) * 100
+            }
+        }
+    },
     methods: {
-        handleSeleRow(reviewPackageId) {
-            // 获取考核详情
-            let url = `/performance/statistics/package/info/${this.user_info.site_id}/${reviewPackageId}`
+        getAssessTree(node, resolve) {
+            if (this.options.length == 0) {
+                return
+            }
+            const { value } = node;
+            
+            this.chooseChildren = node.children // 用来回显选择的文本
+            node.children = []
+            let url = `/performance/statistics/cycle/info/${this.user_info.site_id}/${value}`
             this.$axiosUser("get", url, {}).then(res => {
-                let { data: { data: { cateIds, indicators, startTime, endTime, title, distribution: { items }, users }, code } } = res
-                this.detailInfo = { data: { data: { cateIds, indicators, startTime, endTime, title, distribution: { items }, users }, code }, reviewPackageId }
-            })
+                let { data: { data: { items, cycleType } } } = res
+                if (items && items.length > 0) {
+                    items.forEach(item => {
+                        item.leaf = true;
+                        item.label = item.remark
+                    })
+                    resolve(items)
+                } else {
+                    resolve([])
+                }
+                
+                
+            }).finally(() => {
+                // this.tableDataLoad = false;
+            });
         },
-        // 考核分类列表
-        getCateList() {
-            let url = `/performance/cate/list/${this.user_info.site_id}`;
-            // this.loading = true
-            this.$axiosUser('get', url).then(res => {
-                let { data: { code, data: { list, total } } } = res
-                if (code == 1) {
-                    this.cateList = list;
+        getRecords(cycle) {
+            if (cycle < 0) {
+                cycle = 0
+                return
+            }
+            this.loading = true
+            // 周期种类 0-未定义 1-年度 2-半年度 3-季度 4-月度
+            let url = `/performance/statistics/cycle/info/${this.user_info.site_id}/${cycle}`
+            this.$axiosUser("get", url, {}).then(res => {
+                this.loading = false;
+                let { data: { data: { items, cycleType } } } = res
+                if (items && items.length > 0) {
+                    items.forEach(item => {
+                        item.leaf = true
+                        item.label = item.remark
+                    })
+                    this.options[parseInt(cycle)].children = items
+                    
+                    this.headValue = [cycle + '', this.options[parseInt(cycle)].children[[0]].value]
+                    // if (this.headValue[1]) this.placeholder = this.options[parseInt(cycle)].children[[0]].label || ''
+                } else {
+                    this.getRecords(parseInt(cycle) - 1) // 递归周期类型,获取考核数据,优先按月,季,半年度,年度来调用
                 }
+            })
+        },
 
+        getResultAnalyze() {
+            this.reviewPackageId = 0
+            let url = `/performance/statistics/cycle/${this.user_info.site_id}/${this.headValue[0]}`
+            if (!this.headValue[1]) return
+            this.$axiosUser("get", url, { value: this.headValue[1]}).then(res => {
+                this.detailInfo = res.data.data
+                this.reviewPackageId = 1
             })
         },
+
+
     }
 };
 </script>
 
+<style lang="scss">
+.record-container {
+    .el-cascader .el-input__inner::placeholder {
+        color: #606266;
+        /* 设置为你想要的颜色 */
+    }
+}
+
+</style>
 
 <style scoped="scoped" lang="scss">
+// .el-cascader .el-input{
+//     cursor: pointer;
+//     // #606266
+// }
+
+
 .record-container {
     width: 100%;
     height: 100%;
-    background-color: #f0f4fa;
+    background-color: #fff;
+    border-radius: 5px;
     display: flex;
-    justify-content: space-between;
+    flex-direction: column;
+    .title-container {
+        display: flex;
+        align-items: center;
+
+        .searchBox {
+            width: 300px;
+
+            .search-title {
+                border-bottom: 1px solid #f1f1f1;
+                font-size: 16px;
+                font-weight: 700;
+                padding: 0 10px;
+                padding-bottom: 10px;
+            }
+        }
+
+
+        .title {
+            font-weight: 700;
+            font-size: 16px;
+        }
+    }
 
-    /* 设置滚动条的宽度和背景色 */
-    ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
-        width: 10px;
-        height: 10px;
-        background-color: #f9f9f9;
+    .search-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        width: 100%;
+        height: 50px;
+        padding: 0 10px;
+        box-sizing: border-box;
+
+        .btn-box {
+            padding: 7px 5px;
+            border: 1px solid #DCDFE6;
+            border-radius: 4px;
+
+            &:hover {
+                cursor: pointer;
+            }
+        }
     }
 
-    /* 设置滚动条滑块的样式 */
-    ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
-        border-radius: 6px;
-        background-color: #c1c1c1;
+    .line {
+        width: 100%;
+        height: 1px;
+        background: #f1f1f1;
+        margin-bottom: 10px;
     }
 
-    /* 设置滚动条滑块hover样式 */
-    ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
-        background-color: #a8a8a8;
+
+    .main-content {
+        width: 100%;
+        flex: 1;
+        overflow-y: auto;
+        /* 设置滚动条的宽度和背景色 */
+        &::-webkit-scrollbar {
+            width: 6px;
+            height: 6px;
+            background-color: #f9f9f9;
+        }
+    
+        /* 设置滚动条滑块的样式 */
+        &::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
+    
+        /* 设置滚动条滑块hover样式 */
+        &::-webkit-scrollbar-thumb:hover {
+            background-color: #a8a8a8;
+        }
+    
+        /* 设置滚动条轨道的样式 */
+        &::-webkit-scrollbar-track {
+            box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+            border-radius: 6px;
+            background: #ededed;
+        }
     }
 
-    /* 设置滚动条轨道的样式 */
-    ::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
-        box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
-        border-radius: 6px;
-        background: #ededed;
+
+    .no-data-box {
+        width: 100%;
+        height: 100%;
+        padding: 26px 35px;
+        background-color: #fff;
+        box-sizing: border-box;
+        border-radius: 4px;
+
+        img {
+            width: 200px;
+            height: 200px;
+            margin-bottom: 10px;
+        }
     }
 }
 </style>

+ 5 - 7
src/newPerformance/components/ExamineRecord/LeftExamineRecord.vue

@@ -43,7 +43,6 @@
                     <div class="gd"><i class="el-icon-finished" style="margin-right: 5px; color: #999;"></i>筛选</div>
                 </template>
             </el-popover>
-
         </div>
         <!-- 水平线 -->
         <div class="line"></div>
@@ -86,9 +85,6 @@
             </div>
         </div>
 
-
-
-
     </div>
 </template>
 
@@ -97,9 +93,11 @@
 import { mapGetters } from 'vuex';
 import moment from 'moment';
 import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+
 export default {
     components: {
-        EmployeeSelector
+        EmployeeSelector,
+        RightExamineRecord
     },
     data() {
         return {
@@ -345,7 +343,7 @@ export default {
             align-items: center;
             justify-content: space-between;
             width: 100%;
-            height: 40px;
+            height: 30px;
             .btn-box {
                 padding: 7px 5px;
                 border: 1px solid #DCDFE6;
@@ -360,7 +358,7 @@ export default {
             width: 100%;
             height: 1px;
             background: #f1f1f1;
-            margin: 20px 0;
+            margin: 10px 0;
         }
         ::-webkit-scrollbar {
             width: 10px;

+ 142 - 0
src/newPerformance/components/ExamineRecord/PieChart1.vue

@@ -0,0 +1,142 @@
+<template>
+    <!-- 按考核状态分布 -->
+    <div ref="chart1" :style="myChartStyle"></div>
+</template>
+
+<script>
+
+import echarts from 'echarts';
+
+export default {
+    props: {
+        tableData: {
+            type: Array,
+            default: () => []
+        }
+    },
+    data() {
+        return {
+            myChart: null,
+            myChartStyle: { width: "100%", height: "400px" },
+            colorList: [
+                { c1: ' #40d3ff', c2: '#409EFF' }, { c1: ' #ede737', c2: '#FF9600' }, { c1: '#89e398', c2: '#67c23a' },
+                { c1: ' #85E9C7', c2: '#00C4CB' }, { c1: ' #f393a9', c2: '#f56c6c' }, { c1: '#e69cf3', c2: '#de43f9' },
+            ],
+        }
+    },
+
+    computed: {
+        title() {
+            let users = []
+            this.tableData.forEach(item => {
+                if (item.users && item.users.length > 0) {
+                    item.users.forEach(user => {
+                        users.push(user);
+                    })
+                }
+            })
+            return "考核总人数" + users.length
+        },
+        // 考核表状态
+        examineStatus() {
+            let examineStatus = [
+                { name: "进行中", value: 0 },
+                { name: "已结束", value: 0 },
+            ]
+            let users = []
+            this.tableData.forEach(item => {
+                if (item.users && item.users.length > 0) {
+                    item.users.forEach(user => {
+                        users.push(user);
+                    })
+                }
+            })
+            examineStatus[0].value = users.filter(item => item.status == 0).length;
+            examineStatus[1].value = users.filter(item => item.status == 1).length;
+            return examineStatus
+        },
+    },
+
+    watch: {  //此处为关键,监听tableData值的变化,进行echarts渲染
+        tableData(v){
+            this.initEcharts1(); //值发生改变则渲染一次echarts
+        }
+    },
+
+    mounted() {
+        this.initEcharts1();
+    },
+    beforeDestroy() {
+        if (this.myChart) {
+            this.myChart.clear();//清空当前实例,会移除实例中所有的组件和图表
+            this.myChart.dispose();//销毁实例,实例销毁后无法再被使用
+        }
+    },
+    methods: {
+        initEcharts1() {
+            const colorList = this.colorList;
+
+               
+            const option = {
+                title: {
+                    show: true,
+                    text: this.title,
+                    itemGap: 8,
+                    left: '48%',
+                    top: '0',
+                    textStyle: {
+                        color: '#000',
+                        fontSize: 16,
+                    },
+                    
+                    x: 'center',
+                    y: 'center',
+                    textAlign: 'center'
+                },
+                tooltip: {//悬浮提示组件
+                    trigger: 'item',
+                },
+                legend: {
+                    show: true,
+                    icon: "circle",
+                    itemWidth: 10,  // 设置宽度
+                    itemHeight: 10, // 设置高度
+                    x: 'center',
+                    y: 'bottom',
+                    itemGap: 30
+                },
+
+                series: [
+                    {
+                        type: "pie",
+                        radius: ['40%', '65%'],//小的是内径,大的是外径
+                        data: this.examineStatus,
+                        itemStyle: {
+                            emphasis: {
+                                shadowBlur: 9,
+                                shadowOffsetX: 0,
+                                shadowColor: 'rgba(0, 0, 0, 0.5)'
+                            },
+                            normal: {
+                                label: {
+                                    show: true,
+                                    formatter: '{b} : {c}'
+                                },
+                                labelLine: { show: true },
+                                color: function (params) {//颜色渐变函数 前四个参数分别表示四个位置依次为左、下、右、上
+                                    return new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ offset: 0, color: colorList[params.dataIndex].c1 }, { offset: 1, color: colorList[params.dataIndex].c2 }])
+                                }
+                            }
+                        }
+                    }
+                ]
+            };
+            this.myChart = this.$echarts.init(this.$refs.chart1);
+            this.myChart.setOption(option);
+            window.addEventListener("resize", () => {
+                this.myChart.resize();
+            });
+        },
+    }
+}
+</script>

+ 63 - 59
src/newPerformance/components/ExamineRecord/RightEamineComp/EditScoreList.vue

@@ -1,6 +1,6 @@
 <template>
     <!-- 正态规则 -->
-    <el-popover ref="popoverRef" placement="right" trigger="click" width="300">
+    <el-popover ref="popoverRef" v-model="isPopoverVisible" placement="right" trigger="click" width="300"  @show="handleShow" @hide="handleHide">
         <div class="setting">
             <div class="setting-title-box flex-box-ce" style="justify-content: space-between;">
                 <div class="setting-title">
@@ -8,34 +8,38 @@
                 </div>
                 <el-button class="plus-button" icon="el-icon-plus" circle size="mini" @click="addData()"></el-button>
             </div>
+            <div class="table-box scroll-bar" style="width: 100%; height: 160px; overflow-y: auto;" >
+                <table style="width: 100%;">
+                    <tr style="width: 100%;" v-for="(item, index) in editScoreList" :key="index">
+                        <td style="width: 40%;">
+                            <el-input v-model="item.name" size="mini"></el-input>
+                        </td>
+                        <td style="width: 50%;">
+                            <el-input v-model.number="item.scale" size="mini" maxlength="3">
+                                <template slot="append">%</template>
+                            </el-input>
+                        </td>
+                        <td style="width: 10%;" align="center">
+                            <i class="el-icon-delete" @click="deleteData(index)"></i>
+                        </td>
+                    </tr>
+                </table>
+            </div>
 
-            <table style="width: 100%;">
-                <tr style="width: 100%;" v-for="(item, index) in scoreList" :key="index">
-                    <td style="width: 40%;">
-                        <el-input v-model="item.name" size="mini"></el-input>
-                    </td>
-                    <td style="width: 40%;">
-                        <el-input v-model="item.scale" size="mini">
-                            <template slot="append">%</template>
-                        </el-input>
-                    </td>
-                    <td style="width: 20%;" align="center">
-                        <i class="el-icon-delete" @click="deleteData(index)"></i>
-                    </td>
-                </tr>
-            </table>
             <div class="flex-box-ce" style="margin-top: 20px; justify-content: center;">
                 <el-button size="mini" plain round @click="cancelChangeScore">取消</el-button>
                 <el-button type="primary" plain round size="mini" @click="confirmChangeScore">确定</el-button>
             </div>
         </div>
-        <el-button slot="reference" size="small" style="margin-left: auto;">正态分布</el-button>
+        <el-button slot="reference" style="margin-left: auto;">正态分布</el-button>
     </el-popover>
 </template>
 
 
 <script>
 import { mapGetters } from 'vuex';
+import cloneDeep from 'lodash.clonedeep';
+
 export default {
     name: "EditScoreList",
     props: {
@@ -48,36 +52,69 @@ export default {
             default: () => []
         }
     },
+    
+    data() {
+        return {
+            isPopoverVisible: false,
+            distributionId: "",
+            editScoreList: []
+        }
+    },
+    mounted() {
+        
+    },
     computed: {
         ...mapGetters(['user_info']),
     },
     methods: {
+        handleShow() {
+            if (this.scoreList && this.scoreList.length > 0) {
+                this.editScoreList = []
+                this.editScoreList = cloneDeep(this.scoreList)
+                this.distributionId = this.editScoreList[0].id
+            }
+        },
+        handleHide() {
+            if (this.scoreList && this.scoreList.length > 0) {
+                this.editScoreList = []
+                this.editScoreList = cloneDeep(this.scoreList)
+                this.distributionId = this.editScoreList[0].id
+            }
+        },
         addData() {
-            this.scoreList.push({ id: this.distributionId, name: "", scale: 0 })
+            this.editScoreList.push({ id: this.distributionId, name: "", scale: 0 })
         },
         deleteData(index) {
-            this.scoreList.splice(index, 1)
+            this.editScoreList.splice(index, 1)
         },
         cancelChangeScore() {
             this.$refs.popoverRef && this.$refs.popoverRef.doClose();
         },
         confirmChangeScore() {
-            let url = `/performance/statistics/package/distribution/${this.user_info.site_id}`
+            let url = `/performance/statistics/distribution/${this.user_info.site_id}`
             let reviewPackageId = this.reviewPackageId
-            let items = this.scoreList;
+            let items = this.editScoreList;
             let sum = 0;
-            for (let i = 0; i < items.length - 1; i++) {
+            for (let i = 0; i < items.length; i++) {
                 if (!items[i].name) return this.$message.error('请输入正太规则名称')
                 sum += Number(items[i].scale)
             }
             if (sum > 100) return this.$message.error('总分布比例不能超过100')
+            if (sum !== 100) return this.$message.error('总分布比例要等于100')
 
             let data = {
-                reviewPackageId,
                 items
             }
             this.$http.post(url, data).then(res => {
-                this.$refs.popoverRef && this.$refs.popoverRef.doClose();
+                if (res.code == 1) {
+                    this.$message.success("保存成功");
+                    this.$emit("confirm", res.data.items)
+                    this.$refs.popoverRef && this.$refs.popoverRef.doClose();
+                } else {
+                    this.$message.error(res.message || "保存失败");
+                    this.$emit("confirm", [])
+                }
+                
             })
         }
     }
@@ -88,11 +125,10 @@ export default {
 
 <style scoped lang="scss">
 .setting {
-    width: 300px;
-    height: 200px;
+    width: 100%;
+    height: 240px;
     .setting-title-box {
         width: 100%;
-        padding-right: 20px;
         box-sizing: border-box;
         display: flex;
         align-items: center;
@@ -107,39 +143,7 @@ export default {
         }
 
     }
-    // table {
-    //         width: 100%;
-    //         border-collapse: collapse;    // everything will be ok
-    //         /* 合并表格边框 */
-    //         border: 1px solid #ccc;
-    //         /* 设置表格边框样式和颜色 */
-    //         margin: 0 auto 10px auto;
-    //         /* 设置表格外边距 */
-    //         background-color: #f8f8f8;
-    //         /* 设置表格背景颜色 */
-    //         color: #000;
-    //         /* 设置表格文字颜色 */
-    //         text-align: center;
-    //         /* 设置表格文字居中 */
-    //         line-height: 40px;
-    //         border-radius: 6px;
-    
-    //         /* 设置表格行高 */
-    //         tr:nth-child(1) {
-    //             background-color: #f2f2f2;
-    //             /* 偶数行背景色 */
-    //         }
-    
-    //         tr:nth-child(even) {
-    //             background-color: #fff;
-    //             /* 偶数行背景色 */
-    //         }
     
-    //         tr:nth-child(odd) {
-    //             background-color: #f2f2f2;
-    //             /* 奇数行背景色 */
-    //         }
-    //     }
 }
     
 </style>

+ 685 - 0
src/newPerformance/components/ExamineRecord/RightExamineRecord copy.vue

@@ -0,0 +1,685 @@
+<template>
+    <div class="record-right scroll-box" style="overflow-y: auto;" v-loading="loading">
+        <div class="title-container flex-box-ce" style="height: 30px; justify-content: space-between;">
+            <div class="flex-box-ce">
+                <el-popover placement="bottom" trigger="click" ref="searchPopover">
+                    <div class="searchBox">
+                        <div class="search-title"
+                            style="border-bottom: 1px solid #f1f1f1; font-size: 16px; font-weight: 700; padding: 0 10px; padding-bottom: 10px;">
+                            修改考核表
+                        </div>
+
+                        <div style="margin: 10px 0;">考核表名称</div>
+
+                        <EditTitle v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :title="title"
+                            @handleEditTile="changeTitle" />
+                        <div style="margin: 10px 0;">考核表时间</div>
+                        <EditCircle v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :startTime="startTime"
+                            :endTime="endTime" @comfirmChanged="changeDate" />
+                        <div style="margin: 10px 0;">考核分类</div>
+
+                        <EditCateType v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :cateIds="cateIds"
+                            :cateList="cateList" @comfirmChanged="changeCateIds" />
+
+                    </div>
+                    <template slot="reference">
+                        <div class="gd">
+                            <i class="el-icon-edit" style="margin-right: 5px; color: #999;"></i>
+                            <strong style="margin-right: 5px;">{{ title }}</strong>
+                            {{ startTime | formatDate }} 至 {{ endTime | formatDate }}
+                        </div>
+                    </template>
+                </el-popover>
+
+                <el-button v-if="packageOkrs && packageOkrs.length > 0" type="text" style="margin-left: 10px;"
+                    @click="openTargetList(packageOkrs)">考核表OKR</el-button>
+            </div>
+            <div>
+                <!-- 正太规则 -->
+                <EditScoreList v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :scoreList="scoreList" />
+            </div>
+        </div>
+
+        <div class="line"></div>
+
+
+        <!-- 考核人员分数列表 -->
+        <div class="score-list">
+            <div class="score-item heartBeat animated">
+                <div class="score-item-title">总人数</div>
+                <div class="score-item-num">{{ userTotal }}</div>
+                <div class="score-item-percent">{{ userComplete }}人已完成</div>
+            </div>
+            <div v-for="item in gradeLevels" class="score-item heartBeat animated">
+                <div class="score-item-title">{{ item.name }}</div>
+                <div class="score-item-num">{{ users.filter(user => user.level === item.name).length }}</div>
+                <div class="score-item-percent">{{ item.min + '~' + item.max }}</div>
+            </div>
+        </div>
+
+        <!-- 考核人员列表 -->
+        <div class="user-info flex-box-ce">
+            <div class="info-card" v-for="item in users" :key="item.employeeId">
+                <div class="info" style="height: 30px;">{{ item.employeeName }}</div>
+                <div class="info">
+                    <span class="info-label fontColorC">考核状态:</span>
+
+                    <el-tag v-if="item.status" type="success">
+                        已完成
+                    </el-tag>
+                    <el-tag v-else="item.status" type="warning">
+                        进行中
+                    </el-tag>
+                </div>
+                <div class="info">
+                    <span class="info-label fontColorC">评分:</span>
+                    <el-tag v-if="item.level === '未评分'" type="info">
+                        未评分
+                    </el-tag>
+                    <el-tag v-else>
+                        {{ item.level }}
+                    </el-tag>
+                </div>
+                <div class="info">
+                    <span class="info-label fontColorC">正态分布:</span>
+                    <el-tag>
+                        {{ item.scoreResult }}
+                    </el-tag>
+                </div>
+                <div class="info">
+                    <span class="info-label fontColorC">关联okr</span>
+                    <el-button v-if="item.okrs && item.okrs.length > 0" type="text"
+                        @click="openTargetList(item.okrs)">个人OKR</el-button>
+                    <span class="fontColorC" v-else>暂无关联</span>
+                </div>
+            </div>
+        </div>
+        
+
+        <!-- 指标列表 -->
+        <div class="title-container" style="margin: 10px 0;">
+            <div class="title">指标信息</div>
+        </div>
+
+        <el-tabs type="card" v-model="activeName" @tab-click="handleClick">
+            <el-tab-pane label="默认" name="1">
+                <el-table ref="fmeaTableRef2" :data="tableData1" stripe style="width: 100%" border
+                    v-table-move="['fmeaTableRef2']" :header-cell-style="{ background: '#f5f7fa' }" :height="330">
+                    <el-table-column prop="employeeName" label="员工" align="center"></el-table-column>
+                    <el-table-column prop="title" label="指标" align="center">
+                        <template slot-scope="scope">
+                            <el-tooltip class="item" effect="dark" placement="top">
+                                <div v-html="scope.row.title" slot="content" style="max-width:300px"></div>
+                                <div class="oneLine">{{ scope.row.title }}</div>
+                            </el-tooltip>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column prop="target" label="目标" align="center" width="100">
+                        <template slot-scope="scope">
+                            <span v-if="scope.row.target !== null">
+                                {{ `${scope.row.target} ${scope.row.unit ? scope.row.unit : ''}` }}
+                            </span>
+                            <span v-else>--</span>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="result" label="结果" align="center" width="100">
+                        <template slot-scope="scope">
+                            <div>
+                                {{ scope.row.result || '--' }}
+                            </div>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="different" label="差距" align="center" width="100">
+                        <template slot-scope="scope">
+                            <el-tag v-if="scope.row.difference > 0" type="success">
+                                {{ scope.row.difference }}
+                            </el-tag>
+                            <el-tag v-else-if="scope.row.difference < 0" type="danger">
+                                {{ scope.row.difference }}
+                            </el-tag>
+                            <div v-else>
+                                --
+                            </div>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="score" label="评分" align="center" width="100">
+                        <template slot-scope="scope">
+                            <div>
+                                {{ scope.row.score || '--' }}
+                            </div>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column prop="okrs" label="关联OKR" width="100" align="center">
+                        <template slot-scope="scope">
+                            <el-link v-if="scope.row.okrs && scope.row.okrs.length > 0" type="primary"
+                                @click="openTargetList(scope.row.okrs)">
+                                指标OKR
+                            </el-link>
+                            <span v-else class="fontColorC">暂无关联</span>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </el-tab-pane>
+
+            <el-tab-pane label="按指标/目标/单位聚合" name="2">
+                <el-table ref="fmeaTableRef3" :data="tableData2" stripe style="width: 100%" border
+                    v-table-move="['fmeaTableRef3']" :header-cell-style="{ background: '#f5f7fa' }" :height="300">
+                    <el-table-column prop="title" label="指标" align="center">
+                        <template slot-scope="scope">
+                            <el-tooltip class="item" effect="dark" placement="top">
+                                <div v-html="scope.row.title" slot="content" style="max-width:300px"></div>
+                                <div class="oneLine">{{ scope.row.title }}</div>
+                            </el-tooltip>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column prop="target" label="目标" align="center" width="100">
+                        <template slot-scope="scope">
+                            <div>
+                                <span v-if="scope.row.target !== null">
+                                    {{ `${scope.row.target} ${scope.row.unit ? scope.row.unit : ''}` }}
+                                </span>
+                                <span v-else>--</span>
+                            </div>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="avgResult" label="均值" align="center">
+
+                    </el-table-column>
+
+                    <el-table-column prop="standardResultRate" label="超出目标比例" align="center">
+                        <template slot-scope="scope">
+                            <el-tag v-if="parseInt(scope.row.standardResultRate) > 0" type="success">
+                                {{ scope.row.standardResultRate }}
+                            </el-tag>
+                            <el-tag v-else-if="parseInt(scope.row.standardResultRate) < 0" type="danger">
+                                {{ scope.row.standardResultRate }}
+                            </el-tag>
+                            <el-tag v-else type="info">
+                                {{ scope.row.standardResultRate }}
+                            </el-tag>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="standardCount" label="达标数" align="center" />
+                    <el-table-column prop="standardRate" label="达标率" align="center">
+                        <template slot-scope="scope">
+                            <el-tag v-if="parseInt(scope.row.standardRate) > 0" type="success">
+                                {{ scope.row.standardRate }}
+                            </el-tag>
+                            <el-tag v-else-if="parseInt(scope.row.standardRate) < 0" type="danger">
+                                {{ scope.row.standardRate }}
+                            </el-tag>
+                            <el-tag v-else type="info">
+                                {{ scope.row.standardRate }}
+                            </el-tag>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="avgScore" label="平均分" align="center" />
+                </el-table>
+            </el-tab-pane>
+        </el-tabs>
+
+        <!-- 关联okr -->
+        <TargetListComp v-if="targetDialogVisible" :dialogVisible="targetDialogVisible" :ids="okrs"
+            @close="closeTargetList">
+        </TargetListComp>
+    </div>
+</template>
+
+
+<script>
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+import _ from "lodash"
+
+import EditTitle from './RightEamineComp/EditTitle'; // 修改考核标题组件
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+import EditScoreList from './RightEamineComp/EditScoreList'
+import EditCircle from './RightEamineComp/EditCircle.vue';
+import EditCateType from './RightEamineComp/EditCateType.vue';
+import TargetListComp from "@/performance/views/assessManagement/TargetListComp.vue"; // 关联OKR弹框
+
+export default {
+    components: {
+        EditTitle,
+        EditScoreList,
+        EmployeeSelector,
+        EditCircle,
+        EditCateType,
+        TargetListComp
+    },
+    props: {
+        detailInfo: {
+            type: Object,
+            default: () => { }
+        },
+        cateList: {
+            type: Array,
+            default: () => []
+        },
+    },
+    data() {
+        return {
+            loading: false,
+            activeName: "1",
+            reviewPackageId: "",
+            title: "默认标题",
+            startTime: "",
+            endTime: "",
+            scoreList: [],
+            users: [], //考核人员列表
+            tableData1: [], // 考核中的指标列表,
+            tableData2: [], // 按单位/目标/聚合指标列表,
+            distributionId: "",
+            level_enable: false,
+            packages: [],
+            userTotal: 0,
+            userComplete: 0,
+            userIncomplete: 0,
+            infos: [],
+            gradeLevels: [],
+            cateIds: [], // 选择的考核分类
+            targetDialogVisible: false,
+            okrs: [],
+            packageOkrs: []
+        };
+    },
+    watch: {
+        detailInfo(v) {
+            this.loading = true
+            this.activeName = '1'
+            let { data: { data: { cateIds, indicators, startTime, endTime, title, okrs, distribution: { items }, users }, code }, reviewPackageId } = v
+            this.getAllSet();
+            this.packageOkrs = okrs;
+            this.cateIds = cateIds;
+            this.tableData1 = [];
+            this.tableData2 = [];
+            this.reviewPackageId = reviewPackageId;
+            this.title = title;
+            this.startTime = startTime;
+            this.endTime = endTime;
+            this.scoreList = items;
+            this.distributionId = this.scoreList[0].id
+            this.users = users;
+            this.tableData1 = indicators;
+            this.tableData1.forEach(item => {
+                if (item.target && item.result) {
+                    item.difference = item.result - item.target
+                } else {
+                    item.difference = '--'
+                }
+            })
+            this.userTotal = 0;
+            this.userComplete = 0;
+            this.userIncomplete = 0;
+            let distribution = [];
+            let userScores = []
+            this.scoreList.forEach(item => {
+                item.level = item.name;
+                item.ratio = item.scale / 100
+                distribution.push(item)
+            })
+            this.users.forEach(user => {
+                this.userTotal++;
+                this.userComplete += user.status === 1 ? 1 : 0;
+                user.level = this.findGrade(user.score, this.gradeLevels);
+                userScores.push(user.score)
+            })
+            this.infos = [
+                { label: "总人数", num: this.userTotal },
+                { label: "已完成", num: this.userComplete },
+                { label: "未评分", num: this.userIncomplete },
+            ]
+            let scoreResult = this.assignLevels(userScores, distribution);
+
+            this.users.forEach(item => {
+                scoreResult.forEach(result => {
+                    if (result.scores.includes(item.score)) {
+                        item.scoreResult = result.level
+                    }
+                })
+            })
+
+            this.loading = false
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        calcScoreList() {
+            let scoreSet = new Set(this.users.map(item => Number(item.score)))
+
+            let scores = [...scoreSet].sort((a, b) => b - a);
+            let rate = 1;
+            let scoreCount = scores.length; // 总人数
+            let scoreIndex = 0;
+            let scoreResult = {};
+
+            this.scoreList.forEach(item => {
+
+                let scale = item.scale / 100;
+                let count = Math.round((scoreCount - scoreIndex) * scale / rate);
+                rate -= scale;
+                if (count <= 0) return;
+
+                for (let i = scoreIndex; i < (scoreIndex + count); i++) {
+                    scoreResult[scores[i]] = item.name;
+                }
+                scoreIndex += count;
+            });
+            return this.users.map(item => {
+                let result = { ...item };
+                result.scoreResult = scoreResult[item.score] || '--'
+                return result;
+            }).sort((a, b) => b.score - a.score);
+        }
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    created() {
+    },
+    methods: {
+
+        changeCateIds(cateIds) {
+            this.cateIds = cateIds
+            this.$bus.$emit("finishEdit", this.reviewPackageId)
+        },
+        changeTitle(title) {
+            this.title = title;
+            this.$bus.$emit("finishEdit", this.reviewPackageId)
+        },
+
+        changeDate(data) {
+            let { startTime, endTime } = data
+            this.startTime = startTime
+            this.endTime = endTime
+            this.$bus.$emit("finishEdit", this.reviewPackageId)
+        },
+
+        
+
+        // 获取全局等级设置
+        async getAllSet() {
+            let res = await this.$axiosUser('get', 'api/pro/per/user/base_config')
+            let data = res.data.data;
+            let levels = data.level_scope.levels;
+            let gradeLevels = [];
+            let max = 0;//最大值
+            if (levels && levels.length > 0) {
+                levels.forEach((item, index) => {
+                    var obj;
+                    if (index == 0) {
+                        obj = { name: item.name, max: Number(item.value), min: 0 };
+                    } else {
+                        obj = { name: item.name, max: Number(item.value), min: max };//当不是第一个等级时,最小值为上一个的最大值
+                    }
+                    max = item.value;
+                    gradeLevels.push(obj);
+                })
+                this.gradeLevels = gradeLevels
+            }
+        },
+
+        // 查找分数对应的等级
+        findGrade(score, gradeLevels) {
+            for (let i = 0; i < gradeLevels.length; i++) {
+                if (score && score >= gradeLevels[i].min && score && score <= gradeLevels[i].max) {
+                    return gradeLevels[i].name; // 返回对应的等级描述
+                } else if (score && score <= gradeLevels[0].min) {
+                    return gradeLevels[0].name; // 返回对应的等级描述
+                } else if (score && score >= gradeLevels[gradeLevels.length - 1].max) {
+                    return gradeLevels[gradeLevels.length - 1].name; // 返回对应的等级描述
+                }
+            }
+            return "未评分"; // 如果分数不在任何范围内
+        },
+
+        
+
+        assignLevels(scores, levelConfigs) {
+            // 降序排序并去重(假设分数不重复,可省略去重)
+            const sortedScores = [...scores].sort((a, b) => b - a);
+            const total = sortedScores.length;
+            if (total === 0) return [];
+
+            // 归一化处理比例
+            const totalRatio = levelConfigs.reduce((sum, cfg) => sum + cfg.ratio, 0);
+            const normalized = levelConfigs.map(cfg => cfg.ratio / totalRatio);
+
+            // 计算每个等级的初始人数
+            let counts = normalized.map(ratio => Math.floor(total * ratio));
+            let remainder = total - counts.reduce((sum, c) => sum + c, 0);
+
+            // 分配剩余人数,按优先级顺序
+            let idx = 0;
+            while (remainder > 0 && idx < counts.length) {
+                counts[idx]++;
+                remainder--;
+                idx++;
+            }
+
+            // 构建结果:按人数切割数组
+            let start = 0;
+            return counts.map((count, i) => {
+                const end = start + count;
+                const levelScores = sortedScores.slice(start, end);
+                start = end;
+                return {
+                    level: levelConfigs[i].level,
+                    scores: levelScores
+                };
+            });
+        },
+
+        // 选项卡点击事件
+        handleClick(tab, event) {
+            this.tableData2 = []
+            if (this.activeName == 2) {
+                let groups = _.groupBy(this.tableData1, item => `${item.title}(_)${item.target === null || item.target === '' ? 'null' : item.target}(_)${item.unit === null || item.unit === '' ? 'null' : item.unit}`);
+                Object.keys(groups).forEach(key => {
+                    let group = {
+                        title: '',
+                        target: '',
+                        unit: '',
+                        userCount: 0,
+                        scoredCount: 0,
+                        standardCount: 0,
+                        failCount: 0,
+                        standardRate: '--',
+                        totalScore: 0,
+                        totalResult: 0,
+                        avgScore: 0,
+                        avgResult: 0,
+                        standardResultRate: '--'
+                    };
+                    groups[key].forEach(indicator => {
+                        group.title = indicator.title; // 指标名称
+                        group.target = indicator.target; // 目标
+                        group.unit = indicator.unit; // 单位
+                        let standardCount = indicator.difference !== '--' && indicator.difference >= 0 ? 1 : 0; // 
+                        group.userCount += 1;
+                        group.scoredCount += indicator.score !== null ? 1 : 0;
+                        group.standardCount += standardCount;
+                        group.failCount += standardCount === 1 ? 0 : 1;
+                        if (indicator.score !== null) group.totalScore += indicator.score;
+                        if (indicator.result !== null) group.totalResult += indicator.result;
+                    });
+                    group.standardCount = group.standardCount * 100;
+                    if (group.userCount > 0) {
+                        let rate = Math.floor(group.standardCount / group.userCount * 100 * 0.01);
+                        let avgScore = Math.floor(group.totalScore / group.userCount * 100 * 0.01);
+                        let avgResult = Math.floor(group.totalResult / group.userCount * 100 * 0.01);
+                        group.standardRate = rate > 0 ? `${rate}%` : '--';
+                        group.avgScore = avgScore !== 0 ? avgScore : '--';
+                        group.avgResult = avgResult !== 0 ? avgResult : '--';
+
+                        if (group.target !== null && group.avgResult !== '--') {
+                            let standardResultRate = Math.floor((group.avgResult - group.target) / group.target * 100 * 0.01 * 100) ;
+                            group.standardResultRate = standardResultRate !== 0 ? `${standardResultRate}%` : '--';
+                        }
+                    }
+                    this.tableData2.push(group);
+                    console.log(this.tableData2);
+                })
+            }
+        },
+        closeTargetList() {
+            this.targetDialogVisible = false
+        },
+
+        openTargetList(okrs) {
+            if (okrs && okrs.length > 0) {
+                this.okrs = okrs
+                this.targetDialogVisible = true
+            } else {
+                return this.$message.error("暂无关联okr")
+            }
+        }
+    }
+}
+
+</script>
+
+<style>
+.oneLine {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+</style>
+
+
+<style scoped="scoped" lang="scss">
+.record-right {
+    width: 49.6%;
+    height: 100%;
+    border-radius: 5px;
+    background: #fff;
+    padding: 10px 20px;
+    box-sizing: border-box;
+    overflow: hidden;
+    overflow-y: auto;
+
+    .title-container {
+        display: flex;
+        align-items: center;
+
+        .searchBox {
+            width: 300px;
+            .search-title {
+                border-bottom: 1px solid #f1f1f1;
+                font-size: 16px;
+                font-weight: 700;
+                padding: 0 10px;
+                padding-bottom: 10px;
+            }
+        }
+        
+
+        .title {
+            font-weight: 700;
+            font-size: 16px;
+        }
+    }
+
+    .line {
+        width: 100%;
+        height: 1px;
+        background: #f1f1f1;
+        margin: 10px 0;
+    }
+
+    .score-list {
+        display: flex;
+        width: 100%;
+        margin-top: 20px;
+        .score-item {
+            flex: 0 0 calc((100% - 80px) / 5);
+            height: 100px;
+            padding: 10px;
+            margin: 0 20px 20px 0;
+            box-sizing: border-box;
+            border-radius: 6px;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: space-around;
+            font-size: 16px;
+            color: #999;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
+
+            &-title {
+                font-weight: 600;
+                color: #409EFF;
+            }
+
+            &-num {
+                color: #000;
+                font-weight: 600;
+            }
+
+            &:nth-child(5n) {
+                /* 去除第5n个的margin-right */
+                margin-right: 0;
+            }
+        }
+    }
+       
+
+    .user-info {
+        width: 100%;
+        height: 200px;
+        overflow: hidden;
+        overflow-x: auto;
+        /* 设置滚动条的宽度和背景色 */
+        &::-webkit-scrollbar {
+            width: 10px;
+            height: 10px;
+            background-color: #f9f9f9;
+        }
+    
+        /* 设置滚动条滑块的样式 */
+        &::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
+    
+        /* 设置滚动条滑块hover样式 */
+        &::-webkit-scrollbar-thumb:hover {
+            background-color: #a8a8a8;
+        }
+    
+        /* 设置滚动条轨道的样式 */
+        &::-webkit-scrollbar-track {
+            box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+            border-radius: 6px;
+            background: #ededed;
+        }
+        .info-card {
+            width: 220px;
+            height: 100%;
+            margin-right: 20px;
+            border: 1px solid #f1f1f1;
+            padding: 5px 10px;
+            border-radius: 6px;
+            box-sizing: border-box;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
+            .info {
+                display: flex;
+                align-items: center;
+                height: 36px;
+        
+                .info-label {
+                    width: 100px;
+                }
+            }
+        }
+        
+    }
+}
+</style>

+ 304 - 175
src/newPerformance/components/ExamineRecord/RightExamineRecord.vue

@@ -1,35 +1,19 @@
 <template>
     <div class="record-right scroll-box" style="overflow-y: auto;" v-loading="loading">
-        <div class="title-container flex-box-ce" style="justify-content: space-between;">
+        <!-- 考核表详情 -->
+        <div class="title-container" style="margin-bottom: 10px;">
+            <div class="title">考核详情
+            </div>
+        </div>
+        <div class="title-container flex-box-ce" style="height: 30px; justify-content: space-between;">
             <div class="flex-box-ce">
-                <el-popover placement="bottom" trigger="click" ref="searchPopover">
-                    <div class="searchBox">
-                        <div class="search-title"
-                            style="border-bottom: 1px solid #f1f1f1; font-size: 16px; font-weight: 700; padding: 0 10px; padding-bottom: 10px;">
-                            修改考核表
-                        </div>
-
-                        <div style="margin: 10px 0;">考核表名称</div>
-
-                        <EditTitle v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :title="title"
-                            @handleEditTile="changeTitle" />
-                        <div style="margin: 10px 0;">考核表时间</div>
-                        <EditCircle v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :startTime="startTime"
-                            :endTime="endTime" @comfirmChanged="changeDate" />
-                        <div style="margin: 10px 0;">考核分类</div>
-
-                        <EditCateType v-if="reviewPackageId" :reviewPackageId="reviewPackageId" :cateIds="cateIds"
-                            :cateList="cateList" @comfirmChanged="changeCateIds" />
-                            
-                    </div>
-                    <template slot="reference">
-                        <div class="gd">
-                            <i class="el-icon-edit" style="margin-right: 5px; color: #999;"></i>
-                            <strong style="margin-right: 5px;">{{ title }}</strong>
-                            {{ startTime | formatDate }} 至 {{ endTime | formatDate }}
-                        </div>
-                    </template>
-                </el-popover>
+                <div class="gd">
+                    <strong style="margin-right: 5px;">{{ title }}</strong>
+                    {{ startTime | formatDate }} 至 {{ endTime | formatDate }}
+                </div>
+
+                <!-- <el-button v-if="packageOkrs && packageOkrs.length > 0" type="text" style="margin-left: 10px;"
+                    @click="openTargetList(packageOkrs)">考核表OKR</el-button> -->
             </div>
             <div>
                 <!-- 正太规则 -->
@@ -40,8 +24,8 @@
         <div class="line"></div>
 
 
-        <!-- 考核人员列表 -->
-        <div class="score-list">
+        <!-- 考核人员分数列表 -->
+        <!-- <div class="score-list">
             <div class="score-item heartBeat animated">
                 <div class="score-item-title">总人数</div>
                 <div class="score-item-num">{{ userTotal }}</div>
@@ -52,57 +36,86 @@
                 <div class="score-item-num">{{ users.filter(user => user.level === item.name).length }}</div>
                 <div class="score-item-percent">{{ item.min + '~' + item.max }}</div>
             </div>
+        </div> -->
+
+
+
+        <div style="width: 100%; display: flex;">
+            <div style="width: 50%; border-right: 1px solid #f1f1f1;">
+                <div ref="echarts2" class="echarts"></div>
+            </div>
+            <div style="width: 50%; padding: 0 20px; box-sizing: border-box;">
+                <el-table :data="gradeLevels" style="width: 100%;" stripe :header-cell-style="{ background: '#f5f7fa' }">
+                    <el-table-column prop="name" label="等级">
+                        <template slot-scope="scope">
+                            {{ scope.row.name }} ({{ scope.row.min + '~' + scope.row.max }})
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="name" label="人数">
+                        <template slot-scope="scope">
+                            {{ users.filter(user => user.level === scope.row.name).length }}
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </div>
         </div>
 
-        <table>
-            <tr>
-                <td v-for="item in users" :key="item.employeeId">{{ item.employeeName }}</td>
-            </tr>
+        <!-- 考核人员列表 -->
+        <div class="title-container" style="margin: 10px 0;">
+            <div class="title">考核人员列表</div>
+        </div>
 
-            <tr>
-                <td v-for="item in users" :key="item.employeeId">{{ item.score || '--' }}</td>
-            </tr>
+        <el-table :data="users" style="width: 100%; " :header-cell-style="{ background: '#f5f7fa' }">
+            <el-table-column prop="employeeName" label="员工">
+            </el-table-column>
 
-            <tr>
-                <td v-for="item in users" :key="item.employeeId">
-                    <el-tag v-if="item.status" type="success">
+            <el-table-column prop="status" label="考核状态">
+                <template slot-scope="scope">
+                    <el-tag v-if="scope.row.status" type="success">
                         已完成
                     </el-tag>
-                    <el-tag v-else="item.status" type="warning">
+                    <el-tag v-else="scope.row.status" type="warning">
                         进行中
                     </el-tag>
-                </td>
-            </tr>
-            <tr>
-                <td v-for="item in users" :key="item.employeeId">
-                    <el-tag v-if="item.level === '未评分'" type="info">
+                </template>
+            </el-table-column>
+
+            <el-table-column prop="level" label="评分">
+                <template slot-scope="scope">
+                    <el-tag v-if="scope.row.level === '未评分'" type="info">
                         未评分
                     </el-tag>
                     <el-tag v-else>
                         {{ item.level }}
                     </el-tag>
-                </td>
-            </tr>
+                </template>
+            </el-table-column>
 
-            <tr>
-                <td v-for="item in users" :key="item.employeeId">
+            <el-table-column prop="scoreResult" label="正态分布">
+                <template slot-scope="scope">
                     <el-tag>
-                        {{ item.scoreResult }}
+                        {{ scope.row.scoreResult }}
                     </el-tag>
-                </td>
-            </tr>
-        </table>
+                </template>
+            </el-table-column>
+
+            <el-table-column label="操作">
+                <template slot-scope="scope">
+                    <el-link type="primary" @click="getDetails()">查看详情</el-link>
+                </template>
+            </el-table-column>
+
+        </el-table>
 
 
         <!-- 指标列表 -->
-        <div class="title-container" style="margin: 20px 0;">
+        <div class="title-container" style="margin: 10px 0;">
             <div class="title">指标信息</div>
         </div>
 
         <el-tabs type="card" v-model="activeName" @tab-click="handleClick">
             <el-tab-pane label="默认" name="1">
-                <el-table ref="fmeaTableRef2" :data="tableData1" stripe style="width: 100%" border
-                    v-table-move="['fmeaTableRef2']" :header-cell-style="{ background: '#f5f7fa' }" :height="300">
+                <el-table  :data="tableData1" stripe style="width: 100%" border  :header-cell-style="{ background: '#f5f7fa' }" :height="500">
                     <el-table-column prop="employeeName" label="员工" align="center"></el-table-column>
                     <el-table-column prop="title" label="指标" align="center">
                         <template slot-scope="scope">
@@ -113,7 +126,7 @@
                         </template>
                     </el-table-column>
 
-                    <el-table-column prop="target" label="目标" align="center" width="100">
+                    <el-table-column prop="target" label="目标" align="center" >
                         <template slot-scope="scope">
                             <span v-if="scope.row.target !== null">
                                 {{ `${scope.row.target} ${scope.row.unit ? scope.row.unit : ''}` }}
@@ -121,19 +134,19 @@
                             <span v-else>--</span>
                         </template>
                     </el-table-column>
-                    <el-table-column prop="result" label="结果" align="center" width="100">
+                    <el-table-column prop="result" label="结果" align="center">
                         <template slot-scope="scope">
                             <div>
                                 {{ scope.row.result || '--' }}
                             </div>
                         </template>
                     </el-table-column>
-                    <el-table-column prop="different" label="差距" align="center" width="100">
+                    <el-table-column prop="different" label="差距" align="center" >
                         <template slot-scope="scope">
-                            <el-tag v-if="scope.row.difference < 0" type="success">
+                            <el-tag v-if="scope.row.difference > 0" type="success">
                                 {{ scope.row.difference }}
                             </el-tag>
-                            <el-tag v-else-if="scope.row.difference > 0" type="danger">
+                            <el-tag v-else-if="scope.row.difference < 0" type="danger">
                                 {{ scope.row.difference }}
                             </el-tag>
                             <div v-else>
@@ -141,19 +154,30 @@
                             </div>
                         </template>
                     </el-table-column>
-                    <el-table-column prop="score" label="评分" align="center" width="100">
+                    <el-table-column prop="score" label="评分" align="center" >
                         <template slot-scope="scope">
                             <div>
                                 {{ scope.row.score || '--' }}
                             </div>
                         </template>
                     </el-table-column>
+
+                    <el-table-column prop="okrs" label="过程跟踪" align="center">
+                        <template slot-scope="scope">
+                            <el-link v-if="scope.row.okrs && scope.row.okrs.length > 0" type="primary"
+                                @click="openTargetList(scope.row.okrs)">
+                                指标OKR
+                            </el-link>
+                            <span v-else class="fontColorC">暂无关联</span>
+                        </template>
+                    </el-table-column>
                 </el-table>
+                <div style="margin-top: 20px;"></div>
+
             </el-tab-pane>
 
             <el-tab-pane label="按指标/目标/单位聚合" name="2">
-                <el-table ref="fmeaTableRef3" :data="tableData2" stripe style="width: 100%" border
-                    v-table-move="['fmeaTableRef3']" :header-cell-style="{ background: '#f5f7fa' }" :height="300">
+                <el-table :data="tableData2" stripe style="width: 100%" border:header-cell-style="{ background: '#f5f7fa' }" :height="500">
                     <el-table-column prop="title" label="指标" align="center">
                         <template slot-scope="scope">
                             <el-tooltip class="item" effect="dark" placement="top">
@@ -163,7 +187,7 @@
                         </template>
                     </el-table-column>
 
-                    <el-table-column prop="target" label="目标" align="center" width="100">
+                    <el-table-column prop="target" label="目标" align="center">
                         <template slot-scope="scope">
                             <div>
                                 <span v-if="scope.row.target !== null">
@@ -207,24 +231,30 @@
                     <el-table-column prop="avgScore" label="平均分" align="center" />
                 </el-table>
 
-
             </el-tab-pane>
         </el-tabs>
 
+        <div style="height: 50px;"></div>
+
+        <!-- 关联okr -->
+        <TargetListComp v-if="targetDialogVisible" :dialogVisible="targetDialogVisible" :ids="okrs"
+            @close="closeTargetList">
+        </TargetListComp>
     </div>
 </template>
 
-
 <script>
 import { mapGetters } from 'vuex';
 import moment from 'moment';
 import _ from "lodash"
+import ECharts from 'echarts';
 
 import EditTitle from './RightEamineComp/EditTitle'; // 修改考核标题组件
 import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
 import EditScoreList from './RightEamineComp/EditScoreList'
 import EditCircle from './RightEamineComp/EditCircle.vue';
 import EditCateType from './RightEamineComp/EditCateType.vue';
+import TargetListComp from "@/performance/views/assessManagement/TargetListComp.vue"; // 关联OKR弹框
 
 export default {
     components: {
@@ -232,7 +262,8 @@ export default {
         EditScoreList,
         EmployeeSelector,
         EditCircle,
-        EditCateType
+        EditCateType,
+        TargetListComp
     },
     props: {
         detailInfo: {
@@ -264,15 +295,65 @@ export default {
             userIncomplete: 0,
             infos: [],
             gradeLevels: [],
-            cateIds: [] // 选择的考核分类
+            cateIds: [], // 选择的考核分类
+            targetDialogVisible: false,
+            okrs: [],
+            packageOkrs: []
         };
     },
     watch: {
         detailInfo(v) {
+            this.initData();
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        calcScoreList() {
+            let scoreSet = new Set(this.users.map(item => Number(item.score)))
+
+            let scores = [...scoreSet].sort((a, b) => b - a);
+            let rate = 1;
+            let scoreCount = scores.length; // 总人数
+            let scoreIndex = 0;
+            let scoreResult = {};
+
+            this.scoreList.forEach(item => {
+
+                let scale = item.scale / 100;
+                let count = Math.round((scoreCount - scoreIndex) * scale / rate);
+                rate -= scale;
+                if (count <= 0) return;
+
+                for (let i = scoreIndex; i < (scoreIndex + count); i++) {
+                    scoreResult[scores[i]] = item.name;
+                }
+                scoreIndex += count;
+            });
+            return this.users.map(item => {
+                let result = { ...item };
+                result.scoreResult = scoreResult[item.score] || '--'
+                return result;
+            }).sort((a, b) => b.score - a.score);
+        }
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    mounted() {
+        if (this.detailInfo && this.detailInfo.reviewPackageId) this.initData();
+    },
+    created() {
+    },
+    methods: {
+        async initData() {
             this.loading = true
             this.activeName = '1'
-            let { data: { data: { cateIds, indicators, startTime, endTime, title, distribution: { items }, users }, code }, reviewPackageId } = v
-            this.getAllSet();
+            let { data: { data: { cateIds, indicators, startTime, endTime, title, okrs, distribution: { items }, users }, code }, reviewPackageId } = this.detailInfo
+            await this.getAllSet();
+            this.packageOkrs = okrs;
             this.cateIds = cateIds;
             this.tableData1 = [];
             this.tableData2 = [];
@@ -321,50 +402,66 @@ export default {
                     }
                 })
             })
-
+            this.getResult();
             this.loading = false
-        }
-    },
-    computed: {
-        ...mapGetters(['user_info']),
-        calcScoreList() {
-            let scoreSet = new Set(this.users.map(item => Number(item.score)))
-
-            let scores = [...scoreSet].sort((a, b) => b - a);
-            let rate = 1;
-            let scoreCount = scores.length; // 总人数
-            let scoreIndex = 0;
-            let scoreResult = {};
-
-            this.scoreList.forEach(item => {
-
-                let scale = item.scale / 100;
-                let count = Math.round((scoreCount - scoreIndex) * scale / rate);
-                rate -= scale;
-                if (count <= 0) return;
-
-                for (let i = scoreIndex; i < (scoreIndex + count); i++) {
-                    scoreResult[scores[i]] = item.name;
-                }
-                scoreIndex += count;
-            });
-            return this.users.map(item => {
-                let result = { ...item };
-                result.scoreResult = scoreResult[item.score] || '--'
-                return result;
-            }).sort((a, b) => b.score - a.score);
-        }
-    },
-    filters: {
-        formatDate(val) {
-            if (val) return moment(val).format('YYYY-MM-DD')
-            else return "--"
-        }
-    },
-    created() {
-    },
-    methods: {
+        },
+        getResult() {
+            let xData = [], yData = []
+            console.log(this.gradeLevels)
+            console.log(this.users)
+            this.gradeLevels.forEach(item => {
+                xData.push(item.name)
+                yData.push(this.users.filter(user => user.level === item.name).length)
+            })
+            console.log(xData);
+            console.log(yData);
+            let option = {
+                tooltip: {
+                    trigger: 'axis',
+                    axisPointer: {
+                        // 坐标轴指示器,坐标轴触发有效
+                        type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
+                    }
+                },
+                xAxis: {
+                    type: 'category',
+                    data: xData
+                },
+                yAxis: {
+                    type: 'value',
+                    minInterval: 1
+                },
+                series: [
+                    {
+                        data: yData,
+                        type: 'bar',
+                        itemStyle: {
+                            normal: {
+                                //这里是重点
+                                color: function (params) {
+                                    //注意,如果颜色太少的话,后面颜色不会自动循环,最好多定义几个颜色
+                                    var colorList = ['#409EFF', '#4ECB73', '#36CBCB', '#F2637B', '#FBD437', '#749f83', '#ca8622'];
+                                    var index;
+                                    //给大于颜色数量的柱体添加循环颜色的判断
+                                    if (params.dataIndex >= colorList.length) {
+                                        index = params.dataIndex - colorList.length;
+                                        return colorList[index];
+                                    }
+                                    return colorList[params.dataIndex];
+                                }
+                            }
+                        },
+                        barMaxWidth: 30
+                    }
+                ]
+            };
+            var myChart = ECharts.init(this.$refs.echarts2);
+            myChart.setOption(option);
+        },
 
+        getDetails() {
+            this.$emit('changeCurrentId', { currentId: '2', reviewId: this.reviewPackageId })
+        },
         changeCateIds(cateIds) {
             this.cateIds = cateIds
             this.$bus.$emit("finishEdit", this.reviewPackageId)
@@ -419,31 +516,7 @@ export default {
             return "未评分"; // 如果分数不在任何范围内
         },
 
-        // calcScoreResult(users) {
-        //     let scoreSet = new Set(users.map(item => Number(item.score)));
-        //     let scores = [...scoreSet].sort((a, b) => b - a);
-        //     let rate = 1;
-        //     let scoreCount = scores.length;
-        //     let scoreIndex = 0;
-        //     let scoreResult = {};
-        //     this.scoreList.forEach(item => {
-        //         let scale = item.scale / 100;
-        //         let count = Math.round((scoreCount - scoreIndex) * scale / rate);
-        //         rate -= scale;
-        //         if (count <= 0) return;
-
-        //         for (let i = scoreIndex; i < (scoreIndex + count); i++) {
-        //             scoreResult[scores[i]] = item.name;
-        //         }
-        //         scoreIndex += count;
-        //     });
-        //     console.log("-===", scoreResult)
-        //     return users.map(item => {
-        //         let result = { ...item };
-        //         result.scoreResult = scoreResult[item.score] || '--'
-        //         return result;
-        //     }).sort((a, b) => b.score - a.score);
-        // },
+        
 
         assignLevels(scores, levelConfigs) {
             // 降序排序并去重(假设分数不重复,可省略去重)
@@ -502,7 +575,6 @@ export default {
                         standardResultRate: '--'
                     };
                     groups[key].forEach(indicator => {
-                        console.log(indicator)
                         group.title = indicator.title; // 指标名称
                         group.target = indicator.target; // 目标
                         group.unit = indicator.unit; // 单位
@@ -514,6 +586,7 @@ export default {
                         if (indicator.score !== null) group.totalScore += indicator.score;
                         if (indicator.result !== null) group.totalResult += indicator.result;
                     });
+                    group.standardCount = group.standardCount * 100;
                     if (group.userCount > 0) {
                         let rate = Math.floor(group.standardCount / group.userCount * 100 * 0.01);
                         let avgScore = Math.floor(group.totalScore / group.userCount * 100 * 0.01);
@@ -523,7 +596,7 @@ export default {
                         group.avgResult = avgResult !== 0 ? avgResult : '--';
 
                         if (group.target !== null && group.avgResult !== '--') {
-                            let standardResultRate = Math.floor((group.avgResult - group.target) / group.target * 100 * 0.01);
+                            let standardResultRate = Math.floor((group.avgResult - group.target) / group.target * 100 * 0.01 * 100) ;
                             group.standardResultRate = standardResultRate !== 0 ? `${standardResultRate}%` : '--';
                         }
                     }
@@ -531,6 +604,18 @@ export default {
                     console.log(this.tableData2);
                 })
             }
+        },
+        closeTargetList() {
+            this.targetDialogVisible = false
+        },
+
+        openTargetList(okrs) {
+            if (okrs && okrs.length > 0) {
+                this.okrs = okrs
+                this.targetDialogVisible = true
+            } else {
+                return this.$message.error("暂无关联okr")
+            }
         }
     }
 }
@@ -548,14 +633,41 @@ export default {
 
 <style scoped="scoped" lang="scss">
 .record-right {
-    width: 49.6%;
-    height: 100%;
+    width: 100%;
     border-radius: 5px;
     background: #fff;
-    padding: 20px;
+    padding: 10px 20px;
     box-sizing: border-box;
     overflow: hidden;
     overflow-y: auto;
+    .echarts {
+        height: 350px;
+        width: 100%;
+    }
+    /* 设置滚动条的宽度和背景色 */
+    ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
+        width: 6px;
+        height: 6px;
+        background-color: #f9f9f9;
+    }
+
+    /* 设置滚动条滑块的样式 */
+    ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
+        border-radius: 6px;
+        background-color: #c1c1c1;
+    }
+
+    /* 设置滚动条滑块hover样式 */
+    ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
+        background-color: #a8a8a8;
+    }
+
+    /* 设置滚动条轨道的样式 */
+    ::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
+        box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+        border-radius: 6px;
+        background: #ededed;
+    }
 
     .title-container {
         display: flex;
@@ -583,14 +695,13 @@ export default {
         width: 100%;
         height: 1px;
         background: #f1f1f1;
-        margin: 20px 0;
+        margin: 10px 0;
     }
 
     .score-list {
         display: flex;
         width: 100%;
         margin-top: 20px;
-
         .score-item {
             flex: 0 0 calc((100% - 80px) / 5);
             height: 100px;
@@ -622,39 +733,57 @@ export default {
             }
         }
     }
+       
 
-    table {
+    .user-info {
         width: 100%;
-        border-collapse: collapse;
-        /* 合并表格边框 */
-        border: 1px solid #ccc;
-        /* 设置表格边框样式和颜色 */
-        margin: 0 auto 10px auto;
-        /* 设置表格外边距 */
-        background-color: #f8f8f8;
-        /* 设置表格背景颜色 */
-        color: #000;
-        /* 设置表格文字颜色 */
-        text-align: center;
-        /* 设置表格文字居中 */
-        line-height: 40px;
-        border-radius: 6px;
-
-        /* 设置表格行高 */
-        tr:nth-child(1) {
-            background-color: #f2f2f2;
-            /* 偶数行背景色 */
+        height: 200px;
+        overflow: hidden;
+        overflow-x: auto;
+        /* 设置滚动条的宽度和背景色 */
+        &::-webkit-scrollbar {
+            width: 10px;
+            height: 10px;
+            background-color: #f9f9f9;
         }
-
-        tr:nth-child(even) {
-            background-color: #fff;
-            /* 偶数行背景色 */
+    
+        /* 设置滚动条滑块的样式 */
+        &::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
         }
-
-        tr:nth-child(odd) {
-            background-color: #f2f2f2;
-            /* 奇数行背景色 */
+    
+        /* 设置滚动条滑块hover样式 */
+        &::-webkit-scrollbar-thumb:hover {
+            background-color: #a8a8a8;
+        }
+    
+        /* 设置滚动条轨道的样式 */
+        &::-webkit-scrollbar-track {
+            box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+            border-radius: 6px;
+            background: #ededed;
         }
+        .info-card {
+            width: 220px;
+            height: 100%;
+            margin-right: 20px;
+            border: 1px solid #f1f1f1;
+            padding: 5px 10px;
+            border-radius: 6px;
+            box-sizing: border-box;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
+            .info {
+                display: flex;
+                align-items: center;
+                height: 36px;
+        
+                .info-label {
+                    width: 100px;
+                }
+            }
+        }
+        
     }
 }
 </style>

+ 640 - 0
src/newPerformance/components/IndicatorSetting.vue

@@ -0,0 +1,640 @@
+<template>
+    <div class="all">
+        <div class="create-methods flex-box-ce">
+
+            <div class="flex-box-ce">
+                <div class="method-item" v-for="(item, index) in methods"
+                    :class="currentIndex === index ? 'active' : ''" :key="item.id"
+                    @click="changeMethod(item.id, index)">
+                    {{ item.title }}
+                </div>
+            </div>
+            <el-tooltip effect="dark" content="教程指引" placement="top">
+                <div class="icon flex-center-center" @click="initStepData()">
+                    <i class="el-icon-document"></i>
+                </div>
+            </el-tooltip>
+        </div>
+
+
+        <div class="setting-content flex-1" v-if="method == 1">
+            <UploadPublish2 ref="UploadPublish2" />
+        </div>
+
+        <div class="setting-content flex-1" v-if="method == 2">
+            <UploadPublish />
+        </div>
+        
+        <el-dialog :visible.sync="dialogVisible" title="已创建的指标库" center width="600px">
+            <div class="dialog-content">
+                <template v-if="templateList.length > 0">
+                    <div class="flex-box-ce template-item" v-for="(item, index) in templateList" :key="index">
+                        <div class="flex-box flex-1" @click="chooseTemplate(item)">
+                            <div style="width: 20px;"><i class="el-icon-tickets blue"></i></div>
+                            <span class="message-content">{{ item.title || '默认标题' }}</span>
+                        </div>
+
+                        <div>
+                            <el-popconfirm confirm-button-text='确定' cancel-button-text='不用了' icon="el-icon-info"
+                                icon-color="red" title="确定删除这个考核模板吗?" @confirm="confirmDelete(item.templateId)"
+                                @cancel="cancelDelete()">
+                                <el-link slot="reference" type="danger">删除</el-link>
+                            </el-popconfirm>
+                        </div>
+                    </div>
+                </template>
+                <noData v-else content="暂无内容" imgW="120px" imgH="120px"></noData>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import TemplateDetails from './IndicatorSetting/TemplateDetails.vue';
+import PublishComp from './IndicatorSetting/PublishComp.vue';
+import TemplateMixedPublish from './IndicatorSetting/TemplateMixedPublish.vue';
+import UploadPublish from './IndicatorSetting/UploadPublish.vue';
+import UploadPublish2 from './IndicatorSetting/UploadPublish2.vue';
+import indicatorSettingStep from "@/newPerformance/utils/indicatorSettingStep"
+import introJs from 'intro.js'
+import 'intro.js/introjs.css'
+export default {
+    components: {
+        TemplateDetails,
+        PublishComp,
+        TemplateMixedPublish,
+        UploadPublish,
+        UploadPublish2
+    },
+    data() {
+        return {
+            dialogVisible: false,
+            currentIndex: 0,
+            methods: [
+                // { id: 0, title: "方式1:手动创建指标" },
+                { id: 1, title: "方式1:快速创建" },
+                { id: 2, title: "方式2:批量导入指标" },
+            ],
+            method: 1,
+            loading: false,
+            templateList: [],
+            step: 0,
+            activeIndex: 1,
+            templateId: "",
+        }
+        
+    },
+
+    watch: {
+        step(v) {
+            this.activeIndex = v + 1
+        }
+    },
+
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+
+
+    mounted() {
+        this.$nextTick(() => {
+            if (!localStorage.getItem("isLearned")) {
+                this.startGuide()
+            }
+        })
+    },
+
+    created() {
+    },
+
+    methods: {
+
+        startGuide() {
+            introJs().setOptions({
+                nextLabel: '下一个',  // 下一个按钮文字
+                prevLabel: '上一个',  // 上一个按钮文字
+                skipLabel: '跳过',    // 跳过按钮文字
+                doneLabel: '立即体验',// 完成按钮文字
+                tooltipClass: 'intro-tooltip', /* 引导说明文本框的样式 */
+                highlightClass: 'intro-highlight', /* 说明高亮区域的样式 */
+                exitOnEsc: true, /* 是否使用键盘Esc退出 */
+                exitOnOverlayClick: false, /* 是否允许点击空白处退出 */
+                keyboardNavigation: true, /* 是否允许键盘来操作 */
+                showBullets: false, /* 是否使用点显示进度 */
+                showProgress: false, /* 是否显示进度条 */
+                scrollToElement: true, /* 是否滑动到高亮的区域 */
+                overlayOpacity: 0.5, // 遮罩层的透明度 0-1之间
+                positionPrecedence: ['bottom', 'top', 'right', 'left'], /* 当位置选择自动的时候,位置排列的优先级 */
+                disableInteraction: false, /* 是否禁止与元素的相互关联 */
+                hidePrev: true, /* 是否在第一步隐藏上一步 */
+                hidePrev: false,       // 在第一步中是否隐藏上一个按钮
+                hideNext: false,       // 在最后一步中是否隐藏下一个按钮
+                exitOnOverlayClick: false,  // 点击叠加层时是否退出介绍
+                showStepNumbers: false,     // 是否显示红色圆圈的步骤编号
+                disableInteraction: true,   // 是否禁用与突出显示的框内的元素的交互,就是禁止点击
+                showBullets: true,        // 是否显示面板指示点
+                // 配置内容 steps数组,内部一个对象代表一个步骤
+                steps: indicatorSettingStep
+            }).onbeforechange((e) => {
+                console.log(e)
+                // if (e.className.includes("choose-btn")) {
+                //     this.$refs.UploadPublish2.openTemplate()
+                // }
+            }).oncomplete(function () {
+                //点击跳过按钮后执行的事件
+                localStorage.setItem("isLearned", true)
+            }).onexit(function () {
+                //点击结束按钮后, 执行的事件
+                localStorage.setItem("isLearned", true)
+            }).start()
+        },
+
+        // 开启教程指引
+        initStepData() {
+            setTimeout(() => {
+                this.startGuide();
+            }, 300)
+        },
+
+        changeMethod(id, index) {
+            this.method = id;
+            this.currentIndex = index;
+        },
+        chooseTemplate(item) {
+            this.templateId = item.templateId
+            this.step = 1;
+            this.dialogVisible = false;
+        },
+        getTemplateList() {
+            this.$axiosUser("get", `/performance/template/list/self/${this.user_info.site_id}`).then(res => {
+                this.templateList = res.data.data.list;
+            })
+        },
+
+        addTemplate() {
+            this.$axiosUser('post', `/performance/template/create/${this.user_info.site_id}`).then(res => {
+                let { data: { data, code } } = res
+                if (code == 1) {
+                    this.$axiosUser("get", `/performance/template/list/self/${this.user_info.site_id}`).then(res => {
+                        this.templateList = res.data.data.list
+                        this.templateId = this.templateList[this.templateList.length - 1].templateId;
+                        this.$message.success("创建成功")
+                        this.step = 1;
+                    })
+                }
+            });
+        },
+
+
+        // 确认删除模板
+        confirmDelete(templateId) {
+            this.$axiosUser('post', `/performance/template/remove/${this.user_info.site_id}/${templateId}`).then(res => {
+                this.$axiosUser("get", `/performance/template/list/self/${this.user_info.site_id}`).then(res => {
+                    this.templateList = res.data.data.list
+                })
+            })
+        },
+        cancelDelete() {
+            console.log("取消删除");
+        },
+
+        prevStep() {
+            if (this.step <= 0) return;
+            this.step--;
+        },
+        nextStep() {
+            if (this.step >= 2) return;
+            this.step++;
+        },
+
+        // 发布考核弹框的回调
+        onPubishConfirm(params, selectOkrs) {
+            let templateId = this.templateId;
+            let { title, startDate, endDate, cycleType, employeeIds, okrs, cateId } = params
+            let employees = employeeIds.map(employee => ({
+                employeeId: employee,
+                ids: [],
+                interviewFlow: {
+                    nodes: [
+                        {
+                            id: "IT_1894283564934688769",
+                            type: "interview",
+                            allows: [],
+                            enable: false,
+                            weight: 0,
+                            children: [],
+                            assigneeIds: [],
+                            leaderLevel: 1,
+                            assigneeType: "self",
+                            multipleType: "or"
+                        }
+                    ]
+                }
+            }))
+
+            let requireParams = {
+                title,
+                cycleType,
+                startDate,
+                endDate,
+                okrs,
+                templates: [
+                    {
+                        templateId,
+                        employees
+                    }
+                ],
+                cateId
+            }
+            // let url = `/performance/review/publish/${this.user_info.site_id}`;
+            // console.log(requireParams)
+            let url = `/performance/review/publish/templates/${this.user_info.site_id}`;
+            this.loading = true
+            this.$http.post(url, requireParams).then(res => {
+                console.log(res)
+                let { data, code, message } = res
+                this.loading = false
+                if (code == 1) {
+                    this.$message.success("发布成功");
+                    this.$emit("changeCurrentId", {currentId: '1'})
+                    setTimeout(() => {
+                        this.$router.push("newPerformance")
+                    }, 1000);
+                } else {
+                    this.$message.error(message || '发布失败');
+                }
+            })
+        },
+        activeStep(index, flag = true) {
+            this.activeIndex = index;
+            if (flag) this.step = index - 1;
+        },
+        changeStep(step) {
+            this.activeIndex = step + 1;
+        }
+    }
+}
+
+</script>
+
+<style lang="scss">
+        .introjs-helperLayer {
+            box-shadow: rgba(33, 33, 33, 0.8) 0px 0px 1px 0px, rgba(33, 33, 33, 0.5) 0px 0px 0px 5000px !important;
+            border: 3px dashed #409eff;
+        }
+
+        /* 调整 intro.js 弹出框的大小 */
+        .introjs-tooltip {
+            width: auto;
+            /* 自动调整宽度 */
+            max-width: 1600px;
+            /* 最大宽度 */
+            height: auto;
+            /* 自动调整高度 */
+            overflow: hidden;
+            /* 防止内容溢出 */
+        }
+    
+        .new-tips {
+            color: #409eff;
+            line-height: 80px;
+            cursor: pointer;
+        }
+    
+        .introjs-tooltip-title {
+            font-size: 16px;
+            width: 80%;
+            padding-top: 10px;
+        }
+    
+        .warper {
+            width: 200px;
+            height: 100px;
+            line-height: 100px;
+            text-align: center;
+            border: 1px solid saddlebrown;
+        }
+    
+        /* 重置引导组件样式(类似element-ui个人使用) */
+        .intro-tooltip {
+            color: #ffff;
+            background: #2c3e50;
+        }
+    
+        /* 引导提示框的位置 */
+        .introjs-bottom-left-aligned {
+            left: 45% !important;
+        }
+    
+        .introjs-right,
+        .introjs-left {
+            top: 30%;
+        }
+    
+        .intro-highlight {
+            background: rgba(255, 255, 255, 0.5);
+        }
+    
+        .introjs-arrow.left {
+            border-right-color: #2c3e50;
+        }
+    
+        .introjs-arrow.top {
+            border-bottom-color: #2c3e50;
+        }
+    
+        .introjs-arrow.right {
+            border-left-color: #2c3e50;
+        }
+    
+        .introjs-arrow.bottom {
+            border-top-color: #2c3e50;
+        }
+    
+        /* 提示框头部区域 */
+        .introjs-tooltip-header {
+            padding-right: 0 !important;
+            padding-top: 0 !important;
+        }
+    
+        .introjs-skipbutton {
+            color: #409eff !important;
+            font-size: 14px !important;
+            font-weight: normal !important;
+            //   padding: 8px 10px !important ;
+        }
+    
+        .introjs-tooltipbuttons {
+            border: none !important;
+        }
+    
+        .introjs-tooltiptext {
+            font-size: 14px !important;
+            padding: 15px !important;
+        }
+    
+        /* 提示框按钮 */
+        .introjs-tooltipbuttons {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+    
+        .introjs-button {
+            width: 50px !important;
+            text-align: center;
+            padding: 4px !important;
+            font-size: 12px !important;
+            font-weight: 500 !important;
+            border-radius: 3px !important;
+            border: none !important;
+        }
+    
+        .introjs-button:last-child {
+            margin-left: 10px;
+        }
+    
+        .introjs-prevbutton {
+            color: #606266 !important;
+            background: #fff !important;
+            border: 1px solid #dcdfe6 !important;
+        }
+    
+        .introjs-nextbutton {
+            color: #fff !important;
+            background-color: #409eff !important;
+            border-color: #409eff !important;
+        }
+    
+        .introjs-disabled {
+            color: #9e9e9e !important;
+            border-color: #bdbdbd !important;
+            background-color: #f4f4f4 !important;
+        }
+</style>
+
+
+<style scoped lang="scss">
+
+
+.dialog-content {
+    width: 100%;
+    height: 500px;
+    overflow-y: auto;
+    padding-bottom: 20px;
+    box-sizing: border-box;
+
+    .template-item {
+        padding: 5px;
+        box-sizing: border-box;
+        transition: all 0.3s;
+
+        &:hover {
+            cursor: pointer;
+            background: #f7f7f7;
+        }
+    }
+
+    /* 设置滚动条的宽度和背景色 */
+    &::-webkit-scrollbar {
+        width: 6px;
+        height: 6px;
+        background-color: #f9f9f9;
+    }
+
+    /* 设置滚动条滑块的样式 */
+    &::-webkit-scrollbar-thumb {
+        border-radius: 6px;
+        background-color: #c1c1c1;
+    }
+
+    /* 设置滚动条滑块hover样式 */
+    &::-webkit-scrollbar-thumb:hover {
+        background-color: #a8a8a8;
+    }
+
+    /* 设置滚动条轨道的样式 */
+    &::-webkit-scrollbar-track {
+        box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+        border-radius: 6px;
+        background: #ededed;
+    }
+
+}
+
+
+
+.all {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    border-radius: 5px;
+    display: flex;
+    flex-direction: column;
+
+    .create-methods {
+        background: #fff;
+        padding: 10px;
+        box-sizing: border-box;
+        justify-content: space-between;
+        .method-item {
+            padding: 15px;
+            border-radius: 4px;
+            border: 1px solid #ebebeb;
+            font-size: 14px;
+            width: 200px;
+            color: #222;
+            margin-right: 12px;
+            cursor: pointer;
+            box-sizing: border-box;
+            &:hover {
+                background-color: #f5f7fa;
+            }
+        }
+        .active {
+            background-color: #f5f7fa;
+            color: #409EFF;
+        }
+        .icon {
+            width: 40px;
+            height: 40px;
+            border: 1px solid #ccc;
+            border-radius: 50%;
+            font-size: 20px;
+            color: #999;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            transition: all 0.3s;
+
+            &:hover {
+                cursor: pointer;
+                background: #f5f5f5;
+            }
+        }
+    }
+
+    .setting-content {
+        margin: 10px 0;
+        background: #fff;
+        padding: 10px;
+        box-sizing: border-box;
+        border-radius: 5px;
+        display: flex;
+        flex-direction: column;
+        position: relative;
+        overflow: hidden;
+        
+        
+        .operation-box {
+            border: 1px solid #eee;
+            padding: 10px;
+            box-sizing: border-box;
+            border-radius: 5px;
+            overflow-y: auto;
+            /* 设置滚动条的宽度和背景色 */
+            &::-webkit-scrollbar {
+                width: 6px;
+                height: 6px;
+                background-color: #f9f9f9;
+            }
+
+        
+            /* 设置滚动条滑块的样式 */
+            &::-webkit-scrollbar-thumb {
+                border-radius: 6px;
+                background-color: #c1c1c1;
+            }
+        
+            /* 设置滚动条滑块hover样式 */
+            &::-webkit-scrollbar-thumb:hover {
+                background-color: #a8a8a8;
+            }
+        
+            /* 设置滚动条轨道的样式 */
+            &::-webkit-scrollbar-track {
+                box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+                border-radius: 6px;
+                background: #ededed;
+            }
+
+        }
+        
+    }
+}
+
+.step-item2 {
+    padding: 0 10px;
+    color: #d6d6d6;
+    text-align: center;
+    height: 60px;
+    cursor: pointer;
+    position: relative;
+}
+
+.step-item2 span {
+    width: 36px;
+    height: 36px;
+    border-radius: 50%;
+    text-align: center;
+    line-height: 28px;
+    font-size: 20px;
+    border: 4px solid #d6d6d6;
+}
+
+.step-item2 div {
+    font-size: 14px;
+    margin-left: 5px;
+}
+
+.active-step2 span {
+    border: 4px solid #409EFF;
+    color: #fff;
+    background-color: #409EFF;
+}
+
+.active-step2 div {
+    color: #409EFF;
+}
+
+.barder-a::before {
+    position: absolute;
+    content: ' ';
+    height: 4px;
+    width: 120px;
+    top: 28px;
+    background-color: #d6d6d6;
+    border-radius: 3px;
+    right: -60px;
+}
+
+.step-item {
+    width: 140px;
+    color: #777777;
+    text-align: center;
+    height: 60px;
+    cursor: pointer;
+}
+
+.step-item span {
+    width: 24px;
+    height: 24px;
+    border-radius: 50%;
+    text-align: center;
+    line-height: 24px;
+    border: 1px solid #ebebeb;
+    margin-right: 10px;
+}
+
+.active-step {
+    background-color: #1583f2;
+    color: #fff;
+}
+
+.active-step span {
+    border: 1px solid #1583f2;
+    background-color: #fff;
+    color: #1583f2;
+}
+</style>

+ 347 - 0
src/newPerformance/components/IndicatorSetting/PerCcSelector.vue

@@ -0,0 +1,347 @@
+<template>
+  <el-dialog
+    :visible.sync="innerVisible"
+    @close="handleClose"
+    @open="initData"
+    @closed="dataReset"
+    append-to-body
+    :close-on-click-modal="false"
+    center
+    title="结果录入节点配置"
+    width="600px"
+  >
+    <el-form v-if="currentNode" label-width="200">
+      <el-form-item label="启用">
+        <el-switch
+          v-model="currentNode.enable"
+        />
+      </el-form-item>
+
+      <el-form-item label="抄送人">
+        <el-radio-group
+          v-model="currentNode.assigneeType"
+          :disabled="!currentNode.enable"
+          @change="onAssigneeTypeChange"
+        >
+          <el-radio-button label="leader">组织管理员</el-radio-button>
+          <el-radio-button label="user">指定人员</el-radio-button>
+          <el-radio-button label="self">被考核人</el-radio-button>
+          <el-radio-button label="post">岗位</el-radio-button>
+          <el-radio-button label="deptLeader">部门负责人</el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+
+      <el-form-item label="">
+        <template v-if="currentNode.assigneeType === 'leader'">
+          <el-select
+            v-model="currentNode.leaderLevel"
+            placeholder="请选择管理员"
+            :disabled="!currentNode.enable"
+            filterable
+            style="width: 300px;"
+            key="leader"
+            @change="onOrgManagerChange"
+          >
+            <el-option
+              v-for="item in levelOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+              style="width: 300px;"
+            />
+          </el-select>
+        </template>
+        <template v-if="currentNode.assigneeType === 'user'">
+          <el-select
+            v-model="userSelected"
+            placeholder="请选择指定人员"
+            multiple
+            :disabled="!currentNode.enable"
+            filterable
+            style="width: 300px;"
+            key="user"
+            @change="onUserChange"
+          >
+            <el-option
+              v-for="item in employees"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+              style="width: 300px;"
+            />
+          </el-select>
+        </template>
+        <template v-if="currentNode.assigneeType === 'post'">
+          <el-select
+            v-model="postSelected"
+            placeholder="请选择岗位"
+            multiple
+            :disabled="!currentNode.enable"
+            filterable
+            style="width: 300px;"
+            key="post"
+            @change="onPostChange"
+          >
+            <el-option
+              v-for="item in postList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+              style="width: 300px;"
+            />
+          </el-select>
+        </template>
+        <template v-if="currentNode.assigneeType === 'deptLeader'">
+          <el-select
+            v-model="deptSelected"
+            placeholder="请选择部门"
+            multiple
+            :disabled="!currentNode.enable"
+            filterable
+            style="width: 300px;"
+            key="deptLeader"
+            @change="onDeptChange"
+          >
+            <el-option
+              v-for="item in deptList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+              style="width: 300px;"
+            />
+          </el-select>
+        </template>
+      </el-form-item>
+    </el-form>
+  </el-dialog>
+</template>
+
+
+<script>
+import Template from "@/examine/components/Template.vue";
+
+export default {
+  name: "PerCcSelector",
+  components: {Template},
+  props: {
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+    indicator:{
+      type: Object,
+      default: () =>{
+        return null
+      }
+    }
+  },
+  data(){
+    return {
+      userInfo: this.$userInfo(),
+      currentNode: null,
+      innerVisible: this.showVisible,
+      loading:false,
+      employees:[],
+      postList:[],
+      deptList:[],
+      userSelected:[],
+      postSelected:[],
+      deptSelected:[],
+      assigneeMap:{
+        leader:'组织管理员',
+        user:'指定人员',
+        self:'被考核人',
+        post:'岗位',
+        deptLeader:'部门负责人',
+      },
+      levelOptions: [
+        {
+          value: 1,
+          label: '直接管理员'
+        },
+        {
+          value: 2,
+          label: '二级管理员'
+        },
+        {
+          value: 3,
+          label: '三级管理员'
+        },
+        {
+          value: 4,
+          label: '四级管理员'
+        },
+        {
+          value: 5,
+          label: '五级管理员'
+        },
+        {
+          value: 6,
+          label: '六级管理员'
+        }
+      ]
+    }
+  },
+  computed:{
+    employeeMap(){
+      const map = {};
+      this.employees.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    postMap(){
+      const map = {};
+      this.postList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    deptMap(){
+      const map = {};
+      this.deptList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+  },
+  watch:{
+    showVisible(val){
+      this.innerVisible = val;
+    },
+  },
+  methods: {
+    matchErrMsg(err){
+      return err.toString().replace(/[(Error: )]/g,'');
+    },
+    handleClose(){
+      this.$emit('update:showVisible',false);
+    },
+    dataReset(){
+      this.currentNode = null;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    initData(){
+      if (this.loading || !this.indicator) return;
+      this.dataReset();
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$axiosUser('get', '/api/pro/employee/index', {page:0,page_size:10,status:1}, 'v2'),
+        this.$axiosUser('get','/org/post'),
+        this.$axiosUser('get', `/org/departments/${this.userInfo.site_id}`)
+      ])
+        .then(([employeeRes,postRes,deptRes]) => {
+          if (employeeRes.data.code !== 1) throw new Error(employeeRes.data.msg);
+          if (postRes.data.code !== 1) throw new Error(postRes.data.message);
+          if (deptRes.data.code !== 1) throw new Error(deptRes.data.message);
+
+          this.employees = employeeRes.data.data.list
+            .filter(e => e.account_id && e.account_id > 0)
+            .map(e => {
+              return {
+                id:e.id,
+                name:e.name,
+              }
+            });
+
+          this.postList = postRes.data.data.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.deptList = deptRes.data.data.list.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.currentNode = this.indicator.flow.nodes.find(node => node.type === 'cc');
+
+          switch (this.currentNode.assigneeType){
+            case 'user':
+              this.userSelected = this.currentNode.assigneeIds;
+              break;
+            case 'post':
+              this.postSelected = this.currentNode.assigneeIds;
+              break;
+            case 'deptLeader':
+              this.deptSelected = this.currentNode.assigneeIds;
+              break;
+          }
+        })
+        .catch(err => {
+          this.$message.error(this.matchErrMsg(err));
+          result = false;
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) this.handleClose();
+        })
+    },
+    generalId(){
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    onAssigneeTypeChange(v){
+      this.currentNode.assigneeIds = [];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onOrgManagerChange(v){
+      this.currentNode.assigneeIds = [];
+      this.currentNode.leaderLevel = v;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onUserChange(v){
+      this.currentNode.assigneeIds = [...v];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = v;
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onPostChange(v){
+      this.currentNode.assigneeIds = [...v];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = v;
+      this.deptSelected = [];
+    },
+    onDeptChange(v){
+      this.currentNode.assigneeIds = [...v];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = v;
+    },
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.handler-list {
+  width: 500px;
+  margin: 0 auto 16px auto;
+  border-radius: 6px;
+  border: 1px solid #d7dae2;
+  padding: 10px 0 0 10px;
+  box-sizing: border-box;
+  display: flex;
+  flex-wrap: wrap;
+
+  .el-tag {
+    margin: 0 10px 10px 0;
+    cursor: pointer;
+  }
+}
+
+</style>

+ 362 - 0
src/newPerformance/components/IndicatorSetting/PerCcSelectorOnly.vue

@@ -0,0 +1,362 @@
+<template>
+  <el-dialog
+    :visible.sync="innerVisible"
+    @close="handleClose"
+    @open="initData"
+    @closed="dataReset"
+    append-to-body
+    :close-on-click-modal="false"
+    center
+    title="结果录入节点配置"
+    width="600px"
+  >
+    <el-form v-if="currentNode" label-width="200">
+      <el-form-item label="启用">
+        <el-switch
+          v-model="currentNode.enable"
+        />
+      </el-form-item>
+
+      <el-form-item label="抄送人">
+        <el-radio-group
+          v-model="currentNode.assigneeType"
+          :disabled="!currentNode.enable"
+          @change="onAssigneeTypeChange"
+        >
+          <el-radio-button label="leader">组织管理员</el-radio-button>
+          <el-radio-button label="user">指定人员</el-radio-button>
+          <el-radio-button label="self">被考核人</el-radio-button>
+          <el-radio-button label="post">岗位</el-radio-button>
+          <el-radio-button label="deptLeader">部门负责人</el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+
+      <el-form-item label="">
+        <template v-if="currentNode.assigneeType === 'leader'">
+          <el-select
+            v-model="currentNode.leaderLevel"
+            placeholder="请选择管理员"
+            :disabled="!currentNode.enable"
+            filterable
+            style="width: 300px;"
+            key="leader"
+            @change="onOrgManagerChange"
+          >
+            <el-option
+              v-for="item in levelOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+              style="width: 300px;"
+            />
+          </el-select>
+        </template>
+        <template v-if="currentNode.assigneeType === 'user'">
+          <el-select
+            v-model="userSelected"
+            placeholder="请选择指定人员"
+            multiple
+            :disabled="!currentNode.enable"
+            filterable
+            style="width: 300px;"
+            key="user"
+            @change="onUserChange"
+          >
+            <el-option
+              v-for="item in employees"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+              style="width: 300px;"
+            />
+          </el-select>
+        </template>
+        <template v-if="currentNode.assigneeType === 'post'">
+          <el-select
+            v-model="postSelected"
+            placeholder="请选择岗位"
+            multiple
+            :disabled="!currentNode.enable"
+            filterable
+            style="width: 300px;"
+            key="post"
+            @change="onPostChange"
+          >
+            <el-option
+              v-for="item in postList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+              style="width: 300px;"
+            />
+          </el-select>
+        </template>
+        <template v-if="currentNode.assigneeType === 'deptLeader'">
+          <el-select
+            v-model="deptSelected"
+            placeholder="请选择部门"
+            multiple
+            :disabled="!currentNode.enable"
+            filterable
+            style="width: 300px;"
+            key="deptLeader"
+            @change="onDeptChange"
+          >
+            <el-option
+              v-for="item in deptList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+              style="width: 300px;"
+            />
+          </el-select>
+        </template>
+      </el-form-item>
+    </el-form>
+    <template v-if="currentNode" #footer>
+      <el-row type="flex" justify="end">
+        <el-col align="end">
+          <el-button
+            type="primary"
+            @click="onConfirm"
+          >
+            确认
+          </el-button>
+        </el-col>
+      </el-row>
+    </template>
+  </el-dialog>
+</template>
+
+
+<script>
+import Template from "@/examine/components/Template.vue";
+
+export default {
+  name: "PerCcSelectorOnly",
+  components: {Template},
+  props: {
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+  },
+  data(){
+    return {
+      userInfo: this.$userInfo(),
+      currentNode: null,
+      innerVisible: this.showVisible,
+      loading:false,
+      employees:[],
+      postList:[],
+      deptList:[],
+      userSelected:[],
+      postSelected:[],
+      deptSelected:[],
+      assigneeMap:{
+        leader:'组织管理员',
+        user:'指定人员',
+        self:'被考核人',
+        post:'岗位',
+        deptLeader:'部门负责人',
+      },
+      levelOptions: [
+        {
+          value: 1,
+          label: '直接管理员'
+        },
+        {
+          value: 2,
+          label: '二级管理员'
+        },
+        {
+          value: 3,
+          label: '三级管理员'
+        },
+        {
+          value: 4,
+          label: '四级管理员'
+        },
+        {
+          value: 5,
+          label: '五级管理员'
+        },
+        {
+          value: 6,
+          label: '六级管理员'
+        }
+      ]
+    }
+  },
+  computed:{
+    employeeMap(){
+      const map = {};
+      this.employees.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    postMap(){
+      const map = {};
+      this.postList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    deptMap(){
+      const map = {};
+      this.deptList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+  },
+  watch:{
+    showVisible(val){
+      this.innerVisible = val;
+    },
+  },
+  methods: {
+    defaultNode(){
+      const node = JSON.parse("{\"id\":\"CC_1907236768800382984\",\"type\":\"cc\",\"enable\":false,\"assigneeType\":\"user\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[],\"weight\":0,\"children\":[]}");
+      node.id = "CC_" + this.generalId();
+      return node;
+    },
+    matchErrMsg(err){
+      return err.toString().replace(/[(Error: )]/g,'');
+    },
+    handleClose(){
+      this.$emit('update:showVisible',false);
+    },
+    dataReset(){
+      this.currentNode = null;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    initData(){
+      if (this.loading) return;
+      this.dataReset();
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$axiosUser('get', '/api/pro/employee/index', {page:0,page_size:10,status:1}, 'v2'),
+        this.$axiosUser('get','/org/post'),
+        this.$axiosUser('get', `/org/departments/${this.userInfo.site_id}`)
+      ])
+        .then(([employeeRes,postRes,deptRes]) => {
+          if (employeeRes.data.code !== 1) throw new Error(employeeRes.data.msg);
+          if (postRes.data.code !== 1) throw new Error(postRes.data.message);
+          if (deptRes.data.code !== 1) throw new Error(deptRes.data.message);
+
+          this.employees = employeeRes.data.data.list
+            .filter(e => e.account_id && e.account_id > 0)
+            .map(e => {
+              return {
+                id:e.id,
+                name:e.name,
+              }
+            });
+
+          this.postList = postRes.data.data.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.deptList = deptRes.data.data.list.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.currentNode = this.defaultNode();
+
+          switch (this.currentNode.assigneeType){
+            case 'user':
+              this.userSelected = this.currentNode.assigneeIds;
+              break;
+            case 'post':
+              this.postSelected = this.currentNode.assigneeIds;
+              break;
+            case 'deptLeader':
+              this.deptSelected = this.currentNode.assigneeIds;
+              break;
+          }
+        })
+        .catch(err => {
+          this.$message.error(this.matchErrMsg(err));
+          result = false;
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) this.handleClose();
+        })
+    },
+    generalId(){
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    onAssigneeTypeChange(v){
+      this.currentNode.assigneeIds = [];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onOrgManagerChange(v){
+      this.currentNode.assigneeIds = [];
+      this.currentNode.leaderLevel = v;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onUserChange(v){
+      this.currentNode.assigneeIds = [...v];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = v;
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onPostChange(v){
+      this.currentNode.assigneeIds = [...v];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = v;
+      this.deptSelected = [];
+    },
+    onDeptChange(v){
+      this.currentNode.assigneeIds = [...v];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = v;
+    },
+    onConfirm(){
+      this.$emit('confirm',this.currentNode);
+      this.handleClose();
+    },
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.handler-list {
+  width: 500px;
+  margin: 0 auto 16px auto;
+  border-radius: 6px;
+  border: 1px solid #d7dae2;
+  padding: 10px 0 0 10px;
+  box-sizing: border-box;
+  display: flex;
+  flex-wrap: wrap;
+
+  .el-tag {
+    margin: 0 10px 10px 0;
+    cursor: pointer;
+  }
+}
+
+</style>

+ 316 - 0
src/newPerformance/components/IndicatorSetting/PerEmployeeSelector.vue

@@ -0,0 +1,316 @@
+<template>
+  <el-dialog :visible="innerVisible" @close="handleClose" @open="initData" append-to-body width="650px" center
+    title="选择用户">
+    <div>
+      <div class="search-box">
+        <el-input v-model="searchValue" placeholder="请输入员工名称"></el-input>
+      </div>
+      <div class="selected-person-list" style="" v-if="selectedValues && selectedValues.length > 0">
+        <div style="margin: 0 0 10px 0;">表格中已存在的员工</div>
+        <div class="flex-box-ce person-list scorll-bar">
+          <el-tag v-for="item in selectedValues" :key="item.id" style="margin: 0 10px 10px 0;">
+            {{ item.name }}
+          </el-tag>
+        </div>
+      </div>
+
+      <div class="package-list">
+
+        <div class="template-list-box">
+          <div class="fontColorC" style="text-align: center; width: 100%; line-height: 30px;">员工列表</div>
+          <el-checkbox label="全选" v-model="isAllCheck" style="margin-bottom: 10px;"></el-checkbox>
+          <div class="template-list scroll-bar">
+            <div class="template-item" :class="item.isCheck ? 'active' : ''" v-for="item in initEmployeeList"
+              @click="chooseTemplate(item)">
+              {{ item.name }}
+            </div>
+          </div>
+        </div>
+
+        <div class="choose-template-box">
+          <div class="fontColorC" style="text-align: center; width: 100%; line-height: 30px;">已选员工</div>
+          <el-button plain round size="mini" @click="clear" style="width: 100px;">清 空</el-button>
+          <div class="choose-template scroll-bar">
+            <template v-for="(item, index) in chooseExamineList">
+              <div v-if="item.isCheck" class="flex-box-ce choose-template-item" style="justify-content: space-between;">
+                {{ item.name }}
+                <i class="el-icon-close" @click="deleteItem(item, index)"></i>
+              </div>
+            </template>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div slot="footer">
+      <el-button @click="handleClose()">取 消</el-button>
+      <el-button type="primary" @click="onConfirm()">确 定</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import cloneDeep from 'lodash.clonedeep';
+
+export default {
+  name: 'PerEmployeeSelector',
+  props:{
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+    choosePersonList: {
+      type: Array,
+      default: () => []
+    },
+    selectedEmployees:{
+      type: Array,
+      default: () => []
+    },
+    employeeStatus:{
+      type: Number,
+      default: null
+    },
+    multiple:{
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {
+      userInfo: this.$userInfo(),
+      innerVisible: this.showVisible,
+      employeeList: this.$getEmployeeMap(this.employeeStatus),
+      selectedValues: this.selectedEmployees,
+      searchValue: '',
+      isAllCheck: false,
+      initEmployeeList: [],
+      chooseExamineList: [],
+      selectExamineList: [], // 显示选择的数据
+      filterData: []
+    }
+  },
+  watch: {
+    showVisible(val) {
+      this.innerVisible = val;
+    },
+
+    searchValue(v) {
+      let filterData
+      if (v) filterData = this.filterData.filter(item => item.name.includes(v))
+      else filterData = this.filterData.filter(item => 1 == 1)
+      this.initEmployeeList = filterData
+    },
+
+    isAllCheck(v) {
+      if (v) {
+        this.initEmployeeList.forEach(item => item.isCheck = true)
+        this.chooseExamineList = this.initEmployeeList
+      } else {
+        this.initEmployeeList.forEach(item => item.isCheck = false)
+        this.chooseExamineList = []
+      }
+    }
+  },
+  
+  methods: {
+    handleClose(){
+      this.$emit('update:showVisible',false)
+    },
+    initData() {
+      this.initEmployeeList = []
+      this.selectExamineList = []
+      this.chooseExamineList = []
+      this.selectedValues = this.selectedEmployees;
+      let selectedEmployeeId = this.selectedValues.map(item => item.id)
+      this.employeeList.forEach(item => {
+        if (!selectedEmployeeId.includes(item.id)) {
+          this.initEmployeeList.push(item)
+        }
+      })
+      
+      this.chooseExamineList = this.choosePersonList 
+      this.filterData = this.initEmployeeList
+      if (this.initEmployeeList && this.initEmployeeList.length > 0) {
+        this.initEmployeeList.forEach(item => {
+          item.isCheck = false
+          if (this.chooseExamineList && this.chooseExamineList.length > 0) {
+            let flag = this.chooseExamineList.find(chooseExamine => chooseExamine.id == item.id)
+            if (flag) {
+              item.isCheck = true
+            } else {
+              item.isCheck = false
+            }
+          }
+        })
+      }
+      
+    },
+
+    chooseTemplate(item) {
+      item.isCheck = !item.isCheck
+      let flag = this.chooseExamineList.find(chooseExamine => chooseExamine.id == item.id)
+      if (item.isCheck) {
+
+        if (!flag) this.chooseExamineList.push(item)
+      } else {
+        if (flag) {
+          let index = this.chooseExamineList.findIndex(chooseExamine => chooseExamine.id == item.id)
+          this.chooseExamineList.splice(index, 1)
+        }
+      }
+
+      this.chooseExamineList = Array.from(new Set(this.chooseExamineList.map(JSON.stringify))).map(JSON.parse);
+    },
+
+    deleteItem(item, index) {
+      this.initEmployeeList.forEach(examine => {
+
+        if (examine.id == item.id) {
+          examine.isCheck = false
+        }
+
+      })
+      this.initEmployeeList.splice(index, 1)
+    },
+
+    // 清空选择的考核列表
+    clear() {
+      this.chooseExamineList.forEach(chooseExamine => {
+        this.initEmployeeList.forEach(examine => {
+          if (chooseExamine.id == examine.id) {
+            examine.isCheck = false
+          }
+        })
+      })
+      this.isAllCheck = false;
+      this.chooseExamineList = [];
+    },
+    onConfirm() {
+      this.selectExamineList = cloneDeep(this.chooseExamineList);
+      let res = this.selectExamineList.map(item => {
+        return {
+          id: item.id,
+          name: item.name,
+          imgUrl: item.img_url,
+        }
+      });
+      this.$emit('confirm',res);
+      this.handleClose();
+    },
+
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.search-box {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  box-sizing: border-box;
+}
+
+.selected-person-list {
+  width: 100%; 
+  padding: 10px; 
+  box-sizing: border-box; 
+  background-color: #f7f7f7; 
+  margin: 10px 0; 
+  .person-list {
+    flex-wrap: wrap; 
+    max-height: 60px; 
+    overflow-y: auto;
+    /* 设置滚动条的宽度和背景色 */
+      &::-webkit-scrollbar {
+          width: 6px;
+          height: 6px;
+          background-color: #f9f9f9;
+      }
+
+      /* 设置滚动条滑块的样式 */
+      &::-webkit-scrollbar-thumb {
+          border-radius: 6px;
+          background-color: #c1c1c1;
+      }
+
+      /* 设置滚动条滑块hover样式 */
+      &::-webkit-scrollbar-thumb:hover {
+          background-color: #a8a8a8;
+      }
+
+      /* 设置滚动条轨道的样式 */
+      &::-webkit-scrollbar-track {
+          box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+          border-radius: 6px;
+          background: #ededed;
+      }
+  }
+}
+.package-list {
+  width: 100%;
+  height: 300px;
+  display: flex;
+  margin-top: 10px;
+  border: 1px solid #f7f7f7;
+  .template-list-box {
+    width: 50%;
+    padding: 10px;
+    box-sizing: border-box;
+    border-right: 1px solid #f7f7f7;
+    display: flex;
+    flex-direction: column;
+    .template-list {
+      width: 100%;
+      flex: 1;
+      overflow-y: auto;
+      display: flex;
+      flex-direction: column;
+
+      .template-item {
+        width: 100%;
+        height: 30px;
+        line-height: 30px;
+        border: 1px solid #f1f1f1;
+        margin-bottom: 5px;
+        border-radius: 6px;
+        padding-left: 10px;
+      }
+
+      .active {
+        border: 1px solid #409eff;
+        color: #409eff;
+      }
+    }
+  }
+
+  .choose-template-box {
+    width: 50%;
+    padding: 10px;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    .choose-template {
+      width: 100%;
+      flex: 1;
+      overflow-y: auto;
+
+      .line {
+        width: 90%;
+        height: 1px;
+        background-color: #f7f7f7;
+        margin: 10px auto;
+      }
+
+      &-item {
+        width: 90%;
+        height: 30px;
+        line-height: 30px;
+        padding-left: 10px;
+        border-bottom: 1px solid #f7f7f7;
+        margin: 0 auto 10px auto;
+        box-sizing: border-box;
+      }
+    }
+  }
+}
+</style>

+ 316 - 0
src/newPerformance/components/IndicatorSetting/PerResultInputSelector.vue

@@ -0,0 +1,316 @@
+<template>
+  <el-dialog :visible.sync="innerVisible" @close="handleClose" @open="initData" @closed="dataReset" append-to-body
+    :close-on-click-modal="false" center title="结果录入节点配置" width="600px">
+    <el-form v-if="currentNode" label-width="200">
+      <el-form-item label="启用">
+        <el-switch v-model="currentNode.enable" />
+      </el-form-item>
+
+      <el-form-item label="录入人">
+        <el-radio-group v-model="currentNode.assigneeType" :disabled="!currentNode.enable"
+          @change="onAssigneeTypeChange">
+          <el-radio-button label="leader">组织管理员</el-radio-button>
+          <el-radio-button label="user">指定人员</el-radio-button>
+          <el-radio-button label="self">被考核人</el-radio-button>
+          <el-radio-button label="post">岗位</el-radio-button>
+          <el-radio-button label="deptLeader">部门负责人</el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+
+      <div class="flex-center-center" style="margin-left: 100px;">
+        <el-form-item label="">
+          <template v-if="currentNode.assigneeType === 'leader'">
+            <el-select v-model="currentNode.leaderLevel" placeholder="请选择管理员" :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="leader" @change="onOrgManagerChange">
+              <el-option v-for="item in levelOptions" :key="item.value" :label="item.label" :value="item.value"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>被考核人所在部门的直接上级或上上级 )</div>
+          </template>
+          <template v-if="currentNode.assigneeType === 'user'">
+            <el-select v-model="userSelected" placeholder="请选择指定人员" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="user" @change="onUserChange">
+              <el-option v-for="item in employees" :key="item.id" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
+          </template>
+          <template v-if="currentNode.assigneeType === 'post'">
+            <el-select v-model="postSelected" placeholder="请选择岗位" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="post" @change="onPostChange">
+              <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
+          </template>
+          <template v-if="currentNode.assigneeType === 'deptLeader'">
+            <el-select v-model="deptSelected" placeholder="请选择部门" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="deptLeader" @change="onDeptChange">
+              <el-option v-for="item in deptList" :key="item.id" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+          </template>
+        </el-form-item>
+      </div>
+
+      <el-form-item label="多人时">
+        <el-radio-group v-model="currentNode.multipleType" :disabled="!currentNode.enable">
+          <el-radio-button label="or">任一人确认(或签)</el-radio-button>
+          <el-radio-button label="parallel">按顺序确认(会签)</el-radio-button>
+          <el-radio-button label="sequence">同时确认(会签)</el-radio-button>
+        </el-radio-group>
+        <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+          ( <span style="color: red;"> * </span><span>会签要求所有审批人一致同意</span> )
+        </div>
+        <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+          ( <span style="color: red;"> * </span><span>或签只需任一审批人同意即可</span> )
+        </div>
+      </el-form-item>
+
+      <!-- <el-form-item label="允许">
+        <el-checkbox-group
+          v-model="currentNode.allows"
+          :disabled="!currentNode.enable"
+        >
+          <el-checkbox-button label="transfer">转交</el-checkbox-button>
+        </el-checkbox-group>
+      </el-form-item> -->
+    </el-form>
+  </el-dialog>
+</template>
+
+
+<script>
+import Template from "@/examine/components/Template.vue";
+
+
+export default {
+  name: "PerResultInputSelector",
+  components: {Template},
+  props: {
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+    indicator:{
+      type: Object,
+      default: () =>{
+        return null
+      }
+    }
+  },
+  data(){
+    return {
+      userInfo: this.$userInfo(),
+      currentNode: null,
+      innerVisible: this.showVisible,
+      loading:false,
+      employees:[],
+      postList:[],
+      deptList:[],
+      userSelected:[],
+      postSelected:[],
+      deptSelected:[],
+      assigneeMap:{
+        leader:'组织管理员',
+        user:'指定人员',
+        self:'被考核人',
+        post:'岗位',
+        deptLeader:'部门负责人',
+      },
+      levelOptions: [
+        {
+          value: 1,
+          label: '直接管理员'
+        },
+        {
+          value: 2,
+          label: '二级管理员'
+        },
+        {
+          value: 3,
+          label: '三级管理员'
+        },
+        {
+          value: 4,
+          label: '四级管理员'
+        },
+        {
+          value: 5,
+          label: '五级管理员'
+        },
+        {
+          value: 6,
+          label: '六级管理员'
+        }
+      ]
+    }
+  },
+  computed:{
+    employeeMap(){
+      const map = {};
+      this.employees.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    postMap(){
+      const map = {};
+      this.postList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    deptMap(){
+      const map = {};
+      this.deptList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+  },
+  watch:{
+    showVisible(val){
+      this.innerVisible = val;
+    },
+  },
+  methods: {
+    matchErrMsg(err){
+      return err.toString().replace(/[(Error: )]/g,'');
+    },
+    handleClose(){
+      this.$emit('update:showVisible',false);
+    },
+    dataReset(){
+      this.currentNode = null;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    initData(){
+      if (this.loading || !this.indicator) return;
+      this.dataReset();
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$axiosUser('get', '/api/pro/employee/index', {page:0,page_size:10,status:1}, 'v2'),
+        this.$axiosUser('get','/org/post'),
+        this.$axiosUser('get', `/org/departments/${this.userInfo.site_id}`)
+      ])
+        .then(([employeeRes,postRes,deptRes]) => {
+          if (employeeRes.data.code !== 1) throw new Error(employeeRes.data.msg);
+          if (postRes.data.code !== 1) throw new Error(postRes.data.message);
+          if (deptRes.data.code !== 1) throw new Error(deptRes.data.message);
+
+          this.employees = employeeRes.data.data.list
+            .filter(e => e.account_id && e.account_id > 0)
+            .map(e => {
+              return {
+                id:e.id,
+                name:e.name,
+              }
+            });
+
+          this.postList = postRes.data.data.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.deptList = deptRes.data.data.list.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.currentNode = this.indicator.flow.nodes.find(node => node.type === 'resultInput');
+
+          switch (this.currentNode.assigneeType){
+            case 'user':
+              this.userSelected = this.currentNode.assigneeIds;
+              break;
+            case 'post':
+              this.postSelected = this.currentNode.assigneeIds;
+              break;
+            case 'deptLeader':
+              this.deptSelected = this.currentNode.assigneeIds;
+              break;
+          }
+        })
+        .catch(err => {
+          this.$message.error(this.matchErrMsg(err));
+          result = false;
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) this.handleClose();
+        })
+    },
+    generalId(){
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    onAssigneeTypeChange(v){
+      this.currentNode.assigneeIds = [];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onOrgManagerChange(v){
+      this.currentNode.assigneeIds = [];
+      this.currentNode.leaderLevel = v;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onUserChange(v){
+      this.currentNode.assigneeIds = [...v];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = v;
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onPostChange(v){
+      this.currentNode.assigneeIds = [...v];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = v;
+      this.deptSelected = [];
+    },
+    onDeptChange(v){
+      this.currentNode.assigneeIds = [...v];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = v;
+    },
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.handler-list {
+  width: 500px;
+  margin: 0 auto 16px auto;
+  border-radius: 6px;
+  border: 1px solid #d7dae2;
+  padding: 10px 0 0 10px;
+  box-sizing: border-box;
+  display: flex;
+  flex-wrap: wrap;
+
+  .el-tag {
+    margin: 0 10px 10px 0;
+    cursor: pointer;
+  }
+}
+
+</style>

+ 327 - 0
src/newPerformance/components/IndicatorSetting/PerResultInputSelectorOnly.vue

@@ -0,0 +1,327 @@
+<template>
+  <el-dialog :visible.sync="innerVisible" @close="handleClose" @open="initData" @closed="dataReset" append-to-body
+    :close-on-click-modal="false" center title="结果录入节点配置" width="600px">
+    <el-form v-if="currentNode" label-width="200">
+      <el-form-item label="启用">
+        <el-switch v-model="currentNode.enable" />
+      </el-form-item>
+
+      <el-form-item label="录入人">
+        <el-radio-group v-model="currentNode.assigneeType" :disabled="!currentNode.enable"
+          @change="onAssigneeTypeChange">
+          <el-radio-button label="leader">组织管理员</el-radio-button>
+          <el-radio-button label="user">指定人员</el-radio-button>
+          <el-radio-button label="self">被考核人</el-radio-button>
+          <el-radio-button label="post">岗位</el-radio-button>
+          <el-radio-button label="deptLeader">部门负责人</el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+
+      <div class="flex-center-center" style="margin-left: 100px;">
+        <el-form-item label="">
+          <template v-if="currentNode.assigneeType === 'leader'">
+            <el-select v-model="currentNode.leaderLevel" placeholder="请选择管理员" :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="leader" @change="onOrgManagerChange">
+              <el-option v-for="item in levelOptions" :key="item.value" :label="item.label" :value="item.value"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>被考核人所在部门的直接上级或上上级 )</div>
+          </template>
+          <template v-if="currentNode.assigneeType === 'user'">
+            <el-select v-model="userSelected" placeholder="请选择指定人员" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="user" @change="onUserChange">
+              <el-option v-for="item in employees" :key="item.id" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
+          </template>
+          <template v-if="currentNode.assigneeType === 'post'">
+            <el-select v-model="postSelected" placeholder="请选择岗位" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="post" @change="onPostChange">
+              <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
+          </template>
+          <template v-if="currentNode.assigneeType === 'deptLeader'">
+            <el-select v-model="deptSelected" placeholder="请选择部门" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="deptLeader" @change="onDeptChange">
+              <el-option v-for="item in deptList" :key="item.id" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+          </template>
+        </el-form-item>
+      </div>
+        <el-form-item label="多人时">
+          <el-radio-group v-model="currentNode.multipleType" :disabled="!currentNode.enable">
+            <el-radio-button label="or">任一人确认(或签)</el-radio-button>
+            <el-radio-button label="parallel">按顺序确认(会签)</el-radio-button>
+            <el-radio-button label="sequence">同时确认(会签)</el-radio-button>
+          </el-radio-group>
+          <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+            ( <span style="color: red;"> * </span><span>会签要求所有审批人一致同意</span> )
+          </div>
+          <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+            ( <span style="color: red;"> * </span><span>或签只需任一审批人同意即可</span> )
+          </div>
+        </el-form-item>
+
+        <!-- <el-form-item label="允许">
+        <el-checkbox-group
+          v-model="currentNode.allows"
+          :disabled="!currentNode.enable"
+        >
+          <el-checkbox-button label="transfer">转交</el-checkbox-button>
+        </el-checkbox-group>
+      </el-form-item> -->
+    </el-form>
+    <template #footer>
+      <el-row type="flex" justify="end">
+        <el-col align="end">
+          <el-button type="primary" @click="onConfirm">
+            确认
+          </el-button>
+        </el-col>
+      </el-row>
+    </template>
+  </el-dialog>
+</template>
+
+
+<script>
+import Template from "@/examine/components/Template.vue";
+
+
+export default {
+  name: "PerResultInputSelectorOnly",
+  components: {Template},
+  props: {
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+  },
+  data(){
+    return {
+      userInfo: this.$userInfo(),
+      currentNode: null,
+      innerVisible: this.showVisible,
+      loading:false,
+      employees:[],
+      postList:[],
+      deptList:[],
+      userSelected:[],
+      postSelected:[],
+      deptSelected:[],
+      assigneeMap:{
+        leader:'组织管理员',
+        user:'指定人员',
+        self:'被考核人',
+        post:'岗位',
+        deptLeader:'部门负责人',
+      },
+      levelOptions: [
+        {
+          value: 1,
+          label: '直接管理员'
+        },
+        {
+          value: 2,
+          label: '二级管理员'
+        },
+        {
+          value: 3,
+          label: '三级管理员'
+        },
+        {
+          value: 4,
+          label: '四级管理员'
+        },
+        {
+          value: 5,
+          label: '五级管理员'
+        },
+        {
+          value: 6,
+          label: '六级管理员'
+        }
+      ]
+    }
+  },
+  computed:{
+    employeeMap(){
+      const map = {};
+      this.employees.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    postMap(){
+      const map = {};
+      this.postList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    deptMap(){
+      const map = {};
+      this.deptList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+  },
+  watch:{
+    showVisible(val){
+      this.innerVisible = val;
+    },
+  },
+  methods: {
+    idGeneral(){
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    defaultNode(){
+      const node = JSON.parse("{\"id\":\"RI_1907236768800382977\",\"type\":\"resultInput\",\"enable\":true,\"assigneeType\":\"self\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":0,\"children\":[]}");
+      node.id = "RI_" + this.idGeneral();
+      return node;
+    },
+    matchErrMsg(err){
+      return err.toString().replace(/[(Error: )]/g,'');
+    },
+    handleClose(){
+      this.$emit('update:showVisible',false);
+    },
+    dataReset(){
+      this.currentNode = null;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    initData(){
+      if (this.loading) return;
+      this.dataReset();
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$axiosUser('get', '/api/pro/employee/index', {page:0,page_size:10,status:1}, 'v2'),
+        this.$axiosUser('get','/org/post'),
+        this.$axiosUser('get', `/org/departments/${this.userInfo.site_id}`)
+      ])
+        .then(([employeeRes,postRes,deptRes]) => {
+          if (employeeRes.data.code !== 1) throw new Error(employeeRes.data.msg);
+          if (postRes.data.code !== 1) throw new Error(postRes.data.message);
+          if (deptRes.data.code !== 1) throw new Error(deptRes.data.message);
+
+          this.employees = employeeRes.data.data.list
+            .filter(e => e.account_id && e.account_id > 0)
+            .map(e => {
+              return {
+                id:e.id,
+                name:e.name,
+              }
+            });
+
+          this.postList = postRes.data.data.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.deptList = deptRes.data.data.list.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.currentNode = this.defaultNode();
+
+          switch (this.currentNode.assigneeType){
+            case 'user':
+              this.userSelected = this.currentNode.assigneeIds;
+              break;
+            case 'post':
+              this.postSelected = this.currentNode.assigneeIds;
+              break;
+            case 'deptLeader':
+              this.deptSelected = this.currentNode.assigneeIds;
+              break;
+          }
+        })
+        .catch(err => {
+          this.$message.error(this.matchErrMsg(err));
+          result = false;
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) this.handleClose();
+        })
+    },
+    onAssigneeTypeChange(v){
+      this.currentNode.assigneeIds = [];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onOrgManagerChange(v){
+      this.currentNode.assigneeIds = [];
+      this.currentNode.leaderLevel = v;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onUserChange(v){
+      this.currentNode.assigneeIds = [...v];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = v;
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onPostChange(v){
+      this.currentNode.assigneeIds = [...v];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = v;
+      this.deptSelected = [];
+    },
+    onDeptChange(v){
+      this.currentNode.assigneeIds = [...v];
+      this.currentNode.leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = v;
+    },
+    onConfirm(){
+      this.$emit('confirm',this.currentNode);
+      this.handleClose();
+    },
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.handler-list {
+  width: 500px;
+  margin: 0 auto 16px auto;
+  border-radius: 6px;
+  border: 1px solid #d7dae2;
+  padding: 10px 0 0 10px;
+  box-sizing: border-box;
+  display: flex;
+  flex-wrap: wrap;
+
+  .el-tag {
+    margin: 0 10px 10px 0;
+    cursor: pointer;
+  }
+}
+
+</style>

+ 364 - 0
src/newPerformance/components/IndicatorSetting/PerReviewsSelector.vue

@@ -0,0 +1,364 @@
+<template>
+  <el-dialog :visible.sync="innerVisible" @close="handleClose" @open="initData" @closed="dataReset" append-to-body
+    :close-on-click-modal="false" center title="审批节点配置" width="600px">
+    <el-form v-if="currentNode" label-width="200">
+      <el-form-item label="启用">
+        <el-switch v-model="currentNode.enable" />
+      </el-form-item>
+      <el-form-item label="">
+        <div class="handler-list">
+          <el-tag v-for="(item,index) in currentNode.children" :key="`hl_${index}`"
+            :type="childIndex === index ? '' : 'info'" closable @close="removeChild(index)" @click="childSelect(index)">
+            {{assigneeMap[item.assigneeType] || '未知'}}
+          </el-tag>
+          <el-button v-show="currentNode.enable" icon="el-icon-plus" size="mini" style="margin-bottom: 10px;"
+            @click="addChild" />
+        </div>
+      </el-form-item>
+      <el-form-item label="审批人">
+        <el-radio-group v-model="currentNode.children[childIndex].assigneeType" :disabled="!currentNode.enable"
+          @change="onAssigneeTypeChange">
+          <el-radio-button label="leader">组织管理员</el-radio-button>
+          <el-radio-button label="user">指定人员</el-radio-button>
+          <el-radio-button label="self">被考核人</el-radio-button>
+          <el-radio-button label="post">岗位</el-radio-button>
+          <el-radio-button label="deptLeader">部门负责人</el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+
+      <div class="flex-center-center" style="margin-left: 100px;">
+        <el-form-item label="">
+          <template v-if="currentNode.children[childIndex].assigneeType === 'leader'">
+            <el-select v-model="currentNode.children[childIndex].leaderLevel" placeholder="请选择管理员"
+              :disabled="!currentNode.enable" filterable style="width: 300px;" key="leader"
+              @change="onOrgManagerChange">
+              <el-option v-for="item in levelOptions" :key="`leader_${item.value}`" :label="item.label"
+                :value="item.value" style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>被考核人所在部门的直接上级或上上级 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'user'">
+            <el-select v-model="userSelected" placeholder="请选择指定人员" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="user" @change="onUserChange">
+              <el-option v-for="item in employees" :key="`user_${item.id}`" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'post'">
+            <el-select v-model="postSelected" placeholder="请选择岗位" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="post" @change="onPostChange">
+              <el-option v-for="item in postList" :key="`post_${item.id}`" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'deptLeader'">
+            <el-select v-model="deptSelected" placeholder="请选择部门" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="deptLeader" @change="onDeptChange">
+              <el-option v-for="item in deptList" :key="`deptLeader_${item.id}`" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+          </template>
+        </el-form-item>
+      </div>
+
+      <el-form-item label="多人时">
+        <el-radio-group v-model="currentNode.children[childIndex].multipleType" :disabled="!currentNode.enable">
+          <el-radio-button label="or">任一人确认(或签)</el-radio-button>
+          <el-radio-button label="parallel">按顺序确认(会签)</el-radio-button>
+          <el-radio-button label="sequence">同时确认(会签)</el-radio-button>
+        </el-radio-group>
+        <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+          ( <span style="color: red;"> * </span><span>会签要求所有审批人一致同意</span> )
+        </div>
+        <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+          ( <span style="color: red;"> * </span><span>或签只需任一审批人同意即可</span> )
+        </div>
+      </el-form-item>
+      <!-- <el-form-item label="允许">
+        <el-checkbox-group
+          v-model="currentNode.children[childIndex].allows"
+          :disabled="!currentNode.enable"
+        >
+          <el-checkbox-button label="transfer">转交</el-checkbox-button>
+        </el-checkbox-group>
+      </el-form-item> -->
+    </el-form>
+  </el-dialog>
+</template>
+
+
+<script>
+import Template from "@/examine/components/Template.vue";
+
+
+export default {
+  name: "PerReviewsSelector",
+  components: {Template},
+  props: {
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+    indicator:{
+      type: Object,
+      default: () =>{
+        return null
+      }
+    }
+  },
+  data(){
+    return {
+      userInfo: this.$userInfo(),
+      currentNode: null,
+      childIndex: null,
+      innerVisible: this.showVisible,
+      loading:false,
+      employees:[],
+      postList:[],
+      deptList:[],
+      userSelected:[],
+      postSelected:[],
+      deptSelected:[],
+      assigneeMap:{
+        leader:'组织管理员',
+        user:'指定人员',
+        self:'被考核人',
+        post:'岗位',
+        deptLeader:'部门负责人',
+      },
+      levelOptions: [
+        {
+          value: 1,
+          label: '直接管理员'
+        },
+        {
+          value: 2,
+          label: '二级管理员'
+        },
+        {
+          value: 3,
+          label: '三级管理员'
+        },
+        {
+          value: 4,
+          label: '四级管理员'
+        },
+        {
+          value: 5,
+          label: '五级管理员'
+        },
+        {
+          value: 6,
+          label: '六级管理员'
+        }
+      ]
+    }
+  },
+  computed:{
+    employeeMap(){
+      const map = {};
+      this.employees.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    postMap(){
+      const map = {};
+      this.postList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    deptMap(){
+      const map = {};
+      this.deptList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+  },
+  watch:{
+    showVisible(val){
+      this.innerVisible = val;
+    },
+  },
+  methods: {
+    matchErrMsg(err){
+      return err.toString().replace(/[(Error: )]/g,'');
+    },
+    handleClose(){
+      this.$emit('update:showVisible',false);
+    },
+    dataReset(){
+      this.currentNode = null;
+      this.childIndex = null;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    initData(){
+      if (this.loading || !this.indicator) return;
+      this.dataReset();
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$axiosUser('get', '/api/pro/employee/index', {page:0,page_size:10,status:1}, 'v2'),
+        this.$axiosUser('get','/org/post'),
+        this.$axiosUser('get', `/org/departments/${this.userInfo.site_id}`)
+      ])
+        .then(([employeeRes,postRes,deptRes]) => {
+          if (employeeRes.data.code !== 1) throw new Error(employeeRes.data.msg);
+          if (postRes.data.code !== 1) throw new Error(postRes.data.message);
+          if (deptRes.data.code !== 1) throw new Error(deptRes.data.message);
+
+          this.employees = employeeRes.data.data.list
+            .filter(e => e.account_id && e.account_id > 0)
+            .map(e => {
+              return {
+                id:e.id,
+                name:e.name,
+              }
+            });
+
+          this.postList = postRes.data.data.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.deptList = deptRes.data.data.list.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.currentNode = this.indicator.flow.nodes.find(node => node.type === 'reviews');
+
+          this.childIndex = this.currentNode.children && this.currentNode.children.length > 0 ? 0 : null;
+
+          if (this.childIndex !== null && !!this.currentNode.children[this.childIndex]){
+            switch (this.currentNode.children[this.childIndex].assigneeType){
+              case 'user':
+                this.userSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+                break;
+              case 'post':
+                this.postSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+                break;
+              case 'deptLeader':
+                this.deptSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+                break;
+            }
+          }
+        })
+        .catch(err => {
+          this.$message.error(this.matchErrMsg(err));
+          result = false;
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) this.handleClose();
+        })
+    },
+    generalId(){
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    addChild(){
+      let id = "R_" + this.generalId();
+      this.currentNode.children.push({
+        id:id,
+        type:'review',
+        enable:true,
+        assigneeType:'leader',
+        leaderLevel:1,
+        assigneeIds:[],
+        multipleType:'or',
+        allows:['transfer'],
+        weight:0,
+        children:[],
+      });
+    },
+    removeChild(index){
+      if (this.currentNode.children.length <= 1 || !this.currentNode.children[index]) return;
+      let selectChildId = this.currentNode.children[index].id;
+      this.childIndex = 0;
+      this.currentNode.children = this.currentNode.children.filter((_,i) => i !== index);
+      if (selectChildId){
+        this.currentNode.children.forEach((child,i) => {
+          if (child.id === selectChildId) this.childIndex = i;
+        })
+      }
+    },
+    onAssigneeTypeChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onOrgManagerChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [];
+      this.currentNode.children[this.childIndex].leaderLevel = v;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onUserChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = v;
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onPostChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = v;
+      this.deptSelected = [];
+    },
+    onDeptChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = v;
+    },
+    childSelect(index){
+      if (!this.currentNode.children[index]) return;
+      this.childIndex = index;
+      this.userSelected = this.currentNode.children[this.childIndex].assigneeType === 'user' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+      this.postSelected = this.currentNode.children[this.childIndex].assigneeType === 'post' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+      this.deptSelected = this.currentNode.children[this.childIndex].assigneeType === 'deptLeader' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.handler-list {
+  width: 500px;
+  margin: 0 auto 16px auto;
+  border-radius: 6px;
+  border: 1px solid #d7dae2;
+  padding: 10px 0 0 10px;
+  box-sizing: border-box;
+  display: flex;
+  flex-wrap: wrap;
+
+  .el-tag {
+    margin: 0 10px 10px 0;
+    cursor: pointer;
+  }
+}
+
+</style>

+ 380 - 0
src/newPerformance/components/IndicatorSetting/PerReviewsSelectorOnly.vue

@@ -0,0 +1,380 @@
+<template>
+  <el-dialog :visible.sync="innerVisible" @close="handleClose" @open="initData" @closed="dataReset" append-to-body
+    :close-on-click-modal="false" center title="审批节点配置" width="600px">
+    <el-form v-if="currentNode" label-width="200">
+      <el-form-item label="启用">
+        <el-switch v-model="currentNode.enable" />
+      </el-form-item>
+      <el-form-item label="">
+        <div class="handler-list">
+          <el-tag v-for="(item,index) in currentNode.children" :key="`hl_${index}`"
+            :type="childIndex === index ? '' : 'info'" closable @close="removeChild(index)" @click="childSelect(index)">
+            {{assigneeMap[item.assigneeType] || '未知'}}
+          </el-tag>
+          <el-button v-show="currentNode.enable" icon="el-icon-plus" size="mini" style="margin-bottom: 10px;"
+            @click="addChild" />
+        </div>
+      </el-form-item>
+      <el-form-item label="审批人">
+        <el-radio-group v-model="currentNode.children[childIndex].assigneeType" :disabled="!currentNode.enable"
+          @change="onAssigneeTypeChange">
+          <el-radio-button label="leader">组织管理员</el-radio-button>
+          <el-radio-button label="user">指定人员</el-radio-button>
+          <el-radio-button label="self">被考核人</el-radio-button>
+          <el-radio-button label="post">岗位</el-radio-button>
+          <el-radio-button label="deptLeader">部门负责人</el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+
+      <div class="flex-center-center" style="margin-left: 100px;">
+
+        <el-form-item label="">
+          <template v-if="currentNode.children[childIndex].assigneeType === 'leader'">
+            <el-select v-model="currentNode.children[childIndex].leaderLevel" placeholder="请选择管理员"
+              :disabled="!currentNode.enable" filterable style="width: 300px;" key="leader"
+              @change="onOrgManagerChange">
+              <el-option v-for="item in levelOptions" :key="`leader_${item.value}`" :label="item.label"
+                :value="item.value" style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>被考核人所在部门的直接上级或上上级 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'user'">
+            <el-select v-model="userSelected" placeholder="请选择指定人员" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="user" @change="onUserChange">
+              <el-option v-for="item in employees" :key="`user_${item.id}`" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'post'">
+            <el-select v-model="postSelected" placeholder="请选择岗位" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="post" @change="onPostChange">
+              <el-option v-for="item in postList" :key="`post_${item.id}`" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'deptLeader'">
+            <el-select v-model="deptSelected" placeholder="请选择部门" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="deptLeader" @change="onDeptChange">
+              <el-option v-for="item in deptList" :key="`deptLeader_${item.id}`" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+          </template>
+        </el-form-item>
+
+      </div>
+      <el-form-item label="多人时">
+        <el-radio-group v-model="currentNode.children[childIndex].multipleType" :disabled="!currentNode.enable">
+          <el-radio-button label="or">任一人确认(或签)</el-radio-button>
+          <el-radio-button label="parallel">按顺序确认(会签)</el-radio-button>
+          <el-radio-button label="sequence">同时确认(会签)</el-radio-button>
+        </el-radio-group>
+        <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+          ( <span style="color: red;"> * </span><span>会签要求所有审批人一致同意</span> )
+        </div>
+        <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+          ( <span style="color: red;"> * </span><span>或签只需任一审批人同意即可</span> )
+        </div>
+      </el-form-item>
+      <!-- <el-form-item label="允许">
+        <el-checkbox-group
+          v-model="currentNode.children[childIndex].allows"
+          :disabled="!currentNode.enable"
+        >
+          <el-checkbox-button label="transfer">转交</el-checkbox-button>
+        </el-checkbox-group>
+      </el-form-item> -->
+    </el-form>
+    <template #footer>
+      <el-row type="flex" justify="end">
+        <el-col align="end">
+          <el-button type="primary" @click="onConfirm">
+            确认
+          </el-button>
+        </el-col>
+      </el-row>
+    </template>
+  </el-dialog>
+</template>
+
+
+<script>
+import Template from "@/examine/components/Template.vue";
+
+
+export default {
+  name: "PerReviewsSelectorOnly",
+  components: {Template},
+  props: {
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+  },
+  data(){
+    return {
+      userInfo: this.$userInfo(),
+      currentNode: null,
+      childIndex: null,
+      innerVisible: this.showVisible,
+      loading:false,
+      employees:[],
+      postList:[],
+      deptList:[],
+      userSelected:[],
+      postSelected:[],
+      deptSelected:[],
+      assigneeMap:{
+        leader:'组织管理员',
+        user:'指定人员',
+        self:'被考核人',
+        post:'岗位',
+        deptLeader:'部门负责人',
+      },
+      levelOptions: [
+        {
+          value: 1,
+          label: '直接管理员'
+        },
+        {
+          value: 2,
+          label: '二级管理员'
+        },
+        {
+          value: 3,
+          label: '三级管理员'
+        },
+        {
+          value: 4,
+          label: '四级管理员'
+        },
+        {
+          value: 5,
+          label: '五级管理员'
+        },
+        {
+          value: 6,
+          label: '六级管理员'
+        }
+      ]
+    }
+  },
+  computed:{
+    employeeMap(){
+      const map = {};
+      this.employees.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    postMap(){
+      const map = {};
+      this.postList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    deptMap(){
+      const map = {};
+      this.deptList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+  },
+  watch:{
+    showVisible(val){
+      this.innerVisible = val;
+    },
+  },
+  methods: {
+    defaultNode(){
+      const node = JSON.parse("{\"id\":\"RS_1907236768800382982\",\"type\":\"reviews\",\"enable\":true,\"assigneeType\":\"leader\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":0,\"children\":[{\"id\":\"R_1907236768800382983\",\"type\":\"review\",\"enable\":true,\"assigneeType\":\"leader\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":0,\"children\":[]}]}");
+      node.id = "RS_" + this.generalId();
+      node.children.forEach((_,i) => {
+        node.children[i].id = "R_" + this.generalId();
+      });
+      return node;
+    },
+    matchErrMsg(err){
+      return err.toString().replace(/[(Error: )]/g,'');
+    },
+    handleClose(){
+      this.$emit('update:showVisible',false);
+    },
+    dataReset(){
+      this.currentNode = null;
+      this.childIndex = null;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    initData(){
+      if (this.loading) return;
+      this.dataReset();
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$axiosUser('get', '/api/pro/employee/index', {page:0,page_size:10,status:1}, 'v2'),
+        this.$axiosUser('get','/org/post'),
+        this.$axiosUser('get', `/org/departments/${this.userInfo.site_id}`)
+      ])
+        .then(([employeeRes,postRes,deptRes]) => {
+          if (employeeRes.data.code !== 1) throw new Error(employeeRes.data.msg);
+          if (postRes.data.code !== 1) throw new Error(postRes.data.message);
+          if (deptRes.data.code !== 1) throw new Error(deptRes.data.message);
+
+          this.employees = employeeRes.data.data.list
+            .filter(e => e.account_id && e.account_id > 0)
+            .map(e => {
+              return {
+                id:e.id,
+                name:e.name,
+              }
+            });
+
+          this.postList = postRes.data.data.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.deptList = deptRes.data.data.list.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.currentNode = this.defaultNode();
+
+          this.childIndex = this.currentNode.children && this.currentNode.children.length > 0 ? 0 : null;
+
+          if (this.childIndex !== null && !!this.currentNode.children[this.childIndex]){
+            switch (this.currentNode.children[this.childIndex].assigneeType){
+              case 'user':
+                this.userSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+                break;
+              case 'post':
+                this.postSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+                break;
+              case 'deptLeader':
+                this.deptSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+                break;
+            }
+          }
+        })
+        .catch(err => {
+          this.$message.error(this.matchErrMsg(err));
+          result = false;
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) this.handleClose();
+        })
+    },
+    generalId(){
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    addChild(){
+      let id = "R_" + this.generalId();
+      this.currentNode.children.push({
+        id:id,
+        type:'review',
+        enable:true,
+        assigneeType:'leader',
+        leaderLevel:1,
+        assigneeIds:[],
+        multipleType:'or',
+        allows:['transfer'],
+        weight:0,
+        children:[],
+      });
+    },
+    removeChild(index){
+      if (this.currentNode.children.length <= 1 || !this.currentNode.children[index]) return;
+      let selectChildId = this.currentNode.children[index].id;
+      this.childIndex = 0;
+      this.currentNode.children = this.currentNode.children.filter((_,i) => i !== index);
+      if (selectChildId){
+        this.currentNode.children.forEach((child,i) => {
+          if (child.id === selectChildId) this.childIndex = i;
+        })
+      }
+    },
+    onAssigneeTypeChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onOrgManagerChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [];
+      this.currentNode.children[this.childIndex].leaderLevel = v;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onUserChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = v;
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onPostChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = v;
+      this.deptSelected = [];
+    },
+    onDeptChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = v;
+    },
+    childSelect(index){
+      if (!this.currentNode.children[index]) return;
+      this.childIndex = index;
+      this.userSelected = this.currentNode.children[this.childIndex].assigneeType === 'user' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+      this.postSelected = this.currentNode.children[this.childIndex].assigneeType === 'post' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+      this.deptSelected = this.currentNode.children[this.childIndex].assigneeType === 'deptLeader' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+    },
+    onConfirm(){
+      this.$emit('confirm',this.currentNode);
+      this.handleClose();
+    },
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.handler-list {
+  width: 500px;
+  margin: 0 auto 16px auto;
+  border-radius: 6px;
+  border: 1px solid #d7dae2;
+  padding: 10px 0 0 10px;
+  box-sizing: border-box;
+  display: flex;
+  flex-wrap: wrap;
+
+  .el-tag {
+    margin: 0 10px 10px 0;
+    cursor: pointer;
+  }
+}
+
+</style>

+ 93 - 0
src/newPerformance/components/IndicatorSetting/PerScoreEachOtherOnly.vue

@@ -0,0 +1,93 @@
+<template>
+  <el-dialog
+    :visible.sync="innerVisible"
+    @close="handleClose"
+    @open="initData"
+    @closed="dataReset"
+    append-to-body
+    :close-on-click-modal="false"
+    center
+    title="互评节点配置"
+    width="600px"
+  >
+    <el-form v-if="currentNode" label-width="200">
+      <el-form-item label="启用">
+        <el-switch
+          v-model="currentNode.enable"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-row type="flex" justify="end">
+        <el-col align="end">
+          <el-button
+            type="primary"
+            @click="onConfirm"
+          >
+            确认
+          </el-button>
+        </el-col>
+      </el-row>
+    </template>
+  </el-dialog>
+</template>
+
+
+<script>
+
+export default {
+  name: "PerScoreSelfOnly",
+  props: {
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+  },
+  data(){
+    return {
+      userInfo: this.$userInfo(),
+      currentNode: null,
+      innerVisible: this.showVisible,
+      loading:false,
+    }
+  },
+  
+  watch:{
+    showVisible(val){
+      this.innerVisible = val;
+    },
+  },
+  methods: {
+    dataReset() { },
+    idGeneral() {
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    defaultNode() {
+      const node = JSON.parse("{\"id\":\"SEO_1907236768800382977\",\"type\":\"scoreEachOther\",\"enable\":true,\"assigneeType\":\"self\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":0,\"children\":[]}");
+      node.id = "RI_" + this.idGeneral();
+      return node;
+    },
+    
+    initData() {
+      if (this.loading) return;
+      this.dataReset();
+      this.loading = true;
+      this.currentNode = this.defaultNode();
+      this.loading = false;
+    },
+    
+    handleClose(){
+      this.$emit('update:showVisible',false);
+    },
+    
+    onConfirm(){
+      this.$emit('confirm',this.currentNode);
+      this.handleClose();
+    },
+  }
+}
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 95 - 0
src/newPerformance/components/IndicatorSetting/PerScoreSelfOnly.vue

@@ -0,0 +1,95 @@
+<template>
+  <el-dialog
+    :visible.sync="innerVisible"
+    @close="handleClose"
+    @open="initData"
+    @closed="dataReset"
+    append-to-body
+    :close-on-click-modal="false"
+    center
+    title="自评节点配置"
+    width="600px"
+  >
+    <el-form v-if="currentNode" label-width="200">
+      <el-form-item label="启用">
+        <el-switch
+          v-model="currentNode.enable"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-row type="flex" justify="end">
+        <el-col align="end">
+          <el-button
+            type="primary"
+            @click="onConfirm"
+          >
+            确认
+          </el-button>
+        </el-col>
+      </el-row>
+    </template>
+  </el-dialog>
+</template>
+
+
+<script>
+
+export default {
+  name: "PerScoreSelfOnly",
+  props: {
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+  },
+  data(){
+    return {
+      userInfo: this.$userInfo(),
+      currentNode: null,
+      innerVisible: this.showVisible,
+      loading:false,
+    }
+  },
+  
+  watch:{
+    showVisible(val){
+      this.innerVisible = val;
+    },
+  },
+  methods: {
+    dataReset() { },
+    idGeneral() {
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    defaultNode() {
+      const node = JSON.parse("{\"id\":\"SS_1907236768800382977\",\"type\":\"scoreSelf\",\"enable\":true,\"assigneeType\":\"self\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":0,\"children\":[]}");
+      node.id = "RI_" + this.idGeneral();
+      return node;
+    },
+    
+    initData() {
+      if (this.loading) return;
+      this.dataReset();
+      this.loading = true;
+      this.currentNode = this.defaultNode();
+      this.loading = false;
+    },
+    
+    handleClose(){
+      this.$emit('update:showVisible',false);
+    },
+    
+           
+   
+    onConfirm(){
+      this.$emit('confirm',this.currentNode);
+      this.handleClose();
+    },
+  }
+}
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 366 - 0
src/newPerformance/components/IndicatorSetting/PerScoresSelector.vue

@@ -0,0 +1,366 @@
+<template>
+  <el-dialog :visible.sync="innerVisible" @close="handleClose" @open="initData" @closed="dataReset" append-to-body
+    :close-on-click-modal="false" center title="评分节点配置" width="600px">
+    <el-form v-if="currentNode" label-width="200">
+      <el-form-item label="启用">
+        <el-switch v-model="currentNode.enable" disabled />
+      </el-form-item>
+
+      <el-form-item label="">
+        <div class="handler-list">
+          <el-tag v-for="(item,index) in currentNode.children" :key="`hl_${index}`"
+            :type="childIndex === index ? '' : 'info'" closable @close="removeChild(index)" @click="childSelect(index)">
+            {{assigneeMap[item.assigneeType] || '未知'}}
+          </el-tag>
+          <el-button v-show="currentNode.enable" icon="el-icon-plus" size="mini" style="margin-bottom: 10px;"
+            @click="addChild" />
+        </div>
+      </el-form-item>
+      <el-form-item label="评分人">
+        <el-radio-group v-model="currentNode.children[childIndex].assigneeType" :disabled="!currentNode.enable"
+          @change="onAssigneeTypeChange">
+          <el-radio-button label="leader">组织管理员</el-radio-button>
+          <el-radio-button label="user">指定人员</el-radio-button>
+          <el-radio-button label="self">被考核人</el-radio-button>
+          <el-radio-button label="post">岗位</el-radio-button>
+          <el-radio-button label="deptLeader">部门负责人</el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+
+      <div class="flex-center-center" style="margin-left: 100px;">
+        <el-form-item label="">
+          <template v-if="currentNode.children[childIndex].assigneeType === 'leader'">
+            <el-select v-model="currentNode.children[childIndex].leaderLevel" placeholder="请选择管理员"
+              :disabled="!currentNode.enable" filterable style="width: 300px;" key="leader"
+              @change="onOrgManagerChange">
+              <el-option v-for="item in levelOptions" :key="`leader_${item.value}`" :label="item.label"
+                :value="item.value" style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>被考核人所在部门的直接上级或上上级 )</div>
+                    
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'user'">
+            <el-select v-model="userSelected" placeholder="请选择指定人员" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="user" @change="onUserChange">
+              <el-option v-for="item in employees" :key="`user_${item.id}`" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'post'">
+            <el-select v-model="postSelected" placeholder="请选择岗位" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="post" @change="onPostChange">
+              <el-option v-for="item in postList" :key="`post_${item.id}`" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'deptLeader'">
+            <el-select v-model="deptSelected" placeholder="请选择部门" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="deptLeader" @change="onDeptChange">
+              <el-option v-for="item in deptList" :key="`deptLeader_${item.id}`" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+          </template>
+        </el-form-item>
+      </div>
+      <el-form-item label="多人时">
+        <el-radio-group v-model="currentNode.children[childIndex].multipleType" :disabled="!currentNode.enable">
+          <el-radio-button label="or">任一人确认(或签)</el-radio-button>
+          <el-radio-button label="parallel">按顺序确认(会签)</el-radio-button>
+          <el-radio-button label="sequence">同时确认(会签)</el-radio-button>
+        </el-radio-group>
+        <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+          ( <span style="color: red;"> * </span><span>会签要求所有审批人一致同意</span> )
+        </div>
+        <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+          ( <span style="color: red;"> * </span><span>或签只需任一审批人同意即可</span> )
+        </div>
+      </el-form-item>
+
+      <!-- <el-form-item label="允许">
+        <el-checkbox-group
+          v-model="currentNode.children[childIndex].allows"
+          :disabled="!currentNode.enable"
+        >
+          <el-checkbox-button label="transfer">转交</el-checkbox-button>
+        </el-checkbox-group>
+      </el-form-item> -->
+    </el-form>
+  </el-dialog>
+</template>
+
+
+<script>
+import Template from "@/examine/components/Template.vue";
+
+
+export default {
+  name: "PerScoresSelector",
+  components: {Template},
+  props: {
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+    indicator:{
+      type: Object,
+      default: () =>{
+        return null
+      }
+    }
+  },
+  data(){
+    return {
+      userInfo: this.$userInfo(),
+      currentNode: null,
+      childIndex: null,
+      innerVisible: this.showVisible,
+      loading:false,
+      employees:[],
+      postList:[],
+      deptList:[],
+      userSelected:[],
+      postSelected:[],
+      deptSelected:[],
+      assigneeMap:{
+        leader:'组织管理员',
+        user:'指定人员',
+        self:'被考核人',
+        post:'岗位',
+        deptLeader:'部门负责人',
+      },
+      levelOptions: [
+        {
+          value: 1,
+          label: '直接管理员'
+        },
+        {
+          value: 2,
+          label: '二级管理员'
+        },
+        {
+          value: 3,
+          label: '三级管理员'
+        },
+        {
+          value: 4,
+          label: '四级管理员'
+        },
+        {
+          value: 5,
+          label: '五级管理员'
+        },
+        {
+          value: 6,
+          label: '六级管理员'
+        }
+      ]
+    }
+  },
+  computed:{
+    employeeMap(){
+      const map = {};
+      this.employees.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    postMap(){
+      const map = {};
+      this.postList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    deptMap(){
+      const map = {};
+      this.deptList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+  },
+  watch:{
+    showVisible(val){
+      this.innerVisible = val;
+    },
+  },
+  methods: {
+    matchErrMsg(err){
+      return err.toString().replace(/[(Error: )]/g,'');
+    },
+    handleClose(){
+      this.$emit('update:showVisible',false);
+    },
+    dataReset(){
+      this.currentNode = null;
+      this.childIndex = null;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    initData(){
+      if (this.loading || !this.indicator) return;
+      this.dataReset();
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$axiosUser('get', '/api/pro/employee/index', {page:0,page_size:10,status:1}, 'v2'),
+        this.$axiosUser('get','/org/post'),
+        this.$axiosUser('get', `/org/departments/${this.userInfo.site_id}`)
+      ])
+        .then(([employeeRes,postRes,deptRes]) => {
+          if (employeeRes.data.code !== 1) throw new Error(employeeRes.data.msg);
+          if (postRes.data.code !== 1) throw new Error(postRes.data.message);
+          if (deptRes.data.code !== 1) throw new Error(deptRes.data.message);
+
+          this.employees = employeeRes.data.data.list
+            .filter(e => e.account_id && e.account_id > 0)
+            .map(e => {
+              return {
+                id:e.id,
+                name:e.name,
+              }
+            });
+
+          this.postList = postRes.data.data.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.deptList = deptRes.data.data.list.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.currentNode = this.indicator.flow.nodes.find(node => node.type === 'scores');
+
+          this.childIndex = this.currentNode.children && this.currentNode.children.length > 0 ? 0 : null;
+
+          if (this.childIndex !== null && !!this.currentNode.children[this.childIndex]){
+            switch (this.currentNode.children[this.childIndex].assigneeType){
+              case 'user':
+                this.userSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+                break;
+              case 'post':
+                this.postSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+                break;
+              case 'deptLeader':
+                this.deptSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+                break;
+            }
+          }
+        })
+        .catch(err => {
+          this.$message.error(this.matchErrMsg(err));
+          result = false;
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) this.handleClose();
+        })
+    },
+    generalId(){
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    addChild(){
+      let id = "S_" + this.generalId();
+      this.currentNode.children.push({
+        id:id,
+        type:'score',
+        enable:true,
+        assigneeType:'leader',
+        leaderLevel:1,
+        assigneeIds:[],
+        multipleType:'or',
+        allows:['transfer'],
+        weight:100,
+        children:[],
+      });
+    },
+    removeChild(index){
+      if (this.currentNode.children.length <= 1 || !this.currentNode.children[index]) return;
+      let selectChildId = this.currentNode.children[index].id;
+      this.childIndex = 0;
+      this.currentNode.children = this.currentNode.children.filter((_,i) => i !== index);
+      if (selectChildId){
+        this.currentNode.children.forEach((child,i) => {
+          if (child.id === selectChildId) this.childIndex = i;
+        })
+      }
+    },
+    onAssigneeTypeChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onOrgManagerChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [];
+      this.currentNode.children[this.childIndex].leaderLevel = v;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onUserChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = v;
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onPostChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = v;
+      this.deptSelected = [];
+    },
+    onDeptChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = v;
+    },
+    childSelect(index){
+      if (!this.currentNode.children[index]) return;
+      this.childIndex = index;
+      this.userSelected = this.currentNode.children[this.childIndex].assigneeType === 'user' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+      this.postSelected = this.currentNode.children[this.childIndex].assigneeType === 'post' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+      this.deptSelected = this.currentNode.children[this.childIndex].assigneeType === 'deptLeader' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.handler-list {
+  width: 500px;
+  margin: 0 auto 16px auto;
+  border-radius: 6px;
+  border: 1px solid #d7dae2;
+  padding: 10px 0 0 10px;
+  box-sizing: border-box;
+  display: flex;
+  flex-wrap: wrap;
+
+  .el-tag {
+    margin: 0 10px 10px 0;
+    cursor: pointer;
+  }
+}
+
+</style>

+ 382 - 0
src/newPerformance/components/IndicatorSetting/PerScoresSelectorOnly.vue

@@ -0,0 +1,382 @@
+<template>
+  <el-dialog :visible.sync="innerVisible" @close="handleClose" @open="initData" @closed="dataReset" append-to-body
+    :close-on-click-modal="false" center title="评分节点配置" width="600px">
+    <el-form v-if="currentNode" label-width="200">
+      <el-form-item label="启用">
+        <el-switch v-model="currentNode.enable" disabled />
+      </el-form-item>
+
+      <el-form-item label="">
+        <div class="handler-list">
+          <el-tag v-for="(item,index) in currentNode.children" :key="`hl_${index}`"
+            :type="childIndex === index ? '' : 'info'" closable @close="removeChild(index)" @click="childSelect(index)">
+            {{assigneeMap[item.assigneeType] || '未知'}}
+          </el-tag>
+          <el-button v-show="currentNode.enable" icon="el-icon-plus" size="mini" style="margin-bottom: 10px;"
+            @click="addChild" />
+        </div>
+      </el-form-item>
+      <el-form-item label="评分人">
+        <el-radio-group v-model="currentNode.children[childIndex].assigneeType" :disabled="!currentNode.enable"
+          @change="onAssigneeTypeChange">
+          <el-radio-button label="leader">组织管理员</el-radio-button>
+          <el-radio-button label="user">指定人员</el-radio-button>
+          <el-radio-button label="self">被考核人</el-radio-button>
+          <el-radio-button label="post">岗位</el-radio-button>
+          <el-radio-button label="deptLeader">部门负责人</el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+
+      <div class="flex-center-center" style="margin-left: 100px;">
+
+        <el-form-item label="">
+          <template v-if="currentNode.children[childIndex].assigneeType === 'leader'">
+            <el-select v-model="currentNode.children[childIndex].leaderLevel" placeholder="请选择管理员"
+              :disabled="!currentNode.enable" filterable style="width: 300px;" key="leader"
+              @change="onOrgManagerChange">
+              <el-option v-for="item in levelOptions" :key="`leader_${item.value}`" :label="item.label"
+                :value="item.value" style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>被考核人所在部门的直接上级或上上级 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'user'">
+            <el-select v-model="userSelected" placeholder="请选择指定人员" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="user" @change="onUserChange">
+              <el-option v-for="item in employees" :key="`user_${item.id}`" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'post'">
+            <el-select v-model="postSelected" placeholder="请选择岗位" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="post" @change="onPostChange">
+              <el-option v-for="item in postList" :key="`post_${item.id}`" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'deptLeader'">
+            <el-select v-model="deptSelected" placeholder="请选择部门" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="deptLeader" @change="onDeptChange">
+              <el-option v-for="item in deptList" :key="`deptLeader_${item.id}`" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+          </template>
+        </el-form-item>
+      </div>
+      
+        <el-form-item label="多人时">
+          <el-radio-group v-model="currentNode.children[childIndex].multipleType" :disabled="!currentNode.enable">
+            <el-radio-button label="or">任一人确认(或签)</el-radio-button>
+            <el-radio-button label="parallel">按顺序确认(会签)</el-radio-button>
+            <el-radio-button label="sequence">同时确认(会签)</el-radio-button>
+          </el-radio-group>
+          <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+            ( <span style="color: red;"> * </span><span>会签要求所有审批人一致同意</span> )
+          </div>
+          <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+            ( <span style="color: red;"> * </span><span>或签只需任一审批人同意即可</span> )
+          </div>
+        </el-form-item>
+
+        <!-- <el-form-item label="允许">
+        <el-checkbox-group
+          v-model="currentNode.children[childIndex].allows"
+          :disabled="!currentNode.enable"
+        >
+          <el-checkbox-button label="transfer">转交</el-checkbox-button>
+        </el-checkbox-group>
+      </el-form-item> -->
+    </el-form>
+    <template #footer>
+      <el-row type="flex" justify="end">
+        <el-col align="end">
+          <el-button type="primary" @click="onConfirm">
+            确认
+          </el-button>
+        </el-col>
+      </el-row>
+    </template>
+  </el-dialog>
+</template>
+
+
+<script>
+import Template from "@/examine/components/Template.vue";
+
+
+export default {
+  name: "PerScoresSelectorOnly",
+  components: {Template},
+  props: {
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+  },
+  data(){
+    return {
+      userInfo: this.$userInfo(),
+      currentNode: null,
+      childIndex: null,
+      innerVisible: this.showVisible,
+      loading:false,
+      employees:[],
+      postList:[],
+      deptList:[],
+      userSelected:[],
+      postSelected:[],
+      deptSelected:[],
+      assigneeMap:{
+        leader:'组织管理员',
+        user:'指定人员',
+        self:'被考核人',
+        post:'岗位',
+        deptLeader:'部门负责人',
+      },
+      levelOptions: [
+        {
+          value: 1,
+          label: '直接管理员'
+        },
+        {
+          value: 2,
+          label: '二级管理员'
+        },
+        {
+          value: 3,
+          label: '三级管理员'
+        },
+        {
+          value: 4,
+          label: '四级管理员'
+        },
+        {
+          value: 5,
+          label: '五级管理员'
+        },
+        {
+          value: 6,
+          label: '六级管理员'
+        }
+      ]
+    }
+  },
+  computed:{
+    employeeMap(){
+      const map = {};
+      this.employees.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    postMap(){
+      const map = {};
+      this.postList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    deptMap(){
+      const map = {};
+      this.deptList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+  },
+  watch:{
+    showVisible(val){
+      this.innerVisible = val;
+    },
+  },
+  methods: {
+    defaultNode(){
+      const node = JSON.parse("{\"id\":\"SS_1907236768800382980\",\"type\":\"scores\",\"enable\":true,\"assigneeType\":\"leader\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":0,\"children\":[{\"id\":\"S_1907236768800382981\",\"type\":\"score\",\"enable\":true,\"assigneeType\":\"leader\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[\"transfer\"],\"weight\":100,\"children\":[]}]}");
+      node.id = "SS_" + this.generalId();
+      node.children.forEach((_,i) => {
+        node.children[i].id = "S_" + this.generalId();
+      });
+      return node;
+    },
+    matchErrMsg(err){
+      return err.toString().replace(/[(Error: )]/g,'');
+    },
+    handleClose(){
+      this.$emit('update:showVisible',false);
+    },
+    dataReset(){
+      this.currentNode = null;
+      this.childIndex = null;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    initData(){
+      if (this.loading) return;
+      this.dataReset();
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$axiosUser('get', '/api/pro/employee/index', {page:0,page_size:10,status:1}, 'v2'),
+        this.$axiosUser('get','/org/post'),
+        this.$axiosUser('get', `/org/departments/${this.userInfo.site_id}`)
+      ])
+        .then(([employeeRes,postRes,deptRes]) => {
+          if (employeeRes.data.code !== 1) throw new Error(employeeRes.data.msg);
+          if (postRes.data.code !== 1) throw new Error(postRes.data.message);
+          if (deptRes.data.code !== 1) throw new Error(deptRes.data.message);
+
+          this.employees = employeeRes.data.data.list
+            .filter(e => e.account_id && e.account_id > 0)
+            .map(e => {
+              return {
+                id:e.id,
+                name:e.name,
+              }
+            });
+
+          this.postList = postRes.data.data.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.deptList = deptRes.data.data.list.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.currentNode = this.defaultNode();
+
+          this.childIndex = this.currentNode.children && this.currentNode.children.length > 0 ? 0 : null;
+
+          if (this.childIndex !== null && !!this.currentNode.children[this.childIndex]){
+            switch (this.currentNode.children[this.childIndex].assigneeType){
+              case 'user':
+                this.userSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+                break;
+              case 'post':
+                this.postSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+                break;
+              case 'deptLeader':
+                this.deptSelected = [...this.currentNode.children[this.childIndex].assigneeIds];
+                break;
+            }
+          }
+        })
+        .catch(err => {
+          this.$message.error(this.matchErrMsg(err));
+          result = false;
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) this.handleClose();
+        })
+    },
+    generalId(){
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    addChild(){
+      let id = "S_" + this.generalId();
+      this.currentNode.children.push({
+        id:id,
+        type:'score',
+        enable:true,
+        assigneeType:'leader',
+        leaderLevel:1,
+        assigneeIds:[],
+        multipleType:'or',
+        allows:['transfer'],
+        weight:100,
+        children:[],
+      });
+    },
+    removeChild(index){
+      if (this.currentNode.children.length <= 1 || !this.currentNode.children[index]) return;
+      let selectChildId = this.currentNode.children[index].id;
+      this.childIndex = 0;
+      this.currentNode.children = this.currentNode.children.filter((_,i) => i !== index);
+      if (selectChildId){
+        this.currentNode.children.forEach((child,i) => {
+          if (child.id === selectChildId) this.childIndex = i;
+        })
+      }
+    },
+    onAssigneeTypeChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onOrgManagerChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [];
+      this.currentNode.children[this.childIndex].leaderLevel = v;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onUserChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = v;
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onPostChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = v;
+      this.deptSelected = [];
+    },
+    onDeptChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = v;
+    },
+    childSelect(index){
+      if (!this.currentNode.children[index]) return;
+      this.childIndex = index;
+      this.userSelected = this.currentNode.children[this.childIndex].assigneeType === 'user' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+      this.postSelected = this.currentNode.children[this.childIndex].assigneeType === 'post' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+      this.deptSelected = this.currentNode.children[this.childIndex].assigneeType === 'deptLeader' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+    },
+    onConfirm(){
+      this.$emit('confirm',this.currentNode);
+      this.handleClose();
+    },
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.handler-list {
+  width: 500px;
+  margin: 0 auto 16px auto;
+  border-radius: 6px;
+  border: 1px solid #d7dae2;
+  padding: 10px 0 0 10px;
+  box-sizing: border-box;
+  display: flex;
+  flex-wrap: wrap;
+
+  .el-tag {
+    margin: 0 10px 10px 0;
+    cursor: pointer;
+  }
+}
+
+</style>

+ 364 - 0
src/newPerformance/components/IndicatorSetting/PerTargetConfirmSelector.vue

@@ -0,0 +1,364 @@
+<template>
+  <el-dialog :visible.sync="innerVisible" @close="handleClose" @open="initData" @closed="dataReset" append-to-body
+    :close-on-click-modal="false" center title="目标确认节点配置" width="600px">
+    <el-form v-if="currentNode" label-width="200">
+      <el-form-item label="启用">
+        <el-switch v-model="currentNode.enable" />
+      </el-form-item>
+
+      <el-form-item label="">
+        <div class="handler-list">
+          <el-tag v-for="(item,index) in currentNode.children" :key="index" :type="childIndex === index ? '' : 'info'"
+            closable @close="removeChild(index)" @click="childSelect(index)">
+            {{assigneeMap[item.assigneeType] || '未知'}}
+          </el-tag>
+          <el-button v-show="currentNode.enable" icon="el-icon-plus" size="mini" style="margin-bottom: 10px;"
+            @click="addChild" />
+        </div>
+      </el-form-item>
+      <el-form-item label="确认人">
+        <el-radio-group v-model="currentNode.children[childIndex].assigneeType" :disabled="!currentNode.enable"
+          @change="onAssigneeTypeChange">
+          <el-radio-button label="leader">组织管理员</el-radio-button>
+          <el-radio-button label="user">指定人员</el-radio-button>
+          <el-radio-button label="self">被考核人</el-radio-button>
+          <el-radio-button label="post">岗位</el-radio-button>
+          <el-radio-button label="deptLeader">部门负责人</el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+
+      <div class="flex-center-center" style="margin-left: 100px;">
+        <el-form-item label="">
+          <template v-if="currentNode.children[childIndex].assigneeType === 'leader'">
+            <el-select v-model="currentNode.children[childIndex].leaderLevel" placeholder="请选择管理员"
+              :disabled="!currentNode.enable" filterable style="width: 300px;" key="leader"
+              @change="onOrgManagerChange">
+              <el-option v-for="item in levelOptions" :key="item.value" :label="item.label" :value="item.value"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>被考核人所在部门的直接上级或上上级 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'user'">
+            <el-select v-model="userSelected" placeholder="请选择指定人员" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="user" @change="onUserChange">
+              <el-option v-for="item in employees" :key="item.id" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'post'">
+            <el-select v-model="postSelected" placeholder="请选择岗位" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="post" @change="onPostChange">
+              <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'deptLeader'">
+            <el-select v-model="deptSelected" placeholder="请选择部门" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="deptLeader" @change="onDeptChange">
+              <el-option v-for="item in deptList" :key="item.id" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+          </template>
+        </el-form-item>
+      </div>
+      <el-form-item label="多人时">
+        <el-radio-group v-model="currentNode.children[childIndex].multipleType" :disabled="!currentNode.enable">
+          <el-radio-button label="or">任一人确认(或签)</el-radio-button>
+          <el-radio-button label="parallel">按顺序确认(会签)</el-radio-button>
+          <el-radio-button label="sequence">同时确认(会签)</el-radio-button>
+        </el-radio-group>
+        <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+          ( <span style="color: red;"> * </span><span>会签要求所有审批人一致同意</span> )
+        </div>
+        <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+          ( <span style="color: red;"> * </span><span>或签只需任一审批人同意即可</span> )
+        </div>
+      </el-form-item>
+
+
+
+      <el-form-item label="允许">
+        <el-checkbox-group v-model="currentNode.children[childIndex].allows" :disabled="!currentNode.enable">
+          <el-checkbox-button label="edit">修改指标</el-checkbox-button>
+          <!-- <el-checkbox-button label="transfer">转交</el-checkbox-button> -->
+        </el-checkbox-group>
+      </el-form-item>
+    </el-form>
+  </el-dialog>
+</template>
+
+
+<script>
+import Template from "@/examine/components/Template.vue";
+
+export default {
+  name: "PerTargetConfirmSelector",
+  components: {Template},
+  props: {
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+    indicator:{
+      type: Object,
+      default: () =>{
+        return null
+      }
+    }
+  },
+  data(){
+    return {
+      userInfo: this.$userInfo(),
+      currentNode: null,
+      childIndex: null,
+      innerVisible: this.showVisible,
+      loading:false,
+      employees:[],
+      postList:[],
+      deptList:[],
+      userSelected:[],
+      postSelected:[],
+      deptSelected:[],
+      assigneeMap:{
+        leader:'组织管理员',
+        user:'指定人员',
+        self:'被考核人',
+        post:'岗位',
+        deptLeader:'部门负责人',
+      },
+      levelOptions: [
+        {
+          value: 1,
+          label: '直接管理员'
+        },
+        {
+          value: 2,
+          label: '二级管理员'
+        },
+        {
+          value: 3,
+          label: '三级管理员'
+        },
+        {
+          value: 4,
+          label: '四级管理员'
+        },
+        {
+          value: 5,
+          label: '五级管理员'
+        },
+        {
+          value: 6,
+          label: '六级管理员'
+        }
+      ]
+    }
+  },
+  computed:{
+    employeeMap(){
+      const map = {};
+      this.employees.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    postMap(){
+      const map = {};
+      this.postList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    deptMap(){
+      const map = {};
+      this.deptList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+  },
+  watch:{
+    showVisible(val){
+      this.innerVisible = val;
+    },
+  },
+  methods: {
+    matchErrMsg(err){
+      return err.toString().replace(/[(Error: )]/g,'');
+    },
+    handleClose(){
+      this.$emit('update:showVisible',false);
+    },
+    dataReset(){
+      this.currentNode = null;
+      this.childIndex = null;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    initData(){
+      if (this.loading || !this.indicator) return;
+      this.dataReset();
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$axiosUser('get', '/api/pro/employee/index', {page:0,page_size:10,status:1}, 'v2'),
+        this.$axiosUser('get','/org/post'),
+        this.$axiosUser('get', `/org/departments/${this.userInfo.site_id}`)
+      ])
+        .then(([employeeRes,postRes,deptRes]) => {
+          if (employeeRes.data.code !== 1) throw new Error(employeeRes.data.msg);
+          if (postRes.data.code !== 1) throw new Error(postRes.data.message);
+          if (deptRes.data.code !== 1) throw new Error(deptRes.data.message);
+
+          this.employees = employeeRes.data.data.list
+            .filter(e => e.account_id && e.account_id > 0)
+            .map(e => {
+              return {
+                id:e.id,
+                name:e.name,
+              }
+            });
+
+          this.postList = postRes.data.data.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.deptList = deptRes.data.data.list.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.currentNode = this.indicator.flow.nodes.find(node => node.type === 'targetConfirms');
+
+          this.childIndex = this.currentNode.children && this.currentNode.children.length > 0 ? 0 : null;
+
+          if (this.childIndex !== null && !!this.currentNode.children[this.childIndex]){
+            switch (this.currentNode.children[this.childIndex].assigneeType){
+              case 'user':
+                this.userSelected = this.currentNode.children[this.childIndex].assigneeIds;
+                break;
+              case 'post':
+                this.postSelected = this.currentNode.children[this.childIndex].assigneeIds;
+                break;
+              case 'deptLeader':
+                this.deptSelected = this.currentNode.children[this.childIndex].assigneeIds;
+                break;
+            }
+          }
+        })
+        .catch(err => {
+          this.$message.error(this.matchErrMsg(err));
+          result = false;
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) this.handleClose();
+        })
+    },
+    generalId(){
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    addChild(){
+      let id = "TC_" + this.generalId();
+      this.currentNode.children.push({
+        id:id,
+        type:'targetConfirm',
+        enable:true,
+        assigneeType:'self',
+        leaderLevel:1,
+        assigneeIds:[],
+        multipleType:'or',
+        allows:[],
+        weight:0,
+        children:[],
+      });
+    },
+    removeChild(index){
+      if (this.currentNode.children.length <= 1 || !this.currentNode.children[index]) return;
+      let selectChildId = this.currentNode.children[index].id;
+      this.childIndex = 0;
+      this.currentNode.children = this.currentNode.children.filter((_,i) => i !== index);
+      if (selectChildId){
+        this.currentNode.children.forEach((child,i) => {
+          if (child.id === selectChildId) this.childIndex = i;
+        })
+      }
+    },
+    onAssigneeTypeChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onOrgManagerChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [];
+      this.currentNode.children[this.childIndex].leaderLevel = v;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onUserChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = v;
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onPostChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = v;
+      this.deptSelected = [];
+    },
+    onDeptChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = v;
+    },
+    childSelect(index){
+      if (!this.currentNode.children[index]) return;
+      this.childIndex = index;
+      this.userSelected = this.currentNode.children[this.childIndex].assigneeType === 'user' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+      this.postSelected = this.currentNode.children[this.childIndex].assigneeType === 'post' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+      this.deptSelected = this.currentNode.children[this.childIndex].assigneeType === 'deptLeader' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.handler-list {
+  width: 500px;
+  margin: 0 auto 16px auto;
+  border-radius: 6px;
+  border: 1px solid #d7dae2;
+  padding: 10px 0 0 10px;
+  box-sizing: border-box;
+  display: flex;
+  flex-wrap: wrap;
+
+  .el-tag {
+    margin: 0 10px 10px 0;
+    cursor: pointer;
+  }
+}
+
+</style>

+ 389 - 0
src/newPerformance/components/IndicatorSetting/PerTargetConfirmSelectorOnly.vue

@@ -0,0 +1,389 @@
+<template>
+  <el-dialog :visible.sync="innerVisible" @close="handleClose" @open="initData" @closed="dataReset" append-to-body
+    :close-on-click-modal="false" center title="目标确认节点配置" width="600px">
+
+    <!-- <el-alert class="diy-tip" title="可配置多位人员来确定考核的指标" type="success" description>
+      <p>可以是组织管理员,被考核人自己,指定人员,岗位负责人,部门负责人</p>
+      <p>备注:</p>
+      <p>(会签要求所有审批人一致同意)</p>
+      <p>(或签只需任一审批人同意即可)</p>
+    </el-alert> -->
+    <el-form v-if="currentNode" label-width="200">
+      <el-form-item label="启用">
+        <el-switch v-model="currentNode.enable" />
+      </el-form-item>
+
+      <el-form-item label="">
+        <div class="handler-list">
+          <el-tag v-for="(item,index) in currentNode.children" :key="index" :type="childIndex === index ? '' : 'info'"
+            closable @close="removeChild(index)" @click="childSelect(index)">
+            {{assigneeMap[item.assigneeType] || '未知'}}
+          </el-tag>
+          <el-button v-show="currentNode.enable" icon="el-icon-plus" size="mini" style="margin-bottom: 10px;"
+            @click="addChild" />
+        </div>
+      </el-form-item>
+      <el-form-item label="确认人">
+        <el-radio-group v-model="currentNode.children[childIndex].assigneeType" :disabled="!currentNode.enable"
+          @change="onAssigneeTypeChange">
+          <el-radio-button label="leader">组织管理员</el-radio-button>
+          <el-radio-button label="user">指定人员</el-radio-button>
+          <el-radio-button label="self">被考核人</el-radio-button>
+          <el-radio-button label="post">岗位</el-radio-button>
+          <el-radio-button label="deptLeader">部门负责人</el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+
+      <div class="flex-center-center" style="margin-left: 100px;">
+        <el-form-item label="">
+          <template v-if="currentNode.children[childIndex].assigneeType === 'leader'">
+            <el-select v-model="currentNode.children[childIndex].leaderLevel" placeholder="请选择管理员"
+              :disabled="!currentNode.enable" filterable style="width: 300px;" key="leader"
+              @change="onOrgManagerChange">
+              <el-option v-for="item in levelOptions" :key="item.value" :label="item.label" :value="item.value"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>被考核人所在部门的直接上级或上上级 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'user'">
+            <el-select v-model="userSelected" placeholder="请选择指定人员" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="user" @change="onUserChange">
+              <el-option v-for="item in employees" :key="item.id" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'post'">
+            <el-select v-model="postSelected" placeholder="请选择岗位" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="post" @change="onPostChange">
+              <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
+          </template>
+          <template v-if="currentNode.children[childIndex].assigneeType === 'deptLeader'">
+            <el-select v-model="deptSelected" placeholder="请选择部门" multiple :disabled="!currentNode.enable" filterable
+              style="width: 300px;" key="deptLeader" @change="onDeptChange">
+              <el-option v-for="item in deptList" :key="item.id" :label="item.name" :value="item.id"
+                style="width: 300px;" />
+            </el-select>
+            <br>
+            <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+          </template>
+        </el-form-item>
+      </div>
+
+
+      <el-form-item label="多人时">
+        <el-radio-group v-model="currentNode.children[childIndex].multipleType" :disabled="!currentNode.enable">
+          <el-radio-button label="or">任一人确认(或签)</el-radio-button>
+          <el-radio-button label="parallel">按顺序确认(会签)</el-radio-button>
+          <el-radio-button label="sequence">同时确认(会签)</el-radio-button>
+        </el-radio-group>
+        <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+          ( <span style="color: red;"> * </span><span>会签要求所有审批人一致同意</span> )
+        </div>
+        <div class="flex-center-center fontColorC" style="margin-left: 100px;">
+          ( <span style="color: red;"> * </span><span>或签只需任一审批人同意即可</span> )
+        </div>
+      </el-form-item>
+
+      <el-form-item label="允许">
+        <el-checkbox-group v-model="currentNode.children[childIndex].allows" :disabled="!currentNode.enable">
+          <el-checkbox-button label="edit">修改指标</el-checkbox-button>
+          <!-- <el-checkbox-button label="transfer">转交</el-checkbox-button> -->
+        </el-checkbox-group>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-row type="flex" justify="end">
+        <el-col align="end">
+          <el-button type="primary" @click="onConfirm">
+            确认
+          </el-button>
+        </el-col>
+      </el-row>
+    </template>
+  </el-dialog>
+</template>
+
+
+<script>
+import Template from "@/examine/components/Template.vue";
+
+export default {
+  name: "PerTargetConfirmSelectorOnly",
+  components: {Template},
+  props: {
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+  },
+  data(){
+    return {
+      userInfo: this.$userInfo(),
+      currentNode: null,
+      childIndex: null,
+      innerVisible: this.showVisible,
+      loading:false,
+      employees:[],
+      postList:[],
+      deptList:[],
+      userSelected:[],
+      postSelected:[],
+      deptSelected:[],
+      assigneeMap:{
+        leader:'组织管理员',
+        user:'指定人员',
+        self:'被考核人',
+        post:'岗位',
+        deptLeader:'部门负责人',
+      },
+      levelOptions: [
+        {
+          value: 1,
+          label: '直接管理员'
+        },
+        {
+          value: 2,
+          label: '二级管理员'
+        },
+        {
+          value: 3,
+          label: '三级管理员'
+        },
+        {
+          value: 4,
+          label: '四级管理员'
+        },
+        {
+          value: 5,
+          label: '五级管理员'
+        },
+        {
+          value: 6,
+          label: '六级管理员'
+        }
+      ]
+    }
+  },
+  computed:{
+    employeeMap(){
+      const map = {};
+      this.employees.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    postMap(){
+      const map = {};
+      this.postList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+    deptMap(){
+      const map = {};
+      this.deptList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e.name;
+      });
+      return map;
+    },
+  },
+  watch:{
+    showVisible(val){
+      this.innerVisible = val;
+    },
+  },
+  methods: {
+    idGeneral(){
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    defaultNode(){
+      const node = JSON.parse("{\"id\":\"TCS_1906993155344510977\",\"type\":\"targetConfirms\",\"enable\":true,\"assigneeType\":\"self\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[],\"weight\":0,\"children\":[{\"id\":\"TC_1906993155344510978\",\"type\":\"targetConfirm\",\"enable\":true,\"assigneeType\":\"self\",\"leaderLevel\":1,\"assigneeIds\":[],\"multipleType\":\"or\",\"allows\":[],\"weight\":0,\"children\":[]}]}");
+      node.id = "TCS_" + this.idGeneral();
+      node.children.forEach((_,i) => {
+        node.children[i].id = "TC_" + this.idGeneral();
+      });
+      return node;
+    },
+    matchErrMsg(err){
+      return err.toString().replace(/[(Error: )]/g,'');
+    },
+    handleClose(){
+      this.$emit('update:showVisible',false);
+    },
+    dataReset(){
+      this.currentNode = null;
+      this.childIndex = null;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    initData(){
+      if (this.loading) return;
+      this.dataReset();
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$axiosUser('get', '/api/pro/employee/index', {page:0,page_size:10,status:1}, 'v2'),
+        this.$axiosUser('get','/org/post'),
+        this.$axiosUser('get', `/org/departments/${this.userInfo.site_id}`)
+      ])
+        .then(([employeeRes,postRes,deptRes]) => {
+          if (employeeRes.data.code !== 1) throw new Error(employeeRes.data.msg);
+          if (postRes.data.code !== 1) throw new Error(postRes.data.message);
+          if (deptRes.data.code !== 1) throw new Error(deptRes.data.message);
+
+          this.employees = employeeRes.data.data.list
+            .filter(e => e.account_id && e.account_id > 0)
+            .map(e => {
+              return {
+                id:e.id,
+                name:e.name,
+              }
+            });
+
+          this.postList = postRes.data.data.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.deptList = deptRes.data.data.list.map(item => {
+            return {
+              id: item.id,
+              name: item.name,
+            }
+          });
+
+          this.currentNode = this.defaultNode();
+
+          this.childIndex = this.currentNode.children && this.currentNode.children.length > 0 ? 0 : null;
+
+          if (this.childIndex !== null && !!this.currentNode.children[this.childIndex]){
+            switch (this.currentNode.children[this.childIndex].assigneeType){
+              case 'user':
+                this.userSelected = this.currentNode.children[this.childIndex].assigneeIds;
+                break;
+              case 'post':
+                this.postSelected = this.currentNode.children[this.childIndex].assigneeIds;
+                break;
+              case 'deptLeader':
+                this.deptSelected = this.currentNode.children[this.childIndex].assigneeIds;
+                break;
+            }
+          }
+        })
+        .catch(err => {
+          this.$message.error(this.matchErrMsg(err));
+          result = false;
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) this.handleClose();
+        })
+    },
+    generalId(){
+      return Date.now() + Math.floor(Math.random() * 10000);
+    },
+    addChild(){
+      let id = "TC_" + this.generalId();
+      this.currentNode.children.push({
+        id:id,
+        type:'targetConfirm',
+        enable:true,
+        assigneeType:'self',
+        leaderLevel:1,
+        assigneeIds:[],
+        multipleType:'or',
+        allows:[],
+        weight:0,
+        children:[],
+      });
+    },
+    removeChild(index){
+      if (this.currentNode.children.length <= 1 || !this.currentNode.children[index]) return;
+      let selectChildId = this.currentNode.children[index].id;
+      this.childIndex = 0;
+      this.currentNode.children = this.currentNode.children.filter((_,i) => i !== index);
+      if (selectChildId){
+        this.currentNode.children.forEach((child,i) => {
+          if (child.id === selectChildId) this.childIndex = i;
+        })
+      }
+    },
+    onAssigneeTypeChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onOrgManagerChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [];
+      this.currentNode.children[this.childIndex].leaderLevel = v;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onUserChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = v;
+      this.postSelected = [];
+      this.deptSelected = [];
+    },
+    onPostChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = v;
+      this.deptSelected = [];
+    },
+    onDeptChange(v){
+      this.currentNode.children[this.childIndex].assigneeIds = [...v];
+      this.currentNode.children[this.childIndex].leaderLevel = 1;
+      this.userSelected = [];
+      this.postSelected = [];
+      this.deptSelected = v;
+    },
+    childSelect(index){
+      if (!this.currentNode.children[index]) return;
+      this.childIndex = index;
+      this.userSelected = this.currentNode.children[this.childIndex].assigneeType === 'user' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+      this.postSelected = this.currentNode.children[this.childIndex].assigneeType === 'post' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+      this.deptSelected = this.currentNode.children[this.childIndex].assigneeType === 'deptLeader' ? this.currentNode.children[this.childIndex].assigneeIds : [];
+    },
+    onConfirm(){
+      this.$emit('confirm',this.currentNode);
+      this.handleClose();
+    },
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.handler-list {
+  width: 500px;
+  margin: 0 auto 16px auto;
+  border-radius: 6px;
+  border: 1px solid #d7dae2;
+  padding: 10px 0 0 10px;
+  box-sizing: border-box;
+  display: flex;
+  flex-wrap: wrap;
+
+  .el-tag {
+    margin: 0 10px 10px 0;
+    cursor: pointer;
+  }
+}
+
+</style>

+ 627 - 0
src/newPerformance/components/IndicatorSetting/PublishComp.vue

@@ -0,0 +1,627 @@
+<template>
+    <div style="width: 600px; margin: 10px auto; position: relative; ">
+
+        <!-- <el-alert class="bounce animated" type="warning" :title="alertTilte" :closable="false" show-icon
+            style="width: 100%; margin-top: 10px;"></el-alert> -->
+        <div class="flex-box-ce" style="flex-direction: column; justify-content: center;">
+            <!-- <div class="" style="font-size: 16px; font-weight: 600; text-align: center;">发起考核</div> -->
+
+            <div style="margin: 0 auto 20px auto; font-size: 16px; font-weight: 600;">填写考核基本信息</div>
+
+            <el-form :model="form" :rules="rules" ref="ruleForm" label-width="120px" size="small"
+                label-position="right">
+                <!-- <el-form-item label="考核标题" prop="title">
+                    <el-input v-model="form.title" style="width: 300px;"></el-input>
+                </el-form-item> -->
+                <el-form-item label="考核人员" prop="employeeIds">
+                    <el-select v-model="form.employeeIds" placeholder="请选择指定人员" multiple filterable
+                        style="width: 300px;" @change="changeEmployeeIds">
+                        <el-option v-for="item in employeeMap" :key="item.id" :label="item.name"
+                            :value="item.id"></el-option>
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="周期">
+                    <el-radio-group v-model="form.cycleType" @change="changeCircle">
+                        <el-radio-button label="0">自定义</el-radio-button>
+                        <el-radio-button label="1">年度</el-radio-button>
+                        <el-radio-button label="2">半年度 </el-radio-button>
+                        <el-radio-button label="3">季度</el-radio-button>
+                        <el-radio-button label="4">月度</el-radio-button>
+                    </el-radio-group>
+                </el-form-item>
+
+                <!-- <el-form-item label="考核分类">
+                    <el-select v-model="form.cateId" @change="changeCateId" placeholder="请选择考核分类" style="width: 300px;">
+                        <el-option v-for="item in cateList" :key="item.cateId" :label="item.name" :value="item.cateId">
+                        </el-option>
+                    </el-select>
+                </el-form-item> -->
+
+                <el-form-item v-if="form.cycleType == 0" label="开始日期" prop="startDate">
+                    <el-date-picker v-model="form.startDate" type="date" value-format="yyyy-MM-dd" placeholder="选择开始日期"
+                        style="width: 300px;">
+                    </el-date-picker>
+                </el-form-item>
+                <el-form-item v-if="form.cycleType == 0" label="结束日期" prop="endDate">
+                    <el-date-picker v-model="form.endDate" type="date" value-format="yyyy-MM-dd" placeholder="选择结束日期"
+                        style="width: 300px;">
+                    </el-date-picker>
+                </el-form-item>
+
+                <el-form-item v-if="form.cycleType == 1" label="选择年份" prop="year">
+                    <el-date-picker v-model="form.year" type="year" value-format="yyyy" placeholder="选择年">
+                    </el-date-picker>
+                </el-form-item>
+
+                <div class="selectBox" v-if="form.cycleType == 2 || form.cycleType == 3">
+                    <SelectCircle :dateParameter="dateParameter" :dateOptions="dateOptions" @confirm="dateConfirm"
+                        :id="1">
+                        <div class="flex-box-ce cursor">
+                            <div>{{ dateParameter.year }}</div>
+                            <div style="margin: 0 10px;">{{ dateParameter.name }}</div>
+                            <i class="el-icon-caret-bottom fontColorC"></i>
+                        </div>
+                    </SelectCircle>
+                </div>
+
+                <el-form-item v-if="form.cycleType == 4" label="选择月份" prop="month">
+                    <el-date-picker v-model="form.month" type="month" placeholder="选择月" value-format="yyyy-MM">
+                    </el-date-picker>
+                </el-form-item>
+
+                <!-- <InterviewFlow form-label="主持人" dialog-title="面谈" node-type="interview" @onConfirm="finishHandle" /> -->
+
+                <el-form-item label="启用面谈">
+                    <el-switch v-model="currentNode.enable"></el-switch>
+                </el-form-item>
+
+                <el-form-item label="主持人">
+                    <el-radio-group v-model="currentNode.assigneeType" :disabled="!currentNode.enable">
+                        <el-radio-button label="leader">管理员</el-radio-button>
+                        <el-radio-button label="user">指定人员</el-radio-button>
+                        <el-radio-button label="self">被考核人</el-radio-button>
+                        <el-radio-button label="post">岗位</el-radio-button>
+                        <el-radio-button label="deptLeader">部门</el-radio-button>
+                    </el-radio-group>
+                </el-form-item>
+
+
+                <el-form-item v-if="currentNode.assigneeType === 'leader'">
+                    <el-select v-model="selected_manager_ids" placeholder="请选择管理员" :disabled="!currentNode.enable"
+                        filterable style="width: 300px;" @change="changeManagerIds">
+                        <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
+                            :value="item.value" style="width: 300px;"></el-option>
+                    </el-select>
+                </el-form-item>
+
+                <el-form-item v-if="currentNode.assigneeType === 'user'">
+                    <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
+                        :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changeEmployeeIds">
+                        <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
+                            style="width: 300px;"></el-option>
+                    </el-select>
+                </el-form-item>
+
+                <el-form-item v-if="currentNode.assigneeType === 'post'">
+                    <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple :disabled="!currentNode.enable"
+                        filterable style="width: 300px;" @change="changePostIds">
+                        <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
+                            style="width: 300px;"></el-option>
+                    </el-select>
+                </el-form-item>
+
+                <el-form-item v-if="currentNode.assigneeType === 'deptLeader'">
+                    <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
+                        :options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
+                        @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                </el-form-item>
+
+
+                <!-- <el-form-item v-if="form.circle == 1" label="结束日期">
+                    <el-date-picker v-model="form.endDate" type="date" placeholder="选择结束日期" style="width: 300px;">
+                    </el-date-picker>
+                </el-form-item> -->
+
+            </el-form>
+            <!-- <div style="margin: 20px auto;" v-if="selectedData && selectedData.length > 0">
+                <div class="title">已选择的OKR</div>
+                <div class="selected-item" v-for="item in selectedData" @click="changeSelectedOkrs">
+                    {{ item.name }}
+                </div>
+            </div>
+            <el-link v-else type="primary" @click="isShowProject = true">请选择关联OKR</el-link> -->
+            <div style="margin: 20px auto;">
+                <el-button type="primary" @click="submitForm('ruleForm')" size="small">确定</el-button>
+                <el-button @click="resetForm('ruleForm')" size="small">取 消</el-button>
+            </div>
+
+        </div>
+
+
+
+        <el-drawer title="考核分类" :visible.sync="cateDetailsDialog" direction="rtl" :before-close="handleClose">
+            <CateDetails v-if="cateDetailsDialog"></CateDetails>
+        </el-drawer>
+
+
+        <!-- 关联OKR -->
+        <TargetSearch :visible.sync="isShowProject" @confirm="confirmProject" :selectedCheckList="okrs"
+            :showSelectedData="selectedData"></TargetSearch>
+
+    </div>
+</template>
+
+<script>
+import moment from 'moment'
+
+// 计算某一季度开始日期和结束日期
+function getQuarterDates(year, quarter) {
+    // 计算季度的开始日期(第一个月的第一天)
+    const startDate = moment().year(year).quarter(quarter).startOf('quarter');
+    // 计算季度的结束日期(最后一个月的最后一天)
+    const endDate = moment().year(year).quarter(quarter).endOf('quarter');
+
+    return {
+        start: startDate,
+        end: endDate
+    };
+}
+
+import { mapGetters } from 'vuex';
+import SelectCircle from '@/newPerformance/components/TemplateDetails/SelectCircle'; //选择周期
+import CateDetails from "@/newPerformance/components/TemplateDetails/CateDetails.vue" // 考核分类明细抽屉
+import InterviewFlow from "@/newPerformance/components/TemplateDetails/InterviewFlow.vue" // 面谈弹框
+import TargetSearch from '@/performance/views/assessManagement/TargetSearch'; // 对齐目标
+
+export default {
+    components: {
+        SelectCircle,
+        CateDetails,
+        InterviewFlow,
+        TargetSearch
+    },
+    model: {
+        prop: 'showPublishDialog',
+        event: 'close-dialog'
+    },
+    props: {
+        showPublishDialog: {
+            type: Boolean,
+            default: false
+        },
+        templateIds: {
+            type: Array,
+            default: () => []
+        },
+    },
+    data() {
+        return {
+            alertTilte: "如果有一个指标标题为空,将会发布不成功,请仔细检查数据!",
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            cateDetailsDialog: false, // 考核分类弹框
+            form: {
+                title: "",
+                employeeIds: [],
+                startDate: "",
+                endDate: "",
+                cycleType: "0", // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+                year: "",
+                cateId: 0
+            },
+            isShowProject: false,
+            rules: {
+                title: [
+                    { required: true, message: '请输入考核标题', trigger: 'blur' },
+                ],
+                employeeIds: [
+                    { required: true, message: '请选择考核人员', trigger: 'change' }
+                ],
+                startDate: [
+                    { required: true, message: '请选择开始日期', trigger: 'change' }
+                ],
+                endDate: [
+                    { required: true, message: '请选择结束日期', trigger: 'change' }
+                ],
+                year: [
+                    { required: true, message: '请选择年份', trigger: 'change' }
+                ],
+                month: [
+                    { required: true, message: '请选择月份', trigger: 'change' }
+                ],
+            },
+            dateOptions: [],
+            dateParameter: {
+                year: moment().format('YYYY'),
+                cycle_type: 0,
+                dateId: 1,
+                name: '选择年度',
+            },
+            cateList: [], // 考核分类列表,
+            interview: false,
+            params: null,
+            okrs: [], // 关联的OKRS,
+            selectedData: [],
+
+            cascaderProps: {
+                multiple: true, // 启用多选
+                checkStrictly: true, // 父子节点不联动
+                emitPath: false, // 选中值只返回当前节点的值
+            },
+            levelOptions: [
+                {
+                    value: 1,
+                    label: '直接管理员'
+                },
+                {
+                    value: 2,
+                    label: '二级管理员'
+                },
+                {
+                    value: 3,
+                    label: '三级管理员'
+                },
+                {
+                    value: 4,
+                    label: '四级管理员'
+                },
+                {
+                    value: 5,
+                    label: '五级管理员'
+                },
+                {
+                    value: 6,
+                    label: '六级管理员'
+                }
+            ],
+            deptList: JSON.parse(localStorage.getItem("deptList")), // 部门列表 - 树形结构
+            dept_list: JSON.parse(localStorage.getItem("dept_list")), // 部门列表
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            postList: JSON.parse(localStorage.getItem("postList")), // 岗位列表
+            selected_dept_ids: [], // 选择的部门列表
+            selected_manager_ids: '', // 选择的管理员列表
+            selected_post_ids: [], // 选择的岗位列表
+            selected_employee_ids: [], // 选择的员工列表
+            currentNode: {
+                id: "IT_" + Date.now() + Math.floor(Math.random() * 10000),
+                type: "interview",
+                allows: [],
+                enable: true,
+                weight: 0,
+                children: [],
+                assigneeIds: [],
+                leaderLevel: 1,
+                assigneeType: "self",
+                multipleType: "or"
+            }
+        }
+    },
+
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+
+    watch: {
+        showPublish(v) {
+            // if (v) this.getCateList()
+        }
+    },
+
+    mounted() {
+        // this.getCateList()
+    },
+
+    methods: {
+        // 考核分类列表
+        getCateList() {
+            let url = `/performance/cate/list/${this.user_info.site_id}`;
+            // this.loading = true
+            this.$axiosUser('get', url, {}).then(res => {
+                let { data: { code, data: { list, total } } } = res
+                if (code == 1) {
+                    this.cateList = list
+                    this.form.cateId = this.cateList[0].cateId || 0
+                }
+            })
+        },
+
+        // 选择考核分类
+        changeCateId(v) {
+            console.log(v)
+        },
+        // 选择员工
+        changeEmployeeIds(v) {
+            if (v && v.length > 0) {
+                v.forEach(item => {
+                    this.form.employeeIds.push(item)
+                })
+            }
+            this.form.employeeIds = Array.from(new Set(this.form.employeeIds)); // 去重
+        },
+
+        changeCircle(v) {
+            if (v == 2) this.dateOptions = [
+                { name: '上半年', id: 1, cycle_type: 3 },
+                { name: '下半年', id: 2, cycle_type: 3 },
+            ]
+            if (v == 3) this.dateOptions = [
+                { name: '第一季度', id: 1, cycle_type: 2 },
+                { name: '第二季度', id: 2, cycle_type: 2 },
+                { name: '第三季度', id: 3, cycle_type: 2 },
+                { name: '第四季度', id: 4, cycle_type: 2 }
+            ]
+        },
+
+
+
+        dateConfirm(date) {
+            let { year, name } = date;
+            this.dateParameter.year = year;
+            this.dateParameter.name = name;
+            // 上半年度
+            if (name === "上半年") {
+                let startOfYear = moment().year(year).startOf('year'); // 获取年初
+                let endOfFirstHalfYear = moment().year(year).startOf('year').add(6, 'months').subtract(1, 'days'); // 获取上半年结束日(6月30日)
+                this.form.startDate = startOfYear.format('YYYY-MM-DD');
+                this.form.endDate = endOfFirstHalfYear.format('YYYY-MM-DD');
+            }
+            // 下半年度
+            if (name === "下半年") {
+                let startOfSecondHalfYear = moment().year(year).startOf('year').add(6, 'months'); // 获取下半年开始日(7月1日)
+                let endOfYear = moment().year(year).endOf('year'); // 获取年末
+                this.form.startDate = startOfSecondHalfYear.format('YYYY-MM-DD');
+                this.form.endDate = endOfYear.format('YYYY-MM-DD');
+            }
+            // 第一季度
+            if (name === "第一季度") {
+                const dates = getQuarterDates(year, 1);
+                this.form.startDate = dates.start.format('YYYY-MM-DD');
+                this.form.endDate = dates.end.format('YYYY-MM-DD');
+            }
+
+            // 第一季度
+            if (name === "第二季度") {
+                const dates = getQuarterDates(year, 2);
+                this.form.startDate = dates.start.format('YYYY-MM-DD');
+                this.form.endDate = dates.end.format('YYYY-MM-DD');
+            }
+
+            // 第三季度
+            if (name === "第三季度") {
+                const dates = getQuarterDates(year, 3);
+                this.form.startDate = dates.start.format('YYYY-MM-DD');
+                this.form.endDate = dates.end.format('YYYY-MM-DD');
+            }
+
+            // 第四季度
+            if (name === "第四季度") {
+                const dates = getQuarterDates(year, 4);
+                this.form.startDate = dates.start.format('YYYY-MM-DD');
+                this.form.endDate = dates.end.format('YYYY-MM-DD');
+            }
+
+        },
+
+        handleClose() {
+            this.cateDetailsDialog = false
+        },
+
+        // 关闭弹窗
+        handleCloseDialog() {
+            this.$emit('close-dialog', false);
+        },
+
+
+        // 选择管理员
+        changeManagerIds(v) {
+            this.currentNode.leaderLevel = v
+        },
+
+
+        // 选择员工
+        changeEmployeeIds(v) {
+            if (v && v.length > 0) {
+                this.currentNode.assigneeIds = v
+            }
+        },
+
+        // 选择岗位
+        changePostIds(v) {
+            if (v && v.length > 0) {
+                this.currentNode.assigneeIds = v
+            }
+        },
+
+        // 选择部门
+        deptChange(val) {
+            if (val && val.length > 0) {
+                // 获取当前选中的节点
+                const checkedNodes = this.$refs.deptSelectRef.getCheckedNodes();
+                this.selected_dept_ids = checkedNodes.map((node) => node.value);
+                this.currentNode.assigneeIds = this.selected_dept_ids
+                this.currentNode.assigneeIds = Array.from(new Set(this.currentNode.assigneeIds)); // 去重
+            }
+
+        },
+
+
+        checkInterviewNode() {
+
+            // 验证表单数据
+            this.currentNode.assigneeIds = []
+            // 回显选中的管理者
+            if (this.currentNode.assigneeType == 'leader') {
+                if (this.selected_manager_ids) this.currentNode.leaderLevel = this.selected_manager_ids
+                else return this.$message.error("请选择管理员")
+            }
+
+            // 回显选中的被考核人
+            if (this.currentNode.assigneeType == 'self') {
+                this.currentNode.assigneeIds.push(this.user_info.id);
+            }
+
+            // 回显选中的岗位
+            if (this.currentNode.assigneeType == 'post') {
+                if (this.selected_post_ids && this.selected_post_ids.length > 0)
+                    this.selected_post_ids.forEach(postId => {
+                        this.currentNode.assigneeIds.push(postId);
+                    })
+                else return this.$message.error("请选择岗位")
+            }
+
+            // 回显选中的指定人员
+            if (this.currentNode.assigneeType == 'user') {
+                if (this.selected_employee_ids && this.selected_employee_ids.length > 0)
+                    this.selected_employee_ids.forEach(employeeId => {
+                        this.currentNode.assigneeIds.push(employeeId);
+                    })
+                else return this.$message.error("请选择指定人员")
+            }
+
+            // 回显选中的部门列表
+            if (this.currentNode.assigneeType == 'deptLeader') {
+                if (this.selected_dept_ids && this.selected_dept_ids.length > 0)
+                    this.selected_dept_ids.forEach(dept_id => {
+                        this.currentNode.assigneeIds.push(dept_id);
+                    })
+                else return this.$message.error("请选择部门")
+            }
+
+            // 去重
+            this.currentNode.assigneeIds = Array.from(new Set(this.currentNode.assigneeIds))
+
+            // let nodes = [this.currentNode];
+            console.log(this.currentNode)
+        },
+
+        // 确定按钮
+        submitForm(formName) {
+
+            this.$refs[formName].validate((valid) => {
+                if (valid) {
+                    // 周期类型 0 - 未定义 1 - 年度 2 - 半年度 3 - 季度 4 - 月度
+                    // 年度
+                    if (this.form.cycleType == 1) {
+                        let { year } = this.form;
+                        // 计算年份的开始时间(即1月1日)
+                        const startOfYear = moment().year(year).startOf('year');
+                        this.form.startDate = startOfYear.format('YYYY-MM-DD');
+                        // 计算年份的结束时间(即12月31日)
+                        const endOfYear = moment().year(year).endOf('year');
+                        // 年度结束时间
+                        this.form.endDate = endOfYear.format('YYYY-MM-DD');
+                    }
+
+
+                    // 月
+                    if (this.form.cycleType == 4) {
+                        let [year, month] = this.form.month.split("-");
+                        // 计算开始时间(该月的第1天)
+                        const startOfMonth = moment(`${year}-${month}-01`);
+                        this.form.startDate = startOfMonth.format('YYYY-MM-DD');
+
+                        // 计算结束时间(该月的最后一天)
+                        const endOfMonth = moment(startOfMonth).endOf('month');
+                        this.form.endDate = endOfMonth.format('YYYY-MM-DD');
+                    }
+
+                    this.params = {
+                        templateIds: this.templateIds,
+                        ...this.form,
+                        okrs: this.okrs
+                    }
+                    this.checkInterviewNode()
+                    this.params = {
+                        ...this.params,
+                        interviewFlow: {
+                            nodes: [...this.currentNode]
+                        }
+                    },
+                    // this.interview = true
+                    this.$emit('onConfirm', this.params);
+                    // this.handleCloseDialog();
+                } else {
+                    console.log('error submit!!');
+                    return false;
+                }
+            });
+
+
+        },
+        // 重置表单
+        resetForm(formName) {
+            
+            this.$refs[formName].resetFields();
+            this.handleCloseDialog();
+        },
+
+        finishHandle(nodes) {
+            this.params = {
+                ...this.params,
+                interviewFlow: {
+                    nodes
+                }
+            },
+
+            this.$emit('onConfirm', this.params, this.selectedData);
+            this.handleCloseDialog();
+        },
+
+        changeSelectedOkrs() {
+            this.isShowProject = true
+        },
+
+        confirmProject(okrs, selectedData) {
+            this.okrs = okrs;
+            this.selectedData = selectedData;
+        }
+    }
+}
+</script>
+
+<style scoped="scoped" lang="scss">
+
+.selectBox {
+    width: 300px;
+    height: 40px;
+    border-radius: 40px;
+    border: 1px solid #eee;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin: 0 auto;
+}
+.status-btn-box {
+    width: 120px;
+    height: 40px;
+    z-index: 10;
+    position: absolute;
+    top: 20px;
+    left: 20px;
+
+    .status-btn {
+        width: 100%;
+        height: 100%;
+        background: transparent;
+        border: 2px dashed;
+        text-align: center;
+        font-size: 20px;
+        line-height: 40px;
+    }
+}
+
+.title {
+    margin-bottom: 5px;
+}
+
+.selected-item {
+    padding: 5px;
+    box-sizing: border-box;
+    border-radius: 4px;
+    background: #f7f7f7;
+    margin-bottom: 5px;
+
+    &:hover {
+        cursor: pointer;
+    }
+}
+</style>

+ 928 - 0
src/newPerformance/components/IndicatorSetting/TemplateDetails.vue

@@ -0,0 +1,928 @@
+<template>
+    <div class="box boxMinHeight">
+        <el-alert class="bounce animated" type="warning" :title="alertTilte" :closable="false" show-icon
+            style="width: 100%; margin-bottom: 10px;"></el-alert>
+        <header class="header">
+            <div class="header-content flex-box-ce">
+                <!-- 返回按钮 -->
+                <div class="flex-box-ce header-left">
+                    <el-tooltip class="item" effect="dark" :content="templateTitle" placement="bottom">
+                        <div>{{ templateTitle }}</div>
+                    </el-tooltip>
+                    <el-popover ref="popoverRef" placement="right" width="400" trigger="click">
+                        <div class="flex-box-ce">
+                            <el-input v-model="title" placeholder="请输入模板名称" size="small"></el-input>
+                            <el-button v-if="$getRole(1)" type="primary" size="small" style="margin-left: 10px;"
+                                @click="editTitle()">确定</el-button>
+                        </div>
+                        <i class="el-icon-edit" slot="reference" style="margin-left: 10px; color: #999;"></i>
+                    </el-popover>
+                </div>
+
+                <!-- 发布按钮 -->
+                <div class="header-right">
+                    <el-button type="primary" size="small" @click="addData()">添加指标</el-button>
+
+                    <el-button type="danger" :disabled="!(multipleSelection && multipleSelection.length > 0)"
+                        @click="confirmDelete()" size="small">批量删除</el-button>
+
+                    <div class="flex-box-ce" style="margin-left: 10px; color: #999;">
+                        <el-popover placement="right" trigger="hover" class="popover" @show="showPopover">
+                            <div class="flex-box-ce" style="width: 200px; justify-content: space-between;">
+                                <el-checkbox v-model="checkAll" @change="checkAllChangeFn">全选</el-checkbox>
+                                <el-button type="text" @click="reset(true)">重置</el-button>
+                            </div>
+                            <div style="height: 1px; background-color: #DCDFE6; margin: 5px 0;"></div>
+                            <el-checkbox-group v-model="checkColumns" @change="changeColumns"
+                                style="width: 200px; display: flex; flex-direction: column;">
+                                <el-checkbox v-for="(item, key) in this.tableColumn" :label="item.label"
+                                    :key="item.label"></el-checkbox>
+                            </el-checkbox-group>
+                            <div class="flex-box-ce" slot="reference">
+                                <el-button class="primaryBtn" icon="el-icon-s-tools" type="primary"
+                                    size="mini">设置考核流程</el-button>
+                            </div>
+                        </el-popover>
+                    </div>
+                </div>
+            </div>
+        </header>
+
+        <div style="height: 1px; background-color: #DCDFE6; margin-top: 5px;"></div>
+
+        <el-table :data="tableData" v-loading="loading" stripe style="width: 100%; margin-top: 10px;" border
+            :header-cell-style="{ background: '#f5f7fa' }" @selection-change="handleSelectChange">
+            <el-table-column type="selection"></el-table-column>
+            <template v-for="item in tableColumn">
+
+                <el-table-column v-if="item.isShow && item.label === '规则'" :key="item.prop" :prop="item.prop"
+                    :label="item.label" align="center" :min-width="item.width">
+                    <template slot-scope="scope">
+                        <el-tooltip v-if="scope.row.content" class="item" effect="dark" placement="top">
+                            <div v-html="scope.row.content" slot="content"
+                                style="max-width: 300px; white-space: pre-line;">
+                            </div>
+                            <div class="oneLine"
+                                @click="editContent(scope.$index, scope.row.content, scope.row.indicatorId)">
+                                {{ scope.row.content }}
+                            </div>
+                        </el-tooltip>
+                        <el-button v-else
+                            @click="editContent(scope.$index, scope.row.content, scope.row.indicatorId)">编辑规则</el-button>
+                    </template>
+                </el-table-column>
+
+                <el-table-column v-else-if="item.isShow && ['目标', '权重(%)'].includes(item.label)" :key="item.prop"
+                    :prop="item.prop" :label="item.label" align="center" :min-width="item.width">
+                    <template slot-scope="scope">
+                        <el-input v-model="scope.row[item.prop]" :placeholder="item.label" clearable
+                            @blur="handleEdit(item.prop, scope.row[item.prop], scope.row.indicatorId)"
+                            oninput="value=value.replace(/[^\d.]/g,'')"></el-input>
+                    </template>
+                </el-table-column>
+
+                <el-table-column v-else-if="item.isShow && item.label === '计算公式'" prop="formulae" label="计算公式"
+                    align="center" min-width="130">
+                    <template slot-scope="scope">
+                        <el-button @click="openFormula(scope.row, scope.$index)">
+                            {{ scope.row.expression && scope.row.expression.formulas.length > 0 ? `公式
+                            ${scope.row.expression.formulas.length} 条` : '公式' }}
+                        </el-button>
+                    </template>
+                </el-table-column>
+
+                <el-table-column v-else-if="item.isShow && ['指标', '单位'].includes(item.label)" :key="item.prop"
+                    :prop="item.prop" :label="item.label" align="center" :min-width="item.width">
+                    <template slot-scope="scope">
+                        <el-input v-model="scope.row[item.prop]" :placeholder="item.label" clearable
+                            @blur="handleEdit(item.prop, scope.row[item.prop], scope.row.indicatorId)"></el-input>
+                    </template>
+                </el-table-column>
+
+                <el-table-column v-else-if="item.isShow && ['过程跟踪'].includes(item.label)" :key="item.prop"
+                    :prop="item.prop" :label="item.label" align="center" :min-width="item.width">
+                    <template slot-scope="scope">
+                        <el-input v-model="scope.row[item.prop]" :placeholder="item.label" clearable
+                            @blur="handleEdit(item.prop, scope.row[item.prop], scope.row.indicatorId)"></el-input>
+                    </template>
+                </el-table-column>
+
+                <el-table-column v-if="item.isShow && flowColumn.includes(item.label)" :key="item.prop"
+                    :prop="item.prop" :label="item.label" :render-header="(h, obj) => renderHeader(h, item, item.prop)"
+                    align="center" :min-width="item.width" fixed="right">
+                    <template slot-scope="scope">
+                        <template v-if="item.label === '确认目标'">
+                            <el-switch v-if="!scope.row.flow.nodes[0].enable" :value="scope.row.flow.nodes[0].enable"
+                                @input="handleStatusChange(-1, scope.row, 'targetConfirms')"></el-switch>
+                            <ShowDataComp v-else-if="scope.row.flow.nodes[0].enable && isDataShow"
+                                :show-data="scope.row.flow.nodes[0]" :select-nodes="scope.row"
+                                @btnClick="handleBtnClick" />
+                        </template>
+
+                        <template v-if="item.label === '录入结果'">
+                            <el-switch v-if="!scope.row.flow.nodes[1].enable" :value="scope.row.flow.nodes[1].enable"
+                                @input="handleStatusChange(-1, scope.row, 'resultInput')"></el-switch>
+                            <ShowDataComp v-else-if="scope.row.flow.nodes[1].enable && isDataShow"
+                                :show-data="scope.row.flow.nodes[1]" :select-nodes="scope.row"
+                                @btnClick="handleBtnClick" />
+                        </template>
+
+                        <template v-if="item.label === '自评'">
+                            <el-switch v-model="scope.row.flow.nodes[2].enable"
+                                @change="handleScoreSelf(scope.row, scope.$index)"></el-switch>
+                        </template>
+
+                        <template v-if="item.label === '互评'">
+                            <el-switch :value="scope.row.flow.nodes[3].enable"
+                                @input="handleStatusChange(-1, scope.row, 'scoreEachOther')"></el-switch>
+                        </template>
+
+                        <template v-if="item.label === '评分'">
+                            <el-switch v-if="!scope.row.flow.nodes[4].enable" :value="scope.row.flow.nodes[4].enable"
+                                @input="handleStatusChange(-1, scope.row, 'scores')"></el-switch>
+                            <ShowDataComp v-else-if="scope.row.flow.nodes[4].enable && isDataShow"
+                                :show-data="scope.row.flow.nodes[4]" :select-nodes="scope.row"
+                                @btnClick="handleBtnClick" />
+                        </template>
+
+                        <template v-if="item.label === '审批'">
+                            <el-switch v-if="!scope.row.flow.nodes[5].enable" :value="scope.row.flow.nodes[5].enable"
+                                @input="handleStatusChange(-1, scope.row, 'reviews')"></el-switch>
+                            <ShowDataComp v-else-if="scope.row.flow.nodes[5].enable && isDataShow"
+                                :show-data="scope.row.flow.nodes[5]" :select-nodes="scope.row"
+                                @btnClick="handleBtnClick" />
+                        </template>
+
+                        <template v-if="item.label === '抄送'">
+                            <el-switch v-if="!scope.row.flow.nodes[6].enable" :value="scope.row.flow.nodes[6].enable"
+                                @input="handleStatusChange(-1, scope.row, 'cc')"></el-switch>
+                            <ShowDataComp v-else-if="scope.row.flow.nodes[6].enable && isDataShow"
+                                :show-data="scope.row.flow.nodes[6]" :select-nodes="scope.row"
+                                @btnClick="handleBtnClick" />
+                        </template>
+
+                    </template>
+                </el-table-column>
+            </template>
+
+        </el-table>
+
+        <div style="height: 50px;"></div>
+        <!-- <TiptapComp /> -->
+        <!-- 编辑流程节点 -->
+        <!-- <HandleNode v-if="currentIndicator" v-model="dialogVisible" :form-label="formLabel" :dialog-title="dialogTitle"
+            :node-type="nodeType" :template-id="templateId" :select-nodes="selectNodes"
+            :indicator-id="selectIndicatorId" @closeDialog="closeDialog" :tag-index="tagIndex"
+            @onConfirm="finishHandle" /> -->
+
+        <!-- 编辑计算公式 -->
+        <FormulaComp v-if="showFormula" v-model="showFormula"
+            :fixed-props="[{ key: 'target', name: '目标' }, { key: 'weight', name: '权重' }, { key: 'result', name: '结果值' }]"
+            :expressions-props="currentIndicator.expression.formulas || []" @onConfirm="onFormulaConfirm" />
+
+        <!-- 发布考核弹框 -->
+        <PublishComp v-if="showPublish" v-model="showPublish" :template-ids="[templateId]"
+            @onConfirm="onPubishConfirm" />
+
+        <!-- 编辑规则 -->
+        <EditContentComp v-if="showEditContent" v-model="showEditContent" :content="content"
+            :indicator-id="selectIndicatorId" :template-id="templateId" @finishEdit="finishEditContent" />
+
+        <!-- 批量修改流程节点 -->
+        <BatchHandleNode v-if="batchHandleDialog" v-model="batchHandleDialog" :template-id="templateId"
+            :form-label="formLabel" :dialog-title="dialogTitle" :node-type="nodeType" :handle-node="handleNode"
+            @onConfirm="finishBatchHandle" />
+
+        <!-- 编辑确认目标节点 -->
+        <TargetConfirms v-if="targetConfirms" v-model="targetConfirms" :form-label="formLabel"
+            :dialog-title="dialogTitle" :node-type="nodeType" :template-id="templateId" :select-nodes="selectNodes"
+            :indicator-id="selectIndicatorId" @closeDialog="closeDialog" :tag-index="tagIndex"
+            @onConfirm="finishHandle" />
+
+        <!-- 编辑录入结果节点 -->
+        <ResultInput v-if="resultInput" v-model="resultInput" :form-label="formLabel" :dialog-title="dialogTitle"
+            :node-type="nodeType" :template-id="templateId" :select-nodes="selectNodes"
+            :indicator-id="selectIndicatorId" @closeDialog="closeDialog" :tag-index="tagIndex"
+            @onConfirm="finishHandle" />
+
+        <!-- 编辑互评节点 -->
+        <ScoreEachOther v-if="scoreEachOther" v-model="scoreEachOther" :form-label="formLabel"
+            :dialog-title="dialogTitle" :node-type="nodeType" :template-id="templateId" :select-nodes="selectNodes"
+            :indicator-id="selectIndicatorId" @closeDialog="closeDialog" :tag-index="tagIndex"
+            @onConfirm="finishHandle" />
+
+        <!-- 编辑评分节点 -->
+        <Scores v-if="scores" v-model="scores" :form-label="formLabel" :dialog-title="dialogTitle" :node-type="nodeType"
+            :template-id="templateId" :select-nodes="selectNodes" :indicator-id="selectIndicatorId"
+            @closeDialog="closeDialog" :tag-index="tagIndex" @onConfirm="finishHandle" />
+
+        <Reviews v-if="reviews" v-model="reviews" :form-label="formLabel" :dialog-title="dialogTitle"
+            :node-type="nodeType" :template-id="templateId" :select-nodes="selectNodes"
+            :indicator-id="selectIndicatorId" @closeDialog="closeDialog" :tag-index="tagIndex"
+            @onConfirm="finishHandle" />
+
+        <!-- 编辑审批节点 -->
+        <CC v-if="cc" v-model="cc" :form-label="formLabel" :dialog-title="dialogTitle" :node-type="nodeType"
+            :template-id="templateId" :select-nodes="selectNodes" :indicator-id="selectIndicatorId"
+            @closeDialog="closeDialog" :tag-index="tagIndex" @onConfirm="finishHandle" />
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import FormulaComp from '@/newPerformance/components/TemplateDetails/FormulaComp'; // 计算公式弹框
+import HandleNode from '@/newPerformance/components/TemplateDetails/HandleNode'; //单独设置流程节点弹框
+import PublishComp from '@/newPerformance/components/TemplateDetails/PublishComp'; // 发布考核弹框
+import ShowDataComp from '@/newPerformance/components/PublicComp/ShowData'; // 显示节点数据组件
+import EditContentComp from '@/newPerformance/components/TemplateDetails/EditContent'; // 编辑规则组件
+import BatchHandleNode from '@/newPerformance/components/TemplateDetails/BatchHandleNode'; // 批量设置流程节点
+import TargetConfirms from '@/newPerformance/components/TemplateDetails/TargetConfirms'; // 确认目标流程节点
+import ResultInput from '@/newPerformance/components/TemplateDetails/ResultInput'; // 结果录入流程节点
+import ScoreEachOther from '@/newPerformance/components/TemplateDetails/ScoreEachOther'; // 互评流程节点
+import Scores from '@/newPerformance/components/TemplateDetails/Scores'; // 评分流程节点
+import Reviews from '@/newPerformance/components/TemplateDetails/Reviews'; // 审批流程节点
+import CC from '@/newPerformance/components/TemplateDetails/CC'; // 审批流程节点
+
+export default {
+    components: {
+        HandleNode,
+        FormulaComp,
+        PublishComp,
+        ShowDataComp,
+        EditContentComp,
+        BatchHandleNode,
+        TargetConfirms,
+        ResultInput,
+        ScoreEachOther,
+        Scores,
+        Reviews,
+        CC
+    },
+    props: {
+        // 模板ID
+        templateId: {
+            type: Number | String,
+            default: ''
+        }
+    },
+    data() {
+        return {
+            isDataShow: false,
+            isShow: false,
+            templateTitle: "模板名称",
+            loading: false,
+            title: "模板名称",
+            alertTilte: "可在表格中直接编辑指标,规则 (规则支持富文本) ,目标,单位,权重,计算公式以及流程节点, 注意: 每个指标的标题不能为空!每个指标的评分节点一定要开启!",
+            tableData: [], // 表格数据
+            showFormula: false, // 编辑计算公式弹框显示
+            showPublish: false, // 发布考核弹框显示
+            dialogVisible: false, // 编辑流程节点弹框显示
+            dialogTitle: "", // 编辑流程节点弹框标题
+            isDisable: false, // 是否禁用
+            nodeType: "", // 节点类型
+            currentIndicator: null, // 操作的指标
+            selectIndicatorId: "", // 选择的指标ID
+            selectNodes: [], // 选中指标所有的节点
+            selectIndex: 0, // 表格行索引
+            formLabel: "",
+            tagIndex: 0,
+            multipleSelection: [],
+            content: "", // 指标规则
+            showEditContent: false, // 编辑指标内容弹框
+            deptList: [], // 部门列表 - 树形结构
+            dept_list: [], // 部门列表
+            postList: [], // 岗位列表
+            flowColumn: ["确认目标", "录入结果", "自评", "互评", "评分", "审批", "抄送", "过程跟踪"],
+            tableColumn: [
+                { label: "指标", prop: "title", isShow: true, width: 150 },
+                { label: "规则", prop: "content", isShow: true, width: 150 },
+                { label: "目标", prop: "target", isShow: true, width: 80},
+                { label: "单位", prop: "unit", isShow: true, width: 100 },
+                { label: "权重(%)", prop: "weight", isShow: true, width: 100 },
+                { label: "计算公式", prop: "formulae", isShow: true, width: 120 },
+                { label: "过程跟踪", prop: "okrs", isShow: true, width: 120 },
+                { label: "确认目标", prop: "targetConfirms", isShow: false, width: 100 },
+                { label: "录入结果", prop: "resultInput", isShow: false, width: 100 },
+                { label: "自评", prop: "scoreSelf", isShow: false, width: 100 },
+                { label: "互评", prop: "scoreEachOther", isShow: false, width: 100 },
+                { label: "评分", prop: "scores", isShow: false, width: 100 },
+                { label: "审批", prop: "reviews", isShow: false, width: 100 },
+                { label: "抄送", prop: "cc", isShow: false, width: 100 }
+            ],
+            checkColumns: [],
+            checkAll: false,
+            flowInfo: {},
+            handleNode: {},
+            batchHandleDialog: false,
+            targetConfirms: false,
+            resultInput: false,
+            scoreEachOther: false,
+            scores: false,
+            reviews: false,
+            cc: false
+        }
+    },
+    
+    computed: {
+        ...mapGetters(['user_info']),
+        // 指标标题为空不能发起考核
+        isTitleNull() {
+            return this.tableData.find(item => item.title == '' || item.title == null) ? true : false
+        },
+        // 指标评分为禁用不能发起考核
+        isScoreNull() {
+            return this.tableData.find(item => !item.flow.nodes[4].enable) ? true : false
+        }
+    },
+
+
+    created() {
+        this.get_template_detail();
+        this.get_table_data();
+        this.isDataShow = false
+        // 请求岗位列表,部门列表
+        Promise.all([this.get_dept_list(), this.get_post_list()]).then(([deptRes, postRes]) => {
+            if (deptRes.data.code !== 1) return this.$message.error(deptRes.data.msg || "请求部门列表数据出错")
+            if (postRes.data.code !== 1) return this.$message.error(postRes.data.msg || "请求岗位列表数据出错")
+            this.dept_list = deptRes.data.data.list;
+            this.deptList = this.getTreeData(this.dept_list); // 处理成树状结构
+            this.postList = postRes.data.data.list
+            localStorage.setItem("dept_list", JSON.stringify(this.dept_list))
+            localStorage.setItem("deptList", JSON.stringify(this.deptList))
+            localStorage.setItem("postList", JSON.stringify(this.postList))
+            this.isDataShow = true
+        })
+    },
+    methods: {
+
+        // 处理部门树状结构数据
+        getTreeData(data) {
+            for (var i = 0; i < data.length; i++) {
+                data[i].checked = false;
+                if (data[i].children.length < 1) {
+                    // children若为空数组,则将children设为undefined
+                    data[i].children = undefined;
+                } else {
+                    // children若不为空数组,则继续 递归调用 本方法
+                    this.getTreeData(data[i].children);
+                }
+            }
+            return data;
+        },
+
+        // 获取部门
+        get_dept_list() {
+            return this.$axiosUser('get', '/api/pro/department/tree', '', 'v2')
+        },
+
+        // 岗位列表
+        get_post_list() {
+            let data = {
+                page: 1,
+                page_size: 999,
+                cate_id: -1,
+                name: ''
+            }
+            return this.$axiosUser('get', '/api/pro/post/cate_post_list', data)
+        },
+
+        // 自定义表头
+        renderHeader(h, item, type) {
+            let label = ""
+            const labels = {
+                'targetConfirms': '确认目标',
+                'resultInput': '录入结果',
+                'scoreSelf': '自评',
+                'scoreEachOther': '互评',
+                'scores': '评分',
+                'reviews': '审批',
+                'cc': '抄送'
+            }
+            label = labels[type];
+            let that = this;
+            
+            return h('div', {
+                style: { display: "flex", alignItems: "center", justifyContent: "center" }
+            }, [
+
+                h('el-button', {
+                    props: {
+                        size: 'small'
+                    },
+                    on: {
+                        click: function () {
+                            that.clickButton(type);
+                        }
+                    }
+                }, label),
+            ])
+        },
+
+        clickButton(nodeType) {
+            this.handleNode = this.flowInfo.nodes.find(node => node.type === nodeType)
+            let options = {
+                'targetConfirms': ['确认目标', '确认人'],
+                'resultInput': ['录入结果', '录入人'],
+                'scoreSelf': ['自评', ''],
+                'scoreEachOther': ['互评', '互评人'],
+                'scores': ['评分', '评分人'],
+                'reviews': ['审批', '审批人'],
+                'cc': ['抄送', '抄送人']
+            }
+            this.dialogTitle = options[nodeType][0]
+            this.formLabel = options[nodeType][1]
+            this.nodeType = nodeType; // 节点类型
+            this.batchHandleDialog = true;
+        },
+
+        // 打开编辑规则内容弹框
+        editContent(index, content, indicatorId) {
+            this.selectIndex = index;
+            this.content = content;
+            this.selectIndicatorId = indicatorId;
+            this.showEditContent = true
+        },
+
+        finishEditContent(content) {
+            this.tableData[this.selectIndex].content = content
+        },
+        
+        // 获取模板详情
+        get_template_detail() {
+            this.$axiosUser("get", `/performance/template/info/${this.user_info.site_id}/` + this.templateId).then(res => {
+                let { data: { data: { title, flow } } } = res
+                this.templateTitle = title || '模板名称'
+                this.title = title || '模板名称'
+                this.flowInfo = flow || {}
+            })
+        },
+
+        // 获取表格数据
+        get_table_data() {
+            this.loading = true
+            this.$axiosUser("get", `/performance/indicator/list/${this.user_info.site_id}/` + this.templateId).then(res => {
+                this.loading = false
+                this.tableData = res.data.data.list
+            })
+        },
+
+
+        editTableData() {
+        },
+        // 编辑模板标题
+        editTitle() {
+            let title = this.title
+            if (title !== null && title !== '') {
+                let url = `/performance/template/title/${this.user_info.site_id}`
+                this.$http.post(url, { templateId: this.templateId, title }).then(res => {
+                    if (res.code == 1) {
+                        this.$refs.popoverRef && this.$refs.popoverRef.doClose();
+                        this.get_template_detail();
+                    }
+                    
+                })
+            }
+        },
+
+        // 编辑指标名称,规则,权重,目标,单位
+        handleEdit(props, value, id) {
+            let url = '', data = {};
+            url = `/performance/indicator/${props}/${this.user_info.site_id}/${this.templateId}`;
+            data[props] = value;
+            data['indicatorId'] = id;
+            this.$http.post(url, data).then(res => {
+                if(res.code == 0) return this.$message.error(res.msg || '操作失败')
+            })
+        },
+
+        handleBtnClick(index, node, row) {
+            this.handleStatusChange(index, row, node.type)
+        },
+
+        // 自评节点单独操作
+        handleScoreSelf(row, index) {
+            let { indicatorId, flow: { nodes } } = row
+            let data = {
+                indicatorId, // 指标ID
+                nodes
+            }
+            let url = `/performance/indicator/flow/${this.user_info.site_id}/${this.templateId}`
+            this.$http.post(url, data).then(res => {
+                let { code, data } = res
+                if (code == 1) {
+                    this.tableData.splice(index, 1, data); //替换元素
+                    this.$message.success("操作成功");
+                } else {
+                    this.$message.error(res.message || '操作失败');
+                }
+            })
+        },
+
+        // 操作节点
+        handleStatusChange(index, row, nodeType) {
+            if (index !== -1) this.tagIndex = index // 子节点索引
+            this.currentIndicator = row;
+            let { indicatorId } = row;
+            this.selectIndicatorId = indicatorId; // 指标ID
+            this.selectNodes = row.flow.nodes; // 所有节点
+            this.nodeType = nodeType; // 节点类型
+            if (nodeType == 'targetConfirms') {
+                this.dialogTitle = "确认目标"
+                this.formLabel = "确认人"
+                this.targetConfirms = true;
+            }
+            if (nodeType == 'resultInput') {
+                this.dialogTitle = "录入结果"
+                this.formLabel = "录入人"
+                this.resultInput = true;
+            }
+            if (nodeType == 'scoreSelf') {
+                this.dialogTitle = "自评"
+            }
+            if (nodeType == 'scoreEachOther') {
+                this.dialogTitle = "互评"
+                this.formLabel = "互评人"
+                this.scoreEachOther = true
+            }
+            if (nodeType == 'scores') {
+                this.dialogTitle = "评分"
+                this.formLabel = "评分人"
+                this.scores = true
+
+            }
+            if (nodeType == 'reviews') {
+                this.dialogTitle = "审批"
+                this.formLabel = "审批人"
+                this.reviews = true
+            }
+            if (nodeType == 'cc') {
+                this.dialogTitle = "抄送"
+                this.formLabel = "抄送人"
+                this.cc = true
+            }
+            // this.dialogVisible = true;
+        },
+
+
+        // 添加指标
+        addData() {
+            let url = `/performance/indicator/create/${this.user_info.site_id}/${this.templateId}`
+            this.$http.post(url, {}).then(res => {
+                let { data, code } = res
+                if (code) this.tableData.push(data)
+            })
+        },
+
+        handleSelectChange(val) {
+            this.multipleSelection = val;
+        },
+        // 取消删除
+        cancelDelete() {
+            console.log("取消删除");
+        },
+        // 确认删除
+        confirmDelete() {
+            if (this.multipleSelection && this.multipleSelection.length > 0) {
+                this.$confirm('确定删除选中的指标吗?', '提示', {
+                    type: 'warning'
+                }).then(() => {
+                    // 创建一个包含异步任务的数组,每个任务都是一个 axios 请求
+                    const arrays = []
+                    this.multipleSelection.forEach(item => {
+                        arrays.push(this.deleteIndicator(item))
+                    })
+
+                    // 当所有请求都成功完成时,responses 是一个包含所有响应的数组
+                    Promise.all(arrays).then(responses => {
+                        // console.log(responses)
+                        this.get_table_data();
+                    }).catch(error => {
+                        // 如果任何一个请求失败,将会进入这个 catch 块
+                        console.log(error)
+                    })
+                }).catch(() => { });
+            }
+            
+        },
+        // 删除指标
+        deleteIndicator(item) {
+            let url = `/performance/indicator/remove/${this.user_info.site_id}/${this.templateId}/${item.indicatorId}`
+            return this.$axiosUser('post', url).then(res => {
+                return res.data
+            })
+        },
+        // 打开计算公式弹框
+        openFormula(row, index) {
+            this.currentIndicator = null;
+            this.selectIndex = -1;
+            this.currentIndicator = row;
+            this.selectIndex = index;
+            this.showFormula = true;
+        },
+        // 关闭编辑计算公式弹框
+        closeDialog() {
+            
+        },
+
+        // 全选复选框事件监听
+        checkAllChangeFn(val) {
+            if (val) {
+                // 全选
+                this.tableColumn.forEach(item => {
+                    item.isShow = true
+                })
+            } else {
+                // 反全选
+                this.tableColumn.forEach(item => {
+                    if (this.flowColumn.includes(item.label)) {
+                        item.isShow = false;
+                    } else {
+                        item.isShow = true
+                    }
+                })
+            }
+            this.showPopover();
+        },
+        // 重置,flag: Boolean,全部重置为flag
+        reset(flag) {
+            this.tableColumn.forEach(item => {
+                if (this.flowColumn.includes(item.label)) {
+                    item.isShow = false;
+                } else {
+                    item.isShow = true
+                }
+            })
+            this.showPopover();
+            this.refreshTable();
+        },
+        // 表格列是否显示的方法
+        showColumn(currentColumn) {
+            return this.tableColumn.find(item => item.prop == currentColumn).isShow;
+        },
+        /* 选择列 */
+        changeColumns(val) {
+            this.tableColumn.forEach(item => {
+                item.isShow = false;
+            })
+            // columns将val数组存在的值设为true,不存在的设为false
+            val && val.forEach(item => {
+                let current = this.tableColumn.find(i => i.label == item)
+                current.isShow = true;
+            })
+            // 判断是否全选
+            this.judgeIsCheckAll();
+            // this.refreshTable();
+        },
+        // 重新渲染表格
+        refreshTable() {
+            this.$nextTick(() => {
+                if (this.$refs.fmeaTableRef) this.$refs.fmeaTableRef.doLayout();
+            })
+        },
+        // 气泡框出现
+        showPopover() {
+            this.checkColumns = []
+            this.tableColumn.forEach(item => {
+                if (item.isShow) {
+                    this.checkColumns.push(item.label)
+                }
+            })
+            // 判断是否全选
+            this.judgeIsCheckAll();
+        },
+        // 判断是否全选
+        judgeIsCheckAll() {
+            // 选中的长度 = 表格列的长度  全选按钮就选中
+            if (this.checkColumns.length == this.tableColumn.length)
+                this.checkAll = true
+            else
+                this.checkAll = false
+        },
+        
+        finishHandle(nodes) {
+            let data = nodes
+            let url = `/performance/indicator/flow/${this.user_info.site_id}/${this.templateId}`
+            this.$http.post(url, data).then(res => {
+                let { code, data } = res
+                if (code == 1) {
+                    let { indicatorId } = this.currentIndicator;
+                    let index = this.tableData.findIndex(table => table.indicatorId === indicatorId)
+                    this.tableData.splice(index, 1, data); //替换元素
+                    this.$message.success("操作成功");
+                    this.nodeType = ''
+                    this.dialogTitle = ''
+                    this.selectNodes = []
+                    this.currentIndicator = []
+                    this.selectIndicatorId = ''
+                    this.tagIndex = -1
+                } else {
+                    this.$message.error(res.message || '操作失败');
+                }
+            })
+        },
+
+        // 批量操作成功
+        finishBatchHandle(data) {
+            this.flowInfo = data;
+            this.get_table_data();
+        },
+
+        // 编辑计算公式确定的回调
+        onFormulaConfirm(formulas) {
+            let url = `/performance/indicator/expression/${this.user_info.site_id}/${this.templateId}`;
+            let { indicatorId } = this.currentIndicator;
+            let params = {
+                indicatorId,
+                expression: {
+                    formulas: formulas.map(item => {
+                        return {
+                            condition: item.condition,
+                            expression: item.expression
+                        }
+                    })
+                }
+            }
+            this.$http.post(url, params).then(res => {
+                let { data, code } = res
+                if (code) {
+                    this.tableData.splice(this.selectIndex, 1, data); //替换元素
+                }
+            })
+        },
+        // 发布考核
+        publish() {
+            this.showPublish = true;
+        },
+        // 发布考核弹框的回调
+        onPubishConfirm(params, selectOkrs) {
+            let templateId = this.templateId;
+            let { title, startDate, endDate, cycleType, employeeIds, okrs, cateId } = params
+            let employees = employeeIds.map(employee => ({
+                employeeId: employee,
+                ids: [],
+                interviewFlow: {
+                    nodes: [
+                        {
+                            id: "IT_1894283564934688769",
+                            type: "interview",
+                            allows: [],
+                            enable: false,
+                            weight: 0,
+                            children: [],
+                            assigneeIds: [],
+                            leaderLevel: 1,
+                            assigneeType: "self",
+                            multipleType: "or"
+                        }
+                    ]
+                }
+            }))
+
+            let requireParams = {
+                title,
+                cycleType,
+                startDate,
+                endDate,
+                okrs,
+                templates: [
+                    {
+                        templateId,
+                        employees
+                    }
+                ],
+                cateId
+            }
+            // let url = `/performance/review/publish/${this.user_info.site_id}`;
+            // console.log(requireParams)
+            let url = `/performance/review/publish/templates/${this.user_info.site_id}`;
+            this.loading = true
+            this.$http.post(url, requireParams).then(res => {
+                console.log(res)
+                let { data, code } = res
+                this.loading = false
+                if (code == 1) {
+                    this.$message.success("发布成功");
+                    setTimeout(() => {
+                        this.$router.go(-1)
+                    }, 1000);
+                }
+            })
+        }
+    }
+};
+</script>
+
+<style lang="scss">
+.box {
+    .el-switch__core {
+        width: 30px !important;
+        height: 16px;
+    }
+
+    .el-switch__core::after {
+        width: 14px;
+        height: 14px;
+        margin-top: -1px;
+    }
+
+    .el-switch.is-checked .el-switch__core::after {
+        margin-left: -15px;
+    }
+}
+</style>
+<style scoped="scoped" lang="scss">
+.oneLine {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+.box {
+    padding: 0 10px;
+    font-size: 14px;
+    background-color: #fff;
+    box-sizing: border-box;
+    border-radius: 4px;
+
+    /* 设置滚动条的宽度和背景色 */
+    ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
+        width: 10px;
+        height: 10px;
+        background-color: #f9f9f9;
+    }
+
+    /* 设置滚动条滑块的样式 */
+    ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
+        border-radius: 6px;
+        background-color: #c1c1c1;
+    }
+
+    /* 设置滚动条滑块hover样式 */
+    ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
+        background-color: #a8a8a8;
+    }
+
+    /* 设置滚动条轨道的样式 */
+    ::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
+        box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+        border-radius: 6px;
+        background: #ededed;
+    }
+
+    /*头部样式*/
+    .header-content {
+        position: relative;
+        box-sizing: border-box;
+        height: 40px;
+        justify-content: space-between;
+
+        .header-left {
+            width: 230px;
+            box-sizing: border-box;
+            height: 40px;
+            cursor: pointer;
+            .item {
+                font-size: 16px;
+            }
+            .el-icon-arrow-left {
+                font-size: 22px;
+            }
+
+            &:hover {
+                .el-icon-arrow-left {
+                    background-color: #f5f7fa;
+                    color: #222;
+                }
+            }
+
+            .text {
+                font-size: 16px;
+                font-weight: 600;
+                padding-left: 50px;
+
+                &::before {
+                    position: absolute;
+                    content: '';
+                    width: 1px;
+                    height: 36px;
+                    background-color: #ebebeb;
+                    left: 44px;
+                    top: 50%;
+                    margin-top: -18px;
+                }
+            }
+
+        }
+
+        .header-right {
+            display: flex;
+            align-items: center;
+        }
+    }
+
+    .plus-button {
+        display: block;
+        margin: 10px auto;
+    }
+
+}
+</style>

+ 736 - 0
src/newPerformance/components/IndicatorSetting/TemplateMixedPublish.vue

@@ -0,0 +1,736 @@
+<template>
+  <div>
+
+    <el-carousel v-loading="loading" :autoplay="false" :loop="false" :initial-index="0" height="600px"
+      indicator-position="none" arrow="never" ref="carousel">
+      <el-carousel-item style="overflow-y: auto; ">
+        <el-alert :closable="false" type="info" title="考核基础信息,正确描述考核有助于管理员快速了解考核性质。考核分类可帮助管理员快速找到考核。同一类型的考核更有可比性"
+          style="margin-bottom: 10px;" />
+        <div class="flex-box-ce" style="flex-direction: column; justify-content: center; ">
+          <div style="margin: 20px auto; font-size: 16px; font-weight: 600;">填写考核基本信息</div>
+
+          <el-form :model="publishData" label-width="100px">
+            <!-- <el-form-item label="考核名" prop="title">
+              <el-input v-model="publishData.title" maxlength="20" style="width: 400px" />
+            </el-form-item>
+            <el-form-item label="考核分类">
+              <el-select v-model="publishData.cateId">
+                <el-option v-for="cate in cateList" :key="cate.cateId" :label="cate.name" :value="cate.cateId" />
+              </el-select>
+            </el-form-item> -->
+
+            <el-form-item label="周期种类">
+              <el-radio-group v-model="publishData.cycleType" @change="changeCycle">
+                <el-radio-button label="0">自定义</el-radio-button>
+                <el-radio-button label="1">年度</el-radio-button>
+                <el-radio-button label="2">半年度 </el-radio-button>
+                <el-radio-button label="3">季度</el-radio-button>
+                <el-radio-button label="4">月度</el-radio-button>
+              </el-radio-group>
+            </el-form-item>
+
+            <el-form-item v-if="publishData.cycleType === '0'" label="考核时间" prop="startDate">
+              <el-date-picker v-model="dateRange" type="daterange" unlink-panels range-separator="至"
+                start-placeholder="开始日期" end-placeholder="结束日期" :key="1" />
+            </el-form-item>
+            <el-form-item v-if="publishData.cycleType === '1'" label="选择年份" prop="year">
+              <el-date-picker v-model="year" type="year" value-format="yyyy" placeholder="选择年" :key="2" />
+            </el-form-item>
+
+            <el-form-item v-if="publishData.cycleType === '2' || publishData.cycleType === '3'" label="日期区间">
+              <SelectCircle :id="1" :dateParameter="dateParameter" :dateOptions="dateOptions" @confirm="dateConfirm">
+                <div class="flex-box-ce cursor">
+                  <div>{{ dateParameter.year }}</div>
+                  <div style="margin: 0 10px;">{{ dateParameter.name }}</div>
+                  <i class="el-icon-caret-bottom fontColorC"></i>
+                </div>
+              </SelectCircle>
+            </el-form-item>
+
+            <el-form-item v-if="publishData.cycleType === '4'" label="选择月份" prop="month">
+              <el-date-picker v-model="month" type="month" placeholder="月份" value-format="yyyy-MM" :key="3" />
+            </el-form-item>
+
+            <el-form-item label="考核周期">
+              <el-link v-if="startDate && endDate" type="primary">
+                {{ startDate }} 至 {{ endDate }}
+              </el-link>
+              <el-link v-else type="warning">请指定周期</el-link>
+            </el-form-item>
+          </el-form>
+        </div>
+
+      </el-carousel-item>
+      <!-- <el-carousel-item style="overflow-y: auto;">
+        <el-alert :closable="false" type="info" title="关联OKR目标可以帮助考核评审进一步跟踪过程,提高考核的准确性" style="margin-bottom: 10px;" />
+        <el-card style="height: 450px;">
+          <el-row style="height: 50px;" type="flex" justify="space-between" align="middle">
+            <el-col :span="6">
+              <span>关联OKR目标</span>
+            </el-col>
+            <el-col :span="2">
+              <el-button type="text" icon="el-icon-plus" @click.stop="showTargetSearch = true" />
+            </el-col>
+          </el-row>
+          <div style="height: 400px;overflow-y: auto;padding-bottom: 20px;">
+            <el-alert v-for="item in selectedOkrs" :key="item.id" type="success" :title="item.name" :closable="false"
+              style="margin-bottom: 10px;" />
+          </div>
+        </el-card>
+      </el-carousel-item> -->
+      <el-carousel-item style="overflow-y: auto;">
+        <el-alert :closable="false" type="info" style="margin-bottom: 10px;"
+          title="按岗位、职能、角色等方式根据团队实际情况定义指标模板,为不同模板指定人员发起考核更灵活" />
+        <el-row type="flex" justify="start" align="middle" style="height: 30px;">
+          <el-col :span="2">
+            <el-button type="text" icon="el-icon-plus" @click.stop="showTemplateSearch = true">添加考核模板</el-button>
+          </el-col>
+        </el-row>
+        <el-table :data="templateList" height="400px">
+          <el-table-column prop="title" label="模板" />
+          <el-table-column prop="employees" label="考核人员" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <el-link v-for="employee in scope.row.employees" :key="employee.employeeId" style="margin: 5px;"
+                type="primary">
+                {{ employee.name }}
+              </el-link>
+            </template>
+          </el-table-column>
+          <el-table-column prop="others" width="100">
+            <template slot-scope="scope">
+              <el-button type="primary" size="mini" @click="openEmployeeSelector(scope.row)">绑定人员</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-carousel-item>
+      <el-carousel-item style="overflow-y: auto;">
+        <el-alert :closable="false" type="info" style="margin-bottom: 10px;"
+          title="面谈流程可以为每个人考核进行复盘跟进,添加跟踪任务等。对个人提升很有帮助" />
+
+        <div class="flex-box-ce" style="justify-content: center;">
+          <el-form label-width="100px">
+            <el-form-item label="启用面谈">
+              <el-switch v-model="interviewNode.enable"></el-switch>
+            </el-form-item>
+            <el-form-item label="处理人">
+              <el-radio-group v-model="interviewNode.assigneeType" :disabled="!interviewNode.enable" size="mini">
+                <el-radio-button label="leader">管理员</el-radio-button>
+                <el-radio-button label="user">指定人员</el-radio-button>
+                <el-radio-button label="self">被考核人</el-radio-button>
+                <el-radio-button label="post">岗位</el-radio-button>
+                <el-radio-button label="deptLeader">部门负责人</el-radio-button>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item v-if="interviewNode.assigneeType === 'leader'">
+              <el-select v-model="interviewNode.leaderLevel" placeholder="请选择管理员" :disabled="!interviewNode.enable"
+                key="leader" filterable>
+                <el-option v-for="item in levelOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item v-if="interviewNode.assigneeType === 'user'">
+              <el-select v-model="assigneeUsers" placeholder="请选择指定人员" multiple key="user"
+                :disabled="!interviewNode.enable" filterable style="width: 400px;">
+                <el-option v-for="item in employees" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+            <el-form-item v-if="interviewNode.assigneeType === 'post'">
+              <el-select v-model="assigneePosts" placeholder="请选择岗位" multiple key="post"
+                :disabled="!interviewNode.enable" filterable style="width: 400px;">
+                <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+            <el-form-item v-if="interviewNode.assigneeType === 'deptLeader'">
+              <el-cascader v-model="assigneeDept" size="small" :options="deptList"
+                :props="{ multiple: true, checkStrictly: true, emitPath: false }" placeholder="请选择部门" filterable
+                style="width: 400px;" />
+            </el-form-item>
+          </el-form>
+        </div>
+
+      </el-carousel-item>
+    </el-carousel>
+
+    <div class="flex-box-ce" style="justify-content: center; ">
+      <el-button :disabled="!preValidate" @click="prevStep">上一步</el-button>
+      <el-button v-if="step <= 2" type="primary" :disabled="!nextValidate" @click="nextStep">下一步</el-button>
+      <el-button v-if="step >= 3" type="primary" :disabled="!publishValidate"
+        @click.stop="onPublishSubmit">发布</el-button>
+    </div>
+
+
+    <TargetSearch :visible.sync="showTargetSearch" :selectedCheckList="okrs" :showSelectedData="selectedOkrs"
+      @confirm="onOkrSelected" />
+
+    <TemplateSelector :show-visible.sync="showTemplateSearch" :selected-template="templateSelected"
+      @confirm="onTemplateConfirm" />
+
+    <PerEmployeeSelector :show-visible.sync="showEmployeeSearch" :selected-employees="employeeSelected"
+      :employee-status="1" @confirm="onEmployeeConfirm" />
+  </div>
+</template>
+
+
+<script>
+
+import Template from "@/examine/components/Template.vue";
+import moment from "moment/moment";
+import SelectCircle from '@/newPerformance/components/TemplateDetails/SelectCircle';
+import TargetSearch from "@/performance/views/assessManagement/TargetSearch";
+import TemplateSelector from "@/newPerformance/components/TemplateSelector.vue";
+import PerEmployeeSelector from "@/newPerformance/components/IndicatorSetting/PerEmployeeSelector.vue";
+
+export default {
+  name: 'TemplateMixedPublish',
+  components:{
+    PerEmployeeSelector,
+    TemplateSelector,
+    TargetSearch,
+    Template,
+    SelectCircle,
+  },
+  props: {
+    activeIndex:{
+      type: Number,
+      default: 1
+    }
+  },
+  data() {
+    return {
+      userInfo: this.$userInfo(),
+      innerVisible:this.showVisible,
+      showTargetSearch:false,
+      okrs:[],
+      selectedOkrs:[],
+      showTemplateSearch:false,
+      showEmployeeSearch:false,
+      currentTemplate:null,
+      step:0,
+      dateRange:[],
+      year:'',
+      month:'',
+      publishData:{
+        title:'',
+        cycleType:'0',
+        cateId:0,
+      },
+      dateOptions: [],
+      dateParameter: {
+        year: moment().format('YYYY'),
+        cycle_type: 0,
+        dateId: 1,
+        name: '选择年度',
+      },
+      cateList:[
+        { cateId: 0, name: '无分类' },
+      ],
+      templateList:[],
+      loading: false,
+      interviewNode:{
+        id:'',
+        type:'',
+        enable:false,
+        assigneeType:'',
+        leaderLevel:1,
+        assigneeIds:'',
+        multipleType:'',
+        allows:'',
+        weight:'',
+        children:[],
+      },
+      title: "设置考核表基本信息",
+      levelOptions: [
+        {
+          value: 1,
+          label: '直接管理员'
+        },
+        {
+          value: 2,
+          label: '二级管理员'
+        },
+        {
+          value: 3,
+          label: '三级管理员'
+        },
+        {
+          value: 4,
+          label: '四级管理员'
+        },
+        {
+          value: 5,
+          label: '五级管理员'
+        },
+        {
+          value: 6,
+          label: '六级管理员'
+        }
+      ],
+      employees: this.$getEmployeeMap(),
+      postList: [],
+      deptList: [],
+      assigneeUsers:[],
+      assigneePosts:[],
+      assigneeDept:[],
+    }
+  },
+  computed:{
+    startDate(){
+      switch (this.publishData.cycleType){
+        case '0':
+          return !this.dateRange || this.dateRange.length !== 2 ? '' : moment(this.dateRange[0]).format('YYYY-MM-DD');
+        case '1':
+          return !this.year ? '' : moment(`${this.year}-01-01`).startOf('year').format('YYYY-MM-DD');
+        case '2':
+          return !['上半年','下半年'].includes(this.dateParameter.name) ? '' : (this.dateParameter.name === '上半年' ? moment(this.dateParameter.year).startOf('year').format('YYYY-MM-DD') : moment(this.dateParameter.year).startOf('year').add(6,'months').format('YYYY-MM-DD'));
+        case '3':
+          if (!['第一季度','第二季度','第三季度','第四季度'].includes(this.dateParameter.name)) return '';
+          switch (this.dateParameter.name){
+            case '第一季度':
+              return this.getQuarterDates(this.dateParameter.year,1).start.format('YYYY-MM-DD');
+            case '第二季度':
+              return this.getQuarterDates(this.dateParameter.year,2).start.format('YYYY-MM-DD');
+            case '第三季度':
+              return this.getQuarterDates(this.dateParameter.year,3).start.format('YYYY-MM-DD');
+            case '第四季度':
+              return this.getQuarterDates(this.dateParameter.year,4).start.format('YYYY-MM-DD');
+            default:
+              return '';
+          }
+        case '4':
+          if (!this.month) return '';
+          let [year,month] = this.month.split('-');
+          if (!year || !month) return '';
+          return moment(`${year}-${month}-01`).format('YYYY-MM-DD');
+        default:
+          return '';
+      }
+    },
+    endDate(){
+      switch (this.publishData.cycleType){
+        case '0':
+          return !this.dateRange || this.dateRange.length !== 2 ? '' : moment(this.dateRange[1]).format('YYYY-MM-DD');
+        case '1':
+          return !this.year ? '' : moment(`${this.year}-01-01`).endOf('year').format('YYYY-MM-DD');
+        case '2':
+          return !['上半年','下半年'].includes(this.dateParameter.name) ? '' : (this.dateParameter.name === '上半年' ? moment(this.dateParameter.year).startOf('year').add(6,'months').subtract(1,'days').format('YYYY-MM-DD') : moment(this.dateParameter.year).endOf('year').format('YYYY-MM-DD'));
+        case '3':
+          if (!['第一季度','第二季度','第三季度','第四季度'].includes(this.dateParameter.name)) return '';
+          switch (this.dateParameter.name){
+            case '第一季度':
+              return this.getQuarterDates(this.dateParameter.year,1).end.format('YYYY-MM-DD');
+            case '第二季度':
+              return this.getQuarterDates(this.dateParameter.year,2).end.format('YYYY-MM-DD');
+            case '第三季度':
+              return this.getQuarterDates(this.dateParameter.year,3).end.format('YYYY-MM-DD');
+            case '第四季度':
+              return this.getQuarterDates(this.dateParameter.year,4).end.format('YYYY-MM-DD');
+            default:
+              return '';
+          }
+        case '4':
+          if (!this.month) return '';
+          let [year,month] = this.month.split('-');
+          if (!year || !month) return '';
+          return moment(`${year}-${month}-01`).endOf('month').format('YYYY-MM-DD');
+        default:
+          return '';
+      }
+    },
+    templateMap(){
+      let map = {}
+      this.templateList.forEach(item => {
+        map[item.templateId] = item;
+      })
+      return map;
+    },
+    templateSelected(){
+      return this.templateList.map(item => item.templateId);
+    },
+    employeeSelected(){
+      return !this.currentTemplate ? [] : this.currentTemplate.employees.map(item => item.employeeId);
+    },
+    preValidate(){
+      if (this.loading) return false;
+      switch (Number.parseInt(this.step)){
+        case 0:
+          return false;
+        case 1:
+        case 2:
+        default:
+          return true;
+      }
+    },
+    nextValidate(){
+      if (this.loading) return false;
+      switch (Number.parseInt(this.step)){
+        case 0:
+          return this.baseValidate();
+        case 1:
+          return this.baseValidate() && this.okrValidate();
+        case 2:
+          return this.baseValidate() && this.okrValidate() && this.templateValidate();
+        case 3:
+        default:
+          return false;
+      }
+    },
+    publishValidate(){
+      return !this.loading && this.baseValidate() && this.okrValidate() && this.templateValidate() && this.interviewValidate() ;
+    },
+    interviewFlow(){
+      let assigneeMap = {
+        leader: [],
+        self: [],
+        user: this.assigneeUsers,
+        post: this.assigneePosts,
+        deptLeader: this.assigneeDept
+      }
+      let node = {
+        id:this.interviewNode.id,
+        type:this.interviewNode.type,
+        enable:this.interviewNode.enable,
+        assigneeType:this.interviewNode.assigneeType,
+        leaderLevel:this.interviewNode.leaderLevel,
+        assigneeIds:assigneeMap[this.interviewNode.assigneeType] || [],
+        multipleType:this.interviewNode.multipleType,
+        allows:this.interviewNode.allows,
+        weight:this.interviewNode.weight,
+        children:this.interviewNode.children,
+      };
+      return {
+        nodes:[node]
+      }
+    },
+  },
+  watch: {
+    showVisible(val){
+      this.innerVisible = val
+    },
+    activeIndex(v) {
+      this.step = this.activeIndex - 1;
+      this.$nextTick(() => {
+        this.$refs['carousel'].setActiveItem(this.step);
+      });
+
+    },
+    'interviewNode.assigneeType'(v){
+      if (!v) return;
+      switch (v){
+        case 'self':
+          this.interviewNode.leaderLevel = 1;
+          this.assigneeUsers = [];
+          this.assigneeDept = [];
+          this.assigneePosts = [];
+          break;
+        case 'leader':
+          this.assigneeUsers = [];
+          this.assigneeDept = [];
+          this.assigneePosts = [];
+          break;
+        case 'user':
+          this.assigneeDept = [];
+          this.assigneePosts = [];
+          this.interviewNode.leaderLevel = 1;
+          break;
+        case 'post':
+          this.assigneeUsers = [];
+          this.assigneeDept = [];
+          this.interviewNode.leaderLevel = 1;
+          break;
+        case 'deptLeader':
+          this.assigneeUsers = [];
+          this.assigneePosts = [];
+          this.interviewNode.leaderLevel = 1;
+          break;
+      }
+    },
+    step(v) {
+      if (this.step == 0) this.title = "设置考核表基本信息"
+      if (this.step == 1) this.title = "考核表关联OKR"
+      if (this.step == 2) this.title = "选择考核表及考核人员"
+      if (this.step == 3) this.title = "面谈设置"
+    }
+  },
+
+  mounted() {
+    this.initData()
+  },
+  methods: {
+    matchErrMsg(err){
+      return err.toString().replace(/[(Error: )]/g,'');
+    },
+    // 处理部门树状结构数据
+    getTreeData(data) {
+      for (var i = 0; i < data.length; i++) {
+        data[i].checked = false;
+        if (data[i].children.length < 1) {
+          // children若为空数组,则将children设为undefined
+          data[i].children = undefined;
+        } else {
+          // children若不为空数组,则继续 递归调用 本方法
+          this.getTreeData(data[i].children);
+        }
+      }
+      return data;
+    },
+    getQuarterDates(year, quarter) {
+      // 计算季度的开始日期(第一个月的第一天)
+      const startDate = moment().year(year).quarter(quarter).startOf('quarter');
+      // 计算季度的结束日期(最后一个月的最后一天)
+      const endDate = moment().year(year).quarter(quarter).endOf('quarter');
+
+      return {
+        start: startDate,
+        end: endDate
+      };
+    },
+    handleClose(){
+      this.dataReset();
+      this.$refs['carousel'].setActiveItem(this.step);
+      this.$emit('update:showVisible',false);
+    },
+    dataReset(){
+      this.step = 0;
+      this.year = moment().format('YYYY');
+      this.publishData = {
+        title:'',
+        cycleType:'0',
+        cateId:0,
+        templates:[],
+      };
+      this.okrs = [];
+      this.selectedOkrs = [];
+      this.templateList = [];
+      this.dateRange = [];
+      this.interviewNode = {
+        id:`IT_${Date.now() + Math.floor(Math.random() * 10000)}`,
+        type:'interview',
+        enable:false,
+        assigneeType:'self',
+        leaderLevel:1,
+        assigneeIds:[],
+        multipleType:'or',
+        allows:[],
+        weight:0,
+        children:[],
+      };
+      this.assigneeUsers = [];
+      this.assigneePosts = [];
+      this.assigneeDept = [];
+    },
+    initData() {
+      this.step = 0;
+      this.title = "设置考核表基本信息"
+      this.dataReset();
+      this.$nextTick(() => {
+        this.$refs['carousel'].setActiveItem(this.step);
+      });
+
+
+      if (this.loading) return;
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$axiosUser('get',`/performance/cate/list/${this.userInfo.site_id}`),
+        this.$axiosUser('get','/org/post'),
+        this.$axiosUser('get', '/api/pro/department/tree', '', 'v2')
+      ])
+        .then(([cateRes,postRes,deptRes]) => {
+          if (cateRes.data.code !== 1) throw new Error(cateRes.data.message);
+          if (postRes.data.code !== 1) throw new Error(postRes.data.message);
+          if (deptRes.data.code !== 1) throw new Error(deptRes.data.msg);
+
+          this.cateList = [{cateId:0,name: '无分类'},...cateRes.data.data.list];
+          this.postList = postRes.data.data;
+          this.deptList = this.getTreeData(deptRes.data.data.list);
+        })
+        .catch(err => {
+          this.$message.error(this.matchErrMsg(err));
+          result = false;
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) this.handleClose();
+        })
+
+      // this.getCateList();
+    },
+    prevStep(){
+      if (this.step <= 0) return;
+      this.step--;
+      this.$emit("changeStep", this.step)
+      this.$refs['carousel'].prev();
+    },
+    nextStep(){
+      if (this.step >= 3) return;
+      this.step++;
+      this.$emit("changeStep", this.step)
+      this.$refs['carousel'].next();
+    },
+    changeCycle(v){
+      if (v === '2'){
+        this.dateOptions = [
+          { name: '上半年', id: 1, cycle_type: 3 },
+          { name: '下半年', id: 2, cycle_type: 3 },
+        ]
+      } else if (v === '3'){
+        this.dateOptions = [
+          { name: '第一季度', id: 1, cycle_type: 2 },
+          { name: '第二季度', id: 2, cycle_type: 2 },
+          { name: '第三季度', id: 3, cycle_type: 2 },
+          { name: '第四季度', id: 4, cycle_type: 2 }
+        ]
+      }
+    },
+    getCateList(){
+      if (this.loading) return;
+      this.loading = true;
+      let url = `/performance/cate/list/${this.userInfo.site_id}`;
+      this.$axiosUser('get', url, {})
+        .then(res => {
+          let { data: { code, data: { list } } } = res
+          if (code === 1) this.cateList = [{cateId:0,name:'无分类'} ,...list]
+        })
+        .finally(() => {
+          this.loading = false;
+        });
+    },
+    dateConfirm(date) {
+      let { year, name } = date;
+      this.dateParameter.year = year;
+      this.dateParameter.name = name;
+    },
+    onOkrSelected(okrs,data){
+      this.okrs = okrs;
+      this.selectedOkrs = data;
+    },
+    onTemplateConfirm(data){
+      if (!data || data.length <= 0) {
+        //清空模板
+        this.templateList = [];
+        return;
+      }
+
+      let dataMap = {}
+      data.forEach(item => {
+        dataMap[item.templateId] = item;
+      });
+      //移除模板
+      this.templateList = this.templateList.filter(item => !!dataMap[item.templateId]);
+      //添加模板
+      data.forEach(item => {
+        if (this.templateMap[item.templateId]) return;
+        this.templateList.push({
+          templateId:item.templateId,
+          title:item.title,
+          employees:[]
+        })
+      })
+    },
+    openEmployeeSelector(row){
+      this.currentTemplate = row;
+      this.showEmployeeSearch = true;
+    },
+    onEmployeeConfirm(data){
+      if (!this.currentTemplate) return;
+      if (!data || data.length <= 0) {
+        //清空用户
+        this.currentTemplate.employees = [];
+      }
+
+      let dataMap = {};
+      data.forEach(item => {
+        dataMap[item.employeeId] = item;
+      });
+      //移除用户
+      this.currentTemplate.employees = this.currentTemplate.employees.filter(item => !!dataMap[item.employeeId]);
+      //添加用户
+
+      let employeeMap = {};
+      this.currentTemplate.employees.forEach(item => {
+        employeeMap[item.employeeId] = item;
+      });
+      data.forEach(item => {
+        if (employeeMap[item.employeeId]) return;
+        this.currentTemplate.employees.push({
+          employeeId:item.employeeId,
+          name:item.name
+        });
+      });
+      this.currentTemplate = null;
+    },
+    baseValidate(){
+      // return this.publishData.title && this.publishData.title.length > 0 && this.publishData.title.length <= 20 && this.startDate && this.endDate;
+      return this.startDate && this.endDate;
+    },
+    okrValidate(){
+      return true;
+    },
+    templateValidate(){
+      return this.templateList && this.templateList.length > 0 && !this.templateList.some(template => {
+        return !template.employees || template.employees.length <= 0
+      });
+    },
+    interviewValidate(){
+      let node = this.interviewFlow.nodes[0];
+      return node.id && node.id.startsWith("IT_") && node.type === 'interview' && (!node.enable ? true : (node.assigneeType === 'leader' ? node.leaderLevel > 0 : (node.assigneeType === 'self' ? true : (!['post','user','deptLeader'].includes(node.assigneeType) ? false : (node.assigneeIds && node.assigneeIds.length > 0)))));
+    },
+    onPublishSubmit(){
+      if (this.loading) return;
+      this.loading = true;
+
+      const params = {
+        title: this.publishData.title,
+        cateId: this.publishData.cateId,
+        cycleType: this.publishData.cycleType,
+        startDate: this.startDate,
+        endDate: this.endDate,
+        okrs: [...this.okrs],
+        templates: this.templateList.map(template => {
+          return {
+            templateId: template.templateId,
+            employees: template.employees.map(employee => {
+              return {
+                employeeId: employee.employeeId,
+                ids: [],
+                interviewFlow:this.interviewFlow
+              }
+            })
+          }
+        })
+      }
+
+      let url = `/performance/review/publish/templates/${this.userInfo.site_id}`;
+      let result = true;
+      this.$http.post(url,params)
+        .then(res => {
+          if (res.code !== 1) {
+            this.$message.error(res.message || '非法请求');
+            throw new Error(res.message || '非法请求');
+          }
+          this.$message.success("考核已发布");
+        })
+        .catch(err => {
+          result = false;
+        })
+        .finally(() => {
+          this.loading = false;
+          if (result) this.handleClose();
+        })
+    },
+  }
+}
+
+</script>
+
+<style scoped lang="scss">
+
+.selectBox{
+  text-align: center;
+}
+
+/deep/ .card-cycle .el-card__body{
+  padding: 20px 0;
+}
+
+</style>

文件差异内容过多而无法显示
+ 1072 - 0
src/newPerformance/components/IndicatorSetting/UploadPublish.vue


文件差异内容过多而无法显示
+ 1015 - 0
src/newPerformance/components/IndicatorSetting/UploadPublish2 copy.vue


文件差异内容过多而无法显示
+ 1064 - 0
src/newPerformance/components/IndicatorSetting/UploadPublish2.vue


+ 212 - 0
src/newPerformance/components/IndicatorStore.vue

@@ -0,0 +1,212 @@
+<template>
+    <div class="all">
+        <div class="flex-box-ce header">
+            指标库管理
+            <el-button type="primary" size="small" @click="addTemplate()">添加模板</el-button>
+        </div>
+        <div style="height: 1px; background-color: #f1f1f1; margin: 10px 0;"></div>
+        <el-alert class="bounce animated" type="warning" :title="alertTilte" :closable="false" show-icon
+            style="width: 100%; margin-bottom: 10px;"></el-alert>
+        <div class="table-box">
+
+            <el-table v-loading="loading" :data="templateList.slice((page - 1) * pageSize, page * pageSize)"
+                :header-cell-style="{ background: '#f5f7fa' }" style="width: 100%; height: auto;">
+                <el-table-column prop="title" label="模板标题">
+                    <template slot-scope="scope">
+                        {{ scope.row.title || "模板名称" }}
+                    </template>
+                </el-table-column>
+                <el-table-column label="操作" width="200">
+                    <template slot-scope="scope">
+                        <el-link type="primary" @click="getDetails(scope.row.templateId)">编辑</el-link>
+                        <el-popconfirm confirm-button-text='确定' cancel-button-text='不用了' icon="el-icon-info"
+                            icon-color="red" title="确定删除这个考核模板吗?" @confirm="confirmDelete(scope.row.templateId)"
+                            @cancel="cancelDelete()">
+                            <el-link slot="reference" type="danger">删除</el-link>
+                        </el-popconfirm>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <div style="height: 50px;"></div>
+
+        </div>
+
+        <div class="flex-box-ce" style="width: 100%; justify-content: center; height: 50px;">
+            <el-pagination @current-change="handleCurrentChange" :current-page.sync="page" :page-size="pageSize"
+                :page-sizes="[10, 20, 30, 40]" layout="total, prev, pager, next" :total="total">
+            </el-pagination>
+        </div>
+    </div>
+</template>
+
+
+<script>
+import { mapGetters } from 'vuex';
+import Driver from 'driver.js';
+import 'driver.js/dist/driver.min.css';
+export default {
+    data() {
+        return {
+            loading: false,
+            templateId: "",
+            templateList: [],
+            total: 0,
+            page: 1,
+            pageSize: 10,
+            alertTilte: "管理每个考核模板,可在考核模板里面添加,修改指标,设置每个指标的考核流程,发布考核时,直接选择套用考核模板",
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    activated() {
+        this.getTemplateList()
+    },
+    created() {
+        this.getTemplateList()
+    },
+    // mounted() {
+    //     this.$nextTick(() => {
+    //         this.startGuide()
+    //     })
+    // },
+    methods: {
+
+        startGuide() {
+            const steps = [
+                {
+                    element: '.dispose-left',
+                    popover: {
+                        title: '第一步',
+                        description: '这是第一个引导步骤',
+                        position: 'bottom'
+                    }
+                },
+                {
+                    element: '.template-box',
+                    popover: {
+                        title: '第二步',
+                        description: '这是第二个引导步骤',
+                        position: 'bottom'
+                    }
+                }
+            ];
+            this.driver = new Driver({
+                doneBtnText: '完成',
+                animate: true,
+                stageBackground: '#ffffff',
+                nextBtnText: '下一步',
+                prevBtnText: '上一步',
+                closeBtnText: '关闭'
+            });
+            this.driver.defineSteps(steps);
+            this.driver.start();
+        },
+        
+        chooseTemplate(item) {
+            this.templateId = item.templateId
+        },
+        getTemplateList() {
+            this.loading = true
+            this.$axiosUser("get", `/performance/template/list/self/${this.user_info.site_id}`).then(res => {
+                this.templateList = res.data.data.list;
+                this.total = this.templateList.length
+                this.loading = false;
+            })
+        },
+
+        addTemplate() {
+            this.$axiosUser('post', `/performance/template/create/${this.user_info.site_id}`).then(res => {
+                let { data: { data, code } } = res
+                if (code == 1) {
+                    this.$axiosUser("get", `/performance/template/list/self/${this.user_info.site_id}`).then(res => {
+                        this.templateList = res.data.data.list
+                        this.templateId = this.templateList[this.templateList.length - 1].templateId;
+                        this.$message.success("添加成功");
+                        this.$router.push(`/templateDetails/${this.templateId}`)
+                    })
+                }
+            });
+        },
+
+        // 确认删除模板
+        confirmDelete(templateId) {
+            this.$axiosUser('post', `/performance/template/remove/${this.user_info.site_id}/${templateId}`).then(res => {
+                this.$axiosUser("get", `/performance/template/list/self/${this.user_info.site_id}`).then(res => {
+                    this.templateList = res.data.data.list
+                })
+            })
+        },
+        cancelDelete() {
+            console.log("取消删除");
+        },
+
+        // 获取详情
+        getDetails(templateId) {
+            this.$router.push(`/templateDetails/${templateId}`)
+        },
+
+        handleSizeChange(val) {
+            this.pageSize = val
+        },
+        handleCurrentChange(val) {
+            this.page = val
+        }
+    }
+}
+
+</script>
+
+<style scoped lang="scss">
+    .all {
+        width: 100%;
+        height: 100%;
+        background-color: #fff;
+        font-size: 14px;
+        border-radius: 4px;
+        padding: 10px;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        .header {
+            font-size: 16px;
+            font-weight: 600;
+            height: 30px;
+            line-height: 30px;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+        }
+
+        .table-box {
+            flex: 1;
+            width: 100%;
+            overflow-y: auto;
+
+            /* 设置滚动条的宽度和背景色 */
+            &::-webkit-scrollbar {
+                width: 6px;
+                height: 6px;
+                background-color: #f9f9f9;
+            }
+
+            /* 设置滚动条滑块的样式 */
+            &::-webkit-scrollbar-thumb {
+                border-radius: 6px;
+                background-color: #c1c1c1;
+            }
+
+            /* 设置滚动条滑块hover样式 */
+            &::-webkit-scrollbar-thumb:hover {
+                background-color: #a8a8a8;
+            }
+
+            /* 设置滚动条轨道的样式 */
+            &::-webkit-scrollbar-track {
+                box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+                border-radius: 6px;
+                background: #ededed;
+            }
+        }
+    }
+</style>

+ 263 - 0
src/newPerformance/components/LevelSetting copy.vue

@@ -0,0 +1,263 @@
+<template>
+    <div class="all">
+        <div class="flex-box main">
+
+            <div class="flex-5 right">
+                <div v-show="isActive == 1">
+                    <el-form ref="detailForm" :model="ruleForm" :rules="rules" @submit.native.prevent
+                        label-width="150px">
+                        <div class="title">
+                            总分规则
+                            <span class="fontColorB">(量化指标和非量化指标)</span>
+                        </div>
+                        <el-form-item label="自定义" prop="region">
+                            <el-select v-model="ruleForm.region" placeholder="请选择自定义" disabled style="width: 300px;">
+                                <el-option label="量化指标和非量化指标合并计算" value="shanghai"></el-option>
+                                <el-option label="量化指标和非量化指标分开计算" value="beijing"></el-option>
+                            </el-select>
+                        </el-form-item>
+                        <div class="title">量化指标</div>
+                        <el-form-item label="指标类型" prop="name">
+                            <el-select v-model="ruleForm.region" disabled style="width: 300px;">
+                                <el-option label="数字类型" value="shanghai"></el-option></el-select>
+                        </el-form-item>
+                        <el-form-item label="总分自动加和" prop="tel"><el-switch v-model="ruleForm.delivery"
+                                disabled></el-switch></el-form-item>
+                        <el-form-item label="录入方式" prop="company_id">
+                            <el-select v-model="ruleForm.region" disabled style="width: 300px;"><el-option label="文本框输入"
+                                    value="shanghai"></el-option></el-select>
+                        </el-form-item>
+                    </el-form>
+                </div>
+                <div v-show="isActive == 2">
+                    <el-form ref="detailForm" @submit.native.prevent label-width="150px">
+                        <div class="title">绩效等级配置</div>
+                        <!--            <el-form-item label="绩效等级" prop="tel">
+              <template slot="label">
+                <span>绩效等级</span>
+                <el-tooltip effect="dark" content="设置公司的等级名称及对应占比,考核结束后,将按照考核结果的排名,正态强制分布。如:排名前10%的员工绩效等级为3.75" placement="top">
+                  <i class="el-icon-warning"></i>
+                </el-tooltip>
+              </template>
+<el-switch v-model="level_enable" disabled :active-value="1" :inactive-value="0"></el-switch>
+</el-form-item>
+<el-form-item label="录入方式" prop="company_id">
+    <el-select v-model="ruleForm.region" disabled><el-option label="文本框输入" value="shanghai"></el-option></el-select>
+</el-form-item> -->
+                        <div style="height: 30px;"></div>
+                        <ClassSet ref="ClassSet" :inputs="inputs"
+                            :inputsStyle="{ paddingLeft: '150px', width: '700px' }">
+                        </ClassSet>
+                    </el-form>
+                </div>
+            </div>
+        </div>
+        <div class="footer"><el-button type="primary" :loading="loading" @click="save()">保存设置</el-button></div>
+    </div>
+</template>
+
+<script>
+import ClassSet from '@/performance/components/public/ClassSet';
+export default {
+    components: { ClassSet },
+    name: 'BasicsSet',
+    data() {
+        return {
+            loading: false,
+            isActive: 2,
+            ruleForm: {
+                region: 'shanghai',
+                delivery: true
+            },
+            rules: {},
+            inputs: [],//分值区间
+            level_enable: true,//是否开启绩效等级
+            isSave: true
+        };
+    },
+    watch: {},
+    created() {
+        this.getAllSet();
+    },
+    mounted() { },
+    methods: {
+        // 获取全局设置
+        getAllSet() {
+            this.$axiosUser('get', 'api/pro/per/user/base_config').then(res => {
+                let data = res.data.data;
+                let levels = data.level_scope.levels;
+                this.level_enable = data.level_enable;
+                var inputs = [];
+                var max = 0;//最大值
+                if (levels) {
+                    levels.forEach((item, index) => {
+                        var obj;
+                        if (index == 0) {
+                            obj = { name: item.name, max: Number(item.value), min: 0 };
+                        } else {
+                            obj = { name: item.name, max: Number(item.value), min: max };//当不是第一个等级时,最小值为上一个的最大值
+                        }
+                        max = item.value;
+                        inputs.push(obj);
+                    })
+                    this.inputs = inputs
+                }
+            })
+        },
+        //保存
+        save() {
+            let is = true, level_scope = [];
+            let isSave = this.$refs.ClassSet.isSave
+            let inputsList = this.$refs.ClassSet.inputsList
+            if (!isSave) {
+                return false
+            }
+            inputsList.some(item => {
+                if (!item.name) {
+                    this.$message.error('等级的名称不能为空');
+                    is = false;
+                    return true;
+                }
+                if (item.max == 0) {
+                    this.$message.error('最大值不能为空或者0');
+                    is = false;
+                    return true;
+                }
+                level_scope.push({ name: item.name, value: Number(item.max) });
+            })
+
+            var level_scopes = {//参数的数据结构
+                levels: level_scope
+            };
+            if (is) {
+                this.loading = true;
+                this.$axiosUser('post', 'api/pro/per/user/set_base_config', { level_scope: JSON.stringify(level_scopes) }).then(res => {
+                    this.$message.success('设置成功');
+                    this.getAllSet();
+                }).finally(() => {
+                    this.loading = false;
+                })
+            }
+        },
+        // 监听最大值输入
+        InputBiur(e, index) {
+            this.isSave = true
+            var max = this.inputsList[index].max;
+            var min = this.inputsList[index].min;
+            if (max == 0) {
+                return false;
+            }
+            if (min >= max) {
+                this.$message.error('不能小于或等于' + min);
+                this.$set(this.inputsList[index], 'max', 0);
+                this.isSave = false
+                return false;
+            }
+            if (this.inputsList[index + 1]) {
+                this.$set(this.inputsList[index + 1], 'min', max);
+            }
+        },
+        addInput() {
+            if (this.inputsList.length == 10) {
+                this.$message.error('最高10个级别');
+                return false;
+            }
+            var max = this.inputsList[this.inputsList.length - 1].max;
+            this.inputsList.push({ name: '', min: max, max: 0 });
+        },
+        deleteInput(index) {
+            if (index == 0) {
+                this.$message.error('至少保留一个等级');
+                return false;
+            }
+            var min = this.inputsList[index].min; //获取当前元素最小值
+            this.inputsList.splice(index, 1);
+            if (index != this.inputsList.length) {
+                //当删除不是最后一位时
+                this.$set(this.inputsList[index], 'min', min);
+            }
+            // // this.inputsList.some((item, i) => {
+            // //   if (i == index) {
+            //     this.inputsList.splice(i, 1); //在数组的some方法中,如果return true,就会立即终止这个数组的后续循环
+            // //     return true;
+            // //   }
+            // // });
+        },
+        active(index) {
+            this.isActive = index;
+        }
+    }
+};
+</script>
+<style scoped="scoped">
+.all {
+    min-height: calc(100vh - 210px);
+    position: relative;
+    padding: 20px;
+    background-color: #fff;
+}
+
+.title {
+    font-weight: 600;
+    margin-bottom: 10px;
+    background-color: #f5f7fa;
+    padding: 15px;
+}
+
+.main {
+    min-height: calc(100% - 40px);
+}
+
+.title-f {
+    margin-bottom: 20px;
+}
+
+.footer {
+    background-color: #fff;
+    border-top: 1px solid #ebebeb;
+    height: 40px;
+    text-align: center;
+    padding-top: 10px;
+}
+
+.checkChild {
+    background-color: #fbfdff;
+    margin-left: 20px;
+}
+
+.left,
+.right {
+    height: 100%;
+    overflow: auto;
+}
+
+.ul {
+    border: 1px solid #ebebeb;
+    height: 100%;
+    border-bottom: none;
+}
+
+.li {
+    padding: 15px;
+    position: relative;
+}
+
+.li:hover {
+    background-color: #f5f7fa;
+}
+
+.active-li {
+    color: #409EFF !important;
+    background-color: #f5f7fa;
+}
+
+.active-li::before {
+    content: '';
+    position: absolute;
+    width: 3px;
+    height: 100%;
+    background-color: #409EFF;
+    left: 0;
+    top: 0;
+}
+</style>

+ 226 - 0
src/newPerformance/components/LevelSetting.vue

@@ -0,0 +1,226 @@
+<template>
+    <div class="all">
+        <div class="flex-box main">
+            <div class="flex-5 right">
+                <div v-show="isActive == 2" class="flex-center-center">
+                    <el-form ref="detailForm" @submit.native.prevent label-width="150px">
+                        <div class="title">绩效等级配置</div>
+                        <div style="height: 30px;"></div>
+                        <ClassSet ref="ClassSet" :inputs="inputs" :inputsStyle="{ paddingLeft: '150px', width: '700px' }" style="margin: 0 auto;">
+                        </ClassSet>
+                    </el-form>
+                </div>
+            </div>
+        </div>
+        <div class="footer"><el-button type="primary" :loading="loading" @click="save()">保存设置</el-button></div>
+    </div>
+</template>
+
+<script>
+import ClassSet from '@/performance/components/public/ClassSet';
+export default {
+    components: { ClassSet },
+    name: 'BasicsSet',
+    data() {
+        return {
+            loading: false,
+            isActive: 2,
+            ruleForm: {
+                region: 'shanghai',
+                delivery: true
+            },
+            rules: {},
+            inputs: [],//分值区间
+            level_enable: true,//是否开启绩效等级
+            isSave: true
+        };
+    },
+    watch: {},
+    created() {
+        this.getAllSet();
+    },
+    mounted() { },
+    methods: {
+        // 获取全局设置
+        getAllSet() {
+            this.$axiosUser('get', 'api/pro/per/user/base_config').then(res => {
+                let data = res.data.data;
+                let levels = data.level_scope.levels;
+                this.level_enable = data.level_enable;
+                var inputs = [];
+                var max = 0;//最大值
+                if (levels) {
+                    levels.forEach((item, index) => {
+                        var obj;
+                        if (index == 0) {
+                            obj = { name: item.name, max: Number(item.value), min: 0 };
+                        } else {
+                            obj = { name: item.name, max: Number(item.value), min: max };//当不是第一个等级时,最小值为上一个的最大值
+                        }
+                        max = item.value;
+                        inputs.push(obj);
+                    })
+                    this.inputs = inputs
+                }
+            })
+        },
+        //保存
+        save() {
+            let is = true, level_scope = [];
+            let isSave = this.$refs.ClassSet.isSave
+            let inputsList = this.$refs.ClassSet.inputsList
+            if (!isSave) {
+                return false
+            }
+            inputsList.some(item => {
+                if (!item.name) {
+                    this.$message.error('等级的名称不能为空');
+                    is = false;
+                    return true;
+                }
+                if (item.max == 0) {
+                    this.$message.error('最大值不能为空或者0');
+                    is = false;
+                    return true;
+                }
+                level_scope.push({ name: item.name, value: Number(item.max) });
+            })
+
+            var level_scopes = {//参数的数据结构
+                levels: level_scope
+            };
+            if (is) {
+                this.loading = true;
+                this.$axiosUser('post', 'api/pro/per/user/set_base_config', { level_scope: JSON.stringify(level_scopes) }).then(res => {
+                    this.$message.success('设置成功');
+                    this.getAllSet();
+                }).finally(() => {
+                    this.loading = false;
+                })
+            }
+        },
+        // 监听最大值输入
+        InputBiur(e, index) {
+            this.isSave = true
+            var max = this.inputsList[index].max;
+            var min = this.inputsList[index].min;
+            if (max == 0) {
+                return false;
+            }
+            if (min >= max) {
+                this.$message.error('不能小于或等于' + min);
+                this.$set(this.inputsList[index], 'max', 0);
+                this.isSave = false
+                return false;
+            }
+            if (this.inputsList[index + 1]) {
+                this.$set(this.inputsList[index + 1], 'min', max);
+            }
+        },
+        addInput() {
+            if (this.inputsList.length == 10) {
+                this.$message.error('最高10个级别');
+                return false;
+            }
+            var max = this.inputsList[this.inputsList.length - 1].max;
+            this.inputsList.push({ name: '', min: max, max: 0 });
+        },
+        deleteInput(index) {
+            if (index == 0) {
+                this.$message.error('至少保留一个等级');
+                return false;
+            }
+            var min = this.inputsList[index].min; //获取当前元素最小值
+            this.inputsList.splice(index, 1);
+            if (index != this.inputsList.length) {
+                //当删除不是最后一位时
+                this.$set(this.inputsList[index], 'min', min);
+            }
+            // // this.inputsList.some((item, i) => {
+            // //   if (i == index) {
+            //     this.inputsList.splice(i, 1); //在数组的some方法中,如果return true,就会立即终止这个数组的后续循环
+            // //     return true;
+            // //   }
+            // // });
+        },
+        active(index) {
+            this.isActive = index;
+        }
+    }
+};
+</script>
+<style scoped="scoped">
+.all {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    box-sizing: border-box;
+    background-color: #fff;
+}
+
+.title {
+    text-align: center;
+    font-weight: 600;
+    margin-bottom: 10px;
+    background-color: #f5f7fa;
+    padding: 15px;
+    box-sizing: border-box;
+}
+
+.main {
+    min-height: calc(100% - 40px);
+}
+
+.title-f {
+    margin-bottom: 20px;
+}
+
+.footer {
+    background-color: #fff;
+    border-top: 1px solid #ebebeb;
+    height: 40px;
+    text-align: center;
+    padding-top: 10px;
+}
+
+.checkChild {
+    background-color: #fbfdff;
+    margin-left: 20px;
+}
+
+.left,
+.right {
+    height: 100%;
+    overflow: auto;
+}
+
+.ul {
+    border: 1px solid #ebebeb;
+    height: 100%;
+    border-bottom: none;
+}
+
+.li {
+    padding: 15px;
+    position: relative;
+}
+
+.li:hover {
+    background-color: #f5f7fa;
+}
+
+.active-li {
+    color: #409EFF !important;
+    background-color: #f5f7fa;
+}
+
+.active-li::before {
+    content: '';
+    position: absolute;
+    width: 3px;
+    height: 100%;
+    background-color: #409EFF;
+    left: 0;
+    top: 0;
+}
+</style>

+ 514 - 230
src/newPerformance/components/MyPerformance.vue

@@ -1,66 +1,53 @@
 <template>
     <div class="performance">
-        <div class="main">
-            <div class="main-header flex-box-ce">
-                <div class="flex-1 fontColorB item">
-                    <div>{{ title }}</div>
-                    <div class="fontColorC">考核表</div>
-                    <div class="bian"></div>
-                </div>
-
-                <div class="flex-1 fontColorB item">
-                    <div v-if="cycleType == 0">未定义</div>
-                    <div v-else-if="cycleType == 1">年度</div>
-                    <div v-else-if="cycleType == 2">半年度</div>
-                    <div v-else-if="cycleType == 3">季度</div>
-                    <div v-else-if="cycleType == 4">月度</div>
-                    <div v-else="cycleType == -1">未设置</div>
-                    <div class="fontColorC">周期类型</div>
-                </div>
-
-                <div class="flex-1 fontColorB item">
-                    <div>{{ startTime | formatDate }} <br>
-                        {{ endTime | formatDate }}</div>
-                    <div class="fontColorC">考核周期</div>
+        <div class="perform-info" v-if="detailInfo">
+            <div class="perform-title">{{ title }}</div>
+            <div class="base-info-title flex-box-ce">
+                <div class="line"></div>
+                基本信息
+            </div>
+            <div class="base-info">
+                <div class="base-info-item">
+                    <div class="label">被考核人</div>
+                    <div class="value">{{ detailInfo && detailInfo.employeeName || '' }}</div>
                 </div>
-
-                <div class="flex-1 fontColorB item">
-                    <div>{{ score || '暂无评分' }}</div>
-                    <div class="fontColorC">考核评分</div>
+                <div class="base-info-item">
+                    <div class="label">部门</div>
+                    <div class="value" v-if="detailInfo && detailInfo.department && detailInfo.department.length > 0">
+                        {{ detailInfo.department | formatDeptName }}</div>
                 </div>
 
-                <div class="flex-1 fontColorB item">
-                    <div v-if="status == 0">考核中</div>
-                    <div v-if="status == 1">已结束</div>
-                    <div v-if="status == 2">面谈中</div>
-                    <div class="fontColorC">考核状态</div>
-                </div>
-
-                <div class="flex-1 fontColorB item">
-                    <div>{{ create_status }}</div>
-                    <div class="fontColorC">考核中的指标</div>
+                
+                <div class="base-info-item">
+                    <div class="label">考核状态</div>
+                    <div class="value orange-color" v-if="detailInfo && status == 0">考核中</div>
+                    <div class="value green-color" v-if="detailInfo && status == 1">已结束</div>
+                    <div class="value blue-color" v-if="detailInfo && status == 2">面谈中</div>
+                    <div class="value" v-else></div>
                 </div>
 
-                <div class="flex-1 fontColorB item">
-                    <div>{{ finish_status }}</div>
-                    <div class="fontColorC">考核完成的指标</div>
+                <div class="base-info-item">
+                    <div class="label">考核周期</div>
+                    <div class="value">
+                        {{ startTime | formatDate }}
+                        -
+                        {{ endTime | formatDate }}
+                    </div>
                 </div>
             </div>
+            <div class="base-info-title flex-box-ce">
+                <div class="line"></div>
+                考核信息
+            </div>
+
         </div>
 
-        <div class="btn-box flex-box-ce" style="">
-            <div v-if="levelName" class="status-btn-box fadeInDown animated">
-                <div class="status-btn">
-                    {{ levelName }}
-                </div>
+        <div class="btn-box flex-box-ce" v-if="detailInfo && !reviewId">
+            <div>
             </div>
-            <div v-else></div>
             <div class="flex-box-ce">
-                <div class="flex-box-ce" style="margin-right: 10px;">
-                    <el-switch v-model="isShow" style="margin-right: 5px;" @change="changeShow()"></el-switch>显示流程节点
-                </div>
                 <div class="flex-box-ce more" @click="dialogVisible = true">
-                    查看更多
+                    查看历史考核记录
                     <i class="el-icon-d-arrow-right"></i>
                 </div>
             </div>
@@ -68,41 +55,63 @@
         </div>
 
         <!-- 考核详情 -->
-        <div class="perform-list scroll-bar" style="flex: 1;">
-            <el-table ref="fmeaTableRef" v-loading="loading" :data="tableData" stripe
-                style="width: 100%; margin-bottom: 20px;" :height="600" border
-                :header-cell-style="{ background: '#f5f7fa' }" v-table-move="['fmeaTableRef']">
-                <el-table-column prop="title" label="指标" align="center" min-width="200" fixed="left">
+        <div class="table-box" v-if="detailInfo">
+            <el-table v-loading="loading" :data="tableData" stripe style="width: 100%; height: auto;" border
+                :header-cell-style="{ background: '#f5f7fa' }">
+                <el-table-column type="index" label="序号" align="center" width="50"></el-table-column>
+                <el-table-column prop="title" label="指标" align="center" min-width="200">
                 </el-table-column>
-                <el-table-column prop="content" label="规则" align="center" min-width="200">
+                <el-table-column prop="target" label="目标" align="center" min-width="100">
                     <template slot-scope="scope">
-                        <el-tooltip class="item" effect="dark" placement="top">
-                            <div v-html="scope.row.content" slot="content" style="max-width:300px"></div>
-                            <div class="oneLine">{{ scope.row.content }}</div>
-                        </el-tooltip>
+                        {{ scope.row.target ? scope.row.target + scope.row.unit : '--' }}
                     </template>
                 </el-table-column>
-                <el-table-column prop="target" label="目标" align="center" min-width="100">
+
+                <el-table-column prop="result" label="结果值" align="center" min-width="100">
                     <template slot-scope="scope">
-                        {{ scope.row.target + scope.row.unit }}
+                        <div>
+                            <el-tag type="info" v-if="!scope.row.result">--</el-tag>
+                            <el-tag type="success"
+                                v-if="scope.row.result && Number(scope.row.result - scope.row.target) >= 0">{{
+                                scope.row.result }} {{ scope.row.unit || '' }}</el-tag>
+                            <el-tag type="danger"
+                                v-if="scope.row.result && Number(scope.row.result - scope.row.target) < 0">{{
+                                scope.row.result }} {{ scope.row.unit || '' }}</el-tag>
+                        </div>
+                        <div v-if="scope.row.resultFiles && scope.row.resultFiles.length > 0">
+                            <div v-for="(file, index) in scope.row.resultFiles" :key="file">
+                                <el-link type="primary" @click="openFile(file)">
+                                    {{ "附件" + (index + 1) }}
+                                </el-link>
+                            </div>
+
+                        </div>
                     </template>
                 </el-table-column>
 
-                <el-table-column prop="result" label="结果" align="center" min-width="100">
+                <el-table-column prop="weight" label="权重(%)" align="center" min-width="80">
                     <template slot-scope="scope">
-                        {{ scope.row.result + scope.row.unit }}
+                        {{ scope.row.weight || '--' }}
                     </template>
                 </el-table-column>
-                <el-table-column prop="weight" label="权重" align="center" min-width="100">
+
+                <el-table-column prop="content" label="规则" align="center" min-width="200">
+                    <template slot-scope="scope">
+                        <el-tooltip class="item" effect="dark" placement="top">
+                            <div v-html="scope.row.content" slot="content" style="max-width:300px"></div>
+                            <div class="oneLine">{{ scope.row.content || '--' }}</div>
+                        </el-tooltip>
+                    </template>
                 </el-table-column>
 
-                <el-table-column prop="formulae" label="计算公式" align="center" min-width="120">
+                <el-table-column prop="formulae" label="计算公式" align="center" min-width="140">
                     <template slot-scope="scope">
-                        <el-button v-if="scope.row.expression && scope.row.expression.formulas.length > 0"
+                        <el-link type="primary"
+                            v-if="scope.row.expression && scope.row.expression.formulas && scope.row.expression.formulas.length > 0"
                             @click="openFormula(scope.row, scope.$index)">
                             {{ scope.row.expression && scope.row.expression.formulas.length > 0 ? `公式
                             ${scope.row.expression.formulas.length} 条` : '公式' }}
-                        </el-button>
+                        </el-link>
                         <div v-else style="color: #999;">暂无公式</div>
                     </template>
                 </el-table-column>
@@ -113,99 +122,131 @@
                     </template>
                 </el-table-column>
 
-                <el-table-column prop="businessStatus" label="考核状态" align="center" min-width="120">
+                <el-table-column prop="okrs" label="过程管控" align="center">
                     <template slot-scope="scope">
-                        <div v-if="scope.row.businessStatus == 'end'" class="green-color">已结束</div>
-                        <div v-else class="orange-color">考核中</div>
+                        <el-link v-if="scope.row.okrs && scope.row.okrs.length > 0" type="primary"
+                            @click="openTargetList(scope.row.okrs)">
+                            过程跟踪
+                        </el-link>
+                        <span v-else class="fontColorC">暂无关联</span>
                     </template>
                 </el-table-column>
 
-                <el-table-column v-if="isShow" prop="confirm_target" label="确认目标" align="center" width="120"
-                    fixed="right">
-                    <template slot-scope="scope">
-                        <el-switch v-if="!scope.row.flow.nodes[0].enable" v-model="scope.row.flow.nodes[0].enable"
-                            disabled></el-switch>
-                        <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[0]" />
-                    </template>
-                </el-table-column>
-                <el-table-column v-if="isShow" prop="input_result" label="录入结果" align="center" width="120"
-                    fixed="right">
-                    <template slot-scope="scope">
-                        <el-switch v-if="!scope.row.flow.nodes[1].enable" v-model="scope.row.flow.nodes[1].enable"
-                            disabled></el-switch>
-                        <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[1]" />
-                    </template>
-                </el-table-column>
-                <el-table-column v-if="isShow" prop="self_assessment" label="自评" align="center" width="120"
-                    fixed="right">
-                    <template slot-scope="scope">
-                        <el-switch v-if="!scope.row.flow.nodes[2].enable" v-model="scope.row.flow.nodes[2].enable"
-                            disabled></el-switch>
-                        <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[2]" />
-                    </template>
-                </el-table-column>
-                <el-table-column v-if="isShow" prop="peer_assessmen" label="互评" align="center" width="120"
-                    fixed="right">
-                    <template slot-scope="scope">
-                        <el-switch v-if="!scope.row.flow.nodes[3].enable" v-model="scope.row.flow.nodes[3].enable"
-                            disabled></el-switch>
-                        <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[3]" />
-                    </template>
-                </el-table-column>
-                <el-table-column v-if="isShow" prop="grade" label="评分" align="center" width="120" fixed="right">
+                <el-table-column prop="businessStatus" label="指标考核状态" align="center" min-width="120">
                     <template slot-scope="scope">
-                        <el-switch v-if="!scope.row.flow.nodes[4].enable" v-model="scope.row.flow.nodes[4].enable"
-                            disabled></el-switch>
-                        <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[4]" />
-                    </template>
-                </el-table-column>
-                <el-table-column v-if="isShow" prop="approval" label="审批" align="center" width="120" fixed="right">
-                    <template slot-scope="scope">
-                        <el-switch v-if="!scope.row.flow.nodes[5].enable" v-model="scope.row.flow.nodes[5].enable"
-                            disabled></el-switch>
-                        <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[5]" />
+                        <div v-if="scope.row.businessStatus == 'end'" class="green-color">已结束</div>
+                        <div v-else class="orange-color">考核中</div>
                     </template>
                 </el-table-column>
-                <el-table-column v-if="isShow" prop="carbon_copy" label="抄送" align="center" width="120" fixed="right">
+
+                <el-table-column label="操作" align="center" min-width="120">
                     <template slot-scope="scope">
-                        <el-switch v-if="!scope.row.flow.nodes[6].enable" v-model="scope.row.flow.nodes[6].enable"
-                            disabled></el-switch>
-                        <ShowHandlerComp v-else :show-data="scope.row.flow.nodes[6]" />
+                        <el-link type="primary"
+                            @click="getProcessTracing({ businessStatus: scope.row.businessStatus, nodes: scope.row.flow.nodes })">指标审批过程</el-link>
                     </template>
                 </el-table-column>
+
             </el-table>
+            <div v-if="detailInfo && score" class="status-btn-box fadeInDown animated">
+                <div class="status-btn-green">
+                    {{ score }} {{ levelName }}
+                </div>
+            </div>
+            <div v-if="detailInfo && !score" class="status-btn-box fadeInDown animated">
+                <div class="status-btn-orange">
+                    考核中: 未评分
+                </div>
+            </div>
+
         </div>
 
+        <div v-else class="flex-box-v flex-center-center no-data-box" style="">
+            <img src="static/images/invite_new_company.png" style="" />
+            <div class="fontColorB">暂无考核数据</div>
+        </div>
 
         <!-- 编辑计算公式 -->
-        <FormulaComp v-if="currentIndicator" v-model="showFormula"
+        <!-- <FormulaComp v-if="currentIndicator" v-model="showFormula"
             :fixed-props="[{ key: 'target', name: '目标' }, { key: 'weight', name: '权重' }, { key: 'result', name: '结果值' }]"
             :expressions-props="currentIndicator.expression.formulas || []" :is-edit="false"
-            @onConfirm="onFormulaConfirm" />
+            @onConfirm="onFormulaConfirm" /> -->
+
+        <el-dialog :visible.sync="showFormula" append-to-body :close-on-press-escape="true" center width="600px"
+            title="计算公式">
+            <div>
+                <div class="formulas-list-box">
+                    <div class="formulas-list" v-for="(item, index) in formulas" :key="index">
+                        <div class="formulas-title">公式({{ index + 1 }})</div>
+                        <div class="formulas-condition-title">触发条件</div>
+                        <div class="value-box">
+                            <template v-for="(con, conIndex) in item.condition">
+                                <div class="special-value" v-if="con.includes('result')">结果值</div>
+                                <div class="special-value" v-else-if="con.includes('weight')">权重</div>
+                                <div class="special-value" v-else-if="con.includes('target')">目标值</div>
+                                <div class="normal-value" v-else>{{ con }}</div>
+                            </template>
+                        </div>
+                        <div class="formulas-condition-title">计算公式</div>
+                        <div class="value-box">
+                            <template v-for="(expr, exprIndex) in item.expression">
+                                <div class="special-value" v-if="expr.includes('result')">结果值</div>
+                                <div class="special-value" v-else-if="expr.includes('weight')">权重</div>
+                                <div class="special-value" v-else-if="expr.includes('target')">目标值</div>
+                                <div class="normal-value" v-else>{{ expr }}</div>
+                            </template>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </el-dialog>
 
-        <!-- 选择考核列表弹框 -->
+        <!-- 选择历史考核列表弹框 -->
         <SelectExamineComp v-if="dialogVisible" v-model="dialogVisible" @chooseExamine="choosePerformItem" />
+
+        <!-- 关联OKR -->
+        <TargetListComp v-if="targetDialogVisible" :dialogVisible="targetDialogVisible" :ids="okrs"
+            @close="closeTargetList">
+        </TargetListComp>
+
+        <!-- 展示节点处理数据 -->
+        <ShowHandlerComp v-if="showData" v-model="showHandlerDialogVisible" :show-data="showData" />
     </div>
 </template>
 
 <script>
+
+import { mapGetters } from 'vuex';
 import moment from 'moment';
-import ShowHandlerComp from '@/newPerformance/components/PublicComp/ShowHandler'; // 显示节点数据组件
+import ShowHandlerComp from '@/newPerformance/components/PublicComp/ShowHanderDialog2'; // 显示节点数据组件
 import FormulaComp from '@/newPerformance/components/TemplateDetails/FormulaComp'; // 计算公式弹框
 import SelectExamineComp from '@/newPerformance/components/MyPerformance/SelectExamine'; // 选择考核列表弹框
-import { mapGetters } from 'vuex';
+import TargetListComp from "@/performance/views/assessManagement/TargetListComp.vue";
+
 
 export default {
     name: "MyPerformance",
     components: {
         ShowHandlerComp,
         FormulaComp,
-        SelectExamineComp
+        SelectExamineComp,
+        TargetListComp
+    },
+    props: {
+        reviewId: {
+            type: Number | String,
+            default: ''
+        },
+        sendEmployeeId: {
+            type: Number | String,
+            default: ''
+        },
     },
     data() {
         return {
+            fileUrl: "",
             isShow: false,
             dialogVisible: false,
+            targetDialogVisible: false,
             loading: false,
             value: [],
             title: "",
@@ -213,20 +254,22 @@ export default {
             endTime: "",
             cycleType: "",
             score: 0,
-            status: 0,
+            status: '',
             total: 0,
+            imgUrl: "",
             levelName: '', // 考核详情信息
             currentIndicator: null,
+            formulas: [], //公式列表
             showFormula: false, // 公式弹框
             employeeMap: this.$getEmployeeMap(), // 员工列表
             employeeId: '',
             params: {
                 keyword: '',
                 page: 1,
-                pageSize: 15,
-                cateId: '',
+                pageSize: 1,
                 startDate: '',
-                endDate: ''
+                endDate: '',
+                employeeId: ''
             },
             // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
             cycleType: '-1',
@@ -268,19 +311,35 @@ export default {
             date: [],
             performList: [],
             tableData: [],
+            okrs: [],
+            detailInfo: null,
+            showData: null,
+            showHandlerDialogVisible: false
         };
     },
     filters: {
         formatDate(val) {
             if (val) return moment(val).format('YYYY-MM-DD')
             else return "--"
+        },
+        formatDeptName(val) {
+            let str = '';
+            if (val && val.length > 0) {
+                val.forEach(dept => {
+                    str += dept.dept_name + ","
+                })
+                str = str.substr(0, str.length - 1)
+            } else {
+                str = "--"
+            }
+            return str
         }
     },
     computed: {
         ...mapGetters(['user_info']),
         create_status() {
             if (this.tableData && this.tableData.length > 0) {
-                return this.tableData.filter(data => data.businessStatus !== 'end').length 
+                return this.tableData.filter(data => data.businessStatus !== 'end').length
             }
             return '--'
         },
@@ -293,27 +352,72 @@ export default {
     },
 
     created() {
-        this.getTemplateList();
+        
+    },
+
+    mounted() {
+        if (this.sendEmployeeId) {
+            this.params.employeeId = this.sendEmployeeId
+        } else {
+            this.params.employeeId = this.user_info.id
+        }
+        if (this.reviewId) this.getDetails(this.reviewId)
+        else this.getTemplateList();
+    },
+
+    beforeDestroy() {
+        this.$emit('clearReviewId')
     },
 
     methods: {
 
+        changePage() {
+            this.$bus.$emit("changeCurrentId", { currentId: '7' })
+        },
+        openFile(url) {
+            let file = {
+                url
+            }
+            let imgFiles = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg'];
+            let lastIndex = file.url && file.url.lastIndexOf("/") || -1
+            let suffix; //文件后缀名
+
+            if (lastIndex > 0) {
+                suffix = file.url.substr(lastIndex + 1, file.url.length - 1).split(".")[1];
+                if (imgFiles.includes(suffix)) {
+                    this.imgUrl = ''
+                    this.imgUrl = file.url;
+                    this.$viewerApi({
+                        images: [this.imgUrl]
+                    })
+                } else {
+                    window.open(file.url, '_blank');
+                }
+            }
+        },
+
+
+        // 过程跟踪
+        getProcessTracing(row) {
+            this.showData = row;
+            this.showHandlerDialogVisible = true
+        },
         handleChange(value) {
-            if(value[1]) this.getDetails(value[1]);
+            if (value[1]) this.getDetails(value[1]);
         },
-        
+
         choosePerformItem(item) {
             this.getDetails(item.reviewId)
         },
-        
-        
+
+
         getTemplateList() {
             let that = this
-            let url = `/performance/statistics/reviews/${that.user_info.site_id}/${that.user_info.id}`
+            let url = `/performance/statistics/reviews/${that.user_info.site_id}`
             let requestdata;
             if (that.cycleType == '-1') requestdata = { ...that.params }
             else {
-                requestdata = { ...that.params, cycleType: that.cycleType }
+                requestdata = { ...that.params, cycleType: that.cycleType  }
             }
             that.$axiosUser('get', url, requestdata).then(res => {
                 let { data: { data: { list, total }, code } } = res;
@@ -323,52 +427,94 @@ export default {
                         let { reviewId } = that.performList[0] || '';
                         if (reviewId) that.getDetails(reviewId);
                         that.total = total
-                    } 
+                    }
                 } else {
                     that.performList = [];
                 }
             });
         },
 
-        
         getDetails(reviewId) {
             this.loading = true
             let url = `/performance/statistics/review/info/${this.user_info.site_id}/${reviewId}`
             this.$axiosUser('get', url).then(res => {
                 let { data: { title, indicators, levelName, startTime, endTime, cycleType, score, status }, code } = res.data
                 if (code == 1) {
+                    
+                    this.detailInfo = res.data.data;
                     this.title = title || ''
                     this.cycleType = cycleType || ''
                     this.score = score || ''
-                    this.status = status || '0'
+                    this.status = status || ''
                     this.startTime = startTime || ''
                     this.endTime = endTime || ''
                     this.levelName = levelName || ''
-                    this.tableData = indicators || [];
+                    this.tableData = indicators.reverse() || [];
+                    let deptMap = JSON.parse(localStorage.getItem("SET_EMPLOYEE_MAP"));
+                    if (Object.keys(deptMap) && Object.keys(deptMap).length > 0) {
+                        if (this.detailInfo) {
+                            const employeeDetails = deptMap[this.detailInfo.employeeId];
+                            if (employeeDetails) {
+                                this.detailInfo.department = employeeDetails.employee_detail.dept_list;
+                            }
+                        }
+                    }
+                    if (this.tableData && this.tableData.length > 0) {
+                        let resultFiles = []
+                        this.tableData.forEach(item => {
+                            if (item.flow.nodes && item.flow.nodes.length > 0) {
+                                let resultInputNode = item.flow.nodes[1] // 取到结果值节点
+                                if (resultInputNode.tasks && resultInputNode.tasks.length > 0) {
+                                    resultInputNode.tasks.forEach(task => {
+                                        if (task.files && task.files.length > 0) {
+                                            task.files.forEach(file => {
+                                                resultFiles.push(file)
+                                            })
+                                        }
+                                    })
+                                    item.resultFiles = resultFiles
+                                }
+                            }
+                        })
+                    }
                     this.loading = false
                 }
-                
+
             });
         },
-        
+
 
         dialogBeforeClose() {
             this.dialogVisible = false;
         },
-        
 
         // 打开计算公式弹框
         openFormula(row, index) {
             this.currentIndicator = row;
+            this.formulas = row.expression.formulas
             this.showFormula = true;
         },
-        
+
         onFormulaConfirm() { },
 
         changeShow() {
             this.$nextTick(() => {
                 if (this.$refs.fmeaTableRef) this.$refs.fmeaTableRef.doLayout();
             })
+        },
+
+        closeTargetList() {
+            this.targetDialogVisible = false
+        },
+
+        openTargetList(okrs) {
+            if (okrs && okrs.length > 0) {
+                this.okrs = okrs
+                this.targetDialogVisible = true
+            }
+            else {
+                return this.$message.error("暂无关联okr")
+            }
         }
     }
 };
@@ -397,7 +543,6 @@ export default {
     white-space: nowrap;
     text-overflow: ellipsis;
 }
-
 </style>
 
 <style scoped="scoped" lang="scss">
@@ -409,123 +554,262 @@ export default {
 .orange-color {
     color: #e6a23c;
 }
-.btn-box {
-    width: 100%;
-    padding: 20px !important;
-    box-sizing: border-box;
-    justify-content: space-between;
-    color: #999;
-    background-color: #fff;
 
-    .status-btn-box {
-        width: 120px;
-        height: 30px;
-        z-index: 10;
+.blue-color {
+    color: #409eff;
+}
 
-        .status-btn {
-            width: 100%;
-            height: 100%;
-            background: transparent;
-            border: 2px dashed #67C23A;
-            color: #67C23A;
-            text-align: center;
+.green-status {
+    border: 2px solid #67C23A;
+    color: #67c23a;
+    background: #f0f9eb;
+}
+
+.orange-status {
+    color: #e6a23c;
+    background: #fdf6ec;
+    border: 2px solid #f5dab1;
+}
+
+.red-status {
+    border: 2px solid #fbc4c4;
+    color: #f56c6c;
+    background: #fef0f0;
+}
+
+.status-btn-box {
+    width: 160px;
+    height: 36px;
+    z-index: 10;
+    margin: 10px 0 10px auto;
+    box-sizing: border-box;
+    .status-btn-green {
+        width: 100%;
+        height: 100%;
+        background: transparent;
+        border: 2px dashed #67C23A;
+        color: #67C23A;
+        text-align: center;
+        font-size: 20px;
+        height: 36px;
+    }
+    .status-btn-orange {
+        width: 100%;
+        height: 100%;
+        background: transparent;
+        border: 2px dashed #e6a23c;
+        color: #e6a23c;
+        text-align: center;
+        font-size: 20px;
+        height: 36px;
+    }
+}
+
+.formulas-list-box {
+    width: 100%;
+    padding: 10px;
+    box-sizing: border-box;
+    background-color: #f7f7f7;
+    border-radius: 4px;
+    .formulas-list {
+        margin-bottom: 20px;
+        .formulas-title {
+            font-size: 18px;
+            font-weight: 500;
+            color: #409eff;
+            padding: 5px;
+            box-sizing: border-box;
+        }
+        .formulas-condition-title {
             font-size: 16px;
-            line-height: 30px;
+            font-weight: 400;
+            margin-bottom: 5px;
+        }
+        .value-box {
+            display: flex;
+            align-items: center;
+        }
+        .special-value {
+            display: inline-block;
+            padding: 3px;
+            border: 1px solid #409eff;
+            color: #409eff;
+            border-radius: 4px;
+        }
+        .normal-value {
+            font-size: 14px;
+            letter-spacing: 2px;
+            margin: 0 3px;
         }
     }
 }
+
+
+
+
 .performance {
     width: 100%;
     height: 100%;
     background-color: #f0f4fa;
     display: flex;
     flex-direction: column;
+    position: relative;
 
-    .more {
-        &:hover {
-            cursor: pointer;
-        }
-    }
-    
-
-    .main {
+    .perform-info {
         width: 100%;
-        height: 100px;
-        margin: 0 0 10px 0;
         background-color: #fff;
-        .main-header {
-            .bian {
-                position: absolute;
-                width: 1px;
-                height: 30px;
-                background-color: #e8e8e8;
-                right: 0;
-                top: 50%;
-                margin-top: -15px;
+        padding: 0 10px;
+        box-sizing: border-box;
+        position: relative;
+        .back {
+            position: absolute;
+            top: 20px;
+            left: 10px;
+            .el-icon-arrow-left {
+                font-size: 18px;
+            }
+        
+            &:hover {
+                .el-icon-arrow-left {
+                    background-color: #f5f7fa;
+                    color: #222;
+                }
             }
         }
-        .item {
+        .perform-title {
+            width: 100%;
+            height: 50px;
             text-align: center;
-            padding: 10px;
-            position: relative;
-            cursor: pointer;
-
-            div:nth-child(1) {
-                font-size: 20px;
-                font-weight: 600;
-                margin-bottom: 10px;
-                color: #409EFF;
+            font-size: 24px;
+            line-height: 50px;
+            font-weight: 600;
+            border-bottom: 1px solid #EBEEF5;
+        }
+
+        .base-info-title {
+            width: 100%;
+            height: 40px;
+            font-size: 16px;
+            font-weight: 600;
+
+            .line {
+                width: 4px;
+                height: 18px;
+                background: #409eff;
+                margin: 0 10px;
             }
         }
-    }
-    
-}
 
+        .base-info {
+            width: 100%;
+            display: flex;
+            border: 1px solid #EBEEF5;
+
+            .base-info-item {
+                flex: 1;
+                height: 40px;
+                display: flex;
+                align-items: center;
+
+                .label {
+                    width: 100px;
+                    height: 40px;
+                    line-height: 40px;
+                    background: #f5f7fa;
+                    color: #909399;
+                    text-align: center;
+                    font-weight: 600;
+                }
 
-/* 设置滚动条的宽度和背景色 */
-::v-deep .el-table__body-wrapper::-webkit-scrollbar {
-    width: 10px;
-    height: 10px;
-    background-color: #f9f9f9;
-}
+                .value {
+                    flex: 1;
+                    height: 40px;
+                    line-height: 40px;
+                    text-align: center;
+                    background: #fff;
+                }
+            }
+        }
 
-/* 设置滚动条滑块的样式 */
-::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
-    border-radius: 6px;
-    background-color: #c1c1c1;
-}
+        .base-info-title {
+            width: 100%;
+            height: 40px;
+            font-size: 16px;
+            font-weight: 600;
 
-/* 设置滚动条滑块hover样式 */
-::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
-    background-color: #a8a8a8;
-}
+            .line {
+                width: 3px;
+                height: 18px;
+                background: #409eff;
+                margin: 0 10px;
+            }
+        }
+    }
 
-/* 设置滚动条轨道的样式 */
-::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
-    box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
-    border-radius: 6px;
-    background: #ededed;
-}
 
+    .btn-box {
+        width: 100%;
+        padding: 10px !important;
+        box-sizing: border-box;
+        justify-content: space-between;
+        color: #999;
+        background-color: #fff;
+        .more {
+            &:hover {
+                cursor: pointer;
+            }
+        }
+    }
+
+    .table-box {
+        flex: 1;
+        width: 100%;
+        padding: 0 10px;
+        box-sizing: border-box;
+        background-color: #fff;
+        overflow-y: auto;
+
+        /* 设置滚动条的宽度和背景色 */
+        &::-webkit-scrollbar {
+            width: 6px;
+            height: 6px;
+            background-color: #f9f9f9;
+        }
 
+        /* 设置滚动条滑块的样式 */
+        &::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
 
-.green-status {
-    border: 2px solid #67C23A;
-    color: #67c23a;
-    background: #f0f9eb;
-}
+        /* 设置滚动条滑块hover样式 */
+        &::-webkit-scrollbar-thumb:hover {
+            background-color: #a8a8a8;
+        }
 
-.orange-status {
-    color: #e6a23c;
-    background: #fdf6ec;
-    border: 2px solid #f5dab1;
-}
+        /* 设置滚动条轨道的样式 */
+        &::-webkit-scrollbar-track {
+            box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+            border-radius: 6px;
+            background: #ededed;
+        }
+    }
 
-.red-status {
-    border: 2px solid #fbc4c4;
-    color: #f56c6c;
-    background: #fef0f0;
+    .no-data-box {
+        width: 100%;
+        height: 100%;
+        padding: 26px 35px;
+        background-color: #fff;
+        box-sizing: border-box;
+        border-radius: 4px;
+        img {
+            width: 200px;
+            height: 200px;
+            margin-bottom: 10px;
+        }
+    }
 }
 
 
+
 </style>

+ 23 - 16
src/newPerformance/components/MyPerformance/SelectExamine.vue

@@ -1,5 +1,5 @@
 <template>
-    <el-dialog :visible.sync="dialogVisible" width="800px" :before-close="dialogBeforeClose">
+    <el-dialog title="历史考核记录" :visible.sync="dialogVisible" width="800px" :before-close="dialogBeforeClose" append-to-body >
         <div>
             <!-- 搜索框 -->
             <div class="search-box">
@@ -20,11 +20,11 @@
                         size="small">
                     </el-date-picker>
 
-                    <el-select v-model="params.cateId" placeholder="请选择考核分类" style="width: 180px; margin-right: 10px;"
+                    <!-- <el-select v-model="params.cateId" placeholder="请选择考核分类" style="width: 180px; margin-right: 10px;"
                         size="small" @chang="changeCateId">
                         <el-option v-for="item in cateList" :key="item.cateId" :label="item.name" :value="item.cateId">
                         </el-option>
-                    </el-select>
+                    </el-select> -->
 
                     <el-button type="primary" size="mini" @click="getTemplateList()">查询</el-button>
                     <el-button size="mini" @click="resetSearch()">重置</el-button>
@@ -92,7 +92,6 @@ export default {
                     this.params.cateId = cateId + ''
                 }
                 
-                this.getCateList()
                 this.getTemplateList()
             }
         }
@@ -106,10 +105,10 @@ export default {
                 pageSize: 15,
                 cateId: '',
                 startDate: '',
-                endDate: ''
+                endDate: '',
+                employeeId: '',
             },
-            employeeId: '',
-            // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+            // 周期类型 -1全部 0-未定义 1-年度 2-半年度 3-季度 4-月度
             cycleType: '-1',
             cycleOptions: [
                 { label: "全部", value: '-1' },
@@ -152,6 +151,8 @@ export default {
             total: 0
         }
     },
+
+    
     filters: {
         formatDate(val) {
             if (val) return moment(val).format('YYYY-MM-DD')
@@ -163,15 +164,14 @@ export default {
     },
     created() {
         if (!_.isEmpty(this.searchOptions)) {
-            let { employeeId, cycleType, cateId } = this.searchOptions
-            this.employeeId = employeeId
+            let { employeeId, cycleType } = this.searchOptions
+            this.params.employeeId = employeeId
             this.params.cycleType = cycleType
             this.cycleType = cycleType
-            this.params.cateId = cateId
         } else {
-            this.employeeId = this.user_info.id
+            this.params.employeeId = this.user_info.id
         }
-        this.getCateList()
+        // this.getCateList()
         this.getTemplateList()
     },
     methods: {
@@ -189,7 +189,7 @@ export default {
 
         getTemplateList() {
             let that = this
-            let url = `/performance/statistics/reviews/${that.user_info.site_id}/${that.employeeId}`
+            let url = `/performance/statistics/reviews/${that.user_info.site_id}`
             let requestdata;
             if (that.cycleType == '-1') requestdata = { ...that.params }
             else requestdata = { ...that.params, cycleType: that.cycleType } 
@@ -204,8 +204,6 @@ export default {
             });
         },
 
-        
-
         choosePerformItem(item) {
             this.$emit('chooseExamine', item)
             this.$emit('close-dialog', false)
@@ -227,18 +225,27 @@ export default {
                 startDate: '',
                 endDate: ''
             };
-            this.cycleType = ''
+            this.cycleType = '-1'
+            this.date = []
+            if (this.searchOptions && this.searchOptions.employeeId) {
+                this.params.employeeId = this.searchOptions.employeeId
+            } else {
+                this.params.employeeId = this.user_info.id
+            }
             this.getTemplateList();
         },
 
+        
         // 日期选择时间
         changeDate(v) {
+            this.params.page = 1;
             if (this.date[0]) this.params.startDate = this.date[0] || ''
             if (this.date[1]) this.params.endDate = this.date[1] || ''
         },
 
 
         changeCircle(v) {
+            this.params.page = 1;
             this.params.cycleType = v;
         },
 

+ 194 - 0
src/newPerformance/components/NormalDistribution.vue

@@ -0,0 +1,194 @@
+<template>
+    <div class="all">
+        <div class="flex-box-ce header">
+            正态分布设置
+        </div>
+
+        <div class="flex-box-ce" style="margin: 0 auto 10px auto;">
+            <el-button size="small" @click="addDistribution()">添加</el-button>
+        </div>
+
+        <div class="table-box">
+            <el-table :data="distributions" style="width: 600px; height: auto; margin: 0 auto;" border stripe>
+                <el-table-column prop="name" label="名称" align="center">
+                    <template slot-scope="scope">
+                        <el-input v-model="scope.row.name" placeholder="请输入名称"></el-input>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="scale" label="比例(%)" align="center">
+                    <template slot-scope="scope">
+                        <el-input v-model.number="scope.row.scale" placeholder="请输入比例(数字)"></el-input>
+                    </template>
+                </el-table-column>
+                <el-table-column label="操作" align="center">
+                    <template slot-scope="scope">
+                        <el-link type="danger" @click="deleteData(scope.$index)">删除</el-link>
+                    </template>
+                </el-table-column>
+            </el-table>
+        </div>
+        <div class="footer">
+            <el-button type="primary"  @click="confirmChangeScore()">保存设置</el-button>
+        </div>
+
+    </div>
+</template>
+
+<script>
+    import { mapGetters } from 'vuex';
+
+    export default {
+        data() {
+            return {
+                distributions: [],
+                distributionId: "",
+                isSave: true
+            }
+        },
+        computed: {
+            ...mapGetters(['user_info']),
+        },
+        created() {
+            this.getDistributions()
+        },
+        beforeDestroy() {
+            console.log("即将离开页面")
+            // if (!this.isSave) {
+            //     console.log("未保存")
+            //     this.$confirm('未保存数据,是否离开?', '提示', {
+            //         confirmButtonText: '保存数据',
+            //         cancelButtonText: '取消',
+            //         type: 'warning'
+            //     }).then(() => {
+            //         // this.$message({
+            //         //     type: 'success',
+            //         //     message: '删除成功!'
+            //         // });
+            //         this.confirmChangeScore()
+            //     }).catch(() => {
+            //         // this.$message({
+            //         //     type: 'info',
+            //         //     message: '已取消删除'
+            //         // });
+            //     });
+            // }
+        },
+        methods: {
+            async getDistributions() {
+                let url = `/performance/statistics/distribution/${this.user_info.site_id}`
+                let res = await this.$axiosUser('get', url)
+                this.distributions = res.data.data.items
+                if (this.distributions && this.distributions.length > 0) {
+                    this.distributionId = this.distributions[0].id
+                }
+            },
+
+            addDistribution() {
+                this.isSave = false
+                this.distributions.push({ id: this.distributionId, name: "", scale: 0 })
+            },
+
+            deleteData(index) {
+                this.isSave = false
+                this.distributions.splice(index, 1)
+            },
+
+            confirmChangeScore() {
+                let url = `/performance/statistics/distribution/${this.user_info.site_id}`
+                let items = this.distributions;
+                let sum = 0;
+                for (let i = 0; i < items.length; i++) {
+                    if (!items[i].name) return this.$message.error('请输入正太规则名称')
+                    console.log(items[i].scale)
+                    sum += Number(items[i].scale)
+                }
+                console.log(sum)
+                if (sum > 100) return this.$message.error('总分布比例不能超过100')
+
+                let data = {
+                    items
+                }
+                this.$http.post(url, data).then(res => {
+                    this.isSave = true
+                    if (res.code == 1) {
+                        this.$message.success("保存成功");
+                        this.distributions = res.data.items
+                    } else {
+                        this.$message.error(res.message || "保存失败");
+                    }
+                })
+            }
+        }
+    }
+</script>
+
+<style scoped="scoped" lang="scss">
+.all {
+    width: 100%;
+    height: 100%;
+    font-size: 14px;
+    border-radius: 4px;
+    box-sizing: border-box;
+    background-color: #fff;
+    display: flex;
+    flex-direction: column;
+    .header {
+        width: 100%;
+        font-weight: 600;
+        height: 50px;
+        line-height: 50px;
+        font-weight: 600;
+        margin-bottom: 10px;
+        background-color: #f5f7fa;
+        padding: 15px 0;
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+    }
+
+    .table-box {
+        flex: 1;
+        width: 100%;
+        padding: 0 10px;
+        box-sizing: border-box;
+        background-color: #fff;
+        overflow-y: auto;
+
+        /* 设置滚动条的宽度和背景色 */
+        &::-webkit-scrollbar {
+            width: 6px;
+            height: 6px;
+            background-color: #f9f9f9;
+        }
+
+        /* 设置滚动条滑块的样式 */
+        &::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
+
+        /* 设置滚动条滑块hover样式 */
+        &::-webkit-scrollbar-thumb:hover {
+            background-color: #a8a8a8;
+        }
+
+        /* 设置滚动条轨道的样式 */
+        &::-webkit-scrollbar-track {
+            box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+            border-radius: 6px;
+            background: #ededed;
+        }
+    }
+
+    .footer {
+        background-color: #fff;
+        border-top: 1px solid #ebebeb;
+        height: 40px;
+        text-align: center;
+        padding-top: 10px;
+        box-sizing: border-box;
+        margin-top: 10px;
+    }
+}
+</style>

+ 214 - 117
src/newPerformance/components/OrganizationExamine.vue

@@ -8,24 +8,14 @@
                     </el-option>
                 </el-select>
 
-                <!-- <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple filterable style="width: 300px;"
-                    @change="changeEmployeeIds" clearable size="small">
-                    <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"></el-option>
-                </el-select> -->
-
                 <el-cascader ref="dept" v-model="selected_dept_ids" size="small"
                     style="width: 300px; margin-left: 10px; " :options="deptList" @change="deptChange"
                     :props="{ checkStrictly: true, multiple: true }" filterable change-on-select placeholder="请选择部门"
                     clearable></el-cascader>
 
-                <el-select v-model="cateId" @change="changeCateId" placeholder="请选择考核分类"
-                    style="width: 300px; margin-left: 10px;" clearable size="small">
-                    <el-option v-for="item in cateList" :key="item.cateId" :label="item.name" :value="item.cateId">
-                    </el-option>
-                </el-select>
             </div>
             <div class="flex-box-ce">
-                <el-button type="primary" @click="choosePerson = true" size="small">添加人员</el-button>
+                <el-button type="primary" @click="openChoosePerson" size="small">添加人员</el-button>
                 <el-button type="danger" :disabled="!(multipleSelection && multipleSelection.length > 0)"
                     @click="confirmDelete()" size="small">批量删除</el-button>
                 <el-button type="warning" size="small" @click="reset()">重置表格</el-button>
@@ -35,84 +25,108 @@
 
         </div>
 
+
         <el-alert class="bounce animated" type="warning" :title="alertTilte" :closable="false" show-icon
             style="width: 100%; margin-top: 10px;"></el-alert>
+        <div class="table-box">
+            <el-table v-loading="loading" ref="multipleTable" id="myTable" :data="tableData" style="width: 100%; "
+                :header-cell-style="{ background: '#f5f7fa' }" border stripe @selection-change="handleSelectChange">
+                <el-table-column type="selection"></el-table-column>
+                <el-table-column prop="employeeName" label="姓名" min-width="100" align="center" fixed>
+                    <template slot-scope="scope">
+                        <el-link type="primary" @click="chooseExamine(scope.row, scope.$index)">
+                            {{ scope.row.employeeName }}
+                        </el-link>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="deptList" label="部门" min-width="200" align="center">
+                </el-table-column>
+                <el-table-column prop="title" label="考核名称" min-width="120" align="center">
+                </el-table-column>
+                <el-table-column prop="cycleType" label="周期类型" min-width="120" align="center">
+                    <template slot-scope="scope">
+                        <el-tag v-if="scope.row.cycleType" type="primary">{{ scope.row.cycleType | formatCycleType
+                            }}</el-tag>
+                        <el-tag v-else type="info">{{ scope.row.cycleType | formatCycleType }}</el-tag>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="startTime" label="开始时间" min-width="120" align="center">
+                    <template slot-scope="scope">
+                        <div>
+                            {{ scope.row.startTime | formatDate }}
+                        </div>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="endTime" label="结束时间" min-width="120" align="center">
+                    <template slot-scope="scope">
+                        <div>
+                            {{ scope.row.endTime | formatDate }}
+                        </div>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="levelName" label="等级名称" min-width="100" align="center">
+                </el-table-column>
+                <el-table-column prop="lastLevelName" label="上次考核等级名称" min-width="130" align="center">
+                </el-table-column>
+                <el-table-column prop="levelChange" label="较上次" min-width="100" align="center">
+                    <template slot-scope="scope">
+                        <div v-if="scope.row.levelChange && scope.row.levelChange > 0"
+                            style="color: #67c23a !important;">
+                            <i class="el-icon-top"></i>{{ scope.row.levelChange }}
+                        </div>
+                        <div v-else-if="scope.row.levelChange && scope.row.levelChange < 0" class="red"
+                            style="color: #f56c6c !important;">
+                            <i class="el-icon-bottom"></i>{{ scope.row.levelChange }}
+                        </div>
+                        <div v-else class="gray" style="color: #999;">
+                            --
+                        </div>
+                    </template>
+
+                </el-table-column>
+                <el-table-column prop="score" label="得分" min-width="100" align="center">
+                </el-table-column>
+
+                <!-- <el-table-column prop="packageOkrs" label="关联OKR" min-width="100" align="center">
+                    <template slot-scope="scope">
+                        <el-link v-if="scope.row.okrs && scope.row.okrs.length > 0" type="primary"
+                            @click="openTargetList(scope.row.okrs)">
+                            考核表OKR
+                        </el-link>
+                        <span v-else class="fontColorC">暂无关联</span>
+                    </template>
+                </el-table-column> -->
+
+                <el-table-column prop="status" label="考核状态" min-width="100" align="center">
+                    <template slot-scope="scope">
+                        <!-- <div>
+                            {{ scope.row.status | formatStatus }}
+                        </div> -->
+                        <div class="orange-color" v-if="scope.row.status == 0">考核中</div>
+                        <div class="green-color" v-if="scope.row.status == 1">已结束</div>
+                        <div class="blue-color" v-if="scope.row.status == 2">面谈中</div>
+                    </template>
+                </el-table-column>
+
+            </el-table>
+        </div>
 
-        <el-table ref="multipleTable" id="myTable" :data="tableData" style="width: 100%; margin-top: 10px;"
-            :height="500" :header-cell-style="{ background: '#f5f7fa' }" border stripe
-            @selection-change="handleSelectChange">
-            <el-table-column type="selection"></el-table-column>
-            <el-table-column prop="employeeName" label="姓名" min-width="100" align="center">
-                <template slot-scope="scope">
-                    <el-link type="primary" @click="chooseExamine(scope.row, scope.$index)">
-                        {{ scope.row.employeeName }}
-                    </el-link>
-                </template>
-            </el-table-column>
-            <el-table-column prop="deptList" label="部门" min-width="200" align="center">
-            </el-table-column>
-            <el-table-column prop="title" label="考核名称" min-width="120" align="center">
-            </el-table-column>
-            <el-table-column prop="cycleType" label="周期类型" min-width="120" align="center">
-                <template slot-scope="scope">
-                    <el-tag v-if="scope.row.cycleType" type="primary">{{ scope.row.cycleType | formatCycleType
-                        }}</el-tag>
-                    <el-tag v-else type="info">{{ scope.row.cycleType | formatCycleType }}</el-tag>
-                </template>
-            </el-table-column>
-            <el-table-column prop="startTime" label="开始时间" min-width="120" align="center">
-                <template slot-scope="scope">
-                    <div>
-                        {{ scope.row.startTime | formatDate }}
-                    </div>
-                </template>
-            </el-table-column>
-            <el-table-column prop="endTime" label="结束时间" min-width="120" align="center">
-                <template slot-scope="scope">
-                    <div>
-                        {{ scope.row.endTime | formatDate }}
-                    </div>
-                </template>
-            </el-table-column>
-            <el-table-column prop="levelName" label="等级名称" min-width="100" align="center">
-            </el-table-column>
-            <el-table-column prop="lastLevelName" label="上次考核等级名称" min-width="150" align="center">
-            </el-table-column>
-            <el-table-column prop="levelChange" label="较上次" min-width="120" align="center">
-                <template slot-scope="scope">
-                    <div v-if="scope.row.levelChange && scope.row.levelChange > 0" style="color: #67c23a !important;">
-                        <i class="el-icon-top"></i>{{ scope.row.levelChange }}
-                    </div>
-                    <div v-else-if="scope.row.levelChange && scope.row.levelChange < 0" class="red"
-                        style="color: #f56c6c !important;">
-                        <i class="el-icon-bottom"></i>{{ scope.row.levelChange }}
-                    </div>
-                    <div v-else class="gray" style="color: #999;">
-                        --
-                    </div>
-                </template>
-
-            </el-table-column>
-            <el-table-column prop="score" label="得分" min-width="120" align="center">
-            </el-table-column>
-            <el-table-column prop="status" label="考核状态" min-width="120" align="center">
-                <template slot-scope="scope">
-                    <div>
-                        {{ scope.row.startTime | formatStatus }}
-                    </div>
-                </template>
-            </el-table-column>
-
-        </el-table>
 
         <!-- 选择人员组件 -->
-        <EmployeeSelector :visible.sync="choosePerson" :is_filtration_creator="false" :selected="selected"
-            @confirm="confirmChoosePerson" />
+        <!-- <EmployeeSelector :visible.sync="choosePerson" :is_filtration_creator="false" :selected="selected"
+            @confirm="confirmChoosePerson" /> -->
+
+        <PerEmployeeSelector :show-visible.sync="choosePerson" :selected-employees="selectedEmployees"
+            :choosePersonList="[]" :employee-status="1" :multiple="false" @confirm="confirmChoosePerson" />
 
         <!-- 选择考核列表组件 -->
         <SelectExamineComp v-if="dialogVisible" v-model="dialogVisible" @chooseExamine="chooseEaxmineItem"
             :search-options="searchOptions" />
 
+        <!-- 关联OKR -->
+        <TargetListComp v-if="targetDialogVisible" :dialogVisible="targetDialogVisible" :ids="okrs"
+            @close="closeTargetList">
+        </TargetListComp>
     </div>
 </template>
 
@@ -123,15 +137,20 @@ import XLSX from 'xlsx';
 import FileSaver from 'file-saver';
 import EmployeeSelector from '@/components/EmployeeSelector';
 import SelectExamineComp from '@/newPerformance/components/MyPerformance/SelectExamine'; // 选择考核列表弹框
+import TargetListComp from "@/performance/views/assessManagement/TargetListComp.vue";
+import PerEmployeeSelector from "@/newPerformance/components/IndicatorSetting/PerEmployeeSelector.vue";
 
 export default {
     components: {
         EmployeeSelector,
-        SelectExamineComp
+        SelectExamineComp,
+        TargetListComp,
+        PerEmployeeSelector
     },
 
     data() {
         return {
+            selectedEmployees: [],
             selected_employee_ids: [],
             selected_dept_ids: [],
             deptList: [], // 部门列表 - 树形结构
@@ -144,7 +163,6 @@ export default {
             total: 0,
             tableData: [],
             tableIndex: -1,
-            // 添加指标
             selected: { employee: [], dept: [] },//已经选择人员
             choosePerson: false,
             // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
@@ -166,13 +184,16 @@ export default {
             },
             multipleSelection: [],
             alertTilte: "默认显示每个人的最新考核数据,可选择人员添加考核数据,也可以移除某个人员的考核数据,或点击人员姓名替换某条考核数据",
-            dialogVisible: false
+            dialogVisible: false,
+            okrs: [],
+            targetDialogVisible: false,
+            choosePersonList: []
         }
     },
     created() {
         this.getList();
         this.get_dept_list();
-        this.get_cate_list();
+        // this.get_cate_list();
     },
     computed: {
         ...mapGetters(['user_info'])
@@ -223,7 +244,7 @@ export default {
             this.loading = true;
             let url = `/performance/statistics/review/last/${this.user_info.site_id}`
             let requestdata
-            this.cycleType = -1;
+            this.cycleType = '-1';
             this.params = {
                 deptIds: '',
                 employeeIds: '',
@@ -231,11 +252,9 @@ export default {
                 pages: 1,
                 pageSize: 10
             }
-            if (this.cycleType == '-1') requestdata = { ...this.params }
-            else {
-                requestdata = { ...this.params, cycleType: this.cycleType }
-            }
-            delete requestdata['employeeIds'] // 删除部门字段
+            this.selected_dept_ids = []
+            requestdata = { ...this.params }
+            if ('deptIds' in requestdata) delete requestdata['deptIds'] // 删除部门字段
             this.$axiosUser('get', url, requestdata).then(res => {
                 this.loading = false;
                 let { data: { data: { list, total }, code } } = res
@@ -246,10 +265,38 @@ export default {
             });
         },
 
+        openChoosePerson() {
+            this.selectedEmployees = []
+            Object.keys(this.employeeMap).forEach(key => {
+                // if(key )
+                if (this.tableData && this.tableData.length > 0) {
+                    this.tableData.forEach(item => {
+                        if (item.employeeId == key) {
+                            let obj = {
+                                id: this.employeeMap[key].id,
+                                name: this.employeeMap[key].name,
+                                img_url: this.employeeMap[key].img_url
+                            }
+                            this.selectedEmployees.push(obj) // 表格已存在的人员列表
+                        }
+                    })
+                } 
+            })
+            this.choosePerson = true;
+        },
+
+        onEmployeeSelected(data) {
+
+        },
+
         // 表格数据 - 选择人员获取数据
         getListByPerson() {
             this.loading = true;
             let url = `/performance/statistics/review/last/${this.user_info.site_id}`
+            this.selected_dept_ids = []
+            this.params.deptIds = ''
+            this.params.employeeIds = this.params.employeeIds && this.params.employeeIds.length > 0 ? this.params.employeeIds.toString() : ''
+
             this.$axiosUser('get', url, this.params).then(res => {
                 this.loading = false;
                 let { data: { data: { list, total }, code } } = res
@@ -260,6 +307,9 @@ export default {
                         new Set(contactList.map(item => JSON.stringify(item)))
                     ).map(item => JSON.parse(item));
                     this.total = this.tableData.length
+                    this.cycleType = '-1';
+                    this.params.cateId = ""
+                    this.cateId = ""
                 }
             });
         },
@@ -328,7 +378,8 @@ export default {
         deptChange(val) {
             this.selected_dept_ids = Array.from(new Set(this.selected_dept_ids)); // 去重
             this.params.deptIds = this.selected_dept_ids.toString()
-            this.getList();
+            // 数据去重
+                this.getList();
         },
 
 
@@ -341,6 +392,8 @@ export default {
             }
             this.selected_employee_ids = Array.from(new Set(this.selected_employee_ids)); // 去重
             this.params.employeeIds = this.selected_employee_ids.toString()
+            // 数据去重
+            this.params.employeeIds = Array.from(new Set(this.params.employeeIds.map(JSON.stringify))).map(JSON.parse);
             this.getList();
         },
 
@@ -351,13 +404,19 @@ export default {
         },
 
         //选择人员弹框 -- 保存人员
-        confirmChoosePerson(e) {
-            console.log(e)
-            let data = e.employee.length > 0 ? e.employee : []
-            let employeeIds = data.map(item => item.id)
-            this.params.employeeIds = employeeIds.length > 0 ? employeeIds.toString() : ''
-            this.getListByPerson();
-            // this.record_ids = e.employee.length > 0 ? e.employee : []
+        confirmChoosePerson(res) {
+            this.choosePersonList = [];
+            this.params.employeeIds = ''
+            this.choosePersonList = res
+            let employeeIds = []
+            if (res && res.length > 0) {
+                res.forEach(item => {
+                    employeeIds.push(item.id)
+                })
+                this.params.employeeIds = employeeIds
+                this.getListByPerson();
+            }
+            
         },
 
         handleSelectChange(val) {
@@ -369,6 +428,9 @@ export default {
             this.tableData = this.tableData.filter(item => !this.multipleSelection.includes(item));
             this.$message.success("删除成功!");
             this.$refs.multipleTable.clearSelection(); // 清空选中状态
+            this.cycleType = '-1';
+            this.params.cateId = ""
+            this.cateId = ""
         },
 
         chooseExamine(row, index) {
@@ -435,11 +497,35 @@ export default {
             
             this.downloadLoading = false;
         },
+
+        closeTargetList() {
+            this.targetDialogVisible = false
+        },
+
+        openTargetList(okrs) {
+            if (okrs && okrs.length > 0) {
+                this.okrs = okrs
+                this.targetDialogVisible = true
+            } else {
+                return this.$message.error("暂无关联okr")
+            }
+        }
     }
 }
 </script>
 
 <style scoped="scoped" lang="scss">
+.green-color {
+    color: #67C23A;
+}
+
+.orange-color {
+    color: #e6a23c;
+}
+
+.blue-color {
+    color: #409eff;
+}
 .all {
     width: 100%;
     height: 100%;
@@ -448,29 +534,40 @@ export default {
     background: #fff;
     padding: 10px;
     box-sizing: border-box;
-    /* 设置滚动条的宽度和背景色 */
-    ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
-        width: 10px;
-        height: 10px;
-        background-color: #f9f9f9;
-    }
+    display: flex;
+    flex-direction: column;
+    
 
-    /* 设置滚动条滑块的样式 */
-    ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
-        border-radius: 6px;
-        background-color: #c1c1c1;
-    }
+    .table-box {
+        width: 100%;
+        flex: 1;
+        overflow-y: auto;
+        margin: 10px 0;
+
+        /* 设置滚动条的宽度和背景色 */
+        &::-webkit-scrollbar {
+            width: 6px;
+            height: 6px;
+            background-color: #f9f9f9;
+        }
 
-    /* 设置滚动条滑块hover样式 */
-    ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
-        background-color: #a8a8a8;
-    }
+        /* 设置滚动条滑块的样式 */
+        &::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
 
-    /* 设置滚动条轨道的样式 */
-    ::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
-        box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
-        border-radius: 6px;
-        background: #ededed;
+        /* 设置滚动条滑块hover样式 */
+        &::-webkit-scrollbar-thumb:hover {
+            background-color: #a8a8a8;
+        }
+
+        /* 设置滚动条轨道的样式 */
+        &::-webkit-scrollbar-track {
+            box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+            border-radius: 6px;
+            background: #ededed;
+        }
     }
 }
 </style>

+ 4 - 3
src/newPerformance/components/PerInterview.vue

@@ -1,7 +1,7 @@
 <template>
-  <el-container style="height: 100%;">
-    <el-main v-if="interviewInfo" v-loading="loading">
-      <el-card style="height: 10%; margin-bottom: 10px;">
+  <el-container style="height: 100%; ">
+    <el-main v-if="interviewInfo" v-loading="loading" >
+      <el-card style="height: 10%; margin-bottom: 10px; ">
         <el-row type="flex" justify="space-between" align="middle">
           <el-col>
             <el-button
@@ -189,6 +189,7 @@
                 <el-card
                   v-for="log in interviewLogs"
                   :key="log.logId"
+                  style="margin-bottom: 10px;"
                 >
                   <el-row type="flex" justify="start" align="center">
                     <el-col :span="3">

+ 163 - 289
src/newPerformance/components/PerReviewDetail.vue

@@ -1,64 +1,37 @@
 <template>
-  <el-container style="height: 100%;">
+  <el-container style="min-width: 1440px; height: 100%;">
     <el-main v-if="reviewInfo">
       <el-card style="height: 10%;margin-bottom: 20px;">
-        <el-row type="flex"  justify="space-between" align="center">
+        <el-row type="flex" justify="space-between" align="center">
           <el-col>
-            <WaStstistics
-              title="考核表"
-              :value="reviewInfo.title"
-            />
+            <WaStstistics title="考核表" :value="reviewInfo.title" />
           </el-col>
           <el-col>
-            <WaStstistics
-              title="周期种类"
-              :value="cycleMap[reviewInfo.cycleType] || '--'"
-            />
+            <WaStstistics title="周期种类" :value="cycleMap[reviewInfo.cycleType] || '--'" />
           </el-col>
           <el-col>
-            <WaStstistics
-              title="考核时间"
-              :value="timeScope"
-            />
+            <WaStstistics title="考核时间" :value="timeScope" />
           </el-col>
           <el-col>
-            <WaStstistics
-              title="考核状态"
-              :value="statusMap[reviewInfo.status] || '--'"
-            />
+            <WaStstistics title="考核状态" :value="statusMap[reviewInfo.status] || '--'" />
           </el-col>
           <el-col>
-            <WaStstistics
-              title="评分"
-              :value="reviewInfo.score === null ? '--' : reviewInfo.score"
-            />
+            <WaStstistics title="评分" :value="reviewInfo.score === null ? '--' : reviewInfo.score" />
           </el-col>
           <el-col>
-            <WaStstistics
-              title="考核中的指标"
-              :value="reviewInfo.indicators.length > 0 ? (reviewInfo.indicators.length - indicatorEnd) : '--'"
-            />
+            <WaStstistics title="考核中的指标"
+              :value="reviewInfo.indicators.length > 0 ? (reviewInfo.indicators.length - indicatorEnd) : '--'" />
           </el-col>
           <el-col>
-            <WaStstistics
-              title="考核完成的指标"
-              :value="indicatorEnd"
-            />
+            <WaStstistics title="考核完成的指标" :value="indicatorEnd" />
           </el-col>
-          <el-col
-            v-if="reviewInfo.interviews && reviewInfo.interviews.length > 0"
-          >
+          <el-col v-if="reviewInfo.interviews && reviewInfo.interviews.length > 0">
             <el-dropdown trigger="click">
-              <el-button
-                type="text"
-              >
+              <el-button type="text">
                 面谈记录
               </el-button>
               <el-dropdown-menu>
-                <el-dropdown-item
-                  v-for="interview in reviewInfo.interviews"
-                  :key="interview.interviewId"
-                >
+                <el-dropdown-item v-for="interview in reviewInfo.interviews" :key="interview.interviewId">
                   <span @click="openInterview(interview.interviewId)">
                     {{ interview.createTime }}
                   </span>
@@ -68,315 +41,173 @@
           </el-col>
         </el-row>
       </el-card>
-      <el-card style="height: calc(90% - 20px); position: relative; " >
+      <el-card style="height: calc(90% - 20px); position: relative; ">
 
-        <Seal
-          v-if="reviewInfo.levelName"
-          :text="reviewInfo.levelName"
-          :size="100"
-          font-size="24px"
-          :top="10"
-          :left="20"
-          :rotate="-30"
-          :z-index="100"
-        />
-        <div class="flex-box-end" style="margin-bottom: 10px;">
-          <el-switch
-            v-model="showNode"
-            active-text="显示考核节点"
-            inactive-text="隐藏考核节点"
-          />
+        <Seal v-if="reviewInfo.levelName" :text="reviewInfo.levelName" :size="100" font-size="24px" :top="10" :left="20"
+          :rotate="-30" :z-index="100" />
+        <div class="flex-box-ce" style="margin-bottom: 10px; justify-content: flex-end;">
+          <el-button type="text" @click="openTargetList(reviewInfo.okrs)">个人OKR</el-button>
+          <el-button type="text" @click="openTargetList(reviewInfo.packageOkrs)"
+            style="margin-right: 10px;">考核表OKR</el-button>
+          <el-switch v-model="showNode" active-text="显示考核节点" inactive-text="隐藏考核节点" />
         </div>
-        <el-table
-          :data="indicators"
-        >
-          <el-table-column
-            prop="title"
-            label="指标"
-            fixed="left"
-            show-overflow-tooltip
-            align="center"
-          />
-          <el-table-column
-            prop="content"
-            label="规则"
-            show-overflow-tooltip
-            align="center"
-          />
-          <el-table-column
-            prop="target"
-            label="目标"
-            align="center"
-          >
+        <el-table :data="indicators">
+          <el-table-column prop="title" label="指标" fixed="left" show-overflow-tooltip align="center" />
+          <el-table-column prop="content" label="规则" show-overflow-tooltip align="center" />
+          <el-table-column prop="target" label="目标" align="center">
             <template slot-scope="scope">
-              {{ scope.row.target === null ? '--' : (scope.row.unit ? `${scope.row.target} ${scope.row.unit}` : scope.row.target) }}
+              {{ scope.row.target === null ? '--' : (scope.row.unit ? `${scope.row.target} ${scope.row.unit}` :
+              scope.row.target) }}
             </template>
           </el-table-column>
-          <el-table-column
-            prop="result"
-            label="结果"
-            align="center"
-          >
+          <el-table-column prop="result" label="结果" align="center">
             <template slot-scope="scope">
-              {{ scope.row.result === null ? '--' : (scope.row.unit ? `${scope.row.result} ${scope.row.unit}` : scope.row.result) }}
+              {{ scope.row.result === null ? '--' : (scope.row.unit ? `${scope.row.result} ${scope.row.unit}` :
+              scope.row.result) }}
             </template>
           </el-table-column>
-          <el-table-column
-            prop="weight"
-            label="权重"
-            align="center"
-          >
+          <el-table-column prop="weight" label="权重" align="center">
             <template slot-scope="scope">
               {{ scope.row.weight ? `${scope.row.weight} %` : '--' }}
             </template>
           </el-table-column>
-          <el-table-column
-            prop="businessStatus"
-            label="考核状态"
-            align="center"
-          >
+          <el-table-column prop="businessStatus" label="考核状态" align="center">
             <template slot-scope="scope">
-              <el-link
-                :type="scope.row.businessStatus === 'end' ? 'primary' : 'warning'"
-              >
+              <el-link :type="scope.row.businessStatus === 'end' ? 'primary' : 'warning'">
                 {{ indicatorStatusMap[scope.row.businessStatus] || '--' }}
               </el-link>
             </template>
           </el-table-column>
-          <el-table-column
-            prop="score"
-            label="得分"
-            align="center"
-          />
-          <el-table-column
-            v-if="showNode"
-            prop="targetConfirms"
-            label="目标确认"
-            align="center"
-          >
+          <el-table-column prop="score" label="得分" align="center" />
+          <el-table-column prop="okrs" label="指标OKR" align="center">
             <template slot-scope="scope">
-              <el-link
-                v-if="!scope.row.targetConfirms.enable"
-                type="info"
-              >
+              <el-link v-if="scope.row.okrs && scope.row.okrs.length > 0" type="primary"
+                @click="openTargetList(scope.row.okrs)">
+                指标OKR
+              </el-link>
+              <span v-else class="fontColorC">暂无关联</span>
+            </template>
+          </el-table-column>
+          <el-table-column v-if="showNode" prop="targetConfirms" label="目标确认" align="center">
+            <template slot-scope="scope">
+              <el-link v-if="!scope.row.targetConfirms.enable" type="info">
                 禁用
               </el-link>
-              <el-link
-                v-else-if="scope.row.targetConfirms.tasks.length <= 0"
-                type="info"
-              >
+              <el-link v-else-if="scope.row.targetConfirms.tasks.length <= 0" type="info">
                 未开始
               </el-link>
-              <template v-else >
+              <template v-else>
                 <div @click="openTasks(scope.row.targetConfirms.tasks,'目标确认任务',scope.row.unit)">
-                  <el-link
-                    v-if="scope.row.targetConfirms.finishCount > 0"
-                    type="primary"
-                    style="margin-right: 5px;"
-                    icon="el-icon-check"
-                  >
+                  <el-link v-if="scope.row.targetConfirms.finishCount > 0" type="primary" style="margin-right: 5px;"
+                    icon="el-icon-check">
                     {{ scope.row.targetConfirms.finishCount }}
                   </el-link>
-                  <el-link
-                    v-if="scope.row.targetConfirms.runningCount > 0"
-                    type="warning"
-                    icon="el-icon-warning"
-                  >
+                  <el-link v-if="scope.row.targetConfirms.runningCount > 0" type="warning" icon="el-icon-warning">
                     {{ scope.row.targetConfirms.runningCount }}
                   </el-link>
                 </div>
               </template>
             </template>
           </el-table-column>
-          <el-table-column
-            v-if="showNode"
-            prop="resultInput"
-            label="结果录入"
-          >
+          <el-table-column v-if="showNode" prop="resultInput" label="结果录入">
             <template slot-scope="scope">
-              <el-link
-                v-if="!scope.row.resultInput.enable"
-                type="info"
-              >
+              <el-link v-if="!scope.row.resultInput.enable" type="info">
                 禁用
               </el-link>
-              <el-link
-                v-else-if="scope.row.resultInput.tasks.length <= 0"
-                type="info"
-              >
+              <el-link v-else-if="scope.row.resultInput.tasks.length <= 0" type="info">
                 未开始
               </el-link>
-              <template v-else >
+              <template v-else>
                 <div @click="openTasks(scope.row.resultInput.tasks,'结果录入任务',scope.row.unit)">
-                  <el-link
-                    v-if="scope.row.resultInput.finishCount > 0"
-                    type="primary"
-                    style="margin-right: 5px;"
-                    icon="el-icon-check"
-                  >
+                  <el-link v-if="scope.row.resultInput.finishCount > 0" type="primary" style="margin-right: 5px;"
+                    icon="el-icon-check">
                     {{ scope.row.resultInput.finishCount }}
                   </el-link>
-                  <el-link
-                    v-if="scope.row.resultInput.runningCount > 0"
-                    type="warning"
-                    icon="el-icon-warning"
-                  >
+                  <el-link v-if="scope.row.resultInput.runningCount > 0" type="warning" icon="el-icon-warning">
                     {{ scope.row.resultInput.runningCount }}
                   </el-link>
                 </div>
               </template>
             </template>
           </el-table-column>
-          <el-table-column
-            v-if="showNode"
-            prop="scoreSelf"
-            label="自评"
-          >
+          <el-table-column v-if="showNode" prop="scoreSelf" label="自评">
             <template slot-scope="scope">
-              <el-link
-                v-if="!scope.row.scoreSelf.enable"
-                type="info"
-              >
+              <el-link v-if="!scope.row.scoreSelf.enable" type="info">
                 禁用
               </el-link>
-              <el-link
-                v-else-if="scope.row.scoreSelf.tasks.length <= 0"
-                type="info"
-              >
+              <el-link v-else-if="scope.row.scoreSelf.tasks.length <= 0" type="info">
                 未开始
               </el-link>
-              <template v-else >
+              <template v-else>
                 <div @click="openTasks(scope.row.scoreSelf.tasks,'自评任务',scope.row.unit)">
-                  <el-link
-                    v-if="scope.row.scoreSelf.finishCount > 0"
-                    type="primary"
-                    style="margin-right: 5px;"
-                    icon="el-icon-check"
-                  >
+                  <el-link v-if="scope.row.scoreSelf.finishCount > 0" type="primary" style="margin-right: 5px;"
+                    icon="el-icon-check">
                     {{ scope.row.scoreSelf.finishCount }}
                   </el-link>
-                  <el-link
-                    v-if="scope.row.scoreSelf.runningCount > 0"
-                    type="warning"
-                    icon="el-icon-warning"
-                  >
+                  <el-link v-if="scope.row.scoreSelf.runningCount > 0" type="warning" icon="el-icon-warning">
                     {{ scope.row.scoreSelf.runningCount }}
                   </el-link>
                 </div>
               </template>
             </template>
           </el-table-column>
-          <el-table-column
-            v-if="showNode"
-            prop="scoreEachOther"
-            label="互评"
-          >
+          <el-table-column v-if="showNode" prop="scoreEachOther" label="互评">
             <template slot-scope="scope">
-              <el-link
-                v-if="!scope.row.scoreEachOther.enable"
-                type="info"
-              >
+              <el-link v-if="!scope.row.scoreEachOther.enable" type="info">
                 禁用
               </el-link>
-              <el-link
-                v-else-if="scope.row.scoreEachOther.tasks.length <= 0"
-                type="info"
-              >
+              <el-link v-else-if="scope.row.scoreEachOther.tasks.length <= 0" type="info">
                 未开始
               </el-link>
-              <template v-else >
+              <template v-else>
                 <div @click="openTasks(scope.row.scoreEachOther.tasks,'互评任务',scope.row.unit)">
-                  <el-link
-                    v-if="scope.row.scoreEachOther.finishCount > 0"
-                    type="primary"
-                    style="margin-right: 5px;"
-                    icon="el-icon-check"
-                  >
+                  <el-link v-if="scope.row.scoreEachOther.finishCount > 0" type="primary" style="margin-right: 5px;"
+                    icon="el-icon-check">
                     {{ scope.row.scoreEachOther.finishCount }}
                   </el-link>
-                  <el-link
-                    v-if="scope.row.scoreEachOther.runningCount > 0"
-                    type="warning"
-                    icon="el-icon-warning"
-                  >
+                  <el-link v-if="scope.row.scoreEachOther.runningCount > 0" type="warning" icon="el-icon-warning">
                     {{ scope.row.scoreEachOther.runningCount }}
                   </el-link>
                 </div>
               </template>
             </template>
           </el-table-column>
-          <el-table-column
-            v-if="showNode"
-            prop="scores"
-            label="评分"
-          >
+          <el-table-column v-if="showNode" prop="scores" label="评分">
             <template slot-scope="scope">
-              <el-link
-                v-if="!scope.row.scores.enable"
-                type="info"
-              >
+              <el-link v-if="!scope.row.scores.enable" type="info">
                 禁用
               </el-link>
-              <el-link
-                v-else-if="scope.row.scores.tasks.length <= 0"
-                type="info"
-              >
+              <el-link v-else-if="scope.row.scores.tasks.length <= 0" type="info">
                 未开始
               </el-link>
-              <template v-else >
+              <template v-else>
                 <div @click="openTasks(scope.row.scores.tasks,'评分任务',scope.row.unit)">
-                  <el-link
-                    v-if="scope.row.scores.finishCount > 0"
-                    type="primary"
-                    style="margin-right: 5px;"
-                    icon="el-icon-check"
-                  >
+                  <el-link v-if="scope.row.scores.finishCount > 0" type="primary" style="margin-right: 5px;"
+                    icon="el-icon-check">
                     {{ scope.row.scores.finishCount }}
                   </el-link>
-                  <el-link
-                    v-if="scope.row.scores.runningCount > 0"
-                    type="warning"
-                    icon="el-icon-warning"
-                  >
+                  <el-link v-if="scope.row.scores.runningCount > 0" type="warning" icon="el-icon-warning">
                     {{ scope.row.scores.runningCount }}
                   </el-link>
                 </div>
               </template>
             </template>
           </el-table-column>
-          <el-table-column
-            v-if="showNode"
-            prop="reviews"
-            label="审批"
-          >
+          <el-table-column v-if="showNode" prop="reviews" label="审批">
             <template slot-scope="scope">
-              <el-link
-                v-if="!scope.row.reviews.enable"
-                type="info"
-              >
+              <el-link v-if="!scope.row.reviews.enable" type="info">
                 禁用
               </el-link>
-              <el-link
-                v-else-if="scope.row.reviews.tasks.length <= 0"
-                type="info"
-              >
+              <el-link v-else-if="scope.row.reviews.tasks.length <= 0" type="info">
                 未开始
               </el-link>
-              <template v-else >
+              <template v-else>
                 <div @click="openTasks(scope.row.reviews.tasks,'审批任务',scope.row.unit)">
-                  <el-link
-                    v-if="scope.row.reviews.finishCount > 0"
-                    type="primary"
-                    style="margin-right: 5px;"
-                    icon="el-icon-check"
-                  >
+                  <el-link v-if="scope.row.reviews.finishCount > 0" type="primary" style="margin-right: 5px;"
+                    icon="el-icon-check">
                     {{ scope.row.reviews.finishCount }}
                   </el-link>
-                  <el-link
-                    v-if="scope.row.reviews.runningCount > 0"
-                    type="warning"
-                    icon="el-icon-warning"
-                  >
+                  <el-link v-if="scope.row.reviews.runningCount > 0" type="warning" icon="el-icon-warning">
                     {{ scope.row.reviews.runningCount }}
                   </el-link>
                 </div>
@@ -386,50 +217,41 @@
         </el-table>
       </el-card>
     </el-main>
-    <el-dialog
-      v-if="tasksInfo.title"
-      :visible.sync="showTasks"
-      :title="tasksInfo.title"
-      center
-      append-to-body
-    >
+
+    <el-dialog v-if="tasksInfo.title" :visible.sync="showTasks" :title="tasksInfo.title" center append-to-body>
       <div style="max-height: 600px; overflow-y: auto;">
-        <el-card
-          v-if="tasksInfo.tasks"
-          v-for="task in tasksInfo.tasks"
-          :key="task.taskId"
-          style="margin-bottom: 20px;"
-        >
-          <el-descriptions
-            border
-            :column="3"
-            :title="task.assigneeName"
-            size="mini"
-            direction="vertical"
-          >
+        <el-card v-if="tasksInfo.tasks" v-for="task in tasksInfo.tasks" :key="task.taskId" style="margin-bottom: 20px;">
+          <el-descriptions border :column="4" :title="task.assigneeName" size="mini" direction="vertical">
             <el-descriptions-item label="任务状态">{{ task.state === 'completed' ? '已处理' : '待处理' }}</el-descriptions-item>
-            <el-descriptions-item
-              v-if="task.score !== null"
-              label="评分"
-            >
+            <el-descriptions-item v-if="task.score !== null" label="评分">
               {{ task.score }}
             </el-descriptions-item>
-            <el-descriptions-item
-              v-if="task.result !== null"
-              label="结果"
-            >
+            <el-descriptions-item v-if="task.result !== null" label="结果">
               {{ tasksInfo.unit ? `${task.result} ${tasksInfo.unit}` : task.result }}
             </el-descriptions-item>
-            <el-descriptions-item
-              label="评论"
-            >
+            <el-descriptions-item label="评论">
               {{ task.comment }}
             </el-descriptions-item>
+
+            <el-descriptions-item v-if="task.files && task.files.length > 0" label="附件列表">
+              <div class="files">
+                <div class="file-list">
+                  <div class="file-item" v-for="file in task.files" @click="onFilePreView(file)">
+                    {{ file | filterFileName }}
+                  </div>
+                </div>
+              </div>
+            </el-descriptions-item>
+
           </el-descriptions>
         </el-card>
       </div>
 
     </el-dialog>
+
+
+    <TargetListComp v-if="dialogVisible" :dialogVisible="dialogVisible" :ids="okrs" @close="closeTargetList">
+    </TargetListComp>
   </el-container>
 </template>
 
@@ -440,10 +262,11 @@ import moment from "moment";
 import Template from "../../examine/components/Template.vue";
 import Seal from "./tool/Seal.vue";
 import PerInterview from "./PerInterview.vue";
+import TargetListComp from "@/performance/views/assessManagement/TargetListComp.vue";
 
 export default {
   name: "PerReviewDetail",
-  components: {PerInterview, Seal, Template, WaStstistics},
+  components: { PerInterview, Seal, Template, WaStstistics, TargetListComp },
   props: {
     reviewId:{
       type: Number,
@@ -454,7 +277,8 @@ export default {
     return {
       userInfo:this.$userInfo(),
       loading:false,
-      reviewInfo:null,
+      reviewInfo: null,
+      dialogVisible: false,
       cycleMap:{
         0:'未定义',
         1:'年度',
@@ -486,6 +310,17 @@ export default {
         unit:'',
         tasks:[]
       },
+      okrs: []
+    }
+  },
+
+  filters: {
+
+    filterFileName(str) {
+      if (str) {
+        let lastIndex = str.lastIndexOf("/")
+        return str.substr(lastIndex + 1, str.length - 1);
+      }
     }
   },
   computed:{
@@ -515,7 +350,8 @@ export default {
           scoreSelf:null,
           scoreEachOther:null,
           scores:null,
-          reviews:null,
+          reviews: null,
+          okrs: item.okrs,
           cc:null,
         };
         item.flow.nodes.forEach(node => {
@@ -708,6 +544,24 @@ export default {
       if (!interviewId || !this.reviewInfo) return;
       window.open(`#/per/interview/${this.reviewId}/${interviewId}`);
     },
+
+    closeTargetList() {
+      this.dialogVisible = false
+    },
+
+    openTargetList(okrs) {
+      if (okrs && okrs.length > 0) {
+        this.okrs = okrs
+        this.dialogVisible = true
+      }
+      else {
+        return this.$message.error("暂无关联okr")
+      }
+    },
+
+    onFilePreView(url) {
+      window.open(url, '_blank');
+    },
   },
   mounted() {
     this.initData();
@@ -717,6 +571,26 @@ export default {
 </script>
 
 <style scoped lang="scss">
-
+    .files {
+      display: flex;
+      flex-direction: column;
+      padding: 5px;
+      background: #f7f7f7;
+      border-radius: 6px;
+  
+      .title {
+        color: #999;
+      }
+  
+      .file-item {
+        color: #999;
+        transition: 0.2s;
+  
+        &:hover {
+          text-decoration: underline;
+          cursor: pointer;
+        }
+      }
+    }
 
 </style>

+ 84 - 0
src/newPerformance/components/PerformSetting.vue

@@ -0,0 +1,84 @@
+<template>
+    <div class="all">
+        <div class="tabs">
+            <div class="tab-item" v-for="(item, index) in tabs" :key="item.id"
+                :class="currentIdnex == index ? 'active' : ''" @click="changeTab(index)">
+                {{ item.title }}
+            </div>
+        </div>
+
+        <div class="flex-1">
+            <LevelSetting v-if="currentIdnex == 0" />
+            <NormalDistribution v-if="currentIdnex == 1" />
+        </div>
+
+    </div>
+</template>
+
+<script>
+import LevelSetting from './LevelSetting.vue';
+import NormalDistribution from './NormalDistribution.vue';
+export default {
+    components: {
+        LevelSetting,
+        NormalDistribution
+    },
+    data() {
+        return {
+            currentIdnex: 0,
+            tabs: [
+                { id: 1, title: "等级设置" },
+                { id: 2, title: "正态分布" },
+            ]
+        }
+    },
+    methods: {
+        changeTab(index) {
+            this.currentIdnex = index
+        }
+    }
+        
+}
+
+</script>
+
+
+<style scoped="scoped" lang="scss">
+.all {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    padding: 20px 10px;
+    box-sizing: border-box;
+    background-color: #fff;
+    display: flex;
+    flex-direction: column;
+    .tabs {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        .tab-item {
+            width: 120px;
+            height: 40px;
+            border: 1px solid #409EFF;
+            border-radius: 6px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            color: #409EFF;
+            &:hover {
+                cursor: pointer;
+            }
+            &:first-child {
+                margin-right: 10px;
+            }
+
+            
+        }
+        .active {
+            background-color: #409EFF;
+            color: white;
+        }
+    }
+}
+</style>

+ 225 - 0
src/newPerformance/components/PerformSetting/LevelSetting.vue

@@ -0,0 +1,225 @@
+<template>
+    <div class="all">
+        <div class="flex-box main">
+            <div class="flex-5 right">
+                <div v-show="isActive == 2">
+                    <el-form ref="detailForm" @submit.native.prevent label-width="150px">
+                        <div class="title">绩效等级配置</div>
+                        <div style="height: 30px;"></div>
+                        <ClassSet ref="ClassSet" :inputs="inputs"
+                            :inputsStyle="{ paddingLeft: '150px', width: '700px' }">
+                        </ClassSet>
+                    </el-form>
+                </div>
+            </div>
+        </div>
+        <div class="footer"><el-button type="primary" :loading="loading" @click="save()">保存设置</el-button></div>
+    </div>
+</template>
+
+<script>
+import ClassSet from '@/performance/components/public/ClassSet';
+export default {
+    components: { ClassSet },
+    name: 'BasicsSet',
+    data() {
+        return {
+            loading: false,
+            isActive: 2,
+            ruleForm: {
+                region: 'shanghai',
+                delivery: true
+            },
+            rules: {},
+            inputs: [],//分值区间
+            level_enable: true,//是否开启绩效等级
+            isSave: true
+        };
+    },
+    watch: {},
+    created() {
+        this.getAllSet();
+    },
+    mounted() { },
+    methods: {
+        // 获取全局设置
+        getAllSet() {
+            this.$axiosUser('get', 'api/pro/per/user/base_config').then(res => {
+                let data = res.data.data;
+                let levels = data.level_scope.levels;
+                this.level_enable = data.level_enable;
+                var inputs = [];
+                var max = 0;//最大值
+                if (levels) {
+                    levels.forEach((item, index) => {
+                        var obj;
+                        if (index == 0) {
+                            obj = { name: item.name, max: Number(item.value), min: 0 };
+                        } else {
+                            obj = { name: item.name, max: Number(item.value), min: max };//当不是第一个等级时,最小值为上一个的最大值
+                        }
+                        max = item.value;
+                        inputs.push(obj);
+                    })
+                    this.inputs = inputs
+                }
+            })
+        },
+        //保存
+        save() {
+            let is = true, level_scope = [];
+            let isSave = this.$refs.ClassSet.isSave
+            let inputsList = this.$refs.ClassSet.inputsList
+            if (!isSave) {
+                return false
+            }
+            inputsList.some(item => {
+                if (!item.name) {
+                    this.$message.error('等级的名称不能为空');
+                    is = false;
+                    return true;
+                }
+                if (item.max == 0) {
+                    this.$message.error('最大值不能为空或者0');
+                    is = false;
+                    return true;
+                }
+                level_scope.push({ name: item.name, value: Number(item.max) });
+            })
+
+            var level_scopes = {//参数的数据结构
+                levels: level_scope
+            };
+            if (is) {
+                this.loading = true;
+                this.$axiosUser('post', 'api/pro/per/user/set_base_config', { level_scope: JSON.stringify(level_scopes) }).then(res => {
+                    this.$message.success('设置成功');
+                    this.getAllSet();
+                }).finally(() => {
+                    this.loading = false;
+                })
+            }
+        },
+        // 监听最大值输入
+        InputBiur(e, index) {
+            this.isSave = true
+            var max = this.inputsList[index].max;
+            var min = this.inputsList[index].min;
+            if (max == 0) {
+                return false;
+            }
+            if (min >= max) {
+                this.$message.error('不能小于或等于' + min);
+                this.$set(this.inputsList[index], 'max', 0);
+                this.isSave = false
+                return false;
+            }
+            if (this.inputsList[index + 1]) {
+                this.$set(this.inputsList[index + 1], 'min', max);
+            }
+        },
+        addInput() {
+            if (this.inputsList.length == 10) {
+                this.$message.error('最高10个级别');
+                return false;
+            }
+            var max = this.inputsList[this.inputsList.length - 1].max;
+            this.inputsList.push({ name: '', min: max, max: 0 });
+        },
+        deleteInput(index) {
+            if (index == 0) {
+                this.$message.error('至少保留一个等级');
+                return false;
+            }
+            var min = this.inputsList[index].min; //获取当前元素最小值
+            this.inputsList.splice(index, 1);
+            if (index != this.inputsList.length) {
+                //当删除不是最后一位时
+                this.$set(this.inputsList[index], 'min', min);
+            }
+            // // this.inputsList.some((item, i) => {
+            // //   if (i == index) {
+            //     this.inputsList.splice(i, 1); //在数组的some方法中,如果return true,就会立即终止这个数组的后续循环
+            // //     return true;
+            // //   }
+            // // });
+        },
+        active(index) {
+            this.isActive = index;
+        }
+    }
+};
+</script>
+<style scoped="scoped">
+.all {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    box-sizing: border-box;
+    background-color: #fff;
+}
+
+.title {
+    font-weight: 600;
+    margin-bottom: 10px;
+    background-color: #f5f7fa;
+    padding: 15px;
+}
+
+.main {
+    min-height: calc(100% - 40px);
+}
+
+.title-f {
+    margin-bottom: 20px;
+}
+
+.footer {
+    background-color: #fff;
+    border-top: 1px solid #ebebeb;
+    height: 40px;
+    text-align: center;
+    padding-top: 10px;
+}
+
+.checkChild {
+    background-color: #fbfdff;
+    margin-left: 20px;
+}
+
+.left,
+.right {
+    height: 100%;
+    overflow: auto;
+}
+
+.ul {
+    border: 1px solid #ebebeb;
+    height: 100%;
+    border-bottom: none;
+}
+
+.li {
+    padding: 15px;
+    position: relative;
+}
+
+.li:hover {
+    background-color: #f5f7fa;
+}
+
+.active-li {
+    color: #409EFF !important;
+    background-color: #f5f7fa;
+}
+
+.active-li::before {
+    content: '';
+    position: absolute;
+    width: 3px;
+    height: 100%;
+    background-color: #409EFF;
+    left: 0;
+    top: 0;
+}
+</style>

+ 272 - 0
src/newPerformance/components/PerformanceCoaching.vue

@@ -0,0 +1,272 @@
+<template>
+    <div class="coaching-container">
+
+        <div class="base-info-title flex-box-ce">
+            <div class="line"></div>
+            绩效辅导
+            <el-cascader ref="dept" v-model="selected_dept_ids" size="small" style="width: 300px; margin-left: 10px; "
+                :options="deptList" @change="deptChange" :props="{ checkStrictly: true, multiple: true }" filterable
+                change-on-select placeholder="请选择部门" clearable></el-cascader>
+        </div>
+
+        <el-table v-loading="loading" :data="tableData" style="width: 100%"  :header-cell-style="{ background: '#f5f7fa' }" border stripe>
+            <el-table-column prop="title" label="面谈标题" align="center">
+            </el-table-column>
+            <el-table-column prop="employeeName" label="员工姓名" align="center">
+            </el-table-column>
+
+            <!-- <el-table-column label="部门" align="center">
+            </el-table-column> -->
+
+            <el-table-column align="center" prop="createTime" label="面谈创建时间">
+            </el-table-column>
+
+            <el-table-column align="center" prop="businessStatus" label="状态">
+                <template slot-scope="scope">
+                    
+                    <div v-if="scope.row.businessStatus === 'start'">开始</div>
+                    <div v-if="scope.row.businessStatus === 'interview'">面谈中</div>
+                    <div v-if="scope.row.businessStatus === 'end'">结束</div>
+                </template>
+            </el-table-column>
+
+            <el-table-column align="center" label="操作">
+                <template slot-scope="scope">
+                    <el-button v-if="scope.row.businessStatus === 'interview'" type="text" @click="getDetails(scope.row)">查看面谈详情</el-button>
+                    <el-button v-if="scope.row.businessStatus === 'end'" type="text" @click="getDetails2()">查看归档记录</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+
+        <el-dialog :visible.sync="showReviewDetail" title="考核详情" center fullscreen>
+            <div style="height: 85vh;">
+                <PerReviewDetail v-if="currentReviewId" :review-id="currentReviewId" />
+            </div>
+        </el-dialog>
+
+        <el-dialog :visible.sync="dialogVisible" title="归档记录" center width="600px">
+            <div class="dialog-content" style="">
+                <el-descriptions title="" direction="vertical" :column="1" border style="height: 100%;">
+                    <el-descriptions-item label="主持人记录">
+                        <div class="label">存在问题:</div>
+                        <div class="value">暂无发现问题</div>
+                        <div class="label">评价及意见:</div>
+                        <div class="value">还有很大的提升空间</div>
+                        <div class="label">改进方案:</div>
+                        <div class="value">1.改进措施1</div>
+                        <div class="value">2.改进措施2</div>
+                        <div class="value">3.改进措施3</div>
+                        <div class="value">4.改进措施4</div>
+                    </el-descriptions-item>
+                    <el-descriptions-item label="被考核人记录">
+                        <div class="label">存在问题:</div>
+                        <div class="value">暂无发现问题</div>
+                        <div class="label">评价及意见:</div>
+                        <div class="value">还有很大的提升空间</div>
+                        <div class="label">改进方案:</div>
+                        <div class="value">1.改进措施1</div>
+                        <div class="value">2.改进措施2</div>
+                        <div class="value">3.改进措施3</div>
+                        <div class="value">4.改进措施4</div>
+                    </el-descriptions-item>
+                    <el-descriptions-item label="其他人发言记录">
+                        <div class="label">张三</div>
+                        <div class="value">暂无发现问题</div>
+                        <div class="label">李四</div>
+                        <div class="value">暂无发现问题</div>
+                        <div class="label">王五</div>
+                        <div class="value">暂无发现问题</div>
+                        <div class="label">赵六</div>
+                        <div class="value">暂无发现问题</div>
+                    </el-descriptions-item>
+                </el-descriptions>
+            </div>
+        </el-dialog>
+
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import PerReviewDetail from "./PerReviewDetail.vue";
+export default {
+    components: {
+        PerReviewDetail
+    },
+    data() {
+        return {
+            loading: false,
+            tableData: [],
+            total: 0,
+            params: {
+                reviewId: "",
+                keyword: "",
+                employeeId: "",
+                deptIds: "",
+                type: "",
+                page: 1,
+                pageSize: 10
+            },
+            dialogVisible: false,
+            showReviewDetail: false,
+            currentReviewId: '',
+            selected_dept_ids: [],
+            deptList: [], // 部门列表 - 树形结构
+            dept_list: [], // 部门列表
+        }
+    },
+
+    created() {
+        this.getInterviewList();
+        this.get_dept_list();
+    },
+
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    methods: {
+        getInterviewList() {
+            let url = `/performance/interview/list/${this.user_info.site_id}`
+            this.$axiosUser("get", url, this.params).then(res => {
+                this.tableData = res.data.data.list
+            })
+        },
+        //待办
+        getNewAgency() {
+            this.loading = true;
+            this.params.type = 7
+            this.$axiosUser("get", `/performance/review/job/${this.user_info.site_id}`, this.params).then(res => {
+                this.tableData = [];
+                let list = res.data.data.list
+                if (list && list.length > 0) {
+                    list.forEach(item => {
+                        if (item.jobNew) this.tableData.push(item.jobNew)
+                    })
+                    this.total = this.tableData.length || 0;
+                    console.log(this.tableData);
+                    this.tableData[0].status = 1
+                    this.tableData.push({
+                        reviewTitle: "2025-04-01 至 2025-04-30 月度考核",
+                        employeeName: "尹勇",
+                        timeRemark: "2025-04-24 09:00",
+                        status: 0
+                    })
+                    this.loading = false;
+                }
+                // this.editableTabs.forEach(item => {
+                //     if (item.name == this.activeName) item.num = res.data.data.total
+                // })
+                
+            })
+        },
+
+        deptChange(val) {
+        },
+
+        getDetails(row) {
+            this.currentReviewId = row.reviewId;
+            this.showReviewDetail = true;
+        },
+
+        getDetails2() {
+            this.dialogVisible = true
+        },
+        // 处理部门树状结构数据
+        getTreeData(data) {
+            for (var i = 0; i < data.length; i++) {
+                data[i].checked = false;
+                if (data[i].children.length < 1) {
+                    // children若为空数组,则将children设为undefined
+                    data[i].children = undefined;
+                } else {
+                    // children若不为空数组,则继续 递归调用 本方法
+                    this.getTreeData(data[i].children);
+                }
+            }
+            return data;
+        },
+
+        // 获取部门
+        get_dept_list() {
+            this.$axiosUser('get', '/api/pro/department/tree', '', 'v2').then(res => {
+                this.dept_list = res.data.data.list; // 用来回显选择的部门数据
+                this.deptList = this.getTreeData(this.dept_list); // 处理成树状结构
+            });
+        },
+    }
+    
+    }
+
+
+</script>
+
+<style scoped="scoped" lang="scss">
+
+.coaching-container {
+    width: 100%;
+    height: 100%;
+    background-color: #fff;
+    border-radius: 5px;
+    padding: 10px;
+    box-sizing: border-box;
+    .base-info-title {
+        width: 100%;
+        height: 50px;
+        font-size: 16px;
+        font-weight: 600;
+        margin-bottom: 10px;
+        .line {
+            width: 3px;
+            height: 18px;
+            background: #409eff;
+            margin: 0 10px;
+        }
+    }
+}
+
+
+
+.dialog-content {
+    width: 100%;
+    height: 500px;
+    overflow-y: auto;
+    padding-bottom: 20px;
+    box-sizing: border-box;
+    /* 设置滚动条的宽度和背景色 */
+    &::-webkit-scrollbar {
+        width: 6px;
+        height: 6px;
+        background-color: #f9f9f9;
+    }
+
+    /* 设置滚动条滑块的样式 */
+    &::-webkit-scrollbar-thumb {
+        border-radius: 6px;
+        background-color: #c1c1c1;
+    }
+
+    /* 设置滚动条滑块hover样式 */
+    &::-webkit-scrollbar-thumb:hover {
+        background-color: #a8a8a8;
+    }
+
+    /* 设置滚动条轨道的样式 */
+    &::-webkit-scrollbar-track {
+        box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+        border-radius: 6px;
+        background: #ededed;
+        }
+    .label {
+        font-size: 14px;
+        margin-bottom: 5px;
+        font-weight: 600;
+    }
+    
+    .value {
+        padding-left: 20px;
+        color: #888;
+        margin-bottom: 5px;
+    }
+    
+}
+</style>

+ 2 - 3
src/newPerformance/components/PerformanceInterview.vue

@@ -138,9 +138,9 @@
                 <el-table-column prop="title" label="指标名称" width="width">
                 </el-table-column>
             </el-table> -->
-            <el-table ref="fmeaTableRef" :class="isShow ? 'fadeInDown animated' : 'fadeInUp animated'" :data="tableData"
+            <el-table :class="isShow ? 'fadeInDown animated' : 'fadeInUp animated'" :data="tableData"
                 stripe style="width: 100%; margin-bottom: 20px;" :height="600" border
-                :header-cell-style="{ background: '#f5f7fa' }" v-table-move="['fmeaTableRef']">
+                :header-cell-style="{ background: '#f5f7fa' }" >
                 <el-table-column prop="title" label="指标" align="center" min-width="200" fixed="left">
                 </el-table-column>
                 <el-table-column prop="content" label="规则" align="center" min-width="200">
@@ -726,7 +726,6 @@ export default {
     }
 
 
-
     .chat-record {
         width: 24%;
         height: 100%;

+ 304 - 210
src/newPerformance/components/PersonalExamine.vue

@@ -1,92 +1,71 @@
 <template>
     <div class="boxMinHeight">
 
-        <div class="flex-box-ce flex-d-center header">
-            <div style="font-size: 18px;font-weight: 700" class="flex-1">个人考核</div>
-            <el-select class="select" v-model="cycleType" placeholder="周期类型" @change="changeCircle"
-                style="width: 100px; margin-right: 10px;" size="small">
-                <el-option v-for="item in cycleOptions" :key="item.value" :label="item.label" :value="item.value">
-                </el-option>
-            </el-select>
-
-            <el-select class="select" v-model="selected_employee_ids" placeholder="请选择指定人员" filterable
-                style="width: 300px;" @change="changeEmployeeIds" clearable size="small">
-                <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"></el-option>
-            </el-select>
-
-            <el-select class="select" v-model="cateId" @change="changeCateId" placeholder="请选择考核分类"
-                style="width: 300px; margin-left: 10px;" clearable size="small">
-                <el-option v-for="item in cateList" :key="item.cateId" :label="item.name" :value="item.cateId">
-                </el-option>
-            </el-select>
-
-            <el-link style="margin-left: 10px;" type="info" @click="openDetail()">
-                查看明细 
-                <i class="el-icon-arrow-right"></i>
-            </el-link>
-        </div>
-
-
-        <div class="circular_area" style="margin-top: 10px;">
-            <div class="flex-box-ce" style="margin-bottom: 10px;">
-                <div class="circular_item flex-1" style="margin-right: 10px;">
-                    <div class="flex-box">
-                        <div class="circular_title_left flex-1">
-                            <div>考核表状态分布</div>
-                        </div>
+        <!-- <div class="left-container">
+            <el-row :gutter="10" style="height: 100%;">
+                <el-col :span="12"
+                    style="height: 100%; display: flex; flex-direction: column; border-bottom: 1px solid #f1f1f1;">
+                    <div class="info-title">
+                        部门选择
+                        <el-input class="flex-1" placeholder="输入关键字进行过滤" v-model="filterText" size="mini"
+                            style="margin-left: 10px;" />
                     </div>
-                    <div>
-                        <PieChart1 key="PieChart1" v-if="tableData && tableData.length > 0" :table-data="tableData" />
-                        <noData v-else content="暂无数据" imgW="160px" imgH="160px"></noData>
+                    <div class="flex-1 dept-list department_box" style="overflow-x: auto;">
+                        <el-tree ref="tree" accordion :data="treeData" :props="defaultProps"
+                            :filter-node-method="filterNode" :default-checked-keys="checkedKeys"
+                            :check-strictly="singleSelection" :default-expanded-keys="checkedKeys"
+                            @node-click="handleNodeClick" node-key="id">
+                            <div content="tree" class="custom-tree-node" slot-scope="{ node, data }"
+                                style="font-size: 14px;color: #606266; width:100%; text-align: left;">
+                                <img src="static/images/one.png" />
+                                <span class="name">{{ data.name }}</span>
+                            </div>
+                        </el-tree>
                     </div>
-                </div>
-
-                <div class="circular_item flex-1">
-                    <div class="flex-box">
-                        <div class="circular_title_left flex-1">
-                            <div>考核等级分布</div>
-                        </div>
-                    </div>
-                    <div>
-                        <PieChart2 key="PieChart2" v-if="tableData && tableData.length > 0" :table-data="tableData"
-                            :gradeLevels="gradeLevels" />
-                        <noData v-else content="暂无数据" imgW="160px" imgH="160px"></noData>
-                    </div>
-                </div>
-            </div>
-        </div>
-
-        <div class="circular_area" style="margin-top: 10px;">
-            <div class="flex-box-ce" style="margin-bottom: 10px;">
-                <div class="circular_item flex-1" style="margin-right: 10px;">
-                    <div class="flex-box">
-                        <div class="circular_title_left flex-1">
-                            <div>考核周期类型分布</div>
-                        </div>
+                </el-col>
+                <el-col :span="12" style="height: 100%; display: flex; flex-direction: column;">
+                    <div class="info-title">
+                        员工选择
+                        <el-input class="flex-1" placeholder="输入关键字进行过滤" v-model="keyword" size="mini"
+                            style="margin-left: 10px;" />
                     </div>
-                    <div>
-                        <PieChart3 key="PieChart3" v-if="tableData && tableData.length > 0" :table-data="tableData"
-                            :cycleOptions="cycleOptions" />
-                        <noData v-else content="暂无数据" imgW="160px" imgH="160px"></noData>
-                    </div>
-                </div>
 
-                <div class="circular_item flex-1">
-                    <div class="flex-box">
-                        <div class="circular_title_left flex-1">
-                            <div>考核分类分布</div>
-                        </div>
-                    </div>
-                    <div>
-                        <PieChart4 key="PieChart4" v-if="tableData && tableData.length > 0" :table-data="tableData"
-                            :cateList="cateList" />
-                        <noData v-else content="暂无数据" imgW="160px" imgH="160px"></noData>
-                    </div>
+
+                    <div class="flex-1 user-list scroll-bar" style="min-height: 100%; overflow-y: auto;">
+                        <template v-if="userList && userList.length > 0">
+                            <div class=" user-item flex-box-ce" v-for="user in userList" :key="user.id"
+                                @click="chooseUser(user)">
+                                <span class="radio" :class="radioValue == user.id ? 'isCheck' : ''">
+                                    <span v-show="radioValue == user.id" class="white-dot"></span>
+                                </span>
+                                <userImage :id="user.id" :img_url="user.img_url" :user_name="user.name" width="24px"
+                                    height="24px"></userImage>
+                                <span class="username">{{ user.name }}</span>
+                            </div>
+
+                            <div style="height: 100px;"></div>
+                        </template>
+
+<template v-else>
+                            <noData content="暂无内容" imgW="100px" imgH="100px"></noData>
+                        </template>
+</div>
+</el-col>
+</el-row>
+</div> -->
+
+        <!-- <div class="right-container">
+            <ExamineDetails v-if="tableData && tableData.length > 0" :table-data="tableData" :cycle-list="cycleOptions"
+                :cate-list="cateList" :examine-status="examineStatus" :gradeLevels="gradeLevels" :userInfo="userInfo" />
+            <template v-else>
+                <div class="flex-box-ce" style="width: 100%; height: 100%; justify-content: center;">
+                    <noData content="暂无内容" imgW="120px" imgH="120px"></noData>
                 </div>
-            </div>
-        </div>
-        <ExamineDetails v-if="dialogVisible" v-model="dialogVisible" :table-data="tableData" :cycle-list="cycleOptions"
-            :cate-list="cateList" :examine-status="examineStatus"/>
+            </template>
+        </div> -->
+
+
+
     </div>
 </template>
 
@@ -101,7 +80,7 @@ import PieChart2 from '@/newPerformance/components/PersonalExamine/PieChart2'; /
 import PieChart3 from '@/newPerformance/components/PersonalExamine/PieChart3'; // 考核周期类型分布 - 饼图
 import PieChart4 from '@/newPerformance/components/PersonalExamine/PieChart4'; // 考核分类分布 - 饼图
 import ExamineDetails from '@/newPerformance/components/PersonalExamine/ExamineDetails'; // 考核详情
-
+import OrganizationSelect from "@/newPerformance/components/PublicComp/OrganizationSelect" // 组织架构
 export default {
     components: {
         EmployeeSelector,
@@ -110,11 +89,24 @@ export default {
         PieChart2,
         PieChart3,
         PieChart4,
-        ExamineDetails
+        ExamineDetails,
+        OrganizationSelect
     },
 
     data() {
         return {
+            filterText: '',
+            treeData: [],
+            singleSelection: true, // 是否单选
+            checkedKeys: [],
+            defaultProps: {
+                children: 'children',
+                label: 'label'
+            },
+            dept_select_id: '',
+            keyword: '',
+            userList: [],
+            radioValue: '',
             selected_employee_ids: '',
             selected_dept_ids: [],
             deptList: [], // 部门列表 - 树形结构
@@ -146,28 +138,78 @@ export default {
             multipleSelection: [],
             alertTilte: "默认显示每个人的最新考核数据,可选择人员添加考核数据,也可以移除某个人员的考核数据,或点击人员姓名替换某条考核数据",
             dialogVisible: false,
+            organSelectDialog: false,
             gradeLevels: [],
             examineStatus: [
                 { label: "考核中", value: "0" },
                 { label: "已结束", value: "1" },
                 { label: "面谈", value: "2" }
-            ]
+            ],
+            userInfo: null
         }
     },
-    
-    
+
+    watch: {
+        filterText(val) {
+            this.$refs.tree.filter(val);
+        },
+        keyword(val) {
+            if (val) this.userList = this.userList.filter(user => user.name.includes(val))
+        },
+        radioValue(val) {
+            this.selected_employee_ids = val
+            this.getList(val);
+
+        }
+    },
+
+
     created() {
+        this.selected_employee_ids = this.user_info.id
+        this.userInfo = this.user_info
         this.getAllSet();
         this.getList();
         this.get_dept_list();
-        this.get_cate_list();
+        // this.get_cate_list();
     },
+
     computed: {
         ...mapGetters(['user_info']),
     },
-   
+
     methods: {
 
+        get_dept_list() {
+            this.$axiosUser('get', '/api/pro/department/tree', '', 'v2').then(res => {
+                console.log(res)
+                this.treeData = res.data.data.list
+            })
+        },
+
+        get_user_list() {
+            this.$axiosUser('get', '/api/pro/employee/index',
+                { dept_id: this.dept_select_id, keywords: this.keyword, page: 1, page_size: 2000, child: '1' }, 'v2')
+                .then(res => {
+                    this.userList = res.data.data.list;
+                    console.log(this.userList)
+                })
+        },
+
+        filterNode(value, data) {
+            if (!value) return true;
+            return data.label.indexOf(value) !== -1;
+        },
+        handleNodeClick(node) {
+            console.log(node)
+            this.dept_select_id = node.id
+            this.get_user_list();
+        },
+
+        chooseUser(user) {
+            this.radioValue = user.id
+            this.userInfo = user;
+        },
+
         // 表格数据 - 最新考核数据
         getList(employeeId = '') {
             let that = this
@@ -187,78 +229,6 @@ export default {
             });
         },
 
-        // 处理部门树状结构数据
-        getTreeData(data) {
-            for (var i = 0; i < data.length; i++) {
-                data[i].checked = false;
-                if (data[i].children.length < 1) {
-                    // children若为空数组,则将children设为undefined
-                    data[i].children = undefined;
-                } else {
-                    // children若不为空数组,则继续 递归调用 本方法
-                    this.getTreeData(data[i].children);
-                }
-            }
-            return data;
-        },
-
-        // 获取部门
-        get_dept_list() {
-            this.$axiosUser('get', '/api/pro/department/tree', '', 'v2').then(res => {
-                this.dept_list = res.data.data.list; // 用来回显选择的部门数据
-                this.deptList = this.getTreeData(this.dept_list); // 处理成树状结构
-            });
-        },
-
-        // 考核分类列表
-        get_cate_list() {
-            let url = `/performance/cate/list/${this.user_info.site_id}`;
-            // this.loading = true
-            this.$axiosUser('get', url, {}).then(res => {
-                let { data: { code, data: { list, total } } } = res
-                if (code == 1) {
-                    this.cateList = list
-                }
-
-            })
-        },
-
-        changeCircle(v) {
-            this.getList();
-        },
-
-        deptChange(val) {
-            this.selected_dept_ids = Array.from(new Set(this.selected_dept_ids)); // 去重
-            this.params.deptIds = this.selected_dept_ids.toString()
-            this.getList();
-        },
-
-
-        // 选择员工
-        changeEmployeeIds(v) {
-            this.selected_employee_ids = v
-            this.getList(this.selected_employee_ids);
-        },
-
-        // 选择考核分类
-        changeCateId(v) {
-            this.params.cateId = v;
-            this.getList();
-        },
-
-        //选择人员弹框 -- 保存人员
-        confirmChoosePerson(e) {
-            let data = e.employee.length > 0 ? e.employee : []
-            let employeeIds = data.map(item => item.id)
-            this.params.employeeIds = employeeIds.length > 0 ? employeeIds.toString() : ''
-            this.getListByPerson();
-            // this.record_ids = e.employee.length > 0 ? e.employee : []
-        },
-
-        openDetail() {
-            this.dialogVisible = true
-        },
-
         // 获取全局等级设置
         async getAllSet() {
             let res = await this.$axiosUser('get', 'api/pro/per/user/base_config')
@@ -281,75 +251,199 @@ export default {
             }
         },
 
+        // 考核分类列表
+        get_cate_list() {
+            let url = `/performance/cate/list/${this.user_info.site_id}`;
+            // this.loading = true
+            this.$axiosUser('get', url, {}).then(res => {
+                let { data: { code, data: { list, total } } } = res
+                if (code == 1) {
+                    this.cateList = list
+                }
+
+            })
+        },
     }
 }
 </script>
 
 <style scoped="scoped" lang="scss">
-.boxMinHeight {
+.department_box /deep/ .el-tree-node {
+    display: block;
+    text-align: center;
 
-    .header {
-        margin-bottom: 10px;
-        border-radius: 6px;
-        padding: 10px;
-        box-sizing: border-box;
-        background-color: #fff;
-        .select ::v-deep .el-input__inner {
-            border-radius: 25px;
-        }
-    }
+}
+
+.department_box /deep/ .el-tree-node__content {
+    height: auto !important;
+}
+
+.department_box /deep/ .el-tree-node .el-icon-caret-right {
+    display: inline-block;
+    color: #909399;
+    font-size: 16px;
+    padding: 6px 8px;
+}
+
+.department_box /deep/ .el-tree-node .el-icon-caret-right.is-leaf {
+    color: transparent;
+    cursor: default;
+}
+
+.department_box /deep/ .el-tree-node .custom-tree-node {
+    display: flex;
+    align-items: center;
+}
+
+.department_box /deep/ .el-tree-node .custom-tree-node img {
+    margin: 0px 5px 0 0;
+    width: 20px;
+}
+
+.department_box /deep/ .el-tree-node .custom-tree-node span {}
+
+.department_box /deep/ .el-tree-node__content {
+    padding: 12px 0;
+    border-bottom: 1px #f8f8f8 solid;
+    display: flex;
+    align-items: center;
+}
 
-    .circular_item {
+.department_box /deep/ .el-tree-node__content:hover {
+    background: #ecf5ff;
+    border-radius: 4px;
+}
+
+.department_box /deep/ .is-focusable .is-current {
+    border-radius: 4px;
+}
+
+.department_box /deep/ .is-focusable .is-current .name {
+    color: #409EFF;
+    font-weight: normal;
+    transition: 0.35s ease-in-out;
+}
+
+
+.boxMinHeight {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    overflow: hidden;
+
+    .left-container {
+        width: calc(35% - 10px);
+        height: 100%;
+        margin-right: 10px;
         background-color: #fff;
-        padding: 20px;
         border-radius: 5px;
-        box-sizing: border-box;
-        height: 380px;
-        .circular_title_left div:nth-child(1) {
-            font-size: 18px;
-            font-weight: 700;
-        }
-        
+        display: flex;
+        flex-direction: column;
+
+        .info-title {
+            font-size: 16px;
+            font-weight: 600;
+            line-height: 40px;
+            display: flex;
+            margin-bottom: 10px;
+            padding: 0 10px;
 
-        .grey_font {
-            font-weight: normal;
-            font-size: 12px;
-            color: grey;
         }
 
-    }
+        .dept-list,
+        .user-list {
+            width: 100%;
+            flex: 1;
+            padding-bottom: 30px;
+            box-sizing: border-box;
+            overflow-y: auto;
+            border-top: 1px solid #f1f1f1;
+
+            /* 设置滚动条的宽度和背景色 */
+            &::-webkit-scrollbar {
+                width: 6px;
+                height: 6px;
+                background-color: #f9f9f9;
+            }
 
-    .main {
-        width: 100%;
-        height: 100px;
-        margin: 0 0 10px 0;
-        background-color: #fff;
+            /* 设置滚动条滑块的样式 */
+            &::-webkit-scrollbar-thumb {
+                border-radius: 6px;
+                background-color: #c1c1c1;
+            }
+
+            /* 设置滚动条滑块hover样式 */
+            &::-webkit-scrollbar-thumb:hover {
+                background-color: #a8a8a8;
+            }
 
-        .main-header {
-            .bian {
-                position: absolute;
-                width: 1px;
-                height: 30px;
-                background-color: #e8e8e8;
-                right: 0;
-                top: 50%;
-                margin-top: -15px;
+            /* 设置滚动条轨道的样式 */
+            &::-webkit-scrollbar-track {
+                box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+                border-radius: 6px;
+                background: #ededed;
             }
+
         }
 
-        .item {
-            text-align: center;
-            padding: 10px;
-            position: relative;
-            cursor: pointer;
-
-            div:nth-child(1) {
-                font-size: 20px;
-                font-weight: 600;
-                margin-bottom: 10px;
-                color: #409EFF;
+
+        .user-list {
+            border-left: 1px solid #f1f1f1;
+
+            .user-item {
+                width: 100%;
+                display: flex;
+                align-items: center;
+                padding: 0 10px;
+                box-sizing: border-box;
+                height: 40px;
+
+                .radio {
+                    border: 1px solid #DCDFE6;
+                    border-radius: 100%;
+                    width: 14px;
+                    height: 14px;
+                    background-color: #FFF;
+                    cursor: pointer;
+                    margin-right: 6px !important;
+                    position: relative;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+
+                    .white-dot {
+                        display: block;
+                        width: 6px;
+                        height: 6px;
+                        background-color: #fff;
+                        border-radius: 50%;
+                    }
+                }
+
+                .isCheck {
+                    border-color: #409EFF;
+                    background: #409EFF;
+                }
+
+                .username {
+                    margin-left: 10px;
+                }
+
+                &:hover {
+                    cursor: pointer;
+                    background: #F5F7FA;
+                }
             }
         }
     }
+
+    .right-container {
+        width: 100%;
+        height: 100%;
+        background-color: #fff;
+        border-radius: 5px;
+    }
+
+
 }
 </style>

+ 126 - 95
src/newPerformance/components/PersonalExamine/ExamineDetails copy.vue

@@ -1,12 +1,40 @@
 <template>
     <el-dialog :visible.sync="dialogVisible" width="1000px" :before-close="dialogBeforeClose">
         <div>
+            <div class="search-box">
+                <div class="flex-box">
+                    <el-select v-model="filters.cycleType" placeholder="请选择周期" size="small">
+                        <el-option v-for="item in cycleList" multiple :key="item.value" :label="item.label"
+                            :value="item.value">
+                        </el-option>
+                    </el-select>
+
+                    <el-select v-model="filters.cateType" placeholder="请选择分类" size="small" style="margin: 0 10px;">
+                        <el-option v-for="item in cateList" multiple :key="item.cateId" :label="item.name"
+                            :value="item.cateId">
+                        </el-option>
+                    </el-select>
+
+                    <el-select v-model="filters.gradeLevels" placeholder="请选择等级" size="small">
+                        <el-option v-for="item in gradeLevels" :key="item.label" :label="item.label"
+                            :value="item.value">
+                        </el-option>
+                    </el-select>
+                </div>
+
+                <div class="flex-box">
+                    <el-button type="primary" @click="applyFilters" size="small">筛选</el-button>
+                    <el-button @click="clearFilters" size="small">清空</el-button>
+                </div>
+
+            </div>
+
             <!-- 考核模板列表 -->
-            <div class="perform-list scroll-bar" style="margin-top: 10px; height: 450px; overflow-y: auto;">
+            <div class="perform-list scroll-bar" style="margin: 10px 0; height: 450px; overflow-y: auto;">
                 <el-table ref="multipleTable" v-loading="loading"
-                    :data="tableData.slice(params.page - 1, params.page * params.pageSize)"
+                    :data="filteredData.slice(params.page - 1, params.page * params.pageSize)"
                     style="width: 100%; margin-top: 10px;" :height="450" :header-cell-style="{ background: '#f5f7fa' }"
-                    border stripe @filter-change="handleFilterChange">
+                    border stripe size="small">
                     <el-table-column prop="title" label="考核名称" min-width="160" align="center">
                     </el-table-column>
                     <el-table-column prop="startTime" label="考核时间" min-width="160" align="center">
@@ -17,16 +45,14 @@
                         </template>
                     </el-table-column>
 
-                    <el-table-column prop="cycleType" label="周期类型" min-width="80" align="center"
-                        :filters="cycle_type_list" :filter-method="filterMethods">
+                    <el-table-column prop="cycleType" label="周期类型" min-width="80" align="center">
                         <template slot-scope="scope">
                             <el-tag type="primary">{{ scope.row.cycleType | formatCycleType }}</el-tag>
                         </template>
                     </el-table-column>
 
 
-                    <el-table-column prop="cateId" label="考核分类" min-width="100" align="center" :filters="cate_type_list"
-                        :filter-method="filterByCateType">
+                    <el-table-column prop="cateId" label="考核分类" min-width="100" align="center">
                         <template slot-scope="scope">
                             <div v-if="scope.row.cateIds && scope.row.cateIds">
                                 <template v-for="item in cateList">
@@ -36,11 +62,10 @@
                                     </el-tag>
                                 </template>
                             </div>
-
                         </template>
                     </el-table-column>
-                    <el-table-column prop="levelName" label="得分" min-width="100" align="center" :filters="gradeLevels"
-                        :filter-method="filterMethods">
+
+                    <el-table-column prop="levelName" label="得分" min-width="100" align="center">
                         <template slot-scope="scope">
                             <div>
                                 {{ scope.row.score || '--' }}
@@ -48,8 +73,18 @@
                             </div>
                         </template>
                     </el-table-column>
-                    <el-table-column prop="status" label="考核状态" min-width="100" align="center"
-                        :filters="examine_status_list" :filter-method="filterMethods">
+
+                    <el-table-column prop="packageOkrs" label="关联OKR" min-width="100" align="center">
+                        <template slot-scope="scope">
+                            <el-link v-if="scope.row.okrs && scope.row.okrs.length > 0" type="primary"
+                                @click="openTargetList(scope.row.okrs)">
+                                考核表OKR
+                            </el-link>
+                            <span v-else class="fontColorC">暂无关联</span>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column prop="status" label="考核状态" min-width="100" align="center">
                         <template slot-scope="scope">
                             <div v-if="scope.row.status == 0" class="orange-color">
                                 {{ scope.row.status | formatStatus }}
@@ -71,14 +106,22 @@
                 </el-pagination>
             </div>
         </div>
+
+        <!-- 关联okr -->
+        <TargetListComp v-if="targetDialogVisible" :dialogVisible="targetDialogVisible" :ids="okrs" @close="closeTargetList">
+        </TargetListComp>
     </el-dialog>
 
 </template>
 
 <script>
 import moment from 'moment';
-import TableHeaderRender from "./TableHeaderRender.vue"
+import TargetListComp from "@/performance/views/assessManagement/TargetListComp.vue"; // 关联OKR弹框
+
 export default {
+    components: {
+        TargetListComp
+    },
     model: {
         prop: 'dialogVisible',
         event: 'close-dialog'
@@ -110,10 +153,24 @@ export default {
     data() {
         return {
             loading: false,
+            visible: false,
+            value: '',
+            iconColor: false,
             params: {
                 page: 1,
                 pageSize: 15
-            }
+            },
+            gradeLevels: [],
+            // 筛选条件
+            filters: {
+                cycleType: [],
+                cateType: [],
+                gradeLevels: [],
+            },
+            // 筛选后的数据
+            filteredData: [],
+            okrs: [],
+            targetDialogVisible: false
         }
     },
     filters: {
@@ -139,74 +196,20 @@ export default {
     computed: {
         total() {
             return this.tableData && this.tableData.length
-        },
-        // 考核周期过滤数据
-        cycle_type_list() {
-            let cycle_type_list = []
-            if (this.cycleList && this.cycleList.length > 0) {
-                cycle_type_list = this.cycleList.filter(item => item.label !== '全部')
-
-                cycle_type_list = cycle_type_list.map(item => ({
-                    text: item.label,
-                    value: item.value,
-                }))
-                // 数组去重
-                cycle_type_list = Array.from(new Set(cycle_type_list.map(JSON.stringify))).map(JSON.parse);
-            }
-            return cycle_type_list
-        },
-
-        // 考核分类过滤数据
-        cate_type_list() {
-            let cate_type_list = []
-            if (this.cateList && this.cateList.length > 0) {
-                cate_type_list = this.cateList.map(item => ({
-                    text: item.name,
-                    value: item.cateId,
-                }))
-                // 数组去重
-                cate_type_list = Array.from(new Set(cate_type_list.map(JSON.stringify))).map(JSON.parse);
-            }
-            return cate_type_list
-        },
-
-        // 考核等级过滤数据
-        gradeLevels() {
-            let gradeLevels = []
-            if (this.tableData && this.tableData.length > 0) {
-
-                gradeLevels = this.tableData.map(item => {
-                    return {
-                        text: item.levelName || '--',
-                        value: item.levelName || '--',
-                    }
-                    
-                })
-                // 数组去重
-                gradeLevels = Array.from(new Set(gradeLevels.map(JSON.stringify))).map(JSON.parse);
-            }
-            return gradeLevels
-        },
-
-        examine_status_list() {
-            let examine_status_list = []
-            if (this.examineStatus && this.examineStatus.length > 0) {
-
-                examine_status_list = this.examineStatus.map(item => {
-                    return {
-                        text: item.label,
-                        value: item.value,
-                    }
-
-                })
-                // 数组去重
-                examine_status_list = Array.from(new Set(examine_status_list.map(JSON.stringify))).map(JSON.parse);
-            }
-            return examine_status_list
         }
     },
 
     mounted() {
+        this.filteredData = this.tableData
+        this.gradeLevels = this.tableData.map(item => {
+            return {
+                label: item.levelName || '--',
+                value: item.levelName || '--',
+            }
+
+        })
+        // 数组去重
+        this.gradeLevels = Array.from(new Set(this.gradeLevels.map(JSON.stringify))).map(JSON.parse);
     },
 
     methods: {
@@ -217,30 +220,53 @@ export default {
         
         handleSizeChange(v) {
             this.params.pageSize = v
-            // this.getList();
         },
         handleCurrentChange(v) {
             this.params.page = v
-            // this.getList();
         },
 
-        filterMethods(value, row, column) {
-            console.log(column)
-            const property = column["property"];
-            return row[property] == value
-        },
 
-        filterByCateType(value, row, column) {
-            return row['cateIds'].includes(value)
-        },
+        // 应用筛选条件
+        applyFilters() {
+            this.filteredData = this.tableData.filter((item) => {
+                // 周期筛选
+                if (this.filters.cycleType && this.filters.cycleType !== '-1' && !this.filters.cycleType.includes(item.cycleType)) {
+                    return false;
+                }
 
-        handleFilterChange(filters) {
-            console.log(filters)
-            // 保存筛选条件
-            // for (const key in filters) {
-            //     this.filters[key] = filters[key][0]; // 只取第一个筛选值
-            // }
+                // 分类筛选
+                if (this.filters.cateType && !item.cateIds.includes(this.filters.cateType)) {
+                    return false;
+                }
+                // 周期筛选
+                if (
+                    this.filters.gradeLevels && this.filters.gradeLevels.length > 0 &&
+                    !this.filters.gradeLevels.includes(item.levelName)
+                ) {
+                    return false;
+                }
+                
+                return true;
+            });
+        },
+        // 清空筛选条件
+        clearFilters() {
+            this.filters.cycleType = [];
+            this.filters.cateType = [];
+            this.filters.gradeLevels = [];
+            this.applyFilters(); // 重新应用筛选条件
         },
+        
+        closeTargetList() {
+            this.targetDialogVisible = false
+        },
+        
+        openTargetList(okrs) {
+            if (okrs && okrs.length > 0) {
+                this.okrs = okrs
+                this.targetDialogVisible = true
+            } else return this.$message.error("暂无关联okr")
+        }
     }
 }
 </script>
@@ -248,7 +274,12 @@ export default {
 
 <style scoped lang="scss">
 
-
+        
+.search-box {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+}
 .perform-list {
     width: 100%;
     padding: 0 20px;

+ 267 - 162
src/newPerformance/components/PersonalExamine/ExamineDetails.vue

@@ -1,15 +1,67 @@
 <template>
-    <el-dialog :visible.sync="dialogVisible" width="1000px" :before-close="dialogBeforeClose">
-        <div>
-            <!-- 考核模板列表 -->
-            <div class="perform-list scroll-bar" style="margin-top: 10px; height: 450px; overflow-y: auto;">
-                <el-table ref="multipleTable" v-loading="loading"
-                    :data="tableData.slice(params.page - 1, params.page * params.pageSize)"
-                    style="width: 100%; margin-top: 10px;" :height="450" :header-cell-style="{ background: '#f5f7fa' }"
-                    border stripe>
+    <div class="examine-details" v-loading="loading">
+        <div class="info-title flex-box-ce" style="margin-top: 10px;">
+            考核人员
+            <div class="flex-box-ce fontColorC">
+                <userImage :id="userInfo.id" :img_url="userInfo.img_url" :user_name="userInfo.name" width="36px"
+                    height="36px" style="margin:0 10px;"></userImage>
+                {{ userInfo.name }}
+            </div>
+        </div>
+        <div style="border: 1px solid #eee; border-radius: 5px; padding: 10px; box-sizing: border-box; ">
+            <div class="info-title">
+                考核统计
+            </div>
+            <div class="flex-box-ce flex-d-wrap" style="justify-content: center;">
+                <div v-for="(item, index) in generalizeList" :key="index" class="generalize-item cursor"
+                    @click="composite_state = item.id">
+                    <div class="fontColorC">{{ item.name }}</div>
+                    <div style="font-size: 36px; font-weight: 600;margin: 5px 0;">{{ item.val }}</div>
+                    <div style="width: 60px;height: 2px;" :style="{ backgroundColor: item.color }"></div>
+                </div>
+            </div>
+        </div>
+
+
+        <div class="flex-1" style="display: flex; flex-direction: column;">
+            <div class="info-title">
+                考核列表
+            </div>
+            <div class="search-box">
+                <div class="flex-box">
+                    <el-select v-model="filters.cycleType" placeholder="请选择周期" size="small">
+                        <el-option v-for="item in cycleList" multiple :key="item.value" :label="item.label"
+                            :value="item.value">
+                        </el-option>
+                    </el-select>
+
+                    <el-select v-model="filters.cateType" placeholder="请选择分类" size="small" style="margin: 0 10px;">
+                        <el-option v-for="item in cateList" multiple :key="item.cateId" :label="item.name"
+                            :value="item.cateId">
+                        </el-option>
+                    </el-select>
+
+                    <el-select v-model="filters.gradeLevels" placeholder="请选择等级" size="small">
+                        <el-option v-for="item in gradeLevels" :key="item.label" :label="item.label"
+                            :value="item.value">
+                        </el-option>
+                    </el-select>
+                </div>
+
+                <div class="flex-box">
+                    <el-button type="primary" @click="applyFilters" size="small">筛选</el-button>
+                    <el-button @click="clearFilters" size="small">清空</el-button>
+                </div>
+
+            </div>
+
+            <div class="flex-1">
+                <el-table v-loading="loading" :data="filteredData.slice(params.page - 1, params.page * params.pageSize)"
+                    style="width: 100%; margin: 10px 0;" :header-cell-style="{ background: '#f5f7fa' }" border stripe
+                    size="small" height="490">
                     <el-table-column prop="title" label="考核名称" min-width="160" align="center">
                     </el-table-column>
-                    <el-table-column prop="startTime" label="考核时间" min-width="160" align="center">
+                    <el-table-column prop="startTime" label="考核时间" min-width="150" align="center">
                         <template slot-scope="scope">
                             <div>
                                 {{ scope.row.startTime | formatDate }} 至 {{ scope.row.endTime | formatDate }}
@@ -17,44 +69,27 @@
                         </template>
                     </el-table-column>
 
-                    <el-table-column prop="cycleType" label="周期类型" min-width="80" align="center"
-                        :filters="cycle_type_list" :filter-method="filterMethods">
-                        <template #header>
-                            <el-popover placement="bottom" width="200" trigger="manual" v-model="visible"
-                                @show="showPopover" popper-class="table-header-popover">
-                                <div class="table-header-popover-template">
-                                    <el-input placeholder="请输入内容" v-model="value" size="small" clearable
-                                        @keyup.enter.native="confirm" ref="sInput"></el-input>
-                                </div>
-                                <span slot="reference" style="margin-left: 5px" @click.stop="popClick"
-                                    v-click-outside="closeOver">
-                                    <i class="filter-icon el-icon-search"
-                                        :style="{ color: iconColor ? '#9a4b9b' : '#909399' }"></i>
-                                </span>
-                            </el-popover>
-                        </template>
-                        <!-- <template #default={ scope }>
+                    <el-table-column prop="cycleType" label="周期类型" min-width="80" align="center">
+                        <template slot-scope="scope">
                             <el-tag type="primary">{{ scope.row.cycleType | formatCycleType }}</el-tag>
-                        </template> -->
+                        </template>
                     </el-table-column>
 
 
-                    <el-table-column prop="cateId" label="考核分类" min-width="100" align="center" :filters="cate_type_list"
-                        :filter-method="filterByCateType">
+                    <el-table-column prop="cateId" label="考核分类" min-width="120" align="center">
                         <template slot-scope="scope">
                             <div v-if="scope.row.cateIds && scope.row.cateIds">
                                 <template v-for="item in cateList">
                                     <el-tag :key="item.cateId" v-if="scope.row.cateIds.includes(item.cateId)"
                                         style="margin-bottom: 5px;">
-                                        {{ item.name}}
+                                        {{ item.name }}
                                     </el-tag>
                                 </template>
                             </div>
-
                         </template>
                     </el-table-column>
-                    <el-table-column prop="levelName" label="得分" min-width="100" align="center" :filters="gradeLevels"
-                        :filter-method="filterMethods">
+
+                    <el-table-column prop="levelName" label="得分" min-width="100" align="center">
                         <template slot-scope="scope">
                             <div>
                                 {{ scope.row.score || '--' }}
@@ -62,8 +97,18 @@
                             </div>
                         </template>
                     </el-table-column>
-                    <el-table-column prop="status" label="考核状态" min-width="100" align="center"
-                        :filters="examine_status_list" :filter-method="filterMethods">
+
+                    <el-table-column prop="packageOkrs" label="关联OKR" min-width="100" align="center">
+                        <template slot-scope="scope">
+                            <el-link v-if="scope.row.okrs && scope.row.okrs.length > 0" type="primary"
+                                @click="openTargetList(scope.row.okrs)">
+                                考核表OKR
+                            </el-link>
+                            <span v-else class="fontColorC">暂无关联</span>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column prop="status" label="考核状态" min-width="100" align="center">
                         <template slot-scope="scope">
                             <div v-if="scope.row.status == 0" class="orange-color">
                                 {{ scope.row.status | formatStatus }}
@@ -78,21 +123,42 @@
                     </el-table-column>
                 </el-table>
             </div>
-            <div class="flex-box-ce" style="justify-content: center; margin-top: 10px;">
+
+
+
+            <div class="flex-box-ce" style="justify-content: center;">
                 <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
                     :current-page="params.page" :page-sizes="[15, 30, 45, 60]" :page-size="params.pageSize"
                     layout="total, sizes, prev, pager, next" :total="total">
                 </el-pagination>
             </div>
+
+            <!-- 考核模板列表 -->
+            <!-- <div class="perform-list scroll-bar" style="margin: 10px 0;  overflow-y: auto;">
+                
+            </div>
+
+             -->
         </div>
-    </el-dialog>
+
+        <div style="height: 20px;"></div>
+        <!-- 关联okr -->
+        <TargetListComp v-if="targetDialogVisible" :dialogVisible="targetDialogVisible" :ids="okrs"
+            @close="closeTargetList">
+        </TargetListComp>
+    </div>
 
 </template>
 
 <script>
+import { mapGetters } from 'vuex';
 import moment from 'moment';
-import TableHeaderRender from "./TableHeaderRender.vue"
+import TargetListComp from "@/performance/views/assessManagement/TargetListComp.vue"; // 关联OKR弹框
+
 export default {
+    components: {
+        TargetListComp
+    },
     model: {
         prop: 'dialogVisible',
         event: 'close-dialog'
@@ -118,6 +184,10 @@ export default {
             type: Array,
             default: () => []
         },
+        userInfo: {
+            type: Object,
+            default: () => {}
+        },
     },
 
 
@@ -130,7 +200,36 @@ export default {
             params: {
                 page: 1,
                 pageSize: 15
-            }
+            },
+            gradeLevels: [],
+            // 筛选条件
+            filters: {
+                cycleType: [],
+                cateType: [],
+                gradeLevels: [],
+            },
+            // 筛选后的数据
+            filteredData: [],
+            okrs: [],
+            targetDialogVisible: false,
+            generalizeList: [
+                { name: '考核总数', val: 10, color: '#FF9600' },
+                { name: '已结束', val: 10, color: '#409EFF' },
+                { name: '进行中', val: 10, color: '#67c23a' },
+                { name: '面谈中', val: 10, color: '#f56c6c' },
+                { name: '已取消', val: 10, color: '#5F7294' },
+                { name: '未开始', val: 10, color: '#5F7294' },
+                { name: '暂停中', val: 10, color: '#f56c6c' }
+            ],
+        }
+    },
+
+    watch: {
+        tableData(v) {
+            this.loading = true;
+            setTimeout(() => { 
+                this.loading = false;
+            },500)
         }
     },
     filters: {
@@ -157,132 +256,84 @@ export default {
         total() {
             return this.tableData && this.tableData.length
         },
-        // 考核周期过滤数据
-        cycle_type_list() {
-            let cycle_type_list = []
-            if (this.cycleList && this.cycleList.length > 0) {
-                cycle_type_list = this.cycleList.filter(item => item.label !== '全部')
-
-                cycle_type_list = cycle_type_list.map(item => ({
-                    text: item.label,
-                    value: item.value,
-                }))
-                // 数组去重
-                cycle_type_list = Array.from(new Set(cycle_type_list.map(JSON.stringify))).map(JSON.parse);
-            }
-            return cycle_type_list
-        },
+        ...mapGetters(['user_info']),
+    },
 
-        // 考核分类过滤数据
-        cate_type_list() {
-            let cate_type_list = []
-            if (this.cateList && this.cateList.length > 0) {
-                cate_type_list = this.cateList.map(item => ({
-                    text: item.name,
-                    value: item.cateId,
-                }))
-                // 数组去重
-                cate_type_list = Array.from(new Set(cate_type_list.map(JSON.stringify))).map(JSON.parse);
+    mounted() {
+        this.filteredData = this.tableData;
+        this.gradeLevels = this.tableData.map(item => {
+            return {
+                label: item.levelName || '--',
+                value: item.levelName || '--',
             }
-            return cate_type_list
-        },
-
-        // 考核等级过滤数据
-        gradeLevels() {
-            let gradeLevels = []
-            if (this.tableData && this.tableData.length > 0) {
+        })
 
-                gradeLevels = this.tableData.map(item => {
-                    return {
-                        text: item.levelName || '--',
-                        value: item.levelName || '--',
-                    }
-                    
-                })
-                // 数组去重
-                gradeLevels = Array.from(new Set(gradeLevels.map(JSON.stringify))).map(JSON.parse);
-            }
-            return gradeLevels
-        },
 
-        examine_status_list() {
-            let examine_status_list = []
-            if (this.examineStatus && this.examineStatus.length > 0) {
+        // 数组去重
+        this.gradeLevels = Array.from(new Set(this.gradeLevels.map(JSON.stringify))).map(JSON.parse);
+        console.log(this.gradeLevels);
+        this.initData();
 
-                examine_status_list = this.examineStatus.map(item => {
-                    return {
-                        text: item.label,
-                        value: item.value,
-                    }
-
-                })
-                // 数组去重
-                examine_status_list = Array.from(new Set(examine_status_list.map(JSON.stringify))).map(JSON.parse);
-            }
-            return examine_status_list
-        }
-    },
-
-    mounted() {
     },
 
     methods: {
-
-        dialogBeforeClose() {
-            this.$emit('close-dialog', false)
-        },
-        
+        initData() {
+            if (this.tableData && this.tableData.length > 0) {
+                this.generalizeList[0].val = this.tableData.length;
+                this.generalizeList[2].val = this.tableData.filter(item => item.status === 0).length;
+                this.generalizeList[1].val = this.tableData.filter(item => item.status === 1).length;
+                this.generalizeList[3].val = this.tableData.filter(item => item.status === 2).length;
+            }
+        },        
         handleSizeChange(v) {
             this.params.pageSize = v
-            // this.getList();
         },
         handleCurrentChange(v) {
             this.params.page = v
-            // this.getList();
-        },
-
-        filterMethods(value, row, column) {
-            console.log(column)
-            const property = column["property"];
-            return row[property] == value
         },
 
-        filterByCateType(value, row, column) {
-            return row['cateIds'].includes(value)
-        },
 
-        handleFilterChange(filters) {
-            console.log(filters)
-            // 保存筛选条件
-            // for (const key in filters) {
-            //     this.filters[key] = filters[key][0]; // 只取第一个筛选值
-            // }
-        },
+        // 应用筛选条件
+        applyFilters() {
+            this.filteredData = this.tableData.filter((item) => {
+                // 周期筛选
+                if (this.filters.cycleType && this.filters.cycleType !== '-1' && !this.filters.cycleType.includes(item.cycleType)) {
+                    return false;
+                }
 
-        showPopover() {
-            this.$nextTick(() => {
-                this.$refs.sInput.focus();
+                // 分类筛选
+                if (this.filters.cateType && !item.cateIds.includes(this.filters.cateType)) {
+                    return false;
+                }
+                // 周期筛选
+                if (
+                    this.filters.gradeLevels && this.filters.gradeLevels.length > 0 &&
+                    !this.filters.gradeLevels.includes(item.levelName)
+                ) {
+                    return false;
+                }
+                
+                return true;
             });
         },
-        closeOver() {
-            this.visible = false;
+        
+        // 清空筛选条件
+        clearFilters() {
+            this.filters.cycleType = [];
+            this.filters.cateType = [];
+            this.filters.gradeLevels = [];
+            this.applyFilters(); // 重新应用筛选条件
         },
-        popClick(e) {
-            this.visible = !this.visible;
+        
+        closeTargetList() {
+            this.targetDialogVisible = false
         },
-        confirm() {
-            this.visible = false;
-            if (this.value.trim()) {
-                this.iconColor = true;
-                this.filters = [{ text: this.value, value: this.value }];
-            } else {
-                this.filters = [
-                    { text: '张三', value: '张三' },
-                    { text: '李四', value: '李四' },
-                    { text: '王五', value: '王五' },
-                ];
-                this.iconColor = false;
-            }
+        
+        openTargetList(okrs) {
+            if (okrs && okrs.length > 0) {
+                this.okrs = okrs
+                this.targetDialogVisible = true
+            } else return this.$message.error("暂无关联okr")
         },
         
     }
@@ -292,31 +343,20 @@ export default {
 
 <style scoped lang="scss">
 
-
-.perform-list {
+.examine-details {
     width: 100%;
-    padding: 0 20px;
+    height: 100%;
+    border-radius: 6px;
+    background: #fff;
+    padding: 0 10px;
     box-sizing: border-box;
     display: flex;
     flex-direction: column;
-    position: relative;
-    background-color: #fff;
-    .green-color {
-        color: #67c23a;
-    }
 
-    .orange-color {
-        color: #e6a23c;
-    }
-
-    .blue-color {
-        color: #409eff;
-    }
-    
     /* 设置滚动条的宽度和背景色 */
     ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
-        width: 10px;
-        height: 10px;
+        width: 6px;
+        height: 6px;
         background-color: #f9f9f9;
     }
 
@@ -337,5 +377,70 @@ export default {
         border-radius: 6px;
         background: #ededed;
     }
+    .info-title {
+        font-size: 16px;
+        font-weight: 600;
+        line-height: 40px;
+    }
+    .generalize-item {
+        width: 140px;
+        margin-bottom: 20px;
+    }
+
+    .search-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+    }
+
+    .perform-list {
+        width: 100%;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        position: relative;
+        background-color: #fff;
+
+        .green-color {
+            color: #67c23a;
+        }
+
+        .orange-color {
+            color: #e6a23c;
+        }
+
+        .blue-color {
+            color: #409eff;
+        }
+
+        /* 设置滚动条的宽度和背景色 */
+        ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
+            width: 10px;
+            height: 10px;
+            background-color: #f9f9f9;
+        }
+
+        /* 设置滚动条滑块的样式 */
+        ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
+
+        /* 设置滚动条滑块hover样式 */
+        ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
+            background-color: #a8a8a8;
+        }
+
+        /* 设置滚动条轨道的样式 */
+        ::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
+            box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+            border-radius: 6px;
+            background: #ededed;
+        }
+    }
 }
+
+
+        
+
 </style>

+ 855 - 0
src/newPerformance/components/ProcessTracking.vue

@@ -0,0 +1,855 @@
+<template>
+    <div class="record-container">
+        <div class="main-content">
+            <div class="search-box">
+                <div class="flex-box-ce">
+
+                    <el-select style="width: 120px;" v-model="params.cycleType" placeholder="请选择周期种类"
+                        @change="changeCycleType">
+                        <el-option v-for="item in cycleOptions" :key="item.id" :label="item.name" :value="item.id">
+                        </el-option>
+                    </el-select>
+
+                    <el-date-picker class="cursor" style="width: 150px; margin-left: 10px;" v-model="params.year"
+                        type="year" placeholder="选择年" value-format="yyyy" @change="changeYear">
+                    </el-date-picker>
+
+                    <el-date-picker class="cursor" v-if="params.cycleType == '0'"
+                        style="width: 300px; margin-left: 10px;" v-model="dateRange" type="daterange"
+                        range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" format="yyyy-MM-dd"
+                        value-format="yyyy-MM-dd"></el-date-picker>
+
+                    <el-select class="cursor" v-if="params.cycleType == '2'" style="width: 150px; margin-left: 10px;"
+                        v-model="cycleValue" placeholder="请选择半年度" @change="changeCycleValue">
+                        <el-option label="上半年" value="1">
+                        </el-option>
+                        <el-option label="下半年" value="2">
+                        </el-option>
+                    </el-select>
+
+                    <el-select class="cursor" v-if="params.cycleType == '3'" style="width: 150px; margin-left: 10px;"
+                        v-model="cycleValue" placeholder="请选择季度" @change="changeCycleValue">
+                        <el-option label="第一季度" value="1">
+                        </el-option>
+                        <el-option label="第二季度" value="2">
+                        </el-option>
+                        <el-option label="第三季度" value="3">
+                        </el-option>
+                        <el-option label="第四季度" value="4">
+                        </el-option>
+                    </el-select>
+
+                    <el-select class="cursor" v-if="params.cycleType == '4'" style="width: 150px; margin-left: 10px;"
+                        v-model="cycleValue" placeholder="请选择月度" @change="changeCycleValue">
+                        <el-option label="一月" value="1">
+                        </el-option>
+                        <el-option label="二月" value="2">
+                        </el-option>
+                        <el-option label="三月" value="3">
+                        </el-option>
+                        <el-option label="四月" value="4">
+                        </el-option>
+                        <el-option label="五月" value="5">
+                        </el-option>
+                        <el-option label="六月" value="6">
+                        </el-option>
+                        <el-option label="七月" value="7">
+                        </el-option>
+                        <el-option label="八月" value="8">
+                        </el-option>
+                        <el-option label="九月" value="9">
+                        </el-option>
+                        <el-option label="十月" value="10">
+                        </el-option>
+                        <el-option label="十一月" value="11">
+                        </el-option>
+                        <el-option label="十二月" value="12">
+                        </el-option>
+                    </el-select>
+
+                    <div class="dept_wdiv flex-box-ce" style="margin: 0 0 0 10px;">
+                        <div class="dept_inp" @click="show_dept_selector = true">
+                            <span v-if="deptVisibleName != ''">{{ deptVisibleName }}</span>
+                            <span v-else style="color: #b9b9b9;">选择部门</span>
+                        </div>
+                        <i class="el-icon-arrow-down"></i>
+                    </div>
+
+
+                    <el-select class="cursor" style="width: 260px; margin-left: 10px;" v-model="selectEmployeeId"
+                        filterable clearable multiple placeholder="员工姓名搜索" @change="changeEmployeeIds">
+                        <el-option v-for="item in employees" :key="item.id" :label="item.name"
+                            :value="item.id"></el-option>
+                    </el-select>
+
+                </div>
+
+                <el-button type="primary" @click="exportToExcel('过程分析', '#myTable')" size="small"
+                    :loading="downloadLoading">导出明细</el-button>
+            </div>
+            <!-- 水平线 -->
+            <div class="line"></div>
+
+            <div class="flex-box-ce flex-d-wrap"
+                style="justify-content: center; padding: 20px 0; box-sizing: border-box;">
+                <div v-for="(item, index) in generalizeList" :key="index" @click="chooseExamineStatus(item.name)"
+                    class="generalize-item">
+                    <div class=" fontColorC">{{ item.name }}</div>
+                    <div style="font-size: 36px; font-weight: 600;margin: 5px 0;">{{ item.val }}</div>
+                    <div style="width: 60px;height: 2px;" :style="{ backgroundColor: item.color }"></div>
+                </div>
+                <div v-for="(item, index) in gradeLevels" @click="chooseExamineLevel(item.name)" :key="item.name"
+                    class="generalize-item">
+                    <div class="fontColorC">{{ item.name }}</div>
+                    <div style="font-size: 36px; font-weight: 600;margin: 5px 0;">
+                        {{tableData.filter(user => user.levelName === item.name).length}}
+                    </div>
+                    <div style="width: 60px;height: 2px;" :style="{ backgroundColor: item.color }"></div>
+                </div>
+            </div>
+
+
+            <div class="table-box">
+                <el-table id="myTable" :data="filteredData" style="width: 100%; height: auto;"
+                    :header-cell-style="{ background: '#f5f7fa' }" border stripe>
+                    <el-table-column prop="date" label="考核时间" align="center"></el-table-column>
+                    <el-table-column prop="employeeName" label="员工姓名" align="center"></el-table-column>
+                    <el-table-column prop="department" label="部门" width="300" align="center">
+                        <template slot-scope="scope">
+                            {{ scope.row.department | formatDeptName }}
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="status" label="考核状态" align="center">
+                        <template slot-scope="scope">
+                            <el-tag v-if="scope.row.status" type="success">
+                                已完成
+                            </el-tag>
+                            <el-tag v-else="scope.row.status" type="warning">
+                                进行中
+                            </el-tag>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column prop="score" label="评分" align="center">
+                        <template slot-scope="scope">
+                            <el-tag v-if="!scope.row.score" type="info">
+                                未评分
+                            </el-tag>
+                            <el-tag v-else>
+                                {{ scope.row.score }}
+                            </el-tag>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column prop="levelName" label="等级" align="center">
+                        <template slot-scope="scope">
+                            <el-tag type="info">
+                                {{ scope.row.levelName || '--' }}
+                            </el-tag>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column label="操作" align="center">
+                        <template slot-scope="scope">
+                            <el-button type="text" @click="getTemplateDetails(scope.row)">查看明细</el-button>
+                        </template>
+                    </el-table-column>
+
+                </el-table>
+                <div style="height: 50px;"></div>
+            </div>
+
+        </div>
+        <!-- 关联okr -->
+        <TargetListComp v-if="targetDialogVisible" :dialogVisible="targetDialogVisible" :ids="okrs"
+            @close="closeTargetList">
+        </TargetListComp>
+
+        <!-- 部门选择 -->
+        <EmployeeSelector :title="'选择部门'" :isChecKedAll="false" :can_select_employee="false" :can_select_dept="true"
+            :dept_children="false" :selected="dept_selected" :visible.sync="show_dept_selector"
+            @confirm="dept_confirm" />
+
+        <el-dialog title="员工绩效详情" :visible.sync="detailDialogVisible" @close="handleClose" :close-on-click-modal="false"
+            :close-on-press-escape="true" center fullscreen :show-close="false">
+            <div style=" width: 100%; height: 100%; position: relative;">
+            <el-button round style="position: absolute; top: -65px; left: 0px; z-index: 99;"
+                @click="detailDialogVisible = false">返回</el-button>
+            <!-- 我的考核 -->
+            <MyPerformance v-if="detailDialogVisible" :reviewId="reviewId" :sendEmployeeId='sendEmployeeId' />
+    </div>
+    </el-dialog>
+    </div>
+
+</template>
+
+
+
+<script>
+let that;
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+import TargetListComp from "@/performance/views/assessManagement/TargetListComp.vue"; // 关联OKR弹框
+import EmployeeSelector from '@/components/EmployeeSelector'; // 部门选择
+import MyPerformance from './MyPerformance'; // 我的考核
+
+import XLSX from 'xlsx';
+import FileSaver from 'file-saver';
+
+export default {
+    components: {
+        TargetListComp,
+        EmployeeSelector,
+        MyPerformance
+    },
+    data() {
+        return {
+            employees: this.$getEmployeeMap(), // 员工列表
+            isLeftShow: true,
+            isRightShow: false,
+            employeeName: '',
+            targetDialogVisible: false,
+            total: 0,
+            loading: false,
+            checked: [],
+            status: -1,
+            cycleValue: '1',
+            downloadLoading: false,
+            show_dept_selector: false,
+            deptVisibleName: '',
+            dept_selected: { dept: [], employee: [] },
+            cycleOptions: [
+                { name: "月度", id: '4' },
+                { name: "季度", id: '3' },
+                { name: "半年度", id: '2' },
+                { name: "年度", id: '1' },
+                { name: "未定义", id: '0' },
+            ],
+            options: [
+                {
+                    value: -1,
+                    label: '全部'
+                }, {
+                    value: '0',
+                    label: '未完成'
+                }, {
+                    value: '1',
+                    label: '已完成'
+                }
+            ],
+            reviewId: "",
+            sendEmployeeId: "",
+            reviewPackageId: 0,
+            selectedReviewPackageId: '',
+            cateIds: [],
+            scoreList: [],
+            distributionId: '',
+            gradeLevels: [],
+            infos: [],
+            generalizeList: [
+                { name: '考核人数', val: 0, color: '#FF9600' },
+                { name: '已结束', val: 0, color: '#409EFF' },
+                { name: '进行中', val: 0, color: '#67c23a' },
+                { name: '面谈中', val: 0, color: '#f56c6c' },
+                // { name: '已取消', val: 0, color: '#5F7294' },
+                // { name: '未开始', val: 0, color: '#08EEA1' },
+                // { name: '暂停中', val: 0, color: '#f56c6c' }
+            ],
+            treeData: [],
+            singleSelection: true, // 是否单选
+            checkedKeys: [],
+            defaultProps: {
+                children: 'children',
+                label: 'label'
+            },
+            dept_select_id: '',
+            keyword: '',
+            deptList: [], // 部门列表 - 树形结构
+            dept_list: [], // 部门列表
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            loading: false,
+            total: 0,
+            tableData: [],
+            filteredData: [],
+            selectEmployeeId: [],
+            // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+            params: {
+                cycleType: "4",
+                startDate: '',
+                endDate: '',
+                deptIds: '',
+                year: ''
+            },
+            status: '',
+            level: '',
+            dialogVisible: false,
+            organSelectDialog: false,
+            gradeLevels: [],
+            examineStatus: [
+                { label: "考核中", value: "0" },
+                { label: "已结束", value: "1" },
+                { label: "面谈", value: "2" }
+            ],
+            userInfo: null,
+            isContentLeft: true,
+            isContentRight: true,
+            chooseUserIds: [],
+            employeeName: '', 
+            isFold: false,
+            dateRange: this.getMonthFirstAndLastDay(), // 设置默认值
+            detailDialogVisible: false,
+            pickerOptions: {
+                shortcuts: [{
+                    text: '最近一周',
+                    onClick(picker) {
+                        const end = new Date();
+                        const start = new Date();
+                        start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+                        picker.$emit('pick', [start, end]);
+                    }
+                }, {
+                    text: '最近一个月',
+                    onClick(picker) {
+                        const end = new Date();
+                        const start = new Date();
+                        start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+                        picker.$emit('pick', [start, end]);
+                    }
+                }, {
+                    text: '最近三个月',
+                    onClick(picker) {
+                        const end = new Date();
+                        const start = new Date();
+                        start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+                        picker.$emit('pick', [start, end]);
+                    }
+                }]
+            },
+        }
+    },
+
+    watch: {
+        dateRange(v) {
+            console.log(v);
+        }
+    },
+
+    async created() {
+        that = this;
+        await this.getAllSet();
+        this.getDefaultTime();
+    },
+
+    // 
+    computed: {
+        ...mapGetters(['user_info']),
+    },
+
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        },
+        filterProgress(list) {
+            if (list && list.length > 0) {
+                let sum = 0;
+                list.forEach(item => {
+                    if (item.status == 1) sum += 1 // 完成的个数
+                })
+                return Number(sum / list.length) * 100
+            }
+        },
+        formatDeptName(val) {
+            let str = '';
+            if (val && val.length > 0) {
+                val.forEach(dept => {
+                    str += dept.dept_name + ","
+                })
+                str = str.substr(0, str.length - 1)
+            } else {
+                str = "--"
+            }
+            return str
+        }
+    },
+
+    methods: {
+        getMonthFirstAndLastDay() {
+            const today = new Date();
+            const year = today.getFullYear();
+            const month = today.getMonth(); // 月份从 0 开始,需要加 1
+
+            // 获取当月第一天
+            const firstDay = new Date(year, month, 1);
+            const firstDayStr = this.formatDate(firstDay);
+
+            // 获取当月最后一天
+            const lastDay = new Date(year, month + 1, 0); // 下个月的第 0 天是当前月的最后一天
+            const lastDayStr = this.formatDate(lastDay);
+            // console.log(firstDayStr)
+            // console.log(lastDayStr)
+            
+            return [firstDayStr, lastDayStr];
+
+        },
+
+        formatDate(date) {
+            const year = date.getFullYear();
+            const month = String(date.getMonth() + 1).padStart(2, '0');
+            const day = String(date.getDate()).padStart(2, '0');
+            return `${year}-${month}-${day}`;
+        },
+
+        // 周期筛选
+        changeCycleType(v) {
+            if (v !== '0') { 
+                this.params.startDate = ''
+                this.params.endDate = ''
+            } else if (v == 1) {
+                // console.log("-----------", 'cycleValue' in this.params)
+                this.params.year = this.currentYear
+                this.cycleValue = ''
+                if ('cycleValue' in this.params) delete this.params.cycleValue
+            } else if (v == 2) {
+                this.params.year = this.currentYear
+                this.params = { ...this.params, cycleValue: this.cycleValue }
+            } else if (v == 3) {
+                this.params.year = this.currentYear
+                this.params = { ...this.params, cycleValue: this.cycleValue }
+            } else if (v == 4) {
+                this.params.year = this.currentYear
+                this.params = { ...this.params, cycleValue: this.cycleValue }
+            }
+            this.getRecords();
+
+        },
+
+        // 周期筛选
+        changeDate(v) {
+            if (v && v.length > 0) {
+                this.dateRange[0] = v[0]
+                this.dateRange[1] = v[1]
+                this.params.startDate = v[0]
+                this.params.endDate = v[1]
+                this.getRecords();
+            }
+        },
+
+        changeYear(v) {
+            this.params.year = v;
+            this.params.startDate = ''
+            this.params.endDate = ''
+            this.getRecords();
+        },
+
+        changeCycleValue(v) {
+            this.params = { ...this.params, cycleValue: this.cycleValue }
+            this.getRecords();
+
+        },
+
+        dept_confirm(data) {
+            //部门选择
+            this.dept_selected = { dept: [], employee: [] };
+            this.deptVisibleName = '';
+            let deptList_id = [];
+            if (data.dept !== null && data.dept.length != 0) {
+                this.dept_selected = data;
+                data.dept.forEach((element, index) => {
+                    deptList_id.push(element.dept_id);
+                    this.deptVisibleName += element.dept_name;
+                    if (data.dept.length - index > 1) {
+                        this.deptVisibleName += ',';
+                    }
+                });
+            }
+            this.params.deptIds = deptList_id.toString()
+            this.getRecords();
+        },
+
+        // 员工筛选
+        changeEmployeeIds(v) {
+            this.selectEmployeeId = v
+            this.applyFilters();
+        },
+
+        // 考核状态筛选
+        chooseExamineStatus(name) {
+            this.status = name
+            this.applyFilters();
+        },
+
+        // 考核等级筛选
+        chooseExamineLevel(name) {
+            this.level = name
+            this.applyFilters();
+        },
+
+        // 应用筛选条件
+        applyFilters() {
+            this.filteredData = this.tableData.filter((item) => {
+                const employeeMatch = this.selectEmployeeId.length === 0 || this.selectEmployeeId.includes(item.employeeId);
+                // let statusMatch = true;
+                // if (this.status == '考核人数') statusMatch = true
+                // if (this.status == '进行中') statusMatch = item.status == 0
+                // if (this.status == '已结束') statusMatch = item.status == 1
+                // if (this.status == '面谈中') statusMatch = item.status == 2
+                // let levelMatch = !this.level || this.level && item.levelName == this.level 
+                // console.log(levelMatch)
+                return employeeMatch 
+                // return employeeMatch && statusMatch && levelMatch;
+            });
+        },
+
+        
+        // 时间格式化
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        },
+
+        // 隐藏部门菜单
+        toggle() {
+            this.isFold = !this.isFold
+        },
+
+        // 还原部门菜单
+        back() {
+            this.isLeftShow = true;
+            this.isRightShow = false;
+        },
+
+
+
+        filterNode(value, data) {
+            if (!value) return true;
+            return data.label.indexOf(value) !== -1;
+        },
+        handleNodeClick(node) {
+            this.dept_select_id = node.id
+            
+        },
+        
+
+        getTemplateDetails(row) {
+            this.reviewId = row.reviewId
+            this.sendEmployeeId = row.employeeId
+            this.detailDialogVisible = true
+            // this.$emit('changeCurrentId', { currentId: '2', reviewId })
+        },
+
+
+        // 获取当前时间
+        getDefaultTime() {
+            const date = new Date();
+            this.currentYear = date.getFullYear()
+            this.currentMonth = date.getMonth() + 1; // 注意月份从0开始,所以需要加1
+            const now = new Date(); // 获取当前时间
+            let startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0); // 当前月的第一天
+            let endOfMonth = new Date(startOfMonth.getFullYear(), startOfMonth.getMonth() + 1, 0, 23, 59, 59); // 下个月的第一天的前一天,时间设置为 23:59:59
+            startOfMonth = this.$moment(startOfMonth).format('YYYY-MM-DD')
+            endOfMonth = this.$moment(endOfMonth).format('YYYY-MM-DD')
+            // console.log(startOfMonth); // 输出当前月的结束时间
+            // console.log(endOfMonth); // 输出当前月的结束时间
+            this.params.startDate = startOfMonth
+            this.params.endDate = endOfMonth
+            this.params.year = this.currentYear + ''
+            this.cycleValue = this.currentMonth + ''
+            // console.log("当前月" + this.cycleValue);
+            this.params = { ...this.params, cycleValue: this.cycleValue }
+            this.getRecords();
+        },
+
+        
+        
+        // 获取表格数据
+        getRecords() {
+            this.loading = true
+            this.filteredData = []
+            this.tableData = []
+            let url = `/performance/statistics/reviews/${this.user_info.site_id}`
+            this.$axiosUser("get", url, this.params).then(res => {
+                this.tableData = res.data.data.list;
+                this.filteredData = this.tableData
+                this.filteredData.forEach(item => {
+                    item.date = this.formatDate(item.startTime) + "至" + this.formatDate(item.endTime)
+                })
+
+                let deptMap = JSON.parse(localStorage.getItem("SET_EMPLOYEE_MAP"));
+                if (Object.keys(deptMap) && Object.keys(deptMap).length > 0) {
+                    this.filteredData.forEach(item => {
+                        const employeeDetails = deptMap[item.employeeId];
+                        if (employeeDetails) {
+                            item.department = employeeDetails.employee_detail.dept_list;
+                        }
+                    })
+                }
+                
+                this.generalizeList[0].val = this.tableData.length // 总人数
+                this.generalizeList[1].val = this.tableData.filter(item => item.status == 1).length // 已结束人数
+                this.generalizeList[2].val = this.tableData.filter(item => item.status == 0).length // 进行中人数
+                this.generalizeList[3].val = this.tableData.filter(item => item.status == 2).length // 面谈中人数
+                this.applyFilters();
+                this.loading = false;
+            })
+        },
+
+        
+        // 获取全局等级设置
+        async getAllSet() {
+            let res = await this.$axiosUser('get', 'api/pro/per/user/base_config')
+            let data = res.data.data;
+            let levels = data.level_scope.levels;
+            let gradeLevels = [];
+            let max = 0;//最大值
+            let colorList = ['#5F7294', '#08EEA1', '#f56c6c', '#0887EE', '#92EE08', '#f56c6c']
+            if (levels && levels.length > 0) {
+                levels.forEach((item, index) => {
+                    var obj;
+                    if (index == 0) {
+                        obj = { name: item.name, max: Number(item.value), min: 0 };
+                    } else {
+                        obj = { name: item.name, max: Number(item.value), min: max };//当不是第一个等级时,最小值为上一个的最大值
+                    }
+                    obj.color = colorList[index]
+                    max = item.value;
+                    
+                    gradeLevels.push(obj);
+                })
+                this.gradeLevels = gradeLevels
+            }
+        },
+
+        
+        // 查找分数对应的等级
+        findGrade(score, gradeLevels) {
+            for (let i = 0; i < gradeLevels.length; i++) {
+                if (score && score >= gradeLevels[i].min && score && score <= gradeLevels[i].max) {
+                    return gradeLevels[i].name; // 返回对应的等级描述
+                } else if (score && score <= gradeLevels[0].min) {
+                    return gradeLevels[0].name; // 返回对应的等级描述
+                } else if (score && score >= gradeLevels[gradeLevels.length - 1].max) {
+                    return gradeLevels[gradeLevels.length - 1].name; // 返回对应的等级描述
+                }
+            }
+            return "未评分"; // 如果分数不在任何范围内
+        },
+
+
+        assignLevels(scores, levelConfigs) {
+            // 降序排序并去重(假设分数不重复,可省略去重)
+            const sortedScores = [...scores].sort((a, b) => b - a);
+            const total = sortedScores.length;
+            if (total === 0) return [];
+
+            // 归一化处理比例
+            const totalRatio = levelConfigs.reduce((sum, cfg) => sum + cfg.ratio, 0);
+            const normalized = levelConfigs.map(cfg => cfg.ratio / totalRatio);
+
+            // 计算每个等级的初始人数
+            let counts = normalized.map(ratio => Math.floor(total * ratio));
+            let remainder = total - counts.reduce((sum, c) => sum + c, 0);
+
+            // 分配剩余人数,按优先级顺序
+            let idx = 0;
+            while (remainder > 0 && idx < counts.length) {
+                counts[idx]++;
+                remainder--;
+                idx++;
+            }
+
+            // 构建结果:按人数切割数组
+            let start = 0;
+            return counts.map((count, i) => {
+                const end = start + count;
+                const levelScores = sortedScores.slice(start, end);
+                start = end;
+                return {
+                    level: levelConfigs[i].level,
+                    scores: levelScores
+                };
+            });
+        },
+
+        // 导出表格
+        exportToExcel(tableName, elementName) {
+            if(!(this.filteredData && this.filteredData.length > 0)) return
+            this.downloadLoading = true;
+
+            // 如果未传入文件名,则使用当前时间戳
+            if (!tableName) {
+                tableName = new Date().getTime();
+            }
+
+            // 克隆表格 DOM,避免影响原表格
+            const tableDom = document.querySelector(elementName).cloneNode(true);
+            const tableHeader = tableDom.querySelector('.el-table__header-wrapper');
+            const tableBody = tableDom.querySelector('.el-table__body');
+            tableHeader.childNodes[0].append(tableBody.childNodes[1]);
+
+            // 获取表头 DOM
+            const headerDom = tableHeader.childNodes[0].querySelectorAll('th');
+
+            // 移除复选框列
+            if (headerDom[0].querySelector('.el-checkbox')) {
+                headerDom[0].remove();
+            }
+
+            // 移除操作列
+            for (let key in headerDom) {
+                if (headerDom[key].innerText === '操作') {
+                    headerDom[key].remove();
+                }
+            }
+
+            // 清理表格中的复选框和按钮
+            const tableList = tableHeader.childNodes[0].childNodes[2].querySelectorAll('td');
+            for (let key = 0; key < tableList.length; key++) {
+                if (tableList[key].querySelector('.el-checkbox') || tableList[key].querySelector('.el-button')) {
+                    tableList[key].remove();
+                }
+            }
+
+            // 使用 XLSX 将 DOM 转换为 Excel 文件
+            const webBook = XLSX.utils.table_to_book(tableHeader);
+            const webOut = XLSX.write(webBook, { bookType: 'xlsx', bookSST: true, type: 'array' });
+
+            try {
+                FileSaver.saveAs(new Blob([webOut], { type: 'application/octet-stream' }), `${tableName}.xlsx`);
+            } catch (e) {
+                console.error(e, webOut);
+            }
+
+            this.downloadLoading = false;
+        },
+
+        closeTargetList() {
+            this.targetDialogVisible = false
+        },
+
+        // 打开OKR弹框
+        openTargetList(okrs) {
+            if (okrs && okrs.length > 0) {
+                this.okrs = okrs
+                this.targetDialogVisible = true
+            } else {
+                return this.$message.error("暂无关联okr")
+            }
+        },
+
+        // 关闭绩效弹框回调事件
+        handleClose() {}
+    }
+};
+</script>
+
+
+<style scoped="scoped" lang="scss">
+.cursor {
+    cursor: hand;
+    &:hover {
+        cursor: hand; /* 鼠标悬停时改为手型指针 */
+    }
+}
+
+/* 默认鼠标样式为箭头指针 */
+.record-container {
+    width: 100%;
+    height: 100%;
+    border-radius: 5px;
+
+
+    .main-content {
+        width: 100%;
+        height: 100%;
+        display: flex;
+        flex-direction: column;
+        background: #fff;
+        padding: 0 10px;
+        box-sizing: border-box;
+        .search-box {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            width: 100%;
+            height: 50px;
+            box-sizing: border-box;
+            background: #fff;
+
+            .dept_wdiv {
+                width: 240px;
+                position: relative;
+        
+                .dept_inp {
+                    width: 240px;
+                    z-index: 9;
+                    height: 36px;
+                    line-height: 36px;
+                    border: 1px solid #e0e0e0;
+                    border-radius: 3px;
+                    font-size: 12px;
+                    padding: 0 10px;
+                    overflow: hidden;
+                    white-space: nowrap;
+                    text-overflow: ellipsis;
+                    cursor: pointer;
+                    color: #676767;
+                }
+            
+                i {
+                    position: absolute;
+                    top: 0;
+                    right: 0;
+                    top: 12px;
+                    right: 10px;
+                    font-size: 14px;
+                    color: #b5b5b5;
+                }
+            }
+        }
+        
+        .line {
+            width: 100%;
+            height: 1px;
+            background: #f1f1f1;
+        }
+        
+        .generalize-item {
+            width: 120px;
+            margin-bottom: 20px;
+        }
+        
+        .table-box {
+            flex: 1;
+            width: 100%;
+            overflow-y: auto;
+            /* 设置滚动条的宽度和背景色 */
+            &::-webkit-scrollbar {
+                width: 6px;
+                height: 6px;
+                background-color: #f9f9f9;
+            }
+        
+            /* 设置滚动条滑块的样式 */
+            &::-webkit-scrollbar-thumb {
+                border-radius: 6px;
+                background-color: #c1c1c1;
+            }
+        
+            /* 设置滚动条滑块hover样式 */
+            &::-webkit-scrollbar-thumb:hover {
+                background-color: #a8a8a8;
+            }
+        
+            /* 设置滚动条轨道的样式 */
+            &::-webkit-scrollbar-track {
+                box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+                border-radius: 6px;
+                background: #ededed;
+            }
+        }
+    }
+
+
+}
+</style>

+ 210 - 0
src/newPerformance/components/PublicComp/OrganizationSelect.vue

@@ -0,0 +1,210 @@
+<template>
+    <el-dialog title="组织筛选-请选择指定人员" :visible.sync="organSelectDialog" width="800px" :before-close="dialogBeforeClose">
+        <div>
+            <el-row :gutter="10">
+                <el-col :span="12">
+                    <el-input placeholder="输入关键字进行过滤" v-model="filterText" size="mini" style="margin-bottom: 10px;" />
+                    <div class="dept-list">
+
+                        <el-tree ref="tree" class="filter-tree" accordion :data="treeData" :props="defaultProps"
+                            :filter-node-method="filterNode" :default-checked-keys="checkedKeys"
+                            :check-strictly="singleSelection" :default-expanded-keys="checkedKeys"
+                            @node-click="handleNodeClick" node-key="id">
+                        </el-tree>
+                    </div>
+                </el-col>
+                <el-col :span="12">
+                    <el-input placeholder="输入关键字进行过滤" v-model="keyword" size="mini" style="margin-bottom: 10px;" />
+
+                    <div class="user-list">
+
+                        <div class="user-item flex-box-ce" v-for="user in userList" :key="user.id"
+                            @click="radioValue = user.id">
+                            <span class="radio" :class="radioValue == user.id ? 'isCheck' : ''">
+                                <span v-show="radioValue == user.id" class="white-dot"></span>
+                            </span>
+                            <userImage :id="user.id" :img_url="user.img_url" :user_name="user.name" width="24px"
+                                height="24px"></userImage>
+                            <span class="username">{{ user.name }}</span>
+                        </div>
+
+                    </div>
+                </el-col>
+            </el-row>
+        </div>
+
+        <div slot="footer">
+            <el-button @click="cancel()">取 消</el-button>
+            <el-button type="primary" @click="confirm()">确 定</el-button>
+        </div>
+    </el-dialog>
+
+</template>
+
+<script>
+
+export default {
+    model: {
+        prop: 'organSelectDialog',
+        event: 'close-dialog'
+    },
+    props: {
+        organSelectDialog: {
+            type: Boolean,
+            default: false
+        },
+    },
+
+    data() {
+        return {
+            filterText: '',
+            treeData: [],
+            singleSelection: true, // 是否单选
+            checkedKeys: [],
+            defaultProps: {
+                children: 'children',
+                label: 'label'
+            },
+            dept_select_id: '',
+            keyword: '',
+            userList: [],
+            radioValue: ''
+        }
+    },
+
+    watch: {
+        filterText(val) {
+            this.$refs.tree.filter(val);
+        },
+        keyword(val) {
+            if(val) this.userList = this.userList.filter(user => user.name.includes(val))
+        }
+    },
+
+    mounted() {
+        this.get_dept_list()
+        
+    },
+    methods: {
+        get_dept_list() {
+            this.$axiosUser('get', '/api/pro/department/tree', '', 'v2').then(res => {
+                console.log(res)
+                this.treeData = res.data.data.list
+            })
+        },
+
+        get_user_list() {
+            this.$axiosUser('get', '/api/pro/employee/index',
+                { dept_id: this.dept_select_id, keywords: this.keyword, page: 1, page_size: 2000, child: '1' }, 'v2')
+                .then(res => {
+                    this.userList = res.data.data.list;
+                    console.log(this.userList)
+                })
+        },
+
+        dialogBeforeClose() {
+            this.$emit('close-dialog', false)
+        },
+        filterNode(value, data) {
+            if (!value) return true;
+            return data.label.indexOf(value) !== -1;
+        },
+        handleNodeClick(node) {
+            console.log(node)
+            this.dept_select_id = node.id
+            this.get_user_list();
+        },
+
+        
+        cancel() {
+            this.filterText = '';
+            this.keyword = '';
+            this.radioValue = '';
+            this.dialogBeforeClose();
+        },
+        confirm() {
+            this.dialogBeforeClose();
+            this.$emit('confirm', this.radioValue)
+        }
+    }
+}
+
+</script>
+
+<style scoped lang="scss">
+    .dept-list, .user-list {
+        padding-bottom: 30px;
+        height: 450px;
+        box-sizing: border-box;
+        overflow-y: auto;
+        border-right: 1px solid #f1f1f1;
+        /* 设置滚动条的宽度和背景色 */
+        &::-webkit-scrollbar {
+            width: 6px;
+            height: 6px;
+            background-color: #f9f9f9;
+        }
+    
+        /* 设置滚动条滑块的样式 */
+        &::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
+    
+        /* 设置滚动条滑块hover样式 */
+        &::-webkit-scrollbar-thumb:hover {
+            background-color: #a8a8a8;
+        }
+    
+        /* 设置滚动条轨道的样式 */
+        &::-webkit-scrollbar-track {
+            box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+            border-radius: 6px;
+            background: #ededed;
+        }
+
+
+    }
+
+    .user-list {
+        .user-item {
+            width: 100%;
+            display: flex;
+            align-items: center;
+            padding: 0 10px;
+            box-sizing: border-box;
+            height: 40px;
+            .radio {
+                border: 1px solid #DCDFE6;
+                border-radius: 100%;
+                width: 14px;
+                height: 14px;
+                background-color: #FFF;
+                cursor: pointer;
+                margin-right: 6px !important;
+                position: relative;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                .white-dot {
+                    display: block;
+                    width: 6px;
+                    height: 6px;
+                    background-color: #fff;
+                    border-radius: 50%;
+                }
+            }
+            .isCheck {
+                border-color: #409EFF;
+                background: #409EFF;
+            }
+            .username {
+                margin-left: 10px;
+            }
+            &:hover {
+                cursor: pointer;
+                background: #F5F7FA;
+            }
+        }
+    }
+</style>

+ 124 - 54
src/newPerformance/components/PublicComp/ShowHanderDialog.vue

@@ -15,24 +15,38 @@
                                 <template v-if="item.tasks && item.tasks.length > 0">
                                     <div class="item-box" style="width: 100%;">
                                         <div class="value-box" v-for="task in item.tasks" :key="task.taskId">
-                                            <div v-if="['score'].includes(item.type)" style="width: 100%; margin-bottom: 5px;"
-                                                :class="task && task.state === 'completed' ? 'green-color' : 'orange-color'">
-                                                {{ task.assigneeName }}
-                                                评分: {{ task.score || '--' }}
-                                            </div>
-                                            <div v-else style="width: 100%; margin-bottom: 5px;"
-                                                :class="task && task.state === 'completed' ? 'green-color' : 'orange-color'">
-                                                {{ task.assigneeName }}
-                                            </div>
+                                            <div class="base-info">
+                                                <div v-if="['score'].includes(item.type)"
+                                                    style="width: 100%; margin-bottom: 5px;"
+                                                    :class="task && task.state === 'completed' ? 'green-color' : 'orange-color'">
+                                                    {{ task.assigneeName }}
+                                                    评分: {{ task.score || '--' }}
+                                                </div>
+                                                <div v-else style="width: 100%; margin-bottom: 5px;"
+                                                    :class="task && task.state === 'completed' ? 'green-color' : 'orange-color'">
+                                                    {{ task.assigneeName }}
+                                                </div>
 
-                                            <div v-if="task.comment" style="width: 140px; color: #999;">
-                                                <el-tooltip effect="dark" placement="right">
-                                                    <div v-html="task.comment" slot="content" style="max-width: 300px">
+                                                <div v-if="task.comment" style="width: 140px; color: #999;">
+                                                    <el-tooltip effect="dark" placement="right">
+                                                        <div v-html="task.comment" slot="content"
+                                                            style="max-width: 300px">
+                                                        </div>
+                                                        <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                    </el-tooltip>
+                                                </div>
+                                            </div>
+                                            <div class="files" v-if="task.files && task.files.length > 0">
+                                                <div class="title">附件列表</div>
+                                                <div class="file-list">
+                                                    <div class="file-item" v-for="file in task.files"
+                                                        @click="onFilePreView(file)">
+                                                        {{ file | filterFileName }}
                                                     </div>
-                                                    <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
-                                                </el-tooltip>
+                                                </div>
                                             </div>
                                         </div>
+
                                     </div>
 
                                 </template>
@@ -50,30 +64,42 @@
                                     <template v-if="showData.tasks && showData.tasks.length > 0">
                                         <div class="item-box" style="width: 100%;">
                                             <div class="value-box" v-for="task in showData.tasks" :key="task.taskId">
-                                                <div v-if="['scoreSelf', 'scoreEachOther'].includes(showData.type)"
-                                                    style="width: 100%; margin-bottom: 5px;"
-                                                    :class="task && task.state === 'completed' ? 'green-color' : 'orange-color'">
-                                                    {{ task.assigneeName }}
-                                                    评分: {{ task.score || '--' }}
-                                                </div>
-                                                <div v-else-if="['resultInput'].includes(showData.type)"
-                                                    style="width: 100%; margin-bottom: 5px;"
-                                                    :class="task && task.state === 'completed' ? 'green-color' : 'orange-color'">
-                                                    {{ task.assigneeName }}
-                                                    结果值: {{ task.result || '--' }}
-                                                </div>
-                                                <div v-else style="width: 100%; margin-bottom: 5px;"
-                                                    :class="task && task.state === 'completed' ? 'green-color' : 'orange-color'">
-                                                    {{ task.assigneeName }}
+                                                <div class="base-info">
+                                                    <div v-if="['scoreSelf', 'scoreEachOther'].includes(showData.type)"
+                                                        style="width: 100%; margin-bottom: 5px;"
+                                                        :class="task && task.state === 'completed' ? 'green-color' : 'orange-color'">
+                                                        {{ task.assigneeName }}
+                                                        评分: {{ task.score || '--' }}
+                                                    </div>
+                                                    <div v-else-if="['resultInput'].includes(showData.type)"
+                                                        style="width: 100%; margin-bottom: 5px;"
+                                                        :class="task && task.state === 'completed' ? 'green-color' : 'orange-color'">
+                                                        {{ task.assigneeName }}
+                                                        结果值: {{ task.result || '--' }}
+                                                    </div>
+                                                    <div v-else style="width: 100%; margin-bottom: 5px;"
+                                                        :class="task && task.state === 'completed' ? 'green-color' : 'orange-color'">
+                                                        {{ task.assigneeName }}
+                                                    </div>
+
+                                                    <div v-if="task.comment" style="width: 140px; color: #999;">
+                                                        <el-tooltip v-if="task.comment" effect="dark" placement="right">
+                                                            <div v-html="task.comment" slot="content"
+                                                                style="max-width: 300px">
+                                                            </div>
+                                                            <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                        </el-tooltip>
+                                                    </div>
                                                 </div>
 
-                                                <div v-if="task.comment" style="width: 140px; color: #999;">
-                                                    <el-tooltip v-if="task.comment" effect="dark" placement="right">
-                                                        <div v-html="task.comment" slot="content"
-                                                            style="max-width: 300px">
+                                                <div class="files" v-if="task.files && task.files.length > 0">
+                                                    <div class="title">附件列表</div>
+                                                    <div class="file-list">
+                                                        <div class="file-item" v-for="file in task.files"
+                                                            @click="onFilePreView(file)">
+                                                            {{ file | filterFileName }}
                                                         </div>
-                                                        <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
-                                                    </el-tooltip>
+                                                    </div>
                                                 </div>
                                             </div>
                                         </div>
@@ -116,14 +142,7 @@ export default {
             default: () => { }
         }
     },
-    computed: {
-
-    },
-    data() {
-        return {
-
-        }
-    },
+    
     filters: {
         filterType(v) {
             if (v == 'leader') return v = "管理员"
@@ -131,23 +150,43 @@ export default {
             if (v == 'post') return v = "岗位"
             if (v == 'user') return v = "指定人员"
             if (v == 'deptLeader') return v = "部门"
-        }
-    },
-    watch: {
-        'dialogVisible'(v) {
-            if (v) this.initData()
         },
 
+        filterFileName(str) {
+            if (str) {
+                let lastIndex = str.lastIndexOf("/")
+                return str.substr(lastIndex + 1, str.length - 1);
+            }
+        }
     },
-    mounted() {
-        this.initData();
-    },
+    
+   
     methods: {
-        initData() {
-        },
+
         handleCloseDialog() {
             this.$emit('close-dialog', false)
-        }
+        },
+        onFilePreView(url) {
+            let file = {
+                url
+            }
+            let imgFiles = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg'];
+            let lastIndex = file.url && file.url.lastIndexOf("/") || -1
+            let suffix; //文件后缀名
+
+            if (lastIndex > 0) {
+                suffix = file.url.substr(lastIndex + 1, file.url.length - 1).split(".")[1];
+                if (imgFiles.includes(suffix)) {
+                    this.imgUrl = ''
+                    this.imgUrl = file.url;
+                    this.$viewerApi({
+                        images: [this.imgUrl]
+                    })
+                } else {
+                    window.open(file.url, '_blank');
+                }
+            }
+        },
     }
 }
 
@@ -194,10 +233,41 @@ export default {
                 flex-direction: column;
                 border: 1px solid #f7f7f7;
                 .value-box {
+<<<<<<< HEAD
+                    width: 100%;
+                    
+                    .base-info {
+                        display: flex;
+                        align-items: center;
+                        justify-content: space-between;
+                    }
+                    .files {
+                        display: flex;
+                        flex-direction: column;
+                        padding: 5px;
+                        background: #f7f7f7;
+                        border-radius: 6px;
+                
+                        .title {
+                            color: #999;
+                        }
+                
+                        .file-item {
+                            color: #999;
+                            transition: 0.2s;
+                
+                            &:hover {
+                                text-decoration: underline;
+                                cursor: pointer;
+                            }
+                        }
+                    }
+=======
                     display: flex;
                     align-items: center;
                     justify-content: space-between;
                     width: 100%;
+>>>>>>> c23c758ac5b114f2e4b714b9a5908ff46553bd90
                 }
             }
             

+ 623 - 0
src/newPerformance/components/PublicComp/ShowHanderDialog2 copy.vue

@@ -0,0 +1,623 @@
+<template>
+    <el-dialog :title="dialogTitle" center :visible.sync="showHandlerDialogVisible" width="600px"
+        :before-close="handleCloseDialog">
+        <div>
+            <ol class="steps scroll-bar" style="width: 100%; height: 500px; overflow-y: auto;">
+                <template v-for="node in showData.nodes">
+                    <template v-if="node.type === 'targetConfirms'">
+                        <template v-if="node.enable">
+                            <li class="title" :class="showData.businessStatus === 'target_confirm' ? 'active' : ''">
+                                1.确认目标{{ showData.businessStatus === 'target_confirm' ? '(进行中)' : '' }}
+                            </li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content" v-if="node.children && node.children.length > 0">
+                                    <div class="content-info" v-for="child in node.children" :key="child.id">
+                                        <div class="user-type" v-if="child.tasks && child.tasks.length > 0">
+                                            {{ child.assigneeType | filterType }}
+                                        </div>
+                                        <div class="task-info" v-if="child.tasks && child.tasks.length > 0">
+                                            <div class="info" v-for="task in child.tasks" :key="task.taskId">
+                                                <div class="info-left">
+                                                    <span
+                                                        :class="task.state === 'created' ? 'orange-color' : 'green-color'">
+                                                        {{ task.state === 'created' ? '进行中' : '已完成' }},
+                                                    </span>
+                                                    {{ task.assigneeName }}
+                                                </div>
+                                                <div class="info-right" v-if="task.comment">
+                                                    <el-tooltip effect="dark" placement="right">
+                                                        <div v-html="task.comment" slot="content"
+                                                            style="max-width: 300px">
+                                                        </div>
+                                                        <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                    </el-tooltip>
+                                                </div>
+
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="content" v-else>
+                                    未开始
+                                </div>
+                            </li>
+                        </template>
+
+                        <template v-if="!node.enable">
+                            <li class="title">1.确认目标</li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content">
+                                    未启用
+                                </div>
+                            </li>
+                        </template>
+                    </template>
+
+                    <template v-if="node.type === 'resultInput'">
+                        <template v-if="node.enable">
+                            <li class="title" :class="showData.businessStatus === 'result_input' ? 'active' : ''">
+                                2.录入结果{{ showData.businessStatus === 'result_input' ? '(进行中)' : '' }}
+                            </li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content" v-if="node.tasks && node.tasks.length > 0">
+                                    <div class="content-info">
+                                        <div class="user-type">
+                                            {{ node.assigneeType | filterType }}
+                                        </div>
+                                        <div class="task-info" v-for="task in node.tasks" :key="task.taskId">
+                                            <div class="info">
+                                                <div class="info-left">
+                                                    <span
+                                                        :class="task.state === 'created' ? 'orange-color' : 'green-color'">
+                                                        {{ task.state === 'created' ? '进行中' : '已完成' }},
+                                                    </span>
+                                                    {{ task.assigneeName }},结果值:{{ task.result || '--'}}
+                                                </div>
+                                                <div class="info-right" v-if="task.comment">
+                                                    <el-tooltip effect="dark" placement="right">
+                                                        <div v-html="task.comment" slot="content"
+                                                            style="max-width: 300px">
+                                                        </div>
+                                                        <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                    </el-tooltip>
+                                                </div>
+                                            </div>
+
+                                            <div class="files" v-if="task.files && task.files.length > 0">
+                                                <div class="file-title">附件列表</div>
+                                                <div class="file-list">
+                                                    <div class="file-item" v-for="file in task.files"
+                                                        @click="openFileReview2(file)">
+                                                        {{ file | filterFileName }}
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+
+
+                                </div>
+                                <div v-else class="content">
+                                    未开始
+                                </div>
+                            </li>
+                        </template>
+                        <template v-if="!node.enable">
+                            <li class="title">2.录入结果</li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content">
+                                    未启用
+                                </div>
+                            </li>
+                        </template>
+
+                    </template>
+
+                    <template v-if="node.type === 'scoreSelf'">
+                        <template v-if="node.enable">
+                            <li class="title" :class="showData.businessStatus === 'score_self' ? 'active' : ''">
+                                3.自评{{ showData.businessStatus === 'score_self' ? '(进行中)' : '' }}
+                            </li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content" v-if="node.tasks && node.tasks.length > 0">
+                                    <div class="content-info">
+                                        <div class="user-type">
+                                            {{ node.assigneeType | filterType }}
+                                        </div>
+                                        <div class="task-info" v-for="task in node.tasks" :key="task.taskId">
+                                            <div class="info">
+                                                <div class="info-left">
+                                                    <span
+                                                        :class="task.state === 'created' ? 'orange-color' : 'green-color'">
+                                                        {{ task.state === 'created' ? '进行中' : '已完成' }},
+                                                    </span>
+                                                    {{ task.assigneeName }},评分:{{ task.score || '--' }}
+                                                </div>
+                                                <div class="info-right" v-if="task.comment">
+                                                    <el-tooltip effect="dark" placement="right">
+                                                        <div v-html="task.comment" slot="content"
+                                                            style="max-width: 300px">
+                                                        </div>
+                                                        <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                    </el-tooltip>
+                                                </div>
+                                            </div>
+
+                                            <div class="files" v-if="task.files && task.files.length > 0">
+                                                <div class="file-title">附件列表</div>
+                                                <div class="file-list">
+                                                    <div class="file-item" v-for="file in task.files"
+                                                        @click="openFileReview2(file)">
+                                                        {{ file | filterFileName }}
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div v-else class="content">
+                                    未开始
+                                </div>
+                            </li>
+                        </template>
+                        <template v-if="!node.enable">
+                            <li class="title">3.自评</li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content">
+                                    未启用
+                                </div>
+                            </li>
+                        </template>
+
+                    </template>
+
+                    <template v-if="node.type === 'scoreEachOther'">
+                        <template v-if="node.enable">
+                            <li class="title" :class="showData.businessStatus === 'score_each_other' ? 'active' : ''">
+                                4.互评{{ showData.businessStatus === 'score_each_other' ? '(进行中)' : '' }}
+                            </li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content" v-if="node.tasks && node.tasks.length > 0">
+                                    <div class="content-info">
+                                        <div class="user-type">
+                                            {{ node.assigneeType | filterType }}
+                                        </div>
+                                        <div class="task-info" v-for="task in node.tasks" :key="task.taskId">
+                                            <div class="info">
+                                                <div class="info-left">
+                                                    <span
+                                                        :class="task.state === 'created' ? 'orange-color' : 'green-color'">
+                                                        {{ task.state === 'created' ? '进行中' : '已完成' }},
+                                                    </span>
+                                                    {{ task.assigneeName }},评分:{{ task.score || '--' }}
+                                                </div>
+                                                <div class="info-right" v-if="task.comment">
+                                                    <el-tooltip effect="dark" placement="right">
+                                                        <div v-html="task.comment" slot="content"
+                                                            style="max-width: 300px">
+                                                        </div>
+                                                        <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                    </el-tooltip>
+                                                </div>
+                                            </div>
+
+                                            <div class="files" v-if="task.files && task.files.length > 0">
+                                                <div class="file-title">附件列表</div>
+                                                <div class="file-list">
+                                                    <div class="file-item" v-for="file in task.files"
+                                                        @click="openFileReview2(file)">
+                                                        {{ file | filterFileName }}
+                                                    </div>
+                                                </div>
+                                            </div>
+
+                                        </div>
+                                    </div>
+                                </div>
+                                <div v-else class="content">
+                                    未开始
+                                </div>
+                            </li>
+                        </template>
+                        <template v-if="!node.enable">
+                            <li class="title">4.互评</li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content">
+                                    未启用
+                                </div>
+                            </li>
+                        </template>
+
+                    </template>
+
+                    <template v-if="node.type === 'scores'">
+                        <template v-if="node.enable">
+                            <li class="title" :class="showData.businessStatus === 'score' ? 'active' : ''">
+                                5.评分{{ showData.businessStatus === 'score' ? '(进行中)' : '' }}
+                            </li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content" v-if="node.children && node.children.length > 0">
+                                    <div class="content-info" v-for="child in node.children" :key="child.id">
+                                        <div class="user-type" v-if="child.tasks && child.tasks.length > 0">
+                                            {{ child.assigneeType | filterType }}
+                                        </div>
+                                        <div class="task-info" v-if="child.tasks && child.tasks.length > 0">
+                                            <template v-for="task in child.tasks">
+                                                <div class="info" :key="task.taskId">
+                                                    <div class="info-left">
+                                                        <span
+                                                            :class="task.state === 'created' ? 'orange-color' : 'green-color'">
+                                                            {{ task.state === 'created' ? '进行中' : '已完成' }},
+                                                        </span>
+                                                        {{ task.assigneeName }},评分:{{ task.score }}
+                                                    </div>
+                                                    <div class="info-right" v-if="task.comment">
+                                                        <el-tooltip effect="dark" placement="right">
+                                                            <div v-html="task.comment" slot="content"
+                                                                style="max-width: 300px">
+                                                            </div>
+                                                            <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                        </el-tooltip>
+                                                    </div>
+                                                </div>
+
+                                                <div class="files" v-if="task.files && task.files.length > 0">
+                                                    <div class="file-title">附件列表</div>
+                                                    <div class="file-list">
+                                                        <div class="file-item" v-for="file in task.files"
+                                                            @click="openFileReview2(file)">
+                                                            {{ file | filterFileName }}
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </template>
+                                        </div>
+                                        <div v-else>
+                                            未开始
+                                        </div>
+                                    </div>
+
+                                </div>
+
+                            </li>
+                        </template>
+                        <template v-if="!node.enable">
+                            <li class="title">5.评分</li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content">
+                                    未启用
+                                </div>
+                            </li>
+                        </template>
+
+                    </template>
+
+                    <template v-if="node.type === 'reviews'">
+                        <template v-if="node.enable">
+                            <li class="title" :class="showData.businessStatus === 'review' ? 'active' : ''">
+                                6.审批{{ showData.businessStatus === 'review' ? '(进行中)' : '' }}
+                            </li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content" v-if="node.children && node.children.length > 0">
+
+                                    <div class="content-info" v-for="child in node.children" :key="child.id">
+                                        <div class="user-type" v-if="child.tasks && child.tasks.length > 0">
+                                            {{ child.assigneeType | filterType }}
+                                        </div>
+                                        <div class="task-info" v-if="child.tasks && child.tasks.length > 0">
+
+                                            <template v-for="task in child.tasks">
+                                                <div class="info" :key="task.taskId">
+                                                    <div class="info-left">
+                                                        <span
+                                                            :class="task.state === 'created' ? 'orange-color' : 'green-color'">
+                                                            {{ task.state === 'created' ? '进行中' : '已完成' }},
+                                                        </span>
+                                                        {{ task.assigneeName }},评分:{{ task.score }}
+                                                    </div>
+                                                    <div class="info-right" v-if="task.comment">
+                                                        <el-tooltip effect="dark" placement="right">
+                                                            <div v-html="task.comment" slot="content"
+                                                                style="max-width: 300px">
+                                                            </div>
+                                                            <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                        </el-tooltip>
+                                                    </div>
+                                                </div>
+
+                                                <div class="files" v-if="task.files && task.files.length > 0">
+                                                    <div class="file-title">附件列表</div>
+                                                    <div class="file-list">
+                                                        <div class="file-item" v-for="file in task.files"
+                                                            @click="openFileReview2(file)">
+                                                            {{ file | filterFileName }}
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </template>
+
+
+                                        </div>
+                                        <div v-else>
+                                            未开始
+                                        </div>
+                                    </div>
+                                </div>
+                            </li>
+                        </template>
+                        <template v-if="!node.enable">
+                            <li class="title">6.审批</li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content">
+                                    未启用
+                                </div>
+                            </li>
+                        </template>
+
+                    </template>
+
+                    <template v-if="node.type === 'cc'">
+                        <template v-if="node.enable">
+                            <li class="title" :class="showData.businessStatus === 'cc' ? 'active' : ''">
+                                7.抄送{{ showData.businessStatus === 'cc' ? '(进行中)' : '' }}
+                            </li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content" v-if="node.tasks && node.tasks.length > 0">
+                                    <div class="content-info">
+                                        <div class="user-type">
+                                            {{ node.assigneeType | filterType }}
+                                        </div>
+                                        <div class="task-info" v-for="task in node.tasks" :key="task.taskId">
+                                            <div class="info">
+                                                <div class="info-left">
+                                                    <span
+                                                        :class="task.state === 'created' ? 'orange-color' : 'green-color'">
+                                                        {{ task.state === 'created' ? '进行中' : '已完成' }},
+                                                    </span>
+                                                    {{ task.assigneeName }}
+                                                </div>
+                                                <div class="info-right" v-if="task.comment">
+                                                    <el-tooltip effect="dark" placement="right">
+                                                        <div v-html="task.comment" slot="content"
+                                                            style="max-width: 300px">
+                                                        </div>
+                                                        <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                    </el-tooltip>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="content" v-else>
+                                    未开始
+                                </div>
+                            </li>
+                        </template>
+                        <template v-if="!node.enable">
+                            <li class="title">7.抄送</li>
+                            <li class="info-box">
+                                <div class="line"></div>
+                                <div class="content">
+                                    未启用
+                                </div>
+                            </li>
+                        </template>
+
+
+                    </template>
+
+
+                </template>
+
+
+            </ol>
+            <div style="height: 50px;"></div>
+        </div>
+    </el-dialog>
+</template>
+
+<script>
+
+export default {
+    model: {
+        prop: 'showHandlerDialogVisible',
+        event: 'close-dialog'
+    },
+    props: {
+        showHandlerDialogVisible: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        status: {
+            type: String,
+            default: 'completed'
+        },
+        showData: {
+            type: [],
+            default: () => []
+        }
+    },
+    
+    filters: {
+        filterType(v) {
+            if (v == 'leader') return v = "管理员"
+            if (v == 'self') return v = "被考核人"
+            if (v == 'post') return v = "岗位"
+            if (v == 'user') return v = "指定人员"
+            if (v == 'deptLeader') return v = "部门"
+        },
+
+        filterFileName(str) {
+            if (str) {
+                let lastIndex = str.lastIndexOf("/")
+                return str.substr(lastIndex + 1, str.length - 1);
+            }
+        }
+    },
+
+    
+    methods: {
+        handleCloseDialog() {
+            this.$emit('close-dialog', false)
+        },
+        openFileReview2(file) {
+            window.open(file, '_blank');
+        },
+    }
+}
+
+</script>
+
+<style scoped lang="scss">
+.oneLine {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+.green-color {
+    color: #67c23a;
+}
+
+.orange-color {
+    color: #e6a23c;
+}
+
+.gray-color {
+    color: rgb(144, 147, 153);
+    background: rgb(244, 244, 245);
+    border-color: rgb(144, 147, 153);
+}
+
+
+
+.show-data-box {
+
+    .item-list {
+        .item {
+
+            .value-box {
+                margin-bottom: 5px;
+
+                .content {
+                    display: flex;
+                    align-items: center;
+                    justify-content: space-between;
+
+                    &-left {
+                        display: flex;
+                        align-items: center;
+
+                        &-status {}
+                    }
+                }
+            }
+        }
+    }
+}
+
+.steps {
+    display: flex;
+    flex-direction: column;
+    margin: 0;
+    padding: 0;
+    .title {
+        width: 100%;
+        height: 30px;
+        font-weight: 600;
+        color: #000;
+    }
+    .info-box {
+        display: flex;
+        margin: 10px 0;
+
+        .line {
+            margin: 0 20px;
+            width: 1px;
+            border: 1px dashed #ccc;
+        }
+
+        .content {
+            width: 90%;
+            background-color: #f7f7f7;
+            padding: 10px;
+            box-sizing: border-box;
+            color: #999;
+            &-info {
+                display: flex;
+                flex-direction: column;
+                .user-type {
+                    width: 100px;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    border-radius: 30px;
+                    border: 1px solid;
+                    height: 30px;
+                    color: #409eff;
+                    background: #ecf5ff;
+                    border-color: #b3d8ff;
+                }
+                .task-info {
+                    margin: 5px;
+                    color: #999;
+                    .info {
+                        width: 100%;
+                        height: 30px;
+                        display: flex;
+                        align-items: center;
+                        justify-content: space-between;
+                        .info-right {
+                            width: 120px;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+}
+
+.active {
+    color: #409eff !important;
+}
+
+
+.files {
+    display: flex;
+    flex-direction: column;
+    border-radius: 6px;
+
+    .file-title {
+        color: #999;
+        font-weight: 600;
+    }
+
+    .file-item {
+        color: #999;
+        transition: 0.2s;
+        padding-left: 10px;
+        &:hover {
+            text-decoration: underline;
+            cursor: pointer;
+        }
+    }
+}
+
+</style>

+ 149 - 55
src/newPerformance/components/PublicComp/ShowHanderDialog2.vue

@@ -1,6 +1,6 @@
 <template>
-    <el-dialog :title="dialogTitle" center :visible.sync="dialogVisible" width="600px"
-        :before-close="handleCloseDialog">
+    <el-dialog title="指标审批详情" center :visible.sync="showHandlerDialogVisible" width="600px"
+        :before-close="handleCloseDialog" append-to-body :close-on-press-escape="true" >
         <div>
             <ol class="steps scroll-bar" style="width: 100%; height: 500px; overflow-y: auto;">
                 <template v-for="node in showData.nodes">
@@ -85,8 +85,20 @@
                                                     </el-tooltip>
                                                 </div>
                                             </div>
+
+                                            <div class="files" v-if="task.files && task.files.length > 0">
+                                                <div class="file-title">附件列表</div>
+                                                <div class="file-list">
+                                                    <div class="file-item" v-for="file in task.files"
+                                                        @click="openFileReview2(file)">
+                                                        {{ file | filterFileName }}
+                                                    </div>
+                                                </div>
+                                            </div>
                                         </div>
                                     </div>
+
+
                                 </div>
                                 <div v-else class="content">
                                     未开始
@@ -135,6 +147,16 @@
                                                     </el-tooltip>
                                                 </div>
                                             </div>
+
+                                            <div class="files" v-if="task.files && task.files.length > 0">
+                                                <div class="file-title">附件列表</div>
+                                                <div class="file-list">
+                                                    <div class="file-item" v-for="file in task.files"
+                                                        @click="openFileReview2(file)">
+                                                        {{ file | filterFileName }}
+                                                    </div>
+                                                </div>
+                                            </div>
                                         </div>
                                     </div>
                                 </div>
@@ -185,6 +207,17 @@
                                                     </el-tooltip>
                                                 </div>
                                             </div>
+
+                                            <div class="files" v-if="task.files && task.files.length > 0">
+                                                <div class="file-title">附件列表</div>
+                                                <div class="file-list">
+                                                    <div class="file-item" v-for="file in task.files"
+                                                        @click="openFileReview2(file)">
+                                                        {{ file | filterFileName }}
+                                                    </div>
+                                                </div>
+                                            </div>
+
                                         </div>
                                     </div>
                                 </div>
@@ -205,7 +238,7 @@
 
                     </template>
 
-                    <template v-if="node.type === 'score'">
+                    <template v-if="node.type === 'scores'">
                         <template v-if="node.enable">
                             <li class="title" :class="showData.businessStatus === 'score' ? 'active' : ''">
                                 5.评分{{ showData.businessStatus === 'score' ? '(进行中)' : '' }}
@@ -218,24 +251,35 @@
                                             {{ child.assigneeType | filterType }}
                                         </div>
                                         <div class="task-info" v-if="child.tasks && child.tasks.length > 0">
-                                            <div class="info" v-for="task in child.tasks" :key="task.taskId">
-                                                <div class="info-left">
-                                                    <span
-                                                        :class="task.state === 'created' ? 'orange-color' : 'green-color'">
-                                                        {{ task.state === 'created' ? '进行中' : '已完成' }},
-                                                    </span>
-                                                    {{ task.assigneeName }},评分:{{ task.score }}
+                                            <template v-for="task in child.tasks">
+                                                <div class="info" :key="task.taskId">
+                                                    <div class="info-left">
+                                                        <span
+                                                            :class="task.state === 'created' ? 'orange-color' : 'green-color'">
+                                                            {{ task.state === 'created' ? '进行中' : '已完成' }},
+                                                        </span>
+                                                        {{ task.assigneeName }},评分:{{ task.score }}
+                                                    </div>
+                                                    <div class="info-right" v-if="task.comment">
+                                                        <el-tooltip effect="dark" placement="right">
+                                                            <div v-html="task.comment" slot="content"
+                                                                style="max-width: 300px">
+                                                            </div>
+                                                            <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                        </el-tooltip>
+                                                    </div>
                                                 </div>
-                                                <div class="info-right" v-if="task.comment">
-                                                    <el-tooltip effect="dark" placement="right">
-                                                        <div v-html="task.comment" slot="content"
-                                                            style="max-width: 300px">
+
+                                                <div class="files" v-if="task.files && task.files.length > 0">
+                                                    <div class="file-title">附件列表</div>
+                                                    <div class="file-list">
+                                                        <div class="file-item" v-for="file in task.files"
+                                                            @click="openFileReview2(file)">
+                                                            {{ file | filterFileName }}
                                                         </div>
-                                                        <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
-                                                    </el-tooltip>
+                                                    </div>
                                                 </div>
-
-                                            </div>
+                                            </template>
                                         </div>
                                         <div v-else>
                                             未开始
@@ -272,24 +316,38 @@
                                             {{ child.assigneeType | filterType }}
                                         </div>
                                         <div class="task-info" v-if="child.tasks && child.tasks.length > 0">
-                                            <div class="info" v-for="task in child.tasks" :key="task.taskId">
-                                                <div class="info-left">
-                                                    <span
-                                                        :class="task.state === 'created' ? 'orange-color' : 'green-color'">
-                                                        {{ task.state === 'created' ? '进行中' : '已完成' }},
-                                                    </span>
-                                                    {{ task.assigneeName }},评分:{{ task.score }}
+
+                                            <template v-for="task in child.tasks">
+                                                <div class="info" :key="task.taskId">
+                                                    <div class="info-left">
+                                                        <span
+                                                            :class="task.state === 'created' ? 'orange-color' : 'green-color'">
+                                                            {{ task.state === 'created' ? '进行中' : '已完成' }},
+                                                        </span>
+                                                        {{ task.assigneeName }},评分:{{ task.score }}
+                                                    </div>
+                                                    <div class="info-right" v-if="task.comment">
+                                                        <el-tooltip effect="dark" placement="right">
+                                                            <div v-html="task.comment" slot="content"
+                                                                style="max-width: 300px">
+                                                            </div>
+                                                            <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                        </el-tooltip>
+                                                    </div>
                                                 </div>
-                                                <div class="info-right" v-if="task.comment">
-                                                    <el-tooltip effect="dark" placement="right">
-                                                        <div v-html="task.comment" slot="content"
-                                                            style="max-width: 300px">
+
+                                                <div class="files" v-if="task.files && task.files.length > 0">
+                                                    <div class="file-title">附件列表</div>
+                                                    <div class="file-list">
+                                                        <div class="file-item" v-for="file in task.files"
+                                                            @click="openFileReview2(file)">
+                                                            {{ file | filterFileName }}
                                                         </div>
-                                                        <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
-                                                    </el-tooltip>
+                                                    </div>
                                                 </div>
+                                            </template>
+
 
-                                            </div>
                                         </div>
                                         <div v-else>
                                             未开始
@@ -310,7 +368,7 @@
 
                     </template>
 
-                    <template v-if="node.type === 'cc'">
+                    <!-- <template v-if="node.type === 'cc'">
                         <template v-if="node.enable">
                             <li class="title" :class="showData.businessStatus === 'cc' ? 'active' : ''">
                                 7.抄送{{ showData.businessStatus === 'cc' ? '(进行中)' : '' }}
@@ -359,7 +417,7 @@
                         </template>
 
 
-                    </template>
+                    </template> -->
 
 
                 </template>
@@ -368,6 +426,8 @@
             </ol>
             <div style="height: 50px;"></div>
         </div>
+
+        
     </el-dialog>
 </template>
 
@@ -375,11 +435,11 @@
 
 export default {
     model: {
-        prop: 'dialogVisible',
+        prop: 'showHandlerDialogVisible',
         event: 'close-dialog'
     },
     props: {
-        dialogVisible: {
+        showHandlerDialogVisible: {
             type: Boolean,
             default: false
         },
@@ -396,14 +456,13 @@ export default {
             default: () => []
         }
     },
-    computed: {
 
-    },
     data() {
         return {
-
+            imgUrl: "",
         }
     },
+    
     filters: {
         filterType(v) {
             if (v == 'leader') return v = "管理员"
@@ -411,28 +470,42 @@ export default {
             if (v == 'post') return v = "岗位"
             if (v == 'user') return v = "指定人员"
             if (v == 'deptLeader') return v = "部门"
-        }
-    },
-    watch: {
-        'dialogVisible'(v) {
-            if (v) this.initData()
         },
 
+        filterFileName(str) {
+            if (str) {
+                let lastIndex = str.lastIndexOf("/")
+                return str.substr(lastIndex + 1, str.length - 1);
+            }
+        }
     },
-    computed: {
-        // targetComfirms() {
-        //     return this.showData.filters(item => item.type === "targetComfirms")
-        // }
-    },
-    mounted() {
-        this.initData();
-    },
+
+    
     methods: {
-        initData() {
-        },
         handleCloseDialog() {
             this.$emit('close-dialog', false)
-        }
+        },
+        openFileReview2(url) {
+            let file = {
+                url
+            }
+            let imgFiles = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg'];
+            let lastIndex = file.url && file.url.lastIndexOf("/") || -1
+            let suffix; //文件后缀名
+
+            if (lastIndex > 0) {
+                suffix = file.url.substr(lastIndex + 1, file.url.length - 1).split(".")[1];
+                if (imgFiles.includes(suffix)) {
+                    this.imgUrl = ''
+                    this.imgUrl = file.url;
+                    this.$viewerApi({
+                        images: [this.imgUrl]
+                    })
+                } else {
+                    window.open(file.url, '_blank');
+                }
+            }
+        },
     }
 }
 
@@ -552,4 +625,25 @@ export default {
 }
 
 
+.files {
+    display: flex;
+    flex-direction: column;
+    border-radius: 6px;
+
+    .file-title {
+        color: #999;
+        font-weight: 600;
+    }
+
+    .file-item {
+        color: #999;
+        transition: 0.2s;
+        padding-left: 10px;
+        &:hover {
+            text-decoration: underline;
+            cursor: pointer;
+        }
+    }
+}
+
 </style>

+ 1136 - 0
src/newPerformance/components/ResultAnalysis.vue

@@ -0,0 +1,1136 @@
+<template>
+    <div class="record-right" v-loading="loading">
+        <!-- 考核表详情 -->
+        <div class="title-container" style="height: 30px; justify-content: space-between;">
+            <div class="title flex-box-ce">
+                考核详情
+                <div class="flex-box-ce" style="margin-left: 10px;">
+                    <el-cascader ref="cascader" v-model="headValue" :options="options" :props="props"
+                        :placeholder="placeholder" :no-data-text="noDataText"></el-cascader>
+                </div>
+            </div>
+            <div>
+                <!-- 正太规则 -->
+                <EditScoreList :scoreList="scoreList" @confirm="onConfrim" />
+
+                <el-button type="primary" @click="exportToExcel('mytable', '结果分析')">导出明细</el-button>
+            </div>
+        </div>
+
+
+        <div class="line"></div>
+
+        <div class="info-box">
+            <div style="width: 100%; display: flex;">
+                <div style="width: 50%; border-right: 1px solid #f1f1f1;">
+                    <div style="font-size: 16px; font-weight: 600;">等级分布</div>
+                    <div ref="echarts2" class="echarts"></div>
+                </div>
+                <div style="width: 50%; padding: 0 20px; box-sizing: border-box;">
+                    <div style="font-size: 16px; font-weight: 600;">正态分布</div>
+                    <!-- 考核人员分数列表 -->
+                    <div class="score-list">
+                        <div class="score-item heartBeat animated">
+                            <div class="score-item-title">总人数</div>
+                            <div class="score-item-num">{{ userTotal }}</div>
+                            <div class="score-item-percent">{{ userComplete }}人已完成</div>
+                        </div>
+                        <div v-for="item in scoreList" class="score-item heartBeat animated">
+                            <div class="score-item-title">{{ item.name }}</div>
+                            <div class="score-item-num">{{users.filter(user => user.scoreResult === item.name).length}}
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 考核人员列表 -->
+            <div class="title-container" style="margin: 10px 0;">
+                <div class="title">考核人员列表</div>
+            </div>
+
+            <el-select v-model="deptIds" clearable multiple placeholder="请选择部门"
+                style="width: 300px; margin-bottom: 10px;" @change="changeDeptName">
+                <el-option v-for="item in deptList" :key="item.id" :label="item.name" :value="item.id"></el-option>
+            </el-select>
+
+            <el-table v-if="filterUsers && filterUsers.length > 0" :data="filterUsers" style="width: 100%; "
+                :header-cell-style="{ background: '#f5f7fa' }" :max-height="600">
+                <el-table-column prop="employeeName" label="员工">
+                </el-table-column>
+
+
+                <el-table-column prop="departments" label="部门" align="center" width="300">
+                    <template slot-scope="scope">
+                        <el-tooltip class="item" effect="dark" placement="top">
+                            <div slot="content" style="max-width: 300px; ">
+                                {{ scope.row.departments | formatDeptName }}
+                            </div>
+                            <div class="oneLine">
+                                {{ scope.row.departments | formatDeptName }}
+                            </div>
+                        </el-tooltip>
+                    </template>
+                </el-table-column>
+
+                <el-table-column prop="status" label="考核状态">
+                    <template slot-scope="scope">
+                        <el-tag v-if="scope.row.status" type="success">
+                            已完成
+                        </el-tag>
+                        <el-tag v-else="scope.row.status" type="warning">
+                            进行中
+                        </el-tag>
+                    </template>
+                </el-table-column>
+
+                <el-table-column prop="level" label="评分">
+                    <template slot-scope="scope">
+                        <el-tag v-if="scope.row.level === '未评分'" type="info">
+                            未评分
+                        </el-tag>
+                        <el-tag v-else>
+                            {{ scope.row.score }}
+                        </el-tag>
+                    </template>
+                </el-table-column>
+
+                <el-table-column prop="level" label="评级">
+                    <template slot-scope="scope">
+                        <el-tag v-if="scope.row.level === '未评分'" type="info">
+                            未评级
+                        </el-tag>
+                        <el-tag v-else>
+                            {{ scope.row.level }}
+                        </el-tag>
+                    </template>
+                </el-table-column>
+
+                <el-table-column prop="scoreResult" label="正态分布">
+                    <template slot-scope="scope">
+                        <el-tag>
+                            {{ scope.row.scoreResult }}
+                        </el-tag>
+                    </template>
+                </el-table-column>
+
+                <el-table-column label="操作">
+                    <template slot-scope="scope">
+                        <el-link type="primary"
+                            @click="getDetails(scope.row.reviewId, scope.row.employeeId)">查看详情</el-link>
+                    </template>
+                </el-table-column>
+
+            </el-table>
+
+
+            <!-- 指标列表 -->
+            <div class="title-container" style="margin: 10px 0;">
+                <div class="title">指标信息</div>
+            </div>
+
+            <el-tabs type="card" v-model="activeName" @tab-click="handleClick">
+                <el-tab-pane label="默认" name="1">
+                    <el-table id="mytable" :data="filterTableData1" stripe style="width: 100%" border
+                        :header-cell-style="{ background: '#f5f7fa' }" :max-height="600">
+                        <el-table-column prop="employeeName" label="员工" align="center"></el-table-column>
+                        <el-table-column prop="departments" label="部门" align="center" width="300">
+                            <template slot-scope="scope">
+                                <el-tooltip class="item" effect="dark" placement="top">
+                                    <div slot="content" style="max-width: 300px; ">
+                                        {{ scope.row.departments | formatDeptName }}
+                                    </div>
+                                    <div class="oneLine">
+                                        {{ scope.row.departments | formatDeptName }}
+                                    </div>
+                                </el-tooltip>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="title" label="指标" align="center">
+                            <template slot-scope="scope">
+                                <el-tooltip class="item" effect="dark" placement="top">
+                                    <div v-html="scope.row.title" slot="content" style="max-width:300px"></div>
+                                    <div class="oneLine">{{ scope.row.title }}</div>
+                                </el-tooltip>
+                            </template>
+                        </el-table-column>
+
+                        <el-table-column prop="target" label="目标" align="center">
+                            <template slot-scope="scope">
+                                <span v-if="scope.row.target !== null">
+                                    {{ `${scope.row.target} ${scope.row.unit ? scope.row.unit : ''}` }}
+                                </span>
+                                <span v-else>--</span>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="result" label="实际值" align="center">
+                            <template slot-scope="scope">
+                                <div>
+                                    <span v-if="scope.row.result !== null">
+                                        {{ `${scope.row.result} ${scope.row.unit ? scope.row.unit : ''}` }}
+                                    </span>
+                                    <span v-else>--</span>
+                                </div>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="different" label="差距" align="center">
+                            <template slot-scope="scope">
+                                <el-tag v-if="scope.row.difference > 0" type="success">
+                                    {{ scope.row.difference }}
+                                </el-tag>
+                                <el-tag v-else-if="scope.row.difference < 0" type="danger">
+                                    {{ scope.row.difference }}
+                                </el-tag>
+                                <div v-else>
+                                    --
+                                </div>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="score" label="评分" align="center">
+                            <template slot-scope="scope">
+                                <div>
+                                    {{ scope.row.score || '--' }}
+                                </div>
+                            </template>
+                        </el-table-column>
+
+                        <el-table-column prop="okrs" label="过程跟踪" align="center">
+                            <template slot-scope="scope">
+                                <el-link v-if="scope.row.okrs && scope.row.okrs.length > 0" type="primary"
+                                    @click="openTargetList(scope.row.okrs)">
+                                    指标OKR
+                                </el-link>
+                                <span v-else class="fontColorC">暂无关联</span>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+                    <div style="height: 100px;"></div>
+                </el-tab-pane>
+
+                <el-tab-pane label="按指标/目标/单位聚合" name="2">
+                    <el-table :data="filterTableData2" stripe style="width: 100%" border
+                        :header-cell-style="{ background: '#f5f7fa' }" :max-height="600">
+                        <el-table-column prop="departments" label="部门" align="center" width="300">
+                            <template slot-scope="scope">
+                                <el-tooltip class="item" effect="dark" placement="top">
+                                    <div slot="content" style="max-width: 300px; ">
+                                        {{ scope.row.departments | formatDeptName }}
+                                    </div>
+                                    <div class="oneLine">
+                                        {{ scope.row.departments | formatDeptName }}
+                                    </div>
+                                </el-tooltip>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="title" label="指标" align="center">
+                            <template slot-scope="scope">
+                                <el-tooltip class="item" effect="dark" placement="top">
+                                    <div v-html="scope.row.title" slot="content" style="max-width:300px"></div>
+                                    <div class="oneLine">{{ scope.row.title }}</div>
+                                </el-tooltip>
+                            </template>
+                        </el-table-column>
+
+                        <el-table-column prop="target" label="目标" align="center">
+                            <template slot-scope="scope">
+                                <div>
+                                    <span v-if="scope.row.target !== null">
+                                        {{ `${scope.row.target} ${scope.row.unit ? scope.row.unit : ''}` }}
+                                    </span>
+                                    <span v-else>--</span>
+                                </div>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="avgResult" label="均值" align="center">
+
+                        </el-table-column>
+
+                        <el-table-column prop="standardResultRate" label="超出目标比例" align="center">
+                            <template slot-scope="scope">
+                                <el-tag v-if="parseInt(scope.row.standardResultRate) > 0" type="success">
+                                    {{ scope.row.standardResultRate }}
+                                </el-tag>
+                                <el-tag v-else-if="parseInt(scope.row.standardResultRate) < 0" type="danger">
+                                    {{ scope.row.standardResultRate }}
+                                </el-tag>
+                                <el-tag v-else type="info">
+                                    {{ scope.row.standardResultRate }}
+                                </el-tag>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="standardCount" label="达标数" align="center" />
+                        <el-table-column prop="standardRate" label="达标率" align="center">
+                            <template slot-scope="scope">
+                                <el-tag v-if="parseInt(scope.row.standardRate) > 0" type="success">
+                                    {{ scope.row.standardRate }}
+                                </el-tag>
+                                <el-tag v-else-if="parseInt(scope.row.standardRate) < 0" type="danger">
+                                    {{ scope.row.standardRate }}
+                                </el-tag>
+                                <el-tag v-else type="info">
+                                    {{ scope.row.standardRate }}
+                                </el-tag>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="avgScore" label="平均分" align="center" />
+                    </el-table>
+                    <div style="height: 100px;"></div>
+
+                </el-tab-pane>
+            </el-tabs>
+
+        </div>
+
+        <!-- 关联okr -->
+        <TargetListComp v-if="targetDialogVisible" :dialogVisible="targetDialogVisible" :ids="okrs"
+            @close="closeTargetList">
+        </TargetListComp>
+
+        <!-- 员工绩效详情 -->
+        <el-dialog title="员工绩效详情" :visible.sync="detailDialogVisible" @close="handleClose" :close-on-click-modal="false"
+            :close-on-press-escape="true" center fullscreen :show-close="false">
+            <div style=" width: 100%; height: 100%; position: relative;">
+                <el-button round style="position: absolute; top: -65px; left: 0px; z-index: 99;"
+                    @click="detailDialogVisible = false">返回</el-button>
+                <!-- 我的考核 -->
+                <MyPerformance v-if="detailDialogVisible" :reviewId="reviewId" :sendEmployeeId='sendEmployeeId' />
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+
+<script>
+let that;
+
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+import _ from "lodash"
+import ECharts from 'echarts';
+import EditScoreList from './ExamineRecord/RightEamineComp/EditScoreList'
+import TargetListComp from "@/performance/views/assessManagement/TargetListComp.vue"; // 关联OKR弹框
+import MyPerformance from './MyPerformance'; // 我的考核
+import cloneDeep from 'lodash.clonedeep';
+import FileSaver from "file-saver";
+import XLSX from "xlsx";
+
+export default {
+    components: {
+        EditScoreList,
+        TargetListComp,
+        MyPerformance
+    },
+    
+    data() {
+        return {
+            reviewPackageId: 0,
+            detailInfo: null,
+            year: '2025',
+            headValue: [],
+            options: [
+                { value: '4', label: '月度', leaf: false, children: [] },
+                { value: '3', label: '季度', leaf: false, children: [] },
+                { value: '2', label: '半年度', leaf: false, children: [] },
+                { value: '1', label: '年度', leaf: false, children: [] },
+                { value: '0', label: '未定义', leaf: false, children: [] },
+            ], //
+            props: {
+                value: 'value',
+                label: 'label',
+                children: 'children',
+                lazy: true,
+                lazyLoad(node, resolve) {
+                    that.getAssessTree(node, resolve);
+                }
+            },
+            total: 0,
+            loading: false,
+            tableData: [],
+            deptList: [], // 部门列表 - 树形结构
+            dept_list: [], // 部门列表
+            selected_dept_ids: [], // 选择的部门列表
+            placeholder: "",
+            chooseChildren: [],
+            noDataText: '暂无数据',
+            loading: false,
+            activeName: "1",
+            reviewPackageId: "",
+            title: "默认标题",
+            detailDialogVisible: false,
+            reviewId: '',
+            sendEmployeeId: "",
+            startTime: "",
+            endTime: "",
+            deptIds: [],
+            rank: "",
+            rankList: [],
+            deptList: [],
+            scoreList: [],
+            users: [], //考核人员列表
+            filterUsers: [],
+            tableData1: [], // 考核中的指标列表,
+            tableData2: [], // 按单位/目标/聚合指标列表,
+            filterTableData1: [],
+            filterTableData2: [],
+            distributionId: "",
+            level_enable: false,
+            packages: [],
+            userTotal: 0,
+            userComplete: 0,
+            userIncomplete: 0,
+            infos: [],
+            gradeLevels: [],
+            cateIds: [], // 选择的考核分类
+            targetDialogVisible: false,
+            okrs: [],
+        };
+    },
+    watch: {
+        detailInfo(v) {
+            this.initData();
+        },
+        deptName(v) {},
+        year(val) {
+            this.getRecords()
+        },
+        headValue(val) {
+            if (this.chooseChildren && this.chooseChildren.length > 0) {
+                let obj = this.chooseChildren.find(child => child.value == this.headValue[1])
+                if (obj) {
+                    let value = ''
+                    if (this.headValue[0] && this.headValue[0] == '0') value = "未定义 / "
+                    if (this.headValue[0] && this.headValue[0] == '1') value = "年度 / "
+                    if (this.headValue[0] && this.headValue[0] == '2') value = "半年度 / "
+                    if (this.headValue[0] && this.headValue[0] == '3') value = "季度 / "
+                    if (this.headValue[0] && this.headValue[0] == '4') value = "月度 / "
+                    this.placeholder = obj.label || ''
+                }
+            }
+            this.getResultAnalyze()
+        },
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        calcScoreList() {
+            let scoreSet = new Set(this.users.map(item => Number(item.score)))
+
+            let scores = [...scoreSet].sort((a, b) => b - a);
+            let rate = 1;
+            let scoreCount = scores.length; // 总人数
+            let scoreIndex = 0;
+            let scoreResult = {};
+
+            this.scoreList.forEach(item => {
+
+                let scale = item.scale / 100;
+                let count = Math.round((scoreCount - scoreIndex) * scale / rate);
+                rate -= scale;
+                if (count <= 0) return;
+
+                for (let i = scoreIndex; i < (scoreIndex + count); i++) {
+                    scoreResult[scores[i]] = item.name;
+                }
+                scoreIndex += count;
+            });
+            return this.users.map(item => {
+                let result = { ...item };
+                result.scoreResult = scoreResult[item.score] || '--'
+                return result;
+            }).sort((a, b) => b.score - a.score);
+        }
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        },
+        formatDeptName(val) {
+            let str = '';
+            if (val && val.length > 0) {
+                val.forEach(dept => {
+                    str += dept.name + ","
+                })
+                str = str.substr(0, str.length - 1)
+            } else {
+                str = "--"
+            }
+            return str
+        }
+    },
+    mounted() {
+        if (this.detailInfo) this.initData();
+    },
+    created() {
+        that = this;
+        this.getRecords('4'); // 优先获取当月最新考核数据 递归周期类型,获取考核数据,优先按月,季,半年度,年度来调用 
+    },
+    methods: {
+
+        exportToExcel(tableId, fileName) {
+            // exportToExcel(tableId, fileName)
+            this.downloadLoading = true
+            const xlsxParam = { raw: true };
+            const wb = XLSX.utils.table_to_book(document.getElementById(tableId), xlsxParam);
+
+            // 遍历工作表中的所有单元格,处理换行
+            const ws = wb.Sheets[wb.SheetNames[0]]; // 工作簿1
+            for (const cell in ws) {
+                if (ws[cell].v && typeof ws[cell].v === 'string') {
+                    ws[cell].v = ws[cell].v.replace(/\n/g, '\n');
+                    ws[cell].s = {
+                        alignment: {
+                            wrapText: true // 启用自动换行
+                        }
+                    };
+                }
+            }
+
+            const wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: true, type: 'array' });
+            try {
+                FileSaver.saveAs(new Blob([wbout], { type: 'application/octet-stream' }), fileName + '.xlsx');
+            } catch (e) {
+                console.error(e, wbout);
+            }
+            this.downloadLoading = false;
+        },
+
+        getAssessTree(node, resolve) {
+            if (this.options.length == 0) {
+                return
+            }
+            const { value } = node;
+
+            this.chooseChildren = node.children // 用来回显选择的文本
+            node.children = []
+            let url = `/performance/statistics/cycle/info/${this.user_info.site_id}/${value}`
+            this.$axiosUser("get", url, {}).then(res => {
+                let { data: { data: { items, cycleType } } } = res
+                if (items && items.length > 0) {
+                    items.forEach(item => {
+                        item.leaf = true;
+                        item.label = item.remark
+                    })
+                    resolve(items)
+                } else {
+                    resolve([])
+                }
+
+
+            }).finally(() => {
+                // this.tableDataLoad = false;
+            });
+        },
+        getRecords(cycle) {
+            if (cycle < 0) {
+                cycle = 0
+                return
+            }
+            this.loading = true
+            // 周期种类 0-未定义 1-年度 2-半年度 3-季度 4-月度
+            let url = `/performance/statistics/cycle/info/${this.user_info.site_id}/${cycle}`
+            this.$axiosUser("get", url, {}).then(res => {
+                this.loading = false;
+                let { data: { data: { items, cycleType } } } = res
+                if (items && items.length > 0) {
+                    items.forEach(item => {
+                        item.leaf = true
+                        item.label = item.remark
+                    })
+                    let index = parseInt(4 - cycle)
+                    this.options[index].children = items
+
+                    this.headValue = [cycle + '', this.options[index].children[[0]].value]
+                    // if (this.headValue[1]) this.placeholder = this.options[parseInt(cycle)].children[[0]].label || ''
+                } else {
+                    this.getRecords(parseInt(cycle) - 1) // 递归周期类型,获取考核数据,优先按月,季,半年度,年度来调用
+                }
+            })
+        },
+
+        getResultAnalyze() {
+            this.reviewPackageId = 0
+            let url = `/performance/statistics/cycle/${this.user_info.site_id}/${this.headValue[0]}`
+            if (!this.headValue[1]) return
+            this.$axiosUser("get", url, { value: this.headValue[1] }).then(res => {
+                this.detailInfo = res.data.data
+                this.reviewPackageId = 1
+            })
+        },
+
+        // 关闭绩效弹框回调事件
+        handleClose() { },
+
+        changeDeptName(v) {
+            this.filterUsers = this.users.filter((item) => {
+                const departmentMatch = this.deptIds.length === 0 || this.deptIds.some((depId) => item.departments.some(dep => dep.id == depId));
+                return departmentMatch;
+            });
+
+            this.filterTableData1 = this.tableData1.filter((item) => {
+                const departmentMatch = this.deptIds.length === 0 || this.deptIds.some((depId) => item.departments.some(dep => dep.id == depId));
+                return departmentMatch;
+            });
+
+            this.filterTableData2 = this.tableData2.filter((item) => {
+                const departmentMatch = this.deptIds.length === 0 || this.deptIds.some((depId) => item.departments.some(dep => dep.id == depId));
+                return departmentMatch;
+            });
+        },
+        async initData() {
+            this.loading = true
+            this.activeName = '1'
+            this.deptIds = [];
+            this.deptList = [];
+            this.users = [];
+            this.filterUsers = [];
+            this.isShow = false
+            this.tableData1 = [];
+            this.tableData2 = [];
+            this.filterTableData1 = [];
+            this.filterTableData2 = [];
+            let { indicators, startTime, endTime, distribution: { items }, users } = this.detailInfo
+            await this.getAllSet();
+            this.tableData1 = [];
+            this.tableData2 = [];
+            this.startTime = startTime;
+            this.endTime = endTime;
+            this.scoreList = items;
+            this.distributionId = this.scoreList[0].id
+            this.users = users;
+            this.tableData1 = indicators;
+            this.tableData1.forEach(item => {
+                this.users.forEach(user => {
+                    if (user.employeeId == item.employeeId) {
+                        item.departments = user.departments
+                    }
+                })
+                if (item.target && item.result) {
+                    item.difference = item.result - item.target
+                } else {
+                    item.difference = '--'
+                }
+            })
+            this.filterTableData1 = this.tableData1
+            this.userTotal = 0;
+            this.userComplete = 0;
+            this.userIncomplete = 0;
+            let distribution = [];
+            let userScores = []
+            this.scoreList.forEach(item => {
+                item.level = item.name;
+                item.ratio = item.scale / 100
+                distribution.push(item)
+            })
+
+            if (this.users && this.users.length > 0) {
+                this.users.forEach(user => {
+                    this.userTotal++;
+                    this.userComplete += user.status === 1 ? 1 : 0;
+                    user.level = this.findGrade(user.score, this.gradeLevels);
+                    // this.rankList.push(user.level || '未评分')
+                    userScores.push(user.score)
+                })
+                this.infos = [
+                    { label: "总人数", num: this.userTotal },
+                    { label: "已完成", num: this.userComplete },
+                    { label: "未评分", num: this.userIncomplete },
+                ]
+                let scoreResult = this.assignLevels(userScores, distribution);
+
+                this.users.forEach(item => {
+                    scoreResult.forEach(result => {
+                        if (result.scores.includes(item.score)) {
+                            item.scoreResult = result.level
+                        }
+                    })
+                })
+                this.isShow = true
+                this.filterUsers = cloneDeep(this.users)
+                this.initDeptList();
+                this.getResult();
+            }
+
+            this.loading = false
+        },
+
+        initDeptList() {
+            if (this.users && this.users.length > 0) {
+                this.users.forEach(user => {
+                    if (user.departments && user.departments.length > 0) {
+                        user.departments.forEach(dep => {
+                            this.deptList.push({ id: dep.id, name: dep.name })
+                        })
+                    }
+                })
+                this.deptList = Array.from(new Set(this.deptList.map(JSON.stringify))).map(JSON.parse);
+            }
+        },
+        getResult() {
+            let xData = [], yData = []
+            this.gradeLevels.forEach(item => {
+                xData.push(item.name)
+                yData.push(this.users.filter(user => user.level === item.name).length)
+            })
+            let option = {
+                tooltip: {
+                    trigger: 'axis',
+                    axisPointer: {
+                        // 坐标轴指示器,坐标轴触发有效
+                        type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
+                    }
+                },
+                xAxis: {
+                    type: 'category',
+                    data: xData
+                },
+                yAxis: {
+                    type: 'value',
+                    minInterval: 1
+                },
+                series: [
+                    {
+                        data: yData,
+                        type: 'bar',
+                        itemStyle: {
+                            normal: {
+                                //这里是重点
+                                color: function (params) {
+                                    //注意,如果颜色太少的话,后面颜色不会自动循环,最好多定义几个颜色
+                                    var colorList = ['#409EFF', '#4ECB73', '#36CBCB', '#F2637B', '#FBD437', '#749f83', '#ca8622'];
+                                    var index;
+                                    //给大于颜色数量的柱体添加循环颜色的判断
+                                    if (params.dataIndex >= colorList.length) {
+                                        index = params.dataIndex - colorList.length;
+                                        return colorList[index];
+                                    }
+                                    return colorList[params.dataIndex];
+                                }
+                            }
+                        },
+                        barMaxWidth: 30
+                    }
+                ]
+            };
+            var myChart = ECharts.init(this.$refs.echarts2);
+            myChart.setOption(option);
+            window.onresize = myChart.resize;
+        },
+
+        getDetails(reviewId, employeeId) {
+            this.reviewId = reviewId
+            this.sendEmployeeId = employeeId
+            this.detailDialogVisible = true
+            // this.$bus.$emit('changeCurrentId', { currentId: '2', reviewId, employeeId })
+        },
+        changeCateIds(cateIds) {
+            this.cateIds = cateIds
+            this.$bus.$emit("finishEdit", this.reviewPackageId)
+        },
+        changeTitle(title) {
+            this.title = title;
+            this.$bus.$emit("finishEdit", this.reviewPackageId)
+        },
+
+        changeDate(data) {
+            let { startTime, endTime } = data
+            this.startTime = startTime
+            this.endTime = endTime
+            this.$bus.$emit("finishEdit", this.reviewPackageId)
+        },
+
+
+
+        // 获取全局等级设置
+        async getAllSet() {
+            let res = await this.$axiosUser('get', 'api/pro/per/user/base_config')
+            let data = res.data.data;
+            let levels = data.level_scope.levels;
+            let gradeLevels = [];
+            let max = 0;//最大值
+            if (levels && levels.length > 0) {
+                levels.forEach((item, index) => {
+                    var obj;
+                    if (index == 0) {
+                        obj = { name: item.name, max: Number(item.value), min: 0 };
+                    } else {
+                        obj = { name: item.name, max: Number(item.value), min: max };//当不是第一个等级时,最小值为上一个的最大值
+                    }
+                    max = item.value;
+                    gradeLevels.push(obj);
+                })
+                this.gradeLevels = gradeLevels
+            }
+        },
+
+        // 查找分数对应的等级
+        findGrade(score, gradeLevels) {
+            for (let i = 0; i < gradeLevels.length; i++) {
+                if (score && score >= gradeLevels[i].min && score && score <= gradeLevels[i].max) {
+                    return gradeLevels[i].name; // 返回对应的等级描述
+                } else if (score && score <= gradeLevels[0].min) {
+                    return gradeLevels[0].name; // 返回对应的等级描述
+                } else if (score && score >= gradeLevels[gradeLevels.length - 1].max) {
+                    return gradeLevels[gradeLevels.length - 1].name; // 返回对应的等级描述
+                }
+            }
+            return "未评分"; // 如果分数不在任何范围内
+        },
+
+
+
+        assignLevels(scores, levelConfigs) {
+            // 降序排序并去重(假设分数不重复,可省略去重)
+            const sortedScores = [...scores].sort((a, b) => b - a);
+            const total = sortedScores.length;
+            if (total === 0) return [];
+
+            // 归一化处理比例
+            const totalRatio = levelConfigs.reduce((sum, cfg) => sum + cfg.ratio, 0);
+            const normalized = levelConfigs.map(cfg => cfg.ratio / totalRatio);
+
+            // 计算每个等级的初始人数
+            let counts = normalized.map(ratio => Math.floor(total * ratio));
+            let remainder = total - counts.reduce((sum, c) => sum + c, 0);
+
+            // 分配剩余人数,按优先级顺序
+            let idx = 0;
+            while (remainder > 0 && idx < counts.length) {
+                counts[idx]++;
+                remainder--;
+                idx++;
+            }
+
+            // 构建结果:按人数切割数组
+            let start = 0;
+            return counts.map((count, i) => {
+                const end = start + count;
+                const levelScores = sortedScores.slice(start, end);
+                start = end;
+                return {
+                    level: levelConfigs[i].level,
+                    scores: levelScores
+                };
+            });
+        },
+
+        onConfrim(items) {
+            if (!(items && items.length > 0)) return
+            this.userTotal = 0;
+            this.userComplete = 0;
+            this.userIncomplete = 0;
+            let distribution = [];
+            let userScores = []
+            this.filterUsers = []
+            this.loading = true
+            this.scoreList = items
+            this.scoreList.forEach(item => {
+                item.level = item.name;
+                item.ratio = item.scale / 100
+                distribution.push(item)
+            })
+
+            if (this.users && this.users.length > 0) {
+                this.users.forEach(user => {
+                    this.userTotal++;
+                    this.userComplete += user.status === 1 ? 1 : 0;
+                    user.level = this.findGrade(user.score, this.gradeLevels);
+                    // this.rankList.push(user.level || '未评分')
+                    userScores.push(user.score)
+                })
+                this.infos = [
+                    { label: "总人数", num: this.userTotal },
+                    { label: "已完成", num: this.userComplete },
+                    { label: "未评分", num: this.userIncomplete },
+                ]
+                let scoreResult = this.assignLevels(userScores, distribution);
+
+                this.users.forEach(item => {
+                    scoreResult.forEach(result => {
+                        if (result.scores.includes(item.score)) {
+                            item.scoreResult = result.level
+                        }
+                    })
+                })
+                this.filterUsers = cloneDeep(this.users)
+                this.loading = false
+                console.log(this.filterUsers);
+            }
+        },
+
+        // 选项卡点击事件
+        handleClick(tab, event) {
+            this.tableData2 = []
+            if (this.activeName == 2) {
+                let groups = _.groupBy(this.tableData1, item => `${item.title}(_)${item.target === null || item.target === '' ? 'null' : item.target}(_)${item.unit === null || item.unit === '' ? 'null' : item.unit}`);
+                Object.keys(groups).forEach(key => {
+                    let group = {
+                        title: '',
+                        target: '',
+                        unit: '',
+                        departments: [],
+                        userCount: 0,
+                        scoredCount: 0,
+                        standardCount: 0,
+                        failCount: 0,
+                        standardRate: '--',
+                        totalScore: 0,
+                        totalResult: 0,
+                        avgScore: 0,
+                        avgResult: 0,
+                        standardResultRate: '--'
+                    };
+                    groups[key].forEach(indicator => {
+                        group.title = indicator.title; // 指标名称
+                        group.target = indicator.target; // 目标
+                        group.unit = indicator.unit; // 单位
+                        group.departments = indicator.departments; // 单位
+                        let standardCount = indicator.difference !== '--' && indicator.difference >= 0 ? 1 : 0; // 
+                        group.userCount += 1;
+                        group.scoredCount += indicator.score !== null ? 1 : 0;
+                        group.standardCount += standardCount;
+                        group.failCount += standardCount === 1 ? 0 : 1;
+                        if (indicator.score !== null) group.totalScore += indicator.score;
+                        if (indicator.result !== null) group.totalResult += indicator.result;
+                    });
+                    group.standardCount = group.standardCount;
+                    if (group.userCount > 0) {
+                        let rate = Math.floor(group.standardCount / group.userCount * 100 * 0.01);
+                        let avgScore = Math.floor(group.totalScore / group.userCount * 100 * 0.01);
+                        let avgResult = Math.floor(group.totalResult / group.userCount * 100 * 0.01);
+                        group.standardRate = rate > 0 ? `${rate}%` : '--';
+                        group.avgScore = avgScore !== 0 ? avgScore : '--';
+                        group.avgResult = avgResult !== 0 ? avgResult : '--';
+
+                        if (group.target !== null && group.avgResult !== '--') {
+                            let standardResultRate = Math.floor((group.avgResult - group.target) / group.target * 100 * 0.01 * 100);
+                            group.standardResultRate = standardResultRate !== 0 ? `${standardResultRate}%` : '--';
+                        }
+                    }
+                    this.tableData2.push(group);
+                    this.filterTableData2 = this.tableData2
+                })
+            }
+        },
+        closeTargetList() {
+            this.targetDialogVisible = false
+        },
+
+        openTargetList(okrs) {
+            if (okrs && okrs.length > 0) {
+                this.okrs = okrs
+                this.targetDialogVisible = true
+            } else {
+                return this.$message.error("暂无关联okr")
+            }
+        }
+    }
+}
+
+</script>
+
+<style>
+.oneLine {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+</style>
+
+
+<style scoped="scoped" lang="scss">
+.record-right {
+    width: 100%;
+    border-radius: 5px;
+    background: #fff;
+    padding: 10px 20px;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+    .info-box {
+        width: 100%;
+        flex: 1;
+        overflow-y: auto;
+
+        /* 设置滚动条的宽度和背景色 */
+            &::-webkit-scrollbar {
+                width: 8px;
+                height: 8px;
+                background-color: #f9f9f9;
+            }
+        
+            /* 设置滚动条滑块的样式 */
+            &::-webkit-scrollbar-thumb {
+                border-radius: 6px;
+                background-color: #c1c1c1;
+            }
+        
+            /* 设置滚动条滑块hover样式 */
+            &::-webkit-scrollbar-thumb:hover {
+                background-color: #a8a8a8;
+            }
+        
+            /* 设置滚动条轨道的样式 */
+            &::-webkit-scrollbar-track {
+                box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+                border-radius: 6px;
+                background: #ededed;
+            }
+        
+    }
+
+    .echarts {
+        height: 350px;
+        width: 100%;
+    }
+
+    
+
+    .title-container {
+        display: flex;
+        align-items: center;
+
+        .searchBox {
+            width: 300px;
+
+            .search-title {
+                border-bottom: 1px solid #f1f1f1;
+                font-size: 16px;
+                font-weight: 700;
+                padding: 0 10px;
+                padding-bottom: 10px;
+            }
+        }
+
+
+        .title {
+            font-weight: 700;
+            font-size: 16px;
+        }
+    }
+
+    .line {
+        width: 100%;
+        height: 1px;
+        background: #f1f1f1;
+        margin: 10px 0;
+    }
+
+    .score-list {
+        display: flex;
+        flex-wrap: wrap;
+        width: 100%;
+        margin-top: 20px;
+
+        .score-item {
+            flex: 0 0 calc((100% - 100px) / 4);
+            height: 100px;
+            padding: 10px;
+            margin: 0 20px 20px 0;
+            box-sizing: border-box;
+            border-radius: 6px;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: space-around;
+            font-size: 16px;
+            color: #999;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
+
+            &-title {
+                font-weight: 600;
+                color: #409EFF;
+            }
+
+            &-num {
+                color: #000;
+                font-weight: 600;
+            }
+
+            &:nth-child(5n) {
+                /* 去除第5n个的margin-right */
+                margin-right: 0;
+            }
+        }
+    }
+
+        /* 设置滚动条的宽度和背景色 */
+        ::v-deep .el-table__body-wrapper::-webkit-scrollbar {
+            width: 6px;
+            height: 6px;
+            background-color: #f9f9f9;
+        }
+    
+        /* 设置滚动条滑块的样式 */
+        ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
+    
+        /* 设置滚动条滑块hover样式 */
+        ::v-deep .el-table__body-wrapper::-webkit-scrollbar-thumb:hover {
+            background-color: #a8a8a8;
+        }
+    
+        /* 设置滚动条轨道的样式 */
+        ::v-deep .el-table__body-wrapper::-webkit-scrollbar-track {
+            box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+            border-radius: 6px;
+            background: #ededed;
+        }
+
+
+    .user-info {
+        width: 100%;
+        height: 200px;
+        overflow: hidden;
+        overflow-x: auto;
+
+        /* 设置滚动条的宽度和背景色 */
+        &::-webkit-scrollbar {
+            width: 10px;
+            height: 10px;
+            background-color: #f9f9f9;
+        }
+
+        /* 设置滚动条滑块的样式 */
+        &::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
+
+        /* 设置滚动条滑块hover样式 */
+        &::-webkit-scrollbar-thumb:hover {
+            background-color: #a8a8a8;
+        }
+
+        /* 设置滚动条轨道的样式 */
+        &::-webkit-scrollbar-track {
+            box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+            border-radius: 6px;
+            background: #ededed;
+        }
+
+        .info-card {
+            width: 220px;
+            height: 100%;
+            margin-right: 20px;
+            border: 1px solid #f1f1f1;
+            padding: 5px 10px;
+            border-radius: 6px;
+            box-sizing: border-box;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
+
+            .info {
+                display: flex;
+                align-items: center;
+                height: 36px;
+
+                .info-label {
+                    width: 100px;
+                }
+            }
+        }
+
+    }
+}
+</style>

+ 25 - 1
src/newPerformance/components/RoleSetting.vue

@@ -226,9 +226,33 @@ export default {
 <style scoped="scoped" lang="scss">
 .all {
     min-height: calc(100vh - 210px);
-    overflow: auto;
+    overflow-y: auto;
     padding: 20px;
     background-color: #fff;
+    /* 设置滚动条的宽度和背景色 */
+        &::-webkit-scrollbar {
+            width: 6px;
+            height: 6px;
+            background-color: #f9f9f9;
+        }
+
+        /* 设置滚动条滑块的样式 */
+        &::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
+
+        /* 设置滚动条滑块hover样式 */
+        &::-webkit-scrollbar-thumb:hover {
+            background-color: #a8a8a8;
+        }
+
+        /* 设置滚动条轨道的样式 */
+        &::-webkit-scrollbar-track {
+            box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+            border-radius: 6px;
+            background: #ededed;
+        }
 }
 
 .title {

+ 84 - 25
src/newPerformance/components/TemplateDetails/BatchHandleNode.vue

@@ -7,8 +7,7 @@
                     style="width: 100%; margin-top: 10px;"></el-alert>
                 <el-form ref="form" label-width="80px" size="small">
                     <el-form-item label="启用">
-                        <el-switch v-model="currentNode.enable"
-                            :disabled="dialogTitle == '评分' || dialogTitle == '审批'"></el-switch>
+                        <el-switch v-model="currentNode.enable" :disabled="dialogTitle == '评分'"></el-switch>
                     </el-form-item>
 
                     <div v-if="hasChildren" class="handler-list">
@@ -46,71 +45,91 @@
 
                     <el-form-item
                         v-if="['确认目标', '评分', '审批'].includes(dialogTitle) && currentNode.children[childIndex].assigneeType === 'leader'">
-                        <el-select v-model="currentNode.children[childIndex].leaderLevel" placeholder="请选择管理员"
-                            :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changeManagerIds">
+                        <el-select :key="idGeneral()" v-model="currentNode.children[childIndex].leaderLevel"
+                            placeholder="请选择管理员" :disabled="!currentNode.enable" filterable style="width: 300px;"
+                            @change="changeManagerIds">
                             <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
                                 :value="item.value" style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>被考核人所在部门的直接上级或上上级 )</div>
                     </el-form-item>
 
                     <el-form-item
                         v-if="['确认目标', '评分', '审批'].includes(dialogTitle) && currentNode.children[childIndex].assigneeType === 'user'">
-                        <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
+                        <el-select :key="idGeneral()" v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;"
                             @change="changeEmployeeIds">
                             <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
                     </el-form-item>
 
                     <el-form-item
                         v-if="['确认目标', '评分', '审批'].includes(dialogTitle) && currentNode.children[childIndex].assigneeType === 'post'">
-                        <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple
+                        <el-select :key="idGeneral()" v-model="selected_post_ids" placeholder="请选择岗位" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changePostIds">
                             <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
                     </el-form-item>
 
                     <el-form-item
                         v-if="['确认目标', '评分', '审批'].includes(dialogTitle) && currentNode.children[childIndex].assigneeType === 'deptLeader'">
-                        <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
-                            :options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
-                            @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                        <el-cascader :key="idGeneral()" ref="deptSelectRef" v-model="selected_dept_ids" size="small"
+                            style="width: 300px;" :options="deptList" :props="cascaderProps" placeholder="请选择部门"
+                            filterable clearable @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+
                     </el-form-item>
 
 
-                    <el-form-item
-                        v-if="['确认目标', '评分', '审批'].includes(dialogTitle) && currentNode.children[childIndex].assigneeType === 'leader'">
-                        <el-select v-model="currentNode.children[childIndex].leaderLevel" placeholder="请选择管理员"
+                    <el-form-item v-if="['录入结果', '抄送'].includes(dialogTitle) && currentNode.assigneeType === 'leader'">
+                        <el-select :key="idGeneral()" v-model="currentNode.leaderLevel" placeholder="请选择管理员"
                             :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changeManagerIds">
                             <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
                                 :value="item.value" style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>被考核人所在部门的直接上级或上上级 )</div>
                     </el-form-item>
 
                     <el-form-item v-if="['录入结果', '抄送'].includes(dialogTitle) && currentNode.assigneeType === 'user'">
-                        <el-select v-model="currentNode.userIds" placeholder="请选择指定人员" multiple
+                        <el-select :key="idGeneral()" v-model="currentNode.userIds" placeholder="请选择指定人员" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;"
                             @change="changeEmployeeIds">
                             <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
+
                     </el-form-item>
 
                     <el-form-item v-if="['录入结果', '抄送'].includes(dialogTitle) && currentNode.assigneeType === 'post'">
-                        <el-select v-model="currentNode.postIds" placeholder="请选择岗位" multiple
+                        <el-select :key="idGeneral()" v-model="currentNode.postIds" placeholder="请选择岗位" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changePostIds">
                             <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
+
                     </el-form-item>
 
                     <el-form-item
                         v-if="['录入结果', '抄送'].includes(dialogTitle) && currentNode.assigneeType === 'deptLeader'">
-                        <el-cascader ref="deptSelectRef" v-model="currentNode.deptIds" size="small"
+                        <el-cascader :key="idGeneral()" ref="deptSelectRef" v-model="currentNode.deptIds" size="small"
                             style="width: 300px;" :options="deptList" :props="cascaderProps" placeholder="请选择部门"
                             filterable clearable @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+
                     </el-form-item>
 
                     <el-form-item v-if="['确认目标', '评分', '审批'].includes(dialogTitle)" label="多人时">
@@ -130,6 +149,27 @@
                         </el-radio-group>
                     </el-form-item>
 
+                    <div v-if="['确认目标', '评分', '审批'].includes(dialogTitle)"
+                        style=" width: 100%; height: 50px; display: flex; flex-direction: column; justify-content: space-around; font-size: 14px; color: #999; padding: 0 0 20px 50px; box-sizing: border-box;">
+                        <div class="flex-box-ce">
+                            <span style="color: red;">*</span><span>会签要求所有审批人一致同意</span>
+
+                        </div>
+                        <div class="flex-box-ce">
+                            <span style="color: red;">*</span><span>或签只需任一审批人同意即可</span>
+                        </div>
+                    </div>
+
+                    <div v-if="['录入结果', '抄送'].includes(dialogTitle)"
+                        style=" width: 100%; height: 50px; display: flex; flex-direction: column; justify-content: space-around; font-size: 14px; color: #999; padding: 0 0 20px 50px; box-sizing: border-box;">
+                        <div class="flex-box-ce">
+                            <span style="color: red;">*</span><span>会签要求所有审批人一致同意</span>
+                        </div>
+                        <div class="flex-box-ce">
+                            <span style="color: red;">*</span><span>或签只需任一审批人同意即可</span>
+                        </div>
+                    </div>
+
                     <el-form-item v-if="['评分'].includes(dialogTitle)" label="权重">
                         <el-input v-model="currentNode.children[childIndex].weight" :disabled="!currentNode.enable"
                             style="width: 200px;">
@@ -137,20 +177,20 @@
                         </el-input>
                     </el-form-item>
 
-                    <el-form-item v-if="['确认目标', '评分', '审批'].includes(dialogTitle)" label="允许">
+                    <el-form-item v-if="['确认目标'].includes(dialogTitle)" label="允许">
                         <el-checkbox-group v-model="currentNode.children[childIndex].allows"
                             :disabled="!currentNode.enable">
-                            <el-checkbox-button label="edit" v-if="dialogTitle == '确认目标'">修改指标</el-checkbox-button>
-                            <el-checkbox-button label="transfer"
-                                v-if="['确认目标', '评分', '审批'].includes(dialogTitle)">转交</el-checkbox-button>
+                            <el-checkbox-button label="edit">修改指标</el-checkbox-button>
+                            <!-- <el-checkbox-button label="transfer"
+                                v-if="['确认目标', '评分', '审批'].includes(dialogTitle)">转交</el-checkbox-button> -->
                         </el-checkbox-group>
                     </el-form-item>
 
-                    <el-form-item v-if="['录入结果'].includes(dialogTitle)" label="允许">
+                    <!-- <el-form-item v-if="['录入结果'].includes(dialogTitle)" label="允许">
                         <el-checkbox-group v-model="currentNode.allows" :disabled="!currentNode.enable">
                             <el-checkbox-button label="transfer">转交</el-checkbox-button>
                         </el-checkbox-group>
-                    </el-form-item>
+                    </el-form-item> -->
 
 
                 </el-form>
@@ -278,6 +318,18 @@ export default {
         this.initData();
     },
     methods: {
+        idGeneral() {
+            var s = [];
+            var hexDigits = "0123456789abcdef";
+            for (var i = 0; i < 36; i++) {
+                s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+            }
+            s[14] = "4"; // 代表UUID版本
+            s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // 时钟序列
+            s[8] = s[13] = s[18] = s[23] = "-";
+            var uuid = s.join("");
+            return uuid;
+        },
         initData() {
             this.selected_manager_ids = ''
             this.selected_employee_ids = [];
@@ -347,12 +399,13 @@ export default {
 
         // 选择部门
         deptChange(val) {
+            
             // 获取当前选中的节点
-            const checkedNodes = that.$refs.deptSelectRef.getCheckedNodes();
+            const checkedNodes = this.$refs.deptSelectRef.getCheckedNodes();
             this.selected_dept_ids = checkedNodes.map((node) => node.value);
             if (this.hasChildren) { 
-                this.currentNode.children[that.childIndex].assigneeIds = []
-                this.currentNode.children[that.childIndex].assigneeIds = this.selected_dept_ids.length ? this.selected_dept_ids : []
+                this.currentNode.children[this.childIndex].assigneeIds = []
+                this.currentNode.children[this.childIndex].assigneeIds = this.selected_dept_ids.length ? this.selected_dept_ids : []
             } else {
                 this.currentNode.assigneeIds = []
                 this.currentNode.assigneeIds = this.selected_dept_ids.length ? this.selected_dept_ids : []
@@ -412,6 +465,13 @@ export default {
                 type = "review"
             }
 
+            let assigneeTypes = ['self', 'leader', 'user', 'post', 'deptLeader'];
+            let childrenAssigneeTypes = this.currentNode.children.map(item => item.assigneeType);
+            // 找出 array1 中有而 array2 中没有的元素
+            const diff1 = assigneeTypes.filter(item => !childrenAssigneeTypes.includes(item));
+            let assigneeType = diff1 && diff1.length > 0 ? diff1[0] : ''
+            if (!assigneeType) return 
+
             let obj = {
                 allows: [],
                 assigneeIds: [],
@@ -454,7 +514,6 @@ export default {
             let data = this.parseNode(this.currentNode)
             let url = `/performance/template/node/${this.user_info.site_id}/${this.templateId}`
             this.$http.post(url, data).then(res => {
-                console.log(res)
                 if (res.code == 1) {
                     this.$message.success("操作成功");
                     this.$emit('close-dialog', false)

+ 21 - 15
src/newPerformance/components/TemplateDetails/CC.vue

@@ -1,7 +1,6 @@
 <template>
     <div>
-        <el-dialog :title="dialogTitle" center :visible.sync="reviews" width="600px"
-            :before-close="handleCloseDialog">
+        <el-dialog :title="dialogTitle" center :visible.sync="reviews" width="600px" :before-close="handleCloseDialog">
             <div v-if="currentNode">
                 <el-form ref="form" label-width="80px" size="small">
                     <el-form-item label="启用">
@@ -9,8 +8,7 @@
                     </el-form-item>
 
                     <el-form-item :label="formLabel">
-                        <el-radio-group v-model="currentNode.assigneeType"
-                            :disabled="!currentNode.enable">
+                        <el-radio-group v-model="currentNode.assigneeType" :disabled="!currentNode.enable">
                             <el-radio-button label="leader">管理员</el-radio-button>
                             <el-radio-button label="user">指定人员</el-radio-button>
                             <el-radio-button label="self">被考核人</el-radio-button>
@@ -21,15 +19,15 @@
 
 
                     <el-form-item v-if="currentNode.assigneeType === 'leader'">
-                        <el-select v-model="selected_manager_ids" placeholder="请选择管理员" :disabled="!currentNode.enable"
-                            filterable style="width: 300px;" @change="changeManagerIds">
+                        <el-select :key="idGeneral()" v-model="selected_manager_ids" placeholder="请选择管理员"
+                            :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changeManagerIds">
                             <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
                                 :value="item.value" style="width: 300px;"></el-option>
                         </el-select>
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.assigneeType === 'user'">
-                        <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
+                        <el-select :key="idGeneral()" v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;"
                             @change="changeEmployeeIds">
                             <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
@@ -38,7 +36,7 @@
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.assigneeType === 'post'">
-                        <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple
+                        <el-select :key="idGeneral()" v-model="selected_post_ids" placeholder="请选择岗位" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changePostIds">
                             <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
@@ -46,9 +44,9 @@
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.assigneeType === 'deptLeader'">
-                        <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
-                            :options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
-                            @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                        <el-cascader :key="idGeneral()" ref="deptSelectRef" v-model="selected_dept_ids" size="small"
+                            style="width: 300px;" :options="deptList" :props="cascaderProps" placeholder="请选择部门"
+                            filterable clearable @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
                     </el-form-item>
 
                 </el-form>
@@ -176,6 +174,18 @@ export default {
         this.initData();
     },
     methods: {
+        idGeneral() {
+            var s = [];
+            var hexDigits = "0123456789abcdef";
+            for (var i = 0; i < 36; i++) {
+                s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+            }
+            s[14] = "4"; // 代表UUID版本
+            s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // 时钟序列
+            s[8] = s[13] = s[18] = s[23] = "-";
+            var uuid = s.join("");
+            return uuid;
+        },
         initData() {
             this.selectNodes.forEach(select => {
                 // 正在操作的节点
@@ -188,8 +198,6 @@ export default {
             this.selected_employee_ids = this.currentNode.assigneeType === 'user' ? this.currentNode.assigneeIds : [];
             this.selected_post_ids = this.currentNode.assigneeType === 'post' ? this.currentNode.assigneeIds : [];
             this.selected_dept_ids = this.currentNode.assigneeType === 'deptLeader' ? this.currentNode.assigneeIds : [];
-            console.log("表单信息");
-            console.log(this.currentNode);
         },
 
 
@@ -284,7 +292,6 @@ export default {
                 // 正在操作的节点
                 if (select.type == this.nodeType) {
                     select = this.currentNode
-                    console.log(select)
                 }
                 nodes.push(select)
             })
@@ -292,7 +299,6 @@ export default {
                 indicatorId: this.indicatorId, // 指标ID
                 nodes
             }
-            console.log(data)
             this.$emit("onConfirm", data);
             this.handleCloseDialog();
         },

+ 54 - 24
src/newPerformance/components/TemplateDetails/CateDetails.vue

@@ -1,7 +1,4 @@
 <template>
-    <!-- <el-dialog title="考核分类明细" center :visible.sync="cateDetailsDialog" width="500px" :before-close="handleCloseDialog">
-        
-    </el-dialog> -->
     <div style="padding: 0 10px; box-sizing: border-box;">
         <div class="flex-box-ce">
             <el-button type="primary" size="mini" @click="addData()">添加分类</el-button>
@@ -11,19 +8,23 @@
         </div>
 
         <el-table :data="cateList" style="width: 100%; margin-top: 10px;" stripe border
-            @selection-change="handleSelectChange" size="small" :header-cell-style="{ background: '#f5f7fa' }">
-            <el-table-column type="selection" align="center"></el-table-column>
-            <el-table-column prop="name" label="分类名称" align="center" min-width="150">
-                <template slot-scope="scope">
-                    <el-input v-model="scope.row.name" placeholder="分类名称" clearable
-                        @blur="handleEdit('name', scope.row.name, scope.row.cateId)"></el-input>
-                </template>
+            @selection-change="handleSelectChange" size="small" :header-cell-style="{ background: '#f5f7fa' }"
+            height="450px">
+            <!-- <el-table-column prop="name" label="分类名称" align="center" min-width="150">
 
-            </el-table-column>
+            </el-table-column> -->
             <el-table-column prop="description" label="分类描述" align="center" min-width="200">
                 <template slot-scope="scope">
-                    <el-input type="textarea" v-model="scope.row.description" placeholder="分类描述" clearable
-                        @blur="handleEdit('desc', scope.row.description, scope.row.cateId)" cols="4"></el-input>
+                    <el-tooltip v-if="scope.row.description" class="item" effect="dark" placement="top">
+                        <div v-html="scope.row.description" slot="content"
+                            style="max-width: 300px; white-space: pre-line;">
+                        </div>
+                        <div class="oneLine" @click="editDescription(scope.row.description, scope.row.cateId)">
+                            {{ scope.row.description }}
+                        </div>
+                    </el-tooltip>
+                    <el-button v-else
+                        @click="editDescription(scope.row.description, scope.row.cateId)">编辑分类描述</el-button>
                 </template>
             </el-table-column>
         </el-table>
@@ -34,12 +35,20 @@
                 layout="total, sizes, prev, pager, next" :total="total">
             </el-pagination>
         </div>
+
+        <!-- 编辑分类描述组件 -->
+        <EditDescriptionComp v-if="showEditDescription" v-model="showEditDescription" :content="description"
+            :cate-id="cateId" @finishEdit="finishEditContent" />
     </div>
 </template>
 
 <script>
 import { mapGetters } from 'vuex';
+import EditDescriptionComp from "./EditDescription" // 编辑分类描述组件
 export default {
+    components: {
+        EditDescriptionComp
+    },
     data() {
         return {
             total: 0,
@@ -47,8 +56,11 @@ export default {
                 page: 1,
                 pageSize: 10
             },
+            cateId: "",
             cateList: [],
             multipleSelection: [],
+            showEditDescription: false,
+            description: ""
         }
     },
     computed: {
@@ -56,12 +68,12 @@ export default {
     },
     watch: {
         cateDetailsDialog(v) {
-            if (v) this.getCateList()
+            // if (v) this.getCateList()
         }
     },
 
     mounted() {
-        this.getCateList()
+        // this.getCateList()
     },
     methods: {
         // 考核分类列表
@@ -92,6 +104,11 @@ export default {
                 url = `/performance/cate/${prop}/${this.user_info.site_id}`;
                 this.$http.post(url, data).then(res => {
                     if (res.code == 0) return this.$message.error(res.msg || '操作失败')
+                    else {
+                        let dataIndex = this.cateList.findIndex(cate => cate.cateId == cateId);
+                        let replaceData = res.data;
+                        this.cateList.splice(dataIndex, 1, replaceData);
+                    }
                 })
             // 无ID新增
             } else {
@@ -110,14 +127,7 @@ export default {
 
         // 创建分类
         addData() {
-            // let url = `/performance/cate/create/${this.user_info.site_id}`,
             let data = { name: '', description: '' };
-            // this.$http.post(url, data).then(res => {
-            //     if (res.code == 1) {
-            //         this.getCateList();
-            //     }
-            //     else return this.$message.error(res.msg || '操作失败')
-            // })
             this.cateList.push(data)
         },
 
@@ -149,11 +159,22 @@ export default {
         delCateApi(item) {
             let url = `/performance/cate/remove/${this.user_info.site_id}/${item.cateId}`
             return this.$axiosUser('post', url).then(res => {
-                // if (res.data.code == 1) this.get_table_data();
                 return res.data
             })
         },
 
+        // 打开编辑规则内容弹框
+        editDescription(content, cateId) {
+            this.description = content
+            this.cateId = cateId;
+            this.showEditDescription = true
+        },
+
+
+        finishEditContent(description) {
+            this.handleEdit('desc', description, this.cateId)
+        },
+
         handleSizeChange(v) {
             this.params.pageSize = v
             this.getCateList();
@@ -169,4 +190,13 @@ export default {
 
     }
 }
-</script>
+</script>
+
+<style>
+
+.oneLine {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+</style>

+ 91 - 0
src/newPerformance/components/TemplateDetails/EditDescription.vue

@@ -0,0 +1,91 @@
+<template>
+    <div>
+        <el-dialog title="编辑规则" center :visible.sync="showEditDescription" width="600px" :before-close="handleCloseDialog" append-to-body>
+            <div class="flex-box-ce" style="justify-content: center;" >
+                <el-input type="textarea" v-model="editContent" placeholder="编辑分类描述(按回车可以添加行号)" clearable :rows="10"
+                    @keyup.enter.native="addLineNumber()"></el-input>
+            </div>
+            <div slot="footer">
+                <el-button type="primary" @click="handleEdit()" size="small">确 定</el-button>
+                <el-button @click="cancel" size="small">取 消</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+
+import { mapGetters } from 'vuex';
+export default {
+    model: {
+        prop: 'showEditDescription',
+        event: 'close-dialog'
+    },
+    props: {
+        showEditDescription: {
+            type: Boolean,
+            default: false
+        },
+        indicatorId: {
+            type: String | Number,
+            default: ''
+        },
+        content: {
+            type: String,
+            default: ''
+        },
+        templateId: {
+            type: String | Number,
+            default: ''
+        },
+    },
+    data() {
+        return {
+            editContent: "",
+        }
+    },
+
+    watch: {
+        showEditContent(v) {
+            if (v) this.editContent = this.content
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+    },
+    mounted() {
+        this.editContent = this.content
+    },
+    methods: {
+        addLineNumber() {
+            let content = this.editContent
+            const lines = content.split('\n').length
+            if (lines === 2 && !content.startsWith('1.')) {
+                content = '1.' + content
+            }
+            content = content + `${lines}.`
+            this.editContent = content;
+        },
+
+        handleEdit() {
+            this.$emit('close-dialog', false);
+            this.$emit('finishEdit', this.editContent);
+        },
+        // 关闭弹窗
+        handleCloseDialog() {
+            this.$emit('close-dialog', false);
+        },
+
+        // 确定按钮
+        submit() {
+
+        },
+        // 重置表单
+        cancel() {
+            this.handleCloseDialog()
+        }
+    }
+}
+</script>
+
+<style scoped="scoped" lang="scss"></style>

+ 12 - 3
src/newPerformance/components/TemplateDetails/FormulaComp.vue

@@ -1,10 +1,10 @@
 <template>
     <div>
-        <el-dialog v-if="showFormula" title="计算公式" center :visible.sync="showFormula" width="60%" :before-close="handleCloseDialog">
+        <el-dialog v-if="showFormula" title="计算公式" center :visible.sync="showFormula" width="60%" append-to-body :before-close="handleCloseDialog">
             <div>
                 <!-- 公式列表 -->
                 <div v-if="tagList && tagList.length > 0" class="handler-list" style="height: 50px;">
-                    <el-tag v-for="(item, index) in tagList" :type="currentIndex === index ? '' : 'info'" :key="index" :closable="isEdit"
+                    <el-tag class="tags" v-for="(item, index) in tagList" :type="currentIndex === index ? '' : 'info'" :key="index" :closable="isEdit"
                         @close="handleTagDelete(index)" @click="handleTagClick(index)">
                         {{ '公式' + (index + 1) }}
                     </el-tag>
@@ -199,7 +199,6 @@ export default {
             }
             index.push(2);
             this.panels = Object.keys(index);
-            console.log(this.expressionsProps);
             this.tagList = this.expressionsProps.map(prop => {
                 return {
                     id: idGenerator(),
@@ -308,12 +307,18 @@ export default {
 <style scoped="scoped" lang="scss">
 .no-bg {
     background-color: transparent;
+    &:hover {
+        cursor: pointer;
+    }
 }
 
 .blue-bg {
     background-color: #2196F3;
     color: white;
     box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
+    &:hover {
+        cursor: pointer;
+    }
 }
 
 .black-border {
@@ -335,6 +340,9 @@ export default {
 
     .el-tag {
         margin: 0 10px 10px 0;
+        &:hover {
+            cursor: pointer;
+        }
     }
 }
 .container {
@@ -411,6 +419,7 @@ export default {
                 transition: all 0.3s linear;
                 &:hover {
                     font-size: 20px;
+                    cursor: pointer;
                 }
             }
         }

+ 3 - 2
src/newPerformance/components/TemplateDetails/HandleNode.vue

@@ -250,8 +250,6 @@ export default {
                     this.form = this.currentNode;
                     break;
             }
-            console.log("表单信息");
-            console.log(this.form);
             // 回显选中的管理者
             if (this.form.assigneeType === 'leader') {
                 if (this.form.leaderLevel) {
@@ -610,7 +608,10 @@ export default {
                 }
 
             }
+<<<<<<< HEAD
+=======
             console.log(data)
+>>>>>>> c23c758ac5b114f2e4b714b9a5908ff46553bd90
             this.$emit("onConfirm", data);
             this.handleCloseDialog();
         },

+ 58 - 63
src/newPerformance/components/TemplateDetails/InterviewFlow.vue

@@ -1,66 +1,63 @@
 <template>
     <div>
-        <el-dialog :title="dialogTitle" center :visible.sync="interview" width="600px"
-            :before-close="handleCloseDialog">
-            <div v-if="currentNode" class="flex-box-ce" style="flex-direction: column;">
-                <el-form ref="form" label-width="80px" size="small">
-
-                    <slot></slot>
-
-                    <el-form-item label="启用">
-                        <el-switch v-model="currentNode.enable"></el-switch>
-                    </el-form-item>
-
-
-                    <el-form-item :label="formLabel">
-                        <el-radio-group v-model="currentNode.assigneeType" :disabled="!currentNode.enable">
-                            <el-radio-button label="leader">管理员</el-radio-button>
-                            <el-radio-button label="user">指定人员</el-radio-button>
-                            <el-radio-button label="self">被考核人</el-radio-button>
-                            <el-radio-button label="post">岗位</el-radio-button>
-                            <el-radio-button label="deptLeader">部门</el-radio-button>
-                        </el-radio-group>
-                    </el-form-item>
-
-
-                    <el-form-item v-if="currentNode.assigneeType === 'leader'">
-                        <el-select v-model="selected_manager_ids" placeholder="请选择管理员" :disabled="!currentNode.enable"
-                            filterable style="width: 300px;" @change="changeManagerIds">
-                            <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
-                                :value="item.value" style="width: 300px;"></el-option>
-                        </el-select>
-                    </el-form-item>
-
-                    <el-form-item v-if="currentNode.assigneeType === 'user'">
-                        <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
-                            :disabled="!currentNode.enable" filterable style="width: 300px;"
-                            @change="changeEmployeeIds">
-                            <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
-                                style="width: 300px;"></el-option>
-                        </el-select>
-                    </el-form-item>
-
-                    <el-form-item v-if="currentNode.assigneeType === 'post'">
-                        <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple
-                            :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changePostIds">
-                            <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
-                                style="width: 300px;"></el-option>
-                        </el-select>
-                    </el-form-item>
-
-                    <el-form-item v-if="currentNode.assigneeType === 'deptLeader'">
-                        <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
-                            :options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
-                            @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
-                    </el-form-item>
-
-                </el-form>
-            </div>
-            <div slot="footer">
-                <el-button type="primary" @click="submitForm" size="small">确 定</el-button>
-                <el-button @click="handleCloseDialog" size="small">取 消</el-button>
-            </div>
-        </el-dialog>
+        <div v-if="currentNode" class="flex-box-ce" style="flex-direction: column;">
+            <el-form ref="form" label-width="120px" size="small" label-position="right">
+
+                <slot></slot>
+
+                <el-form-item label="启用面谈">
+                    <el-switch v-model="currentNode.enable"></el-switch>
+                </el-form-item>
+
+
+                <el-form-item :label="formLabel">
+                    <el-radio-group v-model="currentNode.assigneeType" :disabled="!currentNode.enable">
+                        <el-radio-button label="leader">管理员</el-radio-button>
+                        <el-radio-button label="user">指定人员</el-radio-button>
+                        <el-radio-button label="self">被考核人</el-radio-button>
+                        <el-radio-button label="post">岗位</el-radio-button>
+                        <el-radio-button label="deptLeader">部门</el-radio-button>
+                    </el-radio-group>
+                </el-form-item>
+
+
+                <el-form-item v-if="currentNode.assigneeType === 'leader'">
+                    <el-select v-model="selected_manager_ids" placeholder="请选择管理员" :disabled="!currentNode.enable"
+                        filterable style="width: 300px;" @change="changeManagerIds">
+                        <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
+                            :value="item.value" style="width: 300px;"></el-option>
+                    </el-select>
+                </el-form-item>
+
+                <el-form-item v-if="currentNode.assigneeType === 'user'">
+                    <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
+                        :disabled="!currentNode.enable" filterable style="width: 300px;"
+                        @change="changeEmployeeIds">
+                        <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
+                            style="width: 300px;"></el-option>
+                    </el-select>
+                </el-form-item>
+
+                <el-form-item v-if="currentNode.assigneeType === 'post'">
+                    <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple
+                        :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changePostIds">
+                        <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
+                            style="width: 300px;"></el-option>
+                    </el-select>
+                </el-form-item>
+
+                <el-form-item v-if="currentNode.assigneeType === 'deptLeader'">
+                    <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
+                        :options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
+                        @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                </el-form-item>
+
+            </el-form>
+        </div>
+        <!-- <div slot="footer">
+            <el-button type="primary" @click="submitForm" size="small">发起考核</el-button>
+            <el-button @click="handleCloseDialog" size="small">取 消</el-button>
+        </div> -->
 
     </div>
 </template>
@@ -172,8 +169,6 @@ export default {
                 assigneeType: "self",
                 multipleType: "or"
             }
-            console.log("表单信息");
-            console.log(this.currentNode);
         },
 
 

+ 9 - 9
src/newPerformance/components/TemplateDetails/PublishComp.vue

@@ -43,7 +43,7 @@
                         </el-date-picker>
                     </el-form-item>
                     <el-form-item v-if="form.cycleType == 0" label="结束日期" prop="endDate">
-                        <el-date-picker v-model=" form.endDate" type="date" value-format="yyyy-MM-dd"
+                        <el-date-picker v-model="form.endDate" type="date" value-format="yyyy-MM-dd"
                             placeholder="选择结束日期" style="width: 300px;">
                         </el-date-picker>
                     </el-form-item>
@@ -125,8 +125,8 @@ function getQuarterDates(year, quarter) {
 
 import { mapGetters } from 'vuex';
 import SelectCircle from '@/newPerformance/components/TemplateDetails/SelectCircle'; //选择周期
-import CateDetails from "./CateDetails.vue" // 考核分类明细抽屉
-import InterviewFlow from "./InterviewFlow.vue" // 面谈弹框
+import CateDetails from "@/newPerformance/components/TemplateDetails/CateDetails.vue" // 考核分类明细抽屉
+import InterviewFlow from "@/newPerformance/components/TemplateDetails/InterviewFlow.vue" // 面谈弹框
 import TargetSearch from '@/performance/views/assessManagement/TargetSearch'; // 对齐目标
 
 export default {
@@ -205,12 +205,12 @@ export default {
 
     watch: {
         showPublish(v) {
-            if (v) this.getCateList()
+            // if (v) this.getCateList()
         }
     },
 
     mounted() {
-        this.getCateList()
+        // this.getCateList()
     },
 
     methods: {
@@ -229,7 +229,7 @@ export default {
 
         // 选择考核分类
         changeCateId(v) {
-            console.log(v)
+            // console.log(v)
         },
         // 选择员工
         changeEmployeeIds(v) {
@@ -353,7 +353,6 @@ export default {
                     // this.$emit('onConfirm', this.params);
                     // this.handleCloseDialog();
                 } else {
-                    console.log('error submit!!');
                     return false;
                 }
             });
@@ -374,7 +373,7 @@ export default {
                 }
             },
 
-            this.$emit('onConfirm', this.params, this.selectedData);
+                this.$emit('onConfirm', this.params, this.selectedData);
             this.handleCloseDialog();
         },
 
@@ -409,6 +408,7 @@ export default {
         line-height: 40px;
     }
 }
+
 .title {
     margin-bottom: 5px;
 }
@@ -419,9 +419,9 @@ export default {
     border-radius: 4px;
     background: #f7f7f7;
     margin-bottom: 5px;
+
     &:hover {
         cursor: pointer;
     }
 }
-
 </style>

+ 50 - 14
src/newPerformance/components/TemplateDetails/ResultInput.vue

@@ -21,34 +21,44 @@
 
 
                     <el-form-item v-if="currentNode.assigneeType === 'leader'">
-                        <el-select v-model="selected_manager_ids" placeholder="请选择管理员" :disabled="!currentNode.enable"
-                            filterable style="width: 300px;" @change="changeManagerIds">
+                        <el-select :key="idGeneral()" v-model="selected_manager_ids" placeholder="请选择管理员"
+                            :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changeManagerIds">
                             <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
                                 :value="item.value" style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.assigneeType === 'user'">
-                        <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
+                        <el-select :key="idGeneral()" v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;"
                             @change="changeEmployeeIds">
                             <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.assigneeType === 'post'">
-                        <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple
+                        <el-select :key="idGeneral()" v-model="selected_post_ids" placeholder="请选择岗位" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changePostIds">
                             <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.assigneeType === 'deptLeader'">
-                        <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
-                            :options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
-                            @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                        <el-cascader :key="idGeneral()" ref="deptSelectRef" v-model="selected_dept_ids" size="small"
+                            style="width: 300px;" :options="deptList" :props="cascaderProps" placeholder="请选择部门"
+                            filterable clearable @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
                     </el-form-item>
 
                     <el-form-item label="多人时">
@@ -59,12 +69,23 @@
                         </el-radio-group>
                     </el-form-item>
 
+                    <div
+                        style=" width: 100%; height: 50px; display: flex; flex-direction: column; justify-content: space-around; font-size: 14px; color: #999; padding: 0 0 20px 50px; box-sizing: border-box;">
+                        <div class="flex-box-ce">
+                            <span style="color: red;">*</span><span>会签要求所有审批人一致同意</span>
+
+                        </div>
+                        <div class="flex-box-ce">
+                            <span style="color: red;">*</span><span>或签只需任一审批人同意即可</span>
+                        </div>
+                    </div>
 
-                    <el-form-item label="允许">
+
+                    <!-- <el-form-item label="允许">
                         <el-checkbox-group v-model="currentNode.allows" :disabled="!currentNode.enable">
-                            <el-checkbox-button label="transfer" >转交</el-checkbox-button>
+                            <el-checkbox-button label="transfer">转交</el-checkbox-button>
                         </el-checkbox-group>
-                    </el-form-item>
+                    </el-form-item> -->
                 </el-form>
             </div>
             <div slot="footer">
@@ -182,11 +203,30 @@ export default {
         this.initData();
     },
     methods: {
+
+        idGeneral() {
+            var s = [];
+            var hexDigits = "0123456789abcdef";
+            for (var i = 0; i < 36; i++) {
+                s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+            }
+            s[14] = "4"; // 代表UUID版本
+            s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // 时钟序列
+            s[8] = s[13] = s[18] = s[23] = "-";
+            var uuid = s.join("");
+            return uuid;
+        },
+
+        
+
         initData() {
             this.selectNodes.forEach(select => {
                 // 正在操作的节点
                 if (select.type == this.nodeType) this.currentNode = _.cloneDeep(select)
             })
+            console.log("操作节点")
+            console.log(this.currentNode)
+            this.currentNode.enable = false // 默认禁用
             this.selected_manager_ids = 1;
             this.selected_employee_ids = [];
             this.selected_post_ids = [];
@@ -194,8 +234,6 @@ export default {
             this.selected_employee_ids = this.currentNode.assigneeType === 'user' ? this.currentNode.assigneeIds : [];
             this.selected_post_ids = this.currentNode.assigneeType === 'post' ? this.currentNode.assigneeIds : [];
             this.selected_dept_ids = this.currentNode.assigneeType === 'deptLeader' ? this.currentNode.assigneeIds : [];
-            console.log("表单信息");
-            console.log(this.currentNode);
         },
 
 
@@ -290,7 +328,6 @@ export default {
                 // 正在操作的节点
                 if (select.type == this.nodeType) {
                     select = this.currentNode
-                    console.log(select)
                 }
                 nodes.push(select)
             })
@@ -298,7 +335,6 @@ export default {
                 indicatorId: this.indicatorId, // 指标ID
                 nodes
             }
-            console.log(data)
             this.$emit("onConfirm", data);
             this.handleCloseDialog();
         },

+ 51 - 14
src/newPerformance/components/TemplateDetails/Reviews.vue

@@ -4,8 +4,7 @@
             <div v-if="isShow">
                 <el-form ref="form" label-width="80px" size="small">
                     <el-form-item label="启用">
-                        <el-switch v-model="currentNode.enable"
-                            :disabled="dialogTitle == '评分' || dialogTitle == '审批'"></el-switch>
+                        <el-switch v-model="currentNode.enable" :disabled="dialogTitle == '评分'"></el-switch>
                     </el-form-item>
 
                     <div class="handler-list">
@@ -31,34 +30,42 @@
 
 
                     <el-form-item v-if="currentNode.children[childIndex].assigneeType === 'leader'">
-                        <el-select v-model="selected_manager_ids" placeholder="请选择管理员" :disabled="!currentNode.enable"
-                            filterable style="width: 300px;" @change="changeManagerIds">
+                        <el-select :key="idGeneral()" v-model="selected_manager_ids" placeholder="请选择管理员"
+                            :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changeManagerIds">
                             <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
                                 :value="item.value" style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.children[childIndex].assigneeType === 'user'">
-                        <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
+                        <el-select :key="idGeneral()" v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;"
                             @change="changeEmployeeIds">
                             <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.children[childIndex].assigneeType === 'post'">
-                        <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple
+                        <el-select :key="idGeneral()" v-model="selected_post_ids" placeholder="请选择岗位" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changePostIds">
                             <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.children[childIndex].assigneeType === 'deptLeader'">
-                        <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
-                            :options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
-                            @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                        <el-cascader :key="idGeneral()" ref="deptSelectRef" v-model="selected_dept_ids" size="small"
+                            style="width: 300px;" :options="deptList" :props="cascaderProps" placeholder="请选择部门"
+                            filterable clearable @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
                     </el-form-item>
 
                     <el-form-item label="多人时">
@@ -70,12 +77,23 @@
                         </el-radio-group>
                     </el-form-item>
 
-                    <el-form-item label="允许">
+                    <div
+                        style=" width: 100%; height: 50px; display: flex; flex-direction: column; justify-content: space-around; font-size: 14px; color: #999; padding: 0 0 20px 50px; box-sizing: border-box;">
+                        <div class="flex-box-ce">
+                            <span style="color: red;">*</span><span>会签要求所有审批人一致同意</span>
+
+                        </div>
+                        <div class="flex-box-ce">
+                            <span style="color: red;">*</span><span>或签只需任一审批人同意即可</span>
+                        </div>
+                    </div>
+
+                    <!-- <el-form-item label="允许">
                         <el-checkbox-group v-model="currentNode.children[childIndex].allows"
                             :disabled="!currentNode.enable">
                             <el-checkbox-button label="transfer">转交</el-checkbox-button>
                         </el-checkbox-group>
-                    </el-form-item>
+                    </el-form-item> -->
                 </el-form>
             </div>
             <div slot="footer">
@@ -202,6 +220,20 @@ export default {
         this.initData();
     },
     methods: {
+
+        idGeneral() {
+            var s = [];
+            var hexDigits = "0123456789abcdef";
+            for (var i = 0; i < 36; i++) {
+                s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+            }
+            s[14] = "4"; // 代表UUID版本
+            s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // 时钟序列
+            s[8] = s[13] = s[18] = s[23] = "-";
+            var uuid = s.join("");
+            return uuid;
+        },
+
         initData() {
             this.isShow = false
             this.selectNodes.forEach(select => {
@@ -218,8 +250,7 @@ export default {
                 this.selected_post_ids = child.assigneeType === 'post' ? child.assigneeIds : [];
                 this.selected_dept_ids = child.assigneeType === 'deptLeader' ? child.assigneeIds : [];
             });
-            console.log("表单信息");
-            console.log(this.currentNode);
+            
             this.isShow = true
         },
 
@@ -292,6 +323,13 @@ export default {
                 type = "review"
             }
 
+            let assigneeTypes = ['self', 'leader', 'user', 'post', 'deptLeader'];
+            let childrenAssigneeTypes = this.currentNode.children.map(item => item.assigneeType);
+            // 找出 array1 中有而 array2 中没有的元素
+            const diff1 = assigneeTypes.filter(item => !childrenAssigneeTypes.includes(item));
+            let assigneeType = diff1 && diff1.length > 0 ? diff1[0] : ''
+            if (!assigneeType) return 
+
             let obj = {
                 allows: [],
                 assigneeIds: [],
@@ -379,7 +417,6 @@ export default {
                 // 正在操作的节点
                 if (select.type == this.nodeType) { 
                     select = this.currentNode
-                    console.log(select)
                 } 
                 nodes.push(select)
             })

+ 12 - 20
src/newPerformance/components/TemplateDetails/ScoreEachOther.vue

@@ -9,7 +9,7 @@
                     </el-form-item>
 
 
-                    <el-form-item :label="formLabel">
+                    <!-- <el-form-item :label="formLabel">
                         <el-radio-group v-model="currentNode.assigneeType" :disabled="!currentNode.enable">
                             <el-radio-button label="leader">管理员</el-radio-button>
                             <el-radio-button label="user">指定人员</el-radio-button>
@@ -49,22 +49,15 @@
                         <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
                             :options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
                             @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
-                    </el-form-item>
-
-                    <el-form-item label="多人时">
-                        <el-radio-group v-model="currentNode.multipleType" :disabled="!currentNode.enable">
-                            <el-radio-button label="or">任一人确认(或签)</el-radio-button>
-                            <el-radio-button label="parallel">按顺序确认(会签)</el-radio-button>
-                            <el-radio-button label="sequence">同时确认(会签)</el-radio-button>
-                        </el-radio-group>
-                    </el-form-item>
-
-
-                    <el-form-item label="允许">
-                        <el-checkbox-group v-model="currentNode.allows" :disabled="!currentNode.enable">
-                            <el-checkbox-button label="transfer" >转交</el-checkbox-button>
-                        </el-checkbox-group>
-                    </el-form-item>
+                    </el-form-item> -->
+
+                    <!--                   
+                        <el-form-item label="允许">
+                            <el-checkbox-group v-model="currentNode.allows" :disabled="!currentNode.enable">
+                                <el-checkbox-button label="transfer" >转交</el-checkbox-button>
+                            </el-checkbox-group>
+                        </el-form-item> 
+                    -->
                 </el-form>
             </div>
             <div slot="footer">
@@ -142,13 +135,13 @@ export default {
         this.initData();
     },
     methods: {
+        
+
         initData() {
             this.selectNodes.forEach(select => {
                 // 正在操作的节点
                 if (select.type == this.nodeType) this.currentNode = _.cloneDeep(select)
             })
-            console.log("表单信息");
-            console.log(this.currentNode);
         },
 
        
@@ -170,7 +163,6 @@ export default {
                 indicatorId: this.indicatorId, // 指标ID
                 nodes
             }
-            console.log(data)
             this.$emit("onConfirm", data);
             this.handleCloseDialog();
         },

+ 52 - 12
src/newPerformance/components/TemplateDetails/Scores.vue

@@ -31,34 +31,46 @@
 
 
                     <el-form-item v-if="currentNode.children[childIndex].assigneeType === 'leader'">
-                        <el-select v-model="selected_manager_ids" placeholder="请选择管理员" :disabled="!currentNode.enable"
-                            filterable style="width: 300px;" @change="changeManagerIds">
+                        <el-select :key="idGeneral()" v-model="selected_manager_ids" placeholder="请选择管理员"
+                            :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changeManagerIds">
                             <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
                                 :value="item.value" style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.children[childIndex].assigneeType === 'user'">
-                        <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
+                        <el-select :key="idGeneral()" v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;"
                             @change="changeEmployeeIds">
                             <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
+
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.children[childIndex].assigneeType === 'post'">
-                        <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple
+                        <el-select :key="idGeneral()" v-model="selected_post_ids" placeholder="请选择岗位" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changePostIds">
                             <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
+
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.children[childIndex].assigneeType === 'deptLeader'">
-                        <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
-                            :options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
-                            @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                        <el-cascader :key="idGeneral()" ref="deptSelectRef" v-model="selected_dept_ids" size="small"
+                            style="width: 300px;" :options="deptList" :props="cascaderProps" placeholder="请选择部门"
+                            filterable clearable @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
+
                     </el-form-item>
 
                     <el-form-item label="多人时">
@@ -70,6 +82,17 @@
                         </el-radio-group>
                     </el-form-item>
 
+                    <div
+                        style=" width: 100%; height: 50px; display: flex; flex-direction: column; justify-content: space-around; font-size: 14px; color: #999; padding: 0 0 20px 50px; box-sizing: border-box;">
+                        <div class="flex-box-ce">
+                            <span style="color: red;">*</span><span>会签要求所有审批人一致同意</span>
+
+                        </div>
+                        <div class="flex-box-ce">
+                            <span style="color: red;">*</span><span>或签只需任一审批人同意即可</span>
+                        </div>
+                    </div>
+
                     <el-form-item v-if="['评分'].includes(dialogTitle)" label="权重">
                         <el-input v-model="currentNode.children[childIndex].weight" :disabled="!currentNode.enable"
                             style="width: 200px;">
@@ -77,12 +100,12 @@
                         </el-input>
                     </el-form-item>
 
-                    <el-form-item label="允许">
+                    <!-- <el-form-item label="允许">
                         <el-checkbox-group v-model="currentNode.children[childIndex].allows"
                             :disabled="!currentNode.enable">
                             <el-checkbox-button label="transfer">转交</el-checkbox-button>
                         </el-checkbox-group>
-                    </el-form-item>
+                    </el-form-item> -->
                 </el-form>
             </div>
             <div slot="footer">
@@ -208,6 +231,20 @@ export default {
         this.initData();
     },
     methods: {
+        
+        idGeneral() {
+            var s = [];
+            var hexDigits = "0123456789abcdef";
+            for (var i = 0; i < 36; i++) {
+                s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+            }
+            s[14] = "4"; // 代表UUID版本
+            s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // 时钟序列
+            s[8] = s[13] = s[18] = s[23] = "-";
+            var uuid = s.join("");
+            return uuid;
+        },
+
         initData() {
             this.selectNodes.forEach(select => {
                 // 正在操作的节点
@@ -223,8 +260,6 @@ export default {
                 this.selected_post_ids = child.assigneeType === 'post' ? child.assigneeIds : [];
                 this.selected_dept_ids = child.assigneeType === 'deptLeader' ? child.assigneeIds : [];
             });
-            console.log("表单信息");
-            console.log(this.currentNode);
         },
 
 
@@ -297,6 +332,12 @@ export default {
                 type = "review"
             }
 
+            let assigneeTypes = ['self', 'leader', 'user', 'post', 'deptLeader'];
+            let childrenAssigneeTypes = this.currentNode.children.map(item => item.assigneeType);
+            // 找出 array1 中有而 array2 中没有的元素
+            const diff1 = assigneeTypes.filter(item => !childrenAssigneeTypes.includes(item));
+            let assigneeType = diff1 && diff1.length > 0 ? diff1[0] : ''
+            if (!assigneeType) return 
             let obj = {
                 allows: [],
                 assigneeIds: [],
@@ -384,7 +425,6 @@ export default {
                 // 正在操作的节点
                 if (select.type == this.nodeType) { 
                     select = this.currentNode
-                    console.log(select)
                 } 
                 nodes.push(select)
             })

+ 52 - 12
src/newPerformance/components/TemplateDetails/TargetConfirms.vue

@@ -31,34 +31,42 @@
 
 
                     <el-form-item v-if="currentNode.children[childIndex].assigneeType === 'leader'">
-                        <el-select v-model="selected_manager_ids" placeholder="请选择管理员" :disabled="!currentNode.enable"
-                            filterable style="width: 300px;" @change="changeManagerIds">
+                        <el-select :key="idGeneral()" v-model="selected_manager_ids" placeholder="请选择管理员"
+                            :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changeManagerIds">
                             <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
                                 :value="item.value" style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>被考核人所在部门的直接上级或上上级 )</div>
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.children[childIndex].assigneeType === 'user'">
-                        <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
+                        <el-select :key="idGeneral()" v-model="selected_employee_ids" placeholder="请选择指定人员" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;"
                             @change="changeEmployeeIds">
                             <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>直接选择需要的人员 )</div>
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.children[childIndex].assigneeType === 'post'">
-                        <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple
+                        <el-select :key="idGeneral()" v-model="selected_post_ids" placeholder="请选择岗位" multiple
                             :disabled="!currentNode.enable" filterable style="width: 300px;" @change="changePostIds">
                             <el-option v-for="item in postList" :key="item.id" :label="item.name" :value="item.id"
                                 style="width: 300px;"></el-option>
                         </el-select>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构下,所属岗位的成员 )</div>
                     </el-form-item>
 
                     <el-form-item v-if="currentNode.children[childIndex].assigneeType === 'deptLeader'">
-                        <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px;"
-                            :options="deptList" :props="cascaderProps" placeholder="请选择部门" filterable clearable
-                            @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                        <el-cascader :key="idGeneral()" ref="deptSelectRef" v-model="selected_dept_ids" size="small"
+                            style="width: 300px;" :options="deptList" :props="cascaderProps" placeholder="请选择部门"
+                            filterable clearable @change="deptChange" :disabled="!currentNode.enable"></el-cascader>
+                        <br>
+                        <div class="fontColorC">(<span style="color: red"> * </span>组织架构中,所设置的部门管理员 )</div>
                     </el-form-item>
 
                     <el-form-item label="多人时">
@@ -70,11 +78,22 @@
                         </el-radio-group>
                     </el-form-item>
 
+                    <div
+                        style=" width: 100%; height: 50px; display: flex; flex-direction: column; justify-content: space-around; font-size: 14px; color: #999; padding: 0 0 20px 50px; box-sizing: border-box;">
+                        <div class="flex-box-ce">
+                            <span style="color: red;">*</span><span>会签要求所有审批人一致同意</span>
+
+                        </div>
+                        <div class="flex-box-ce">
+                            <span style="color: red;">*</span><span>或签只需任一审批人同意即可</span>
+                        </div>
+                    </div>
+
                     <el-form-item label="允许">
                         <el-checkbox-group v-model="currentNode.children[childIndex].allows"
                             :disabled="!currentNode.enable">
                             <el-checkbox-button label="edit">修改指标</el-checkbox-button>
-                            <el-checkbox-button label="transfer">转交</el-checkbox-button>
+                            <!-- <el-checkbox-button label="transfer">转交</el-checkbox-button> -->
                         </el-checkbox-group>
                     </el-form-item>
                 </el-form>
@@ -202,6 +221,20 @@ export default {
         this.initData();
     },
     methods: {
+
+        idGeneral() {
+            var s = [];
+            var hexDigits = "0123456789abcdef";
+            for (var i = 0; i < 36; i++) {
+                s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
+            }
+            s[14] = "4"; // 代表UUID版本
+            s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // 时钟序列
+            s[8] = s[13] = s[18] = s[23] = "-";
+            var uuid = s.join("");
+            return uuid;
+        },
+
         initData() {
             this.selectNodes.forEach(select => {
                 // 正在操作的节点
@@ -212,13 +245,14 @@ export default {
             this.selected_post_ids = [];
             this.selected_dept_ids = [];
             this.childIndex = this.tagIndex;
+            console.log("操作节点")
+            console.log(this.currentNode)
+            this.currentNode.enable = false // 默认禁用
             this.currentNode.children.forEach(child => {
                 this.selected_employee_ids = child.assigneeType === 'user' ? child.assigneeIds : [];
                 this.selected_post_ids = child.assigneeType === 'post' ? child.assigneeIds : [];
                 this.selected_dept_ids = child.assigneeType === 'deptLeader' ? child.assigneeIds : [];
             });
-            console.log("表单信息");
-            console.log(this.currentNode);
         },
 
 
@@ -291,10 +325,16 @@ export default {
                 type = "review"
             }
 
+            let assigneeTypes = ['self', 'leader', 'user', 'post', 'deptLeader'];
+            let childrenAssigneeTypes = this.currentNode.children.map(item => item.assigneeType);
+            // 找出 array1 中有而 array2 中没有的元素
+            const diff1 = assigneeTypes.filter(item => !childrenAssigneeTypes.includes(item));
+            let assigneeType = diff1 && diff1.length > 0 ? diff1[0] : ''
+            if (!assigneeType) return 
             let obj = {
                 allows: [],
                 assigneeIds: [],
-                assigneeType: 'self',
+                assigneeType: assigneeType,
                 children: [],
                 enable: true,
                 id,
@@ -344,6 +384,7 @@ export default {
             this.selected_employee_ids = [];
             this.selected_post_ids = [];
             this.selected_dept_ids = [];
+            this.$emit('closeDialog')
             this.$emit('close-dialog', false)
         },
         submitForm() {
@@ -380,7 +421,6 @@ export default {
                 // 正在操作的节点
                 if (select.type == this.nodeType) { 
                     select = this.currentNode
-                    console.log(select)
                 } 
                 nodes.push(select)
             })

+ 19 - 9
src/newPerformance/components/TemplateMixedPublish.vue

@@ -6,7 +6,7 @@
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     center
-    title="通过模板发布考核"
+    :title="title"
     width="600px"
   >
 
@@ -89,8 +89,7 @@
               v-if="publishData.cycleType === '2' || publishData.cycleType === '3'"
               label="日期区间"
             >
-              <SelectCircle :dateParameter="dateParameter" :dateOptions="dateOptions" @confirm="dateConfirm"
-                            :id="1">
+              <SelectCircle :id="1" :dateParameter="dateParameter" :dateOptions="dateOptions" @confirm="dateConfirm" >
                 <div class="flex-box-ce cursor">
                   <div>{{ dateParameter.year }}</div>
                   <div style="margin: 0 10px;">{{ dateParameter.name }}</div>
@@ -165,6 +164,7 @@
               type="success"
               :title="item.name"
               :closable="false"
+              style="margin-bottom: 10px;"
             />
           </div>
         </el-card>
@@ -182,12 +182,12 @@
           align="middle"
           style="height: 30px;"
         >
-          <el-col :span="2">
+          <el-col :span="5">
             <el-button
               type="text"
               icon="el-icon-plus"
               @click.stop="showTemplateSearch = true"
-            />
+            >添加考核表</el-button>
           </el-col>
         </el-row>
         <el-table
@@ -234,7 +234,7 @@
         <el-form
           label-width="100px"
         >
-          <el-form-item label="启用">
+          <el-form-item label="启用面谈">
             <el-switch v-model="interviewNode.enable"></el-switch>
           </el-form-item>
           <el-form-item label="处理人">
@@ -347,12 +347,13 @@
 
 <script>
 
-import Template from "../../examine/components/Template.vue";
+import Template from "@/examine/components/Template.vue";
 import moment from "moment/moment";
 import SelectCircle from '@/newPerformance/components/TemplateDetails/SelectCircle';
 import TargetSearch from "@/performance/views/assessManagement/TargetSearch";
 import TemplateSelector from "./TemplateSelector.vue";
-import PerEmployeeSelector from "./PerEmployeeSelector.vue";
+import PerEmployeeSelector from "@/newPerformance/components/IndicatorSetting/PerEmployeeSelector.vue";
+
 
 export default {
   name: 'TemplateMixedPublish',
@@ -412,6 +413,7 @@ export default {
         weight:'',
         children:[],
       },
+      title: "设置考核表基本信息",
       levelOptions: [
         {
           value: 1,
@@ -609,6 +611,12 @@ export default {
           this.interviewNode.leaderLevel = 1;
           break;
       }
+    },
+    step(v) {
+      if (this.step == 0) this.title = "设置考核表基本信息"
+      if (this.step == 1) this.title = "考核表关联OKR"
+      if (this.step == 2) this.title = "选择考核表及考核人员"
+      if (this.step == 3) this.title = "面谈设置"
     }
   },
   methods: {
@@ -674,7 +682,9 @@ export default {
       this.assigneePosts = [];
       this.assigneeDept = [];
     },
-    initData(){
+    initData() {
+      this.step = 0;
+      this.title = "设置考核表基本信息"
       this.dataReset();
       this.$nextTick(() => {
         this.$refs['carousel'].setActiveItem(this.step);

+ 213 - 38
src/newPerformance/components/TemplateSelector.vue

@@ -1,29 +1,49 @@
 <template>
-  <el-dialog
-    :visible="innerVisible"
-    @close="handleClose"
-    @open="initData"
-    append-to-body
-    width="650px"
-    center
-    title="选择模板"
-  >
-    <el-transfer
-      v-model="selectedValues"
-      :data="templates"
-      :titles="['未选模板', '已选模板']"
-      style="margin-bottom: 20px;"
-    ></el-transfer>
-    <el-row type="flex" justify="end">
-      <el-col :span="4">
-        <el-button type="primary" @click="onConfirm">确定</el-button>
-      </el-col>
-    </el-row>
+  <el-dialog :visible="innerVisible" @close="handleClose" @open="initData" append-to-body width="650px" center
+    title="选择指标库">
+
+    <div>
+      <div class="search-box">
+        <el-input v-model="searchValue" placeholder="请输入指标库名称"></el-input>
+      </div>
+      <div class="package-list">
+
+        <div class="template-list-box">
+          <div class="fontColorC" style="text-align: center; width: 100%; line-height: 30px;">指标库列表</div>
+          <el-checkbox label="全选" v-model="isAllCheck" style="margin-bottom: 10px;"></el-checkbox>
+          <div class="template-list scroll-bar">
+            <div class="template-item" :class="item.isCheck ? 'active' : ''" v-for="item in templateList"
+              :key="item.templateId" @click="chooseTemplate(item)">
+              {{ item.title || '默认标题' }}
+            </div>
+          </div>
+        </div>
+
+        <div class="choose-template-box">
+          <div class="fontColorC" style="text-align: center; width: 100%; line-height: 30px;">已选指标库</div>
+          <el-button plain round size="mini" @click="clear">清 空</el-button>
+          <div class="choose-template scroll-bar">
+            <template v-for="(item, index) in chooseExamineList">
+              <div v-if="item.isCheck" class="flex-box-ce choose-template-item" style="justify-content: space-between;">
+                {{ item.title || '默认标题' }}<i class="el-icon-close" @click="deleteItem(item, index)"></i>
+              </div>
+            </template>
+            <div ref="placeholder" style="height: 50px;"></div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div slot="footer">
+      <el-button @click="handleClose()">取 消</el-button>
+      <el-button type="primary" @click="onConfirm()">确 定</el-button>
+    </div>
   </el-dialog>
 </template>
 
 
 <script>
+import cloneDeep from 'lodash.clonedeep';
+
 export default {
   name: "TemplateSelector",
   props:{
@@ -38,46 +58,120 @@ export default {
   },
   data() {
     return {
+      searchValue: '',
+      isAllCheck: false,
+      chooseExamineList: [],
       userInfo: this.$userInfo(),
       innerVisible: this.showVisible,
       templateList:[],
-      selectedValues:this.selectedTemplate,
+      selectedValues: this.selectedTemplate,
+      chooseExamineList: [],
+      selectExamineList: [], // 显示选择的数据
+      filterData: []
     }
   },
+
+  
   watch: {
+    searchValue(v) {
+      let filterData
+      if (v) filterData = this.filterData.filter(item => item.title.includes(v))
+      else filterData = this.filterData.filter(item => 1 == 1)
+      this.templateList = filterData
+    },
     showVisible(val) {
       this.innerVisible = val;
     },
-  },
-  computed:{
-    templates(){
-      return this.templateList.map(item => {
-        return {
-          key:item.templateId,
-          label:item.title
-        }
-      });
+
+    isAllCheck(v) {
+      if (v) {
+        this.templateList.forEach(item => item.isCheck = true)
+        this.chooseExamineList = this.templateList
+      } else {
+        this.templateList.forEach(item => item.isCheck = false)
+        this.chooseExamineList = []
+      }
     }
   },
+  
   methods: {
     handleClose(){
       this.$emit('update:showVisible',false)
     },
+
+    
     initData(){
       this.templateList = [];
-      this.selectedValues = this.selectedTemplate;
+      this.filterData = []
+      this.selectExamineList = this.selectedTemplate;
+      this.chooseExamineList = this.selectExamineList;
       this.$axiosUser("get", `/performance/template/list/self/${this.userInfo.site_id}`).then(res => {
         this.templateList = res.data.data.list;
+        this.filterData = this.templateList 
+        if (this.templateList && this.templateList.length > 0) {
+          this.templateList.forEach(item => {
+            if (this.chooseExamineList && this.chooseExamineList.length > 0) {
+              let flag = this.chooseExamineList.find(chooseExamine => chooseExamine.templateId == item.templateId)
+              if (flag) {
+                item.isCheck = true
+              } else {
+                item.isCheck = false
+              }
+            }
+          })
+        }
       })
     },
-    onConfirm(){
-      let res = this.templateList.filter(item => this.selectedValues.includes(item.templateId)).map(item => {
-        return {
-          templateId:item.templateId,
-          title:item.title
+
+    chooseTemplate(item) {
+      item.isCheck = !item.isCheck
+      let flag = this.chooseExamineList.find(chooseExamine => chooseExamine.templateId == item.templateId)
+      if (item.isCheck) {
+
+        if (!flag) this.chooseExamineList.push(item)
+      } else {
+        if (flag) {
+          let index = this.chooseExamineList.findIndex(chooseExamine => chooseExamine.templateId == item.templateId)
+          this.chooseExamineList.splice(index, 1)
         }
-      });
-      this.$emit('confirm',res);
+      }
+
+      this.chooseExamineList = Array.from(new Set(this.chooseExamineList.map(JSON.stringify))).map(JSON.parse);
+    },
+
+    deleteItem(item, index) {
+      this.templateList.forEach(examine => {
+
+        if (examine.templateId == item.templateId) {
+          examine.isCheck = false
+        }
+
+      })
+      this.chooseExamineList.splice(index, 1)
+    },
+
+    // 清空选择的考核列表
+    clear() {
+      if (this.chooseExamineList && this.chooseExamineList.length > 0) {
+        this.chooseExamineList.forEach(chooseExamine => {
+          if (this.templateList && this.templateList.length > 0) {
+            this.templateList.forEach(examine => {
+              if (chooseExamine.templateId == examine.templateId) {
+                examine.isCheck = false
+              }
+            })
+          }
+          
+        })
+      }
+      
+      this.isAllCheck = false;
+      this.chooseExamineList = [];
+    },
+
+    onConfirm(){
+      this.selectExamineList = cloneDeep(this.chooseExamineList);
+      this.$emit('confirm', this.selectExamineList);
       this.handleClose();
     }
   }
@@ -85,5 +179,86 @@ export default {
 </script>
 
 <style scoped lang="scss">
+ .search-box {
+   display: flex;
+   align-items: center;
+   justify-content: space-between;
+   box-sizing: border-box;
+ }
+
+ .package-list {
+   width: 100%;
+   height: 400px;
+   display: flex;
+   margin-top: 10px;
+   border: 1px solid #f7f7f7;
 
+   .template-list-box {
+     width: 50%;
+     padding: 10px;
+     box-sizing: border-box;
+     border-right: 1px solid #f7f7f7;
+
+     .template-list {
+       width: 100%;
+       height: 320px;
+       overflow-y: auto;
+       display: flex;
+       flex-direction: column;
+
+       .template-item {
+         width: 100%;
+         height: 30px;
+         line-height: 30px;
+         border: 1px solid #f1f1f1;
+         margin-bottom: 5px;
+         border-radius: 6px;
+         padding-left: 10px;
+         transition: all 0.3s;
+         &:hover {
+          color: #409eff;
+          cursor: pointer;
+         }
+       }
+
+       .active {
+         border: 1px solid #409eff;
+         color: #409eff;
+       }
+     }
+   }
+
+   .choose-template-box {
+     width: 50%;
+     padding: 10px;
+     box-sizing: border-box;
+
+     .choose-template {
+       width: 100%;
+       height: 320px;
+       overflow-y: auto;
+
+       .line {
+         width: 90%;
+         height: 1px;
+         background-color: #f7f7f7;
+         margin: 10px auto;
+       }
+
+       &-item {
+         width: 90%;
+         height: 30px;
+         line-height: 30px;
+         padding-left: 10px;
+         border-bottom: 1px solid #f7f7f7;
+         margin: 0 auto 10px auto;
+         box-sizing: border-box;
+        &:hover {
+          background-color: #f7f7f7;
+          cursor: pointer;
+        }
+       }
+     }
+   }
+ }
 </style>

文件差异内容过多而无法显示
+ 237 - 628
src/newPerformance/components/UploadPublish.vue


文件差异内容过多而无法显示
+ 394 - 379
src/newPerformance/components/Workbench.vue


+ 387 - 115
src/newPerformance/components/Workbench/EditNode.vue

@@ -9,24 +9,38 @@
             <div class="dialog-content" v-loading="loading">
                 <div class="dialog-content-left scroll-bar"
                     style="padding-bottom: 50px; min-height: 500px; overflow-y: auto;">
-
                     <!-- 附加属性 -->
-                    <div v-if="dialogData.expand && dialogData.expand.length > 0" class="tips">自定义属性(只有通过excel上传发布的指标才有)
+                    <div class="append-properties" v-if="dialogData.expand && dialogData.expand.length > 0">
+                        <div class="tips">
+                            自定义属性(只有通过excel上传发布的指标才有)
+                        </div>
+                        <table>
+                            <tr>
+                                <td v-for="item in dialogData.expand" :key="item.p">
+                                    <el-tooltip class="item" effect="dark" placement="top">
+                                        <div v-html="item.k" slot="content" style="max-width: 300px;"></div>
+                                        <div class="oneLine">{{ item.k }}</div>
+                                    </el-tooltip>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td v-for="item in dialogData.expand" :key="item.p">
+                                    <el-tooltip class="item" effect="dark" placement="top">
+                                        <div v-html="item.v" slot="content" style="max-width: 300px;"></div>
+                                        <div class="oneLine">{{ item.v }}</div>
+                                    </el-tooltip>
+                                </td>
+                            </tr>
+                        </table>
                     </div>
-                    <table v-if="dialogData.expand && dialogData.expand.length > 0">
-                        <tr>
-                            <td v-for="item in dialogData.expand" :key="item.p">{{ item.k }}</td>
-                        </tr>
-                        <tr>
-                            <td v-for="item in dialogData.expand" :key="item.p">{{ item.v }}</td>
-                        </tr>
-                    </table>
                     <!-- 附加属性 -->
 
+
+                    <!-- 考核基本信息 -->
                     <div class="base-info-box">
                         <div class="label">
                             <i class="el-icon-user"></i>
-                            考核人
+                            被考核人
                         </div>
                         <div class="value">
                             {{ dialogData.task && dialogData.task.assigneeName || '--' }}
@@ -81,7 +95,7 @@
                             指标
                         </div>
                         <div class="value">
-                            {{ dialogData.title || '--'}}
+                            {{ dialogData.title || '--' }}
                         </div>
                     </div>
 
@@ -146,59 +160,48 @@
                             --
                         </div>
                     </div>
-
+                    <!-- 考核基本信息 -->
 
                 </div>
+
                 <!-- 录入信息 -->
                 <div class="dialog-content-right">
 
                     <div class="data-box" v-if="activeName == 1">
                         <el-input v-model="formData && formData.title" placeholder="指标标题" style="width: 300px;"
-                            clearable @blur="handleEdit('title')" :disabled="!isEdit">
+                            clearable @focus="handleFocus" @blur="handleEdit('title')" :disabled="!isEdit || loading">
                             <template slot="prepend">指标</template>
                         </el-input>
                     </div>
 
-
                     <div class="data-box" v-if="activeName == 1" style="height: 120px;">
                         <el-input type="textarea" v-model="formData && formData.content" placeholder="规则说明"
-                            style="width: 300px;" rows="4" cols="4" clearable @blur="handleEdit('content')"
-                            :disabled="!isEdit">
+                            style="width: 300px;" rows="4" cols="4" clearable @focus="handleFocus"
+                            @blur="handleEdit('content')" :disabled="!isEdit || loading">
                             <template slot="prepend">规则</template>
                         </el-input>
                     </div>
 
 
                     <div class="data-box" v-if="activeName == 1">
-                        <el-input placeholder="目标值(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
-                            v-model="formData && formData.target" style="width: 300px;" :disabled="!isEdit" clearable
+                        <el-input placeholder="目标值(数字)" @input="handleInputTarget" v-model="formData && formData.target"
+                            style="width: 300px;" :disabled="!isEdit || loading" clearable @focus="handleFocus"
                             @blur="handleEdit('target')">
                             <template slot="prepend">目标值</template>
                         </el-input>
                     </div>
 
-
+                    <!-- 单位编辑 -->
                     <div class="data-box" v-if="activeName == 1">
                         <el-input placeholder="单位" v-model="formData && formData.unit" style="width: 300px;" clearable
-                            @blur="handleEdit('unit')" :disabled="!isEdit">
+                            @focus="handleFocus" @blur="handleEdit('unit')" :disabled="!isEdit || loading">
                             <template slot="prepend">单位</template>
                         </el-input>
                     </div>
 
-                    <!-- <div class="data-box" v-if="activeName == 1">
-                        <div class="label">
-                            <i class="el-icon-s-check"></i>
-                            权重
-                        </div>
-                        <div class="value">
-                            <el-input placeholder="权重" v-model="formData.weight" style="width: 300px;" clearable
-                                @blur="handleEdit('weight')"></el-input>
-                        </div>
-                    </div> -->
-
                     <div class="data-box" v-if="activeName == 2">
                         <el-input placeholder="结果值" v-model="formData && formData.result" style="width: 300px;"
-                            clearable @blur="handleEdit('result')">
+                            @focus="handleFocus" @blur="handleEdit('result')" clearable :disabled="loading">
                             <template slot="prepend">结果值</template>
                         </el-input>
                     </div>
@@ -206,19 +209,18 @@
 
                     <!-- 自评 -->
                     <div class="data-box" v-if="activeName == 3">
-                        <el-input :controls="false" placeholder="评分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+                        <el-input :controls="false" placeholder="评分(数字)" @input="handleInputScore"
                             v-model="formData && formData.task && formData.task.score" style="width: 300px;" clearable
-                            @blur="handleEdit('scoreSelf')">
+                            @focus="handleFocus" @blur="handleEdit('scoreSelf')" :disabled="loading">
                             <template slot="prepend">评分</template>
                         </el-input>
                     </div>
 
                     <!-- 互评 -->
-
                     <div class="data-box" v-if="activeName == 4">
-                        <el-input placeholder="评分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+                        <el-input placeholder="评分(数字)" @input="handleInputScore"
                             v-model="formData && formData.task && formData.task.score" style="width: 300px;" clearable
-                            @blur="handleEdit('scoreEachOther')">
+                            @focus="handleFocus" @blur="handleEdit('scoreEachOther')" :disabled="loading">
                             <template slot="prepend">评分</template>
                         </el-input>
                     </div>
@@ -230,11 +232,12 @@
                         <div v-if="dialogData && dialogData.scoreExpression" class="flex-box-ce"
                             style="margin-bottom: 10px; ">
                             <div class="flex-box-ce">系统评分: {{ dialogData.scoreExpression }} </div>
-                            <el-link type="primary" style="margin-left: 20px;" @click="handleScore()">立即评分</el-link>
+                            <el-link type="primary" :disabled="loading" style="margin-left: 20px;"
+                                @click="handleScore()">立即评分</el-link>
                         </div>
-                        <el-input placeholder="评分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+                        <el-input placeholder="评分(数字)" @input="handleInputScore"
                             v-model="formData && formData.task && formData.task.score" style="width: 300px;" clearable
-                            @blur="handleEdit('score')">
+                            @focus="handleFocus" @blur="handleEdit('score')" :disabled="loading">
                             <template slot="prepend">评分</template>
                         </el-input>
                     </div>
@@ -247,19 +250,33 @@
                         </el-input>
                     </div>
 
-                    <div class="data-box">
+                    <div class="data-box" style="height: 160px;">
                         <el-input type="textarea" v-model="comment" placeholder="请输入意见" style="width: 300px;" clearable
-                            rows="5" cols="3" @blur="confirmCommentDialog(1)"></el-input>
+                            rows="5" cols="3" @focus="handleFocus" @blur="confirmCommentDialog(1)"
+                            :disabled="loading"></el-input>
                     </div>
 
-
+                    <uploadOss v-if="activeName > 1 && activeName <= 6" :key="Date.now()" class="avatar-uploader"
+                        :headers="$xtoken" :show-file-list="true" :multiple="true" :limit="5" :accept="acceptFile"
+                        :file-list="fileList" :action="$action" :on-preview="onFilePreView" :on-success="handleSuccess"
+                        :on-remove="handleRemove" :before-upload="beforeFilesUpload">
+                        <el-button class="primaryBtn" icon="el-icon-paperclip" plain size="mini">上传附件</el-button>
+                        <!-- <div slot="tip" class="el-upload__tip">(支持上传xlsx,xls,doc,docx,pdf,txt,png,jpeg,jpg,gif, 大小不能超过5M)</div> -->
+                    </uploadOss>
+
+                    <el-button v-if="activeName > 1 && activeName <= 6" class="primaryBtn" icon="el-icon-check"
+                        type="primary" size="mini" @click="comfirmUploadFiles()"
+                        style="margin-top: 10px;">提交附件</el-button>
                 </div>
                 <!-- 录入信息 -->
             </div>
+
             <div slot="footer">
-                <el-button v-if="activeName == 6" type="danger" @click="reset">驳 回</el-button>
-                <el-button type="primary" @click="confirmCommentDialog(2)">结束任务</el-button>
+                <el-button v-if="activeName == 6" type="danger" @click="reset" :disabled="isInputFocused">驳
+                    回</el-button>
+                <el-button type="primary" @click="confirmCommentDialog(2)" :disabled="isInputFocused">提 交</el-button>
             </div>
+
         </el-dialog>
 
         <!-- 输入意见 -->
@@ -273,7 +290,21 @@
             </div>
         </el-dialog>
 
+        <!-- 各节点处理详情 -->
         <DetailsDialog v-model="detailsDialogVisible" :dialog-data="dialogData" />
+
+        <!-- 图片查看 -->
+        <el-dialog title="图片查看" :visible.sync="isShowImg" width="50%">
+            <div class="flex-box-ce  flex-center-center" style="width: 100%; height: 100%;">
+                <div style="width: 500px;">
+                    <img :src="imgUrl" style="width: 100%;" />
+                </div>
+            </div>
+            <span slot="footer" class="dialog-footer">
+                <el-button @click="closePreview">关闭</el-button>
+            </span>
+        </el-dialog>
+        <!-- 图片查看 -->
     </div>
 
 </template>
@@ -284,9 +315,12 @@ import moment from 'moment';
 import { mapGetters } from 'vuex';
 import cloneDeep from 'lodash.clonedeep';
 import DetailsDialog from "./EditNodeDialog.vue"
+import uploadOss from '@/components/upload';
+import { _debounce } from '@/utils/auth';
 export default {
     components: {
-        DetailsDialog
+        DetailsDialog,
+        uploadOss
     },
     model: {
         prop: 'dialogVisible',
@@ -314,12 +348,13 @@ export default {
     watch: {
         dialogData(v) {
             if (this.dialogData) {
-                this.formData = cloneDeep(this.dialogData)
-                this.comment = this.formData.task.comment
+                this.loading = false;
+                this.formData = cloneDeep(this.dialogData);
+                this.comment = this.formData.task.comment || '';
             }
-            
         }
     },
+    
     filters: {
         formatCycleType(val) {
             if (val == 0) return '未定义'
@@ -337,40 +372,49 @@ export default {
     data() {
         return {
             loading: false,
+            isInputFocused: false,
             commentDialog: false,
+            currentInput: null, // 当前操作的 el-input 的标识
             comment: "",
             score: 0,
             reject: false,
             formData: null, // 用于提交的数据,指标,目标,规则,
             baseInfo: null, // 用于展示的数据,指标,目标,规则,权重,标题
-            detailsDialogVisible: false
+            detailsDialogVisible: false,
+            acceptFile: '.jpg,.jpeg,.png,.gif,.bmp,.pdf,.JPG,.JPEG,.PBG,.GIF,.BMP,.PDF',
+            fileList: [], // 附件列表
+            uploadFileList: [], // 上传成功的fileList
+            isShowImg: false, // 图片预览弹框
+            imgUrl: '', // 预览图片地址
+            isUploadFile: true,
         }
     },
     computed: {
         ...mapGetters(['user_info']),
-        // 是否可以编辑
+
+        // 确认目标是否可以编辑
         isEdit() {
-            let children = [], selectChild = null;
+            let children = [], employeeIds = [];
+            let user_id = this.user_info.id.toString()
             const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
-            if (nodes && nodes.length > 0) {
-                nodes.forEach(node => {
-                    if (node.type === 'targetConfirms') {
-                        children = node.children
+            if (nodes && nodes.length > 0)
+                children = nodes.find(node => node.type === 'targetConfirms').children
+            children.forEach(child => {
+                // 是否允许编辑
+                if (child.allows.includes('edit')) {
+                    if (['leader', 'deptLeader', 'post', 'user'].includes(child.assigneeType)) {
+                        if (child.tasks && child.tasks.length > 0) {
+                            child.tasks.forEach(task => {
+                                employeeIds.push(task.assignee)
+                            })
+                        }
                     }
-                })
-            }
-            if (children && children.length > 0) {
-                children.forEach(child => {
-                    if (child.assigneeIds && child.assigneeIds.length > 0) {
-                        child.assigneeIds.forEach(assigneeId => {
-                            if (assigneeId == this.user_info.id) {
-                                selectChild = child
-                            }
-                        })
+                    else {
+                        employeeIds = [user_id]
                     }
-                })
-            }
-            return selectChild && selectChild.allows && selectChild.allows.length > 0 && selectChild.allows.includes('edit')
+                }
+            })
+            return employeeIds && employeeIds.length > 0 && employeeIds.includes(user_id) ? true : false
         },
         // 整个审批的状态 0,进行中 1,已完成
         reviewStatus() {
@@ -406,15 +450,59 @@ export default {
         if (this.dialogData) {
             this.formData = cloneDeep(this.dialogData);
             this.comment = this.formData && this.formData.task && this.formData.task.comment ? this.formData.task.comment : ''
-        } 
+        }
+        // 监听全局 focus 和 blur 事件
+        document.addEventListener('focusin', this.handleGlobalFocus);
+        document.addEventListener('focusout', this.handleGlobalBlur);
     },
+
+    beforeDestroy() {
+        // 移除全局事件监听器
+        document.removeEventListener('focusin', this.handleGlobalFocus);
+        document.removeEventListener('focusout', this.handleGlobalBlur);
+    },
+    
+
     methods: {
+        handleInputTarget(value) {
+            // 使用正则表达式限制输入
+            const regex = /^([0-9])*$/; // 匹配非负整数
+
+            // 如果输入值不符合正则表达式,则恢复为之前的值
+            if (!regex.test(value)) {
+                value = ''; // 重置输入框的值
+                this.formData.task.score = 0
+            } else {
+                // 如果输入值符合正则表达式,更新绑定的值
+                this.formData.target = Number(value);
+            }
+        },
+
+        handleInputScore(value) {
+            // 使用正则表达式限制输入
+            const regex = /^([0-9])*$/; // 匹配非负整数
+
+            // 如果输入值不符合正则表达式,则恢复为之前的值
+            if (!regex.test(value)) {
+                value = ''; // 重置输入框的值
+                if (this.formData && this.formData.task && this.formData.task.score)
+                    this.formData.task.score = 0
+            } else {
+                // 如果输入值符合正则表达式,更新绑定的值
+                if (this.formData && this.formData.task && this.formData.task.score)
+                    this.formData.task.score = Number(value);
+            }
+        },
 
         // 录入系统评分
         handleScore() {
             this.formData.task.score = this.dialogData.scoreExpression
         },
+
         dialogBeforeClose() {
+            this.formData = null
+            this.imgUrl = "";
+            this.isShowImg = false;
             this.comment = '';
             this.$emit('close-dialog', false)
         },
@@ -423,10 +511,32 @@ export default {
             this.commentDialog = false;
         },
 
+        handleFocus() {
+            // 当 el-input 获得焦点时,设置标志变量为 true
+            this.isInputFocused = true;
+        },
+        handleBlur() {
+            // 当 el-input 失去焦点时,设置标志变量为 false
+            this.isInputFocused = false;
+        },
+        handleGlobalFocus(event) {
+            // 检查触发事件的元素是否是 input
+            if (event.target.tagName.toLowerCase() === 'input') {
+                this.isInputFocused = true;
+                console.log('全局:有 input 获得焦点');
+            }
+        },
+        handleGlobalBlur(event) {
+            // 检查触发事件的元素是否是 input
+            if (event.target.tagName.toLowerCase() === 'input') {
+                this.isInputFocused = false;
+                console.log('全局:input 失去焦点');
+            }
+        },
 
         // 编辑节点
-        handleEdit(type) { 
-            this.loading = true
+        handleEdit: _debounce(function(type) { 
+            this.loading = true;
             let { reviewIndicatorId } = this.dialogData
             let { taskId, nodeType } = this.dialogData.task
             let data = { taskId }
@@ -467,11 +577,14 @@ export default {
                     this.$emit('handleEditSuccess', data)
                 }
                 else this.$message.error(res.message || '操作失败');
-                this.loading = false
+                this.loading = false;
+                this.handleBlur();
             })
-        },
+        }, 100),
 
         confirmCommentDialog(type) {
+            // 阻止事件冒泡,避免触发全局点击事件
+            // event.stopPropagation();
             if(this.loading) return
             let { reviewIndicatorId } = this.dialogData;
             let { taskId, nodeType } = this.dialogData.task
@@ -502,11 +615,21 @@ export default {
                 default:
                     break;
             }
+
+            
             let data = {
                 taskId,
                 comment: this.comment,
                 complete: type == 1 ? false : true // false 暂存,true 提交
             }
+
+            // 提交时,提醒用户要提交附件
+            if (type == 2) {
+                if (!this.isUploadFile) {
+                    return this.$message.warning("上传了附件,记得提交哦")
+                }
+                
+            }
             this.$http.post(url, data).then(res => {
                 if (type == 1) {
                     if (res.code == 1) { }
@@ -520,6 +643,8 @@ export default {
                         this.commentDialog = false;
                         this.comment = "";
                         this.reject = false; // 驳回标识
+                        this.uploadFileList = [];
+                        this.fileList = [];
                     } else {
                         this.$message.error(res.message || '操作失败');
                     }
@@ -527,6 +652,109 @@ export default {
                 
             })
         },
+
+        onFilePreView(file) {
+            let imgFiles = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg'];
+            let lastIndex = file.url && file.url.lastIndexOf("/") || -1
+            let suffix; //文件后缀名
+
+            if (lastIndex > 0) {
+                suffix = file.url.substr(lastIndex + 1, file.url.length - 1).split(".")[1];
+                if (imgFiles.includes(suffix)) {
+                    this.imgUrl = file.url;
+                    this.isShowImg = true;
+                } else {
+                    window.open(file.url, '_blank');
+                }
+            }
+            
+        },
+
+        
+        handleSuccess: _debounce(function (response, file, fileList) {
+            
+            this.uploadFileList = fileList.map(item => {
+                return item.url;
+            });
+            this.isUploadFile = false;
+        }),
+        
+        handleRemove(file, fileList) {
+            setTimeout(() => {
+                this.fileList = fileList;
+                if (!(this.fileList && this.fileList.length > 0)) {
+                    this.isUploadFile = true;
+                }
+            }, 500);
+        },
+
+        beforeFilesUpload(file) {
+            const $ext_list = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg', 'xlsx', 'xls', 'doc', 'docx', 'pdf', 'XLSX', 'XLS', 'DOC', 'DOCX', 'PDF'];
+            const isLt2M = file.size / 1024 / 1024 < 5;
+            let len = file.name.split('.').length - 1;
+            const $ext_name = file.name.split('.')[len];
+            let isFile = $ext_list.indexOf($ext_name) != -1;
+            if (!isLt2M) {
+                this.$message.error('文件大小不能超过 5MB!');
+            }
+            if (!isFile) {
+                this.$message.warning('文件格式上传错误,仅支持上传xlsx,xls,doc,docx,pdf)');
+            }
+            return isFile && isLt2M;
+        },
+
+
+
+
+
+        comfirmUploadFiles() {
+            this.isUploadFile = false;
+            let { reviewIndicatorId } = this.dialogData
+            let { taskId, nodeType } = this.dialogData.task
+            let url;
+
+            switch (nodeType) {
+                // 结果值附件录入
+                case 'resultInput':
+                    url = `/performance/review/result/Input/result/file/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 自评附件录入
+                case 'scoreSelf':
+                    url = `/performance/review/score/self/files/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 互评附件录入
+                case 'scoreEachOther':
+                    url = `/performance/review/score/each/other/files/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 评分附件录入
+                case 'score':
+                    url = `/performance/review/score/files/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 审批附件录入
+                case 'review':
+                    url = `/performance/review/review/files/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                default:
+                    break;
+            }
+            
+            let data = {
+                taskId: taskId,
+                files: this.uploadFileList
+            }
+            this.$http.post(url, data).then(res => {
+                this.isUploadFile = true;
+                let { code } = res
+                if (code == 1) return this.$message.success("上传附件成功")
+                else return this.$message.success("上传附件失败")
+            })
+        },
+
+        closePreview() {
+            this.imgUrl = "";
+            this.isShowImg = false;
+        },
+
         // 驳回
         reset() {
             this.reject = true;
@@ -596,14 +824,91 @@ export default {
     width: 100%;
     height: 450px;
     display: flex;
-    .tips {
-        height: 30px;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        color: #999;
-        margin: 0 auto 10px auto;
+    .append-properties {
+        width: 100%;
+        padding: 10px;
+        box-sizing: border-box;
+        border-radius: 6px;
+        background-color: #f7f7f7;
+        overflow-x: auto;
+        margin-bottom: 5px;
+        /* 设置滚动条的宽度和背景色 */
+        &::-webkit-scrollbar {
+            width: 6px;
+            height: 6px;
+            background-color: #f9f9f9;
+        }
+    
+        /* 设置滚动条滑块的样式 */
+        &::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
+    
+        /* 设置滚动条滑块hover样式 */
+        &::-webkit-scrollbar-thumb:hover {
+            background-color: #a8a8a8;
+        }
+    
+        /* 设置滚动条轨道的样式 */
+        &::-webkit-scrollbar-track {
+            box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+            border-radius: 6px;
+            background: #ededed;
+        }
+        .tips {
+            height: 30px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            color: #999;
+            margin: 0 auto 10px auto;
+        }
+
+        table {
+            width: 100%;
+            table-layout: fixed;
+            border-collapse: collapse;
+            /* 合并表格边框 */
+            border: 1px solid #ccc;
+            /* 设置表格边框样式和颜色 */
+            margin: 0 auto 10px auto;
+            /* 设置表格外边距 */
+            background-color: #f8f8f8;
+            /* 设置表格背景颜色 */
+            color: #000;
+            /* 设置表格文字颜色 */
+            text-align: center;
+            /* 设置表格文字居中 */
+            line-height: 40px;
+            border-radius: 6px;
+
+            /* 设置表格行高 */
+            tr:nth-child(1) {
+                background-color: #f2f2f2;
+                /* 偶数行背景色 */
+            }
+
+            tr:nth-child(even) {
+                background-color: #fff;
+                /* 偶数行背景色 */
+            }
+
+            tr:nth-child(odd) {
+                background-color: #f2f2f2;
+                /* 奇数行背景色 */
+            }
+
+            td {
+                width: 100px;
+                overflow: hidden;
+                white-space: nowrap;
+                text-overflow: ellipsis;
+
+            }
+        }
     }
+    
     .dialog-content-left, .dialog-content-right {
         width: 50%;
         height: 100%;
@@ -619,39 +924,6 @@ export default {
     }
     
 }
-table {
-    width: 100%;
-    border-collapse: collapse;
-    /* 合并表格边框 */
-    border: 1px solid #ccc;
-    /* 设置表格边框样式和颜色 */
-    margin: 0 auto 10px auto;
-    /* 设置表格外边距 */
-    background-color: #f8f8f8;
-    /* 设置表格背景颜色 */
-    color: #000;
-    /* 设置表格文字颜色 */
-    text-align: center;
-    /* 设置表格文字居中 */
-    line-height: 40px;
-    border-radius: 6px;
-
-    /* 设置表格行高 */
-    tr:nth-child(1) {
-        background-color: #f2f2f2;
-        /* 偶数行背景色 */
-    }
-
-    tr:nth-child(even) {
-        background-color: #fff;
-        /* 偶数行背景色 */
-    }
-
-    tr:nth-child(odd) {
-        background-color: #f2f2f2;
-        /* 奇数行背景色 */
-    }
-}
 
 .base-info-box {
     width: 100%;

部分文件因为文件数量过多而无法显示