1
0

2 コミット 568f0d6705 ... c23c758ac5

作者 SHA1 メッセージ 日付
  manywhy c23c758ac5 Merge branch 'dev' of http://git.pro.g107.net/guojy/gdy_pc_8 into dev 3 週間 前
  manywhy ddf793720c 上传新绩效系统 3 週間 前
100 ファイル変更35925 行追加101 行削除
  1. BIN
      dist.rar
  2. 31 5
      package.json
  3. 37 11
      src/App.vue
  4. 3612 0
      src/assets/css/animate.css
  5. BIN
      src/assets/perform/gs1.jpg
  6. BIN
      src/assets/perform/gs2.jpg
  7. BIN
      src/assets/perform/gs3.jpg
  8. BIN
      src/assets/perform/gs4.jpg
  9. 1 1
      src/components/EmployeeSelector.vue
  10. 1 1
      src/components/LoadingAll.vue
  11. 6 6
      src/components/PageHead.vue
  12. 3 2
      src/components/PageHeadTwo.vue
  13. 1 1
      src/components/UserImage.vue
  14. 10 9
      src/components/organization/Post.vue
  15. 44 46
      src/components/system/Jurisdiction.vue
  16. 4 4
      src/home.vue
  17. 5 8
      src/index.vue
  18. 39 1
      src/main.js
  19. 127 0
      src/newPerformance/components/CombinationTemplates/BatchTemplatePublish.vue
  20. 171 0
      src/newPerformance/components/CombinationTemplates/EmployeeChooseOkrs.vue
  21. 546 0
      src/newPerformance/components/ExamineContrast copy 2.vue
  22. 549 0
      src/newPerformance/components/ExamineContrast copy 3.vue
  23. 537 0
      src/newPerformance/components/ExamineContrast copy.vue
  24. 549 0
      src/newPerformance/components/ExamineContrast.vue
  25. 91 0
      src/newPerformance/components/ExamineRecord.vue
  26. 383 0
      src/newPerformance/components/ExamineRecord/LeftExamineRecord.vue
  27. 131 0
      src/newPerformance/components/ExamineRecord/RightEamineComp/EditCateType.vue
  28. 259 0
      src/newPerformance/components/ExamineRecord/RightEamineComp/EditCircle.vue
  29. 145 0
      src/newPerformance/components/ExamineRecord/RightEamineComp/EditScoreList.vue
  30. 78 0
      src/newPerformance/components/ExamineRecord/RightEamineComp/EditTitle.vue
  31. 660 0
      src/newPerformance/components/ExamineRecord/RightExamineRecord.vue
  32. 136 0
      src/newPerformance/components/ExamineSetting.vue
  33. 531 0
      src/newPerformance/components/MyPerformance.vue
  34. 317 0
      src/newPerformance/components/MyPerformance/SelectExamine.vue
  35. 476 0
      src/newPerformance/components/OrganizationExamine.vue
  36. 347 0
      src/newPerformance/components/PerCcSelector.vue
  37. 362 0
      src/newPerformance/components/PerCcSelectorOnly.vue
  38. 104 0
      src/newPerformance/components/PerEmployeeSelector.vue
  39. 758 0
      src/newPerformance/components/PerInterview.vue
  40. 367 0
      src/newPerformance/components/PerResultInputSelector.vue
  41. 382 0
      src/newPerformance/components/PerResultInputSelectorOnly.vue
  42. 722 0
      src/newPerformance/components/PerReviewDetail.vue
  43. 424 0
      src/newPerformance/components/PerReviewsSelector.vue
  44. 442 0
      src/newPerformance/components/PerReviewsSelectorOnly.vue
  45. 428 0
      src/newPerformance/components/PerScoresSelector.vue
  46. 446 0
      src/newPerformance/components/PerScoresSelectorOnly.vue
  47. 426 0
      src/newPerformance/components/PerTargetConfirmSelector.vue
  48. 447 0
      src/newPerformance/components/PerTargetConfirmSelectorOnly.vue
  49. 164 0
      src/newPerformance/components/PerformanceInterview copy.vue
  50. 786 0
      src/newPerformance/components/PerformanceInterview.vue
  51. 355 0
      src/newPerformance/components/PersonalExamine.vue
  52. 297 0
      src/newPerformance/components/PersonalExamine/ExamineDetails copy.vue
  53. 341 0
      src/newPerformance/components/PersonalExamine/ExamineDetails.vue
  54. 108 0
      src/newPerformance/components/PersonalExamine/PieChart1.vue
  55. 109 0
      src/newPerformance/components/PersonalExamine/PieChart2.vue
  56. 111 0
      src/newPerformance/components/PersonalExamine/PieChart3.vue
  57. 111 0
      src/newPerformance/components/PersonalExamine/PieChart4.vue
  58. 138 0
      src/newPerformance/components/PersonalExamine/TableHeaderRender.vue
  59. 203 0
      src/newPerformance/components/PublicComp/ShowData.vue
  60. 208 0
      src/newPerformance/components/PublicComp/ShowHanderDialog.vue
  61. 555 0
      src/newPerformance/components/PublicComp/ShowHanderDialog2.vue
  62. 180 0
      src/newPerformance/components/PublicComp/ShowHandler.vue
  63. 292 0
      src/newPerformance/components/RoleSetting.vue
  64. 516 0
      src/newPerformance/components/TemplateDetails/BatchHandleNode.vue
  65. 319 0
      src/newPerformance/components/TemplateDetails/CC.vue
  66. 172 0
      src/newPerformance/components/TemplateDetails/CateDetails.vue
  67. 100 0
      src/newPerformance/components/TemplateDetails/EditContent.vue
  68. 448 0
      src/newPerformance/components/TemplateDetails/FormulaComp.vue
  69. 640 0
      src/newPerformance/components/TemplateDetails/HandleNode copy.vue
  70. 640 0
      src/newPerformance/components/TemplateDetails/HandleNode.vue
  71. 278 0
      src/newPerformance/components/TemplateDetails/InterviewFlow.vue
  72. 427 0
      src/newPerformance/components/TemplateDetails/PublishComp.vue
  73. 314 0
      src/newPerformance/components/TemplateDetails/ResultInput.vue
  74. 416 0
      src/newPerformance/components/TemplateDetails/Reviews.vue
  75. 186 0
      src/newPerformance/components/TemplateDetails/ScoreEachOther.vue
  76. 421 0
      src/newPerformance/components/TemplateDetails/Scores.vue
  77. 157 0
      src/newPerformance/components/TemplateDetails/SelectCircle.vue
  78. 417 0
      src/newPerformance/components/TemplateDetails/TargetConfirms.vue
  79. 888 0
      src/newPerformance/components/TemplateMixedPublish.vue
  80. 89 0
      src/newPerformance/components/TemplateSelector.vue
  81. 1453 0
      src/newPerformance/components/UploadPublish.vue
  82. 990 0
      src/newPerformance/components/Workbench.vue
  83. 127 0
      src/newPerformance/components/Workbench/BatchTemplatePublish.vue
  84. 1007 0
      src/newPerformance/components/Workbench/EditNode copy.vue
  85. 701 0
      src/newPerformance/components/Workbench/EditNode.vue
  86. 448 0
      src/newPerformance/components/Workbench/EditNodeDialog.vue
  87. 486 0
      src/newPerformance/components/Workbench/IndicatorList copy.vue
  88. 540 0
      src/newPerformance/components/Workbench/IndicatorList.vue
  89. 517 0
      src/newPerformance/components/Workbench/InterviewNode.vue
  90. 580 0
      src/newPerformance/components/Workbench/MoreIndicatorList.vue
  91. 91 0
      src/newPerformance/components/tool/Seal.vue
  92. 68 0
      src/newPerformance/components/tool/WaStstistics.vue
  93. 48 0
      src/newPerformance/utils/exportToExcel.js
  94. 306 0
      src/newPerformance/views/CombinationTemplates.vue
  95. 239 0
      src/newPerformance/views/ExamineDetails.vue
  96. 160 0
      src/newPerformance/views/Index.vue
  97. 786 0
      src/newPerformance/views/PerformanceInterview.vue
  98. 920 0
      src/newPerformance/views/TemplateDetails copy.vue
  99. 921 0
      src/newPerformance/views/TemplateDetails.vue
  100. 16 6
      src/okr/components/TargetDetail/Evolve.vue

BIN
dist.rar


+ 31 - 5
package.json

@@ -26,12 +26,26 @@
     "url": "https://github.com/PanJiaChen/vue-element-admin/issues"
   },
   "dependencies": {
+    "@stomp/stompjs": "^7.0.0",
+    "@tiptap/core": "^2.11.5",
+    "@tiptap/extension-bullet-list": "^2.11.5",
+    "@tiptap/extension-document": "^2.11.5",
+    "@tiptap/extension-gapcursor": "^2.11.5",
+    "@tiptap/extension-hard-break": "^2.11.5",
+    "@tiptap/extension-list-item": "^2.11.5",
+    "@tiptap/extension-ordered-list": "^2.11.5",
+    "@tiptap/extension-paragraph": "^2.11.5",
+    "@tiptap/extension-text": "^2.11.5",
+    "@tiptap/pm": "^2.11.5",
+    "@tiptap/starter-kit": "^2.11.5",
+    "@tiptap/vue-2": "^2.11.5",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "^1.0.2",
     "axios": "^1.6.2",
     "babel-runtime": "^6.26.0",
     "clipboard": "^2.0.11",
     "connect": "^3.7.0",
+    "date-fns": "^4.1.0",
     "dhtmlx-gantt": "^8.0.6",
     "echarts": "^4.9.0",
     "element-ui": "^2.14.1",
@@ -47,7 +61,11 @@
     "pikaz-excel-js": "^0.2.16",
     "qrcodejs2": "0.0.2",
     "qs": "^6.11.2",
+    "sockjs-client": "^1.6.1",
+    "stompjs": "^2.3.3",
     "swiper": "^5.4.5",
+    "tiptap": "^1.32.2",
+    "uglify-js": "^3.19.3",
     "vue": "^2.7.15",
     "vue-baidu-map": "^0.21.20",
     "vue-calendar-component": "^2.8.2",
@@ -58,21 +76,27 @@
     "vue-router-storage": "^1.0.9",
     "vue-seamless-scroll": "^1.1.23",
     "vue-shepherd": "^3.0.0",
+    "vue-tree-chart": "^1.2.9",
+    "vue-tree-color": "^2.3.3",
+    "vue2-org-tree": "^1.3.6",
     "vuex": "^3.6.2",
-    "xlsx": "^0.17.5"
+    "ws": "^8.18.1",
+    "xlsx": "^0.17.5",
+    "xlsx-style": "^0.8.13"
   },
   "devDependencies": {
     "autoprefixer": "8.5.0",
-    "babel-core": "6.26.3",
+    "babel-core": "^6.26.3",
     "babel-eslint": "8.2.6",
     "babel-helper-vue-jsx-merge-props": "2.0.3",
-    "babel-loader": "7.1.5",
+    "babel-loader": "^7.1.5",
     "babel-plugin-dynamic-import-node": "2.0.0",
     "babel-plugin-syntax-jsx": "6.18.0",
+    "babel-plugin-transform-remove-console": "^6.9.4",
     "babel-plugin-transform-runtime": "6.23.0",
     "babel-plugin-transform-vue-jsx": "3.7.0",
     "babel-polyfill": "^6.26.0",
-    "babel-preset-env": "1.7.0",
+    "babel-preset-env": "^1.7.0",
     "babel-preset-stage-2": "6.24.1",
     "chalk": "2.4.1",
     "compression-webpack-plugin": "^1.1.11",
@@ -88,6 +112,8 @@
     "hash-sum": "^1.0.2",
     "html-webpack-plugin": "4.0.0-alpha",
     "husky": "0.14.3",
+    "less": "^3.9.0",
+    "less-loader": "^4.1.0",
     "lint-staged": "7.2.2",
     "mini-css-extract-plugin": "0.4.1",
     "node-notifier": "5.2.1",
@@ -105,7 +131,7 @@
     "serve-static": "1.13.2",
     "shelljs": "0.8.2",
     "svg-sprite-loader": "3.8.0",
-    "uglifyjs-webpack-plugin": "1.2.7",
+    "uglifyjs-webpack-plugin": "2.2.0",
     "url-loader": "1.0.1",
     "vue-loader": "15.3.0",
     "vue-okr-tree": "^1.0.12",

+ 37 - 11
src/App.vue

@@ -14,23 +14,24 @@
         getVersions() {
           let versions=this.$getCache('versions');
           this.$axiosUser('get', '/api/pro/version').then(res => {
-                let {set, version}=res.data.data;
-                if(!set){
-                  return false
-                }
-                if(!versions){
-                  this.$setCache('versions',version);
-                  return false
-                }
-                if(versions!=version){
-                  this.$store.dispatch('LogOut')
-                }
+            let {set, version}=res.data.data;
+            if(!set){
+              return false
+            }
+            if(!versions){
+              this.$setCache('versions',version);
+              return false
+            }
+            if(versions!=version){
+              this.$store.dispatch('LogOut')
+            }
           })
         },
     }
   }
 </script>
 <style>
+  @import "assets/css/animate.css";
   /* 解决element-ui的table表格控件表头与内容列不对齐问题 */
   .el-table th.gutter{
     display: table-cell!important;
@@ -53,4 +54,29 @@
     overflow: initial;
     text-overflow: initial;
   }
+
+  /* 设置滚动条的宽度和背景色 */
+  .el-main::-webkit-scrollbar {
+    width: 10px;
+    height: 10px;
+    background-color: #f9f9f9;
+  }
+
+  /* 设置滚动条滑块的样式 */
+    .el-main::-webkit-scrollbar-thumb {
+    border-radius: 10px;
+    background-color: #c1c1c1;
+  }
+
+  /* 设置滚动条滑块hover样式 */
+  .el-main::-webkit-scrollbar-thumb:hover {
+    background-color: #a8a8a8;
+  }
+
+  /* 设置滚动条轨道的样式 */
+    .el-main::-webkit-scrollbar-track {
+    box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+    border-radius: 10px;
+    background: #ededed;
+  }
 </style>

+ 3612 - 0
src/assets/css/animate.css

@@ -0,0 +1,3612 @@
+@charset "UTF-8";
+
+@-webkit-keyframes bounce {
+
+	from,
+	20%,
+	53%,
+	80%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	40%,
+	43% {
+		-webkit-animation-timing-function: cubic-bezier(.755, .05, .855, .06);
+		animation-timing-function: cubic-bezier(.755, .05, .855, .06);
+		-webkit-transform: translate3d(0, -30px, 0);
+		transform: translate3d(0, -30px, 0)
+	}
+
+	70% {
+		-webkit-animation-timing-function: cubic-bezier(.755, .05, .855, .06);
+		animation-timing-function: cubic-bezier(.755, .05, .855, .06);
+		-webkit-transform: translate3d(0, -15px, 0);
+		transform: translate3d(0, -15px, 0)
+	}
+
+	90% {
+		-webkit-transform: translate3d(0, -4px, 0);
+		transform: translate3d(0, -4px, 0)
+	}
+}
+
+@keyframes bounce {
+
+	from,
+	20%,
+	53%,
+	80%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	40%,
+	43% {
+		-webkit-animation-timing-function: cubic-bezier(.755, .05, .855, .06);
+		animation-timing-function: cubic-bezier(.755, .05, .855, .06);
+		-webkit-transform: translate3d(0, -30px, 0);
+		transform: translate3d(0, -30px, 0)
+	}
+
+	70% {
+		-webkit-animation-timing-function: cubic-bezier(.755, .05, .855, .06);
+		animation-timing-function: cubic-bezier(.755, .05, .855, .06);
+		-webkit-transform: translate3d(0, -15px, 0);
+		transform: translate3d(0, -15px, 0)
+	}
+
+	90% {
+		-webkit-transform: translate3d(0, -4px, 0);
+		transform: translate3d(0, -4px, 0)
+	}
+}
+
+.bounce {
+	-webkit-animation-name: bounce;
+	animation-name: bounce;
+	-webkit-transform-origin: center bottom;
+	transform-origin: center bottom
+}
+
+@-webkit-keyframes flash {
+
+	from,
+	50%,
+	to {
+		opacity: 1
+	}
+
+	25%,
+	75% {
+		opacity: 0
+	}
+}
+
+@keyframes flash {
+
+	from,
+	50%,
+	to {
+		opacity: 1
+	}
+
+	25%,
+	75% {
+		opacity: 0
+	}
+}
+
+.flash {
+	-webkit-animation-name: flash;
+	animation-name: flash
+}
+
+@-webkit-keyframes pulse {
+	from {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+
+	50% {
+		-webkit-transform: scale3d(1.05, 1.05, 1.05);
+		transform: scale3d(1.05, 1.05, 1.05)
+	}
+
+	to {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+}
+
+@keyframes pulse {
+	from {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+
+	50% {
+		-webkit-transform: scale3d(1.05, 1.05, 1.05);
+		transform: scale3d(1.05, 1.05, 1.05)
+	}
+
+	to {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+}
+
+.pulse {
+	-webkit-animation-name: pulse;
+	animation-name: pulse
+}
+
+@-webkit-keyframes rubberBand {
+	from {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+
+	30% {
+		-webkit-transform: scale3d(1.25, .75, 1);
+		transform: scale3d(1.25, .75, 1)
+	}
+
+	40% {
+		-webkit-transform: scale3d(.75, 1.25, 1);
+		transform: scale3d(.75, 1.25, 1)
+	}
+
+	50% {
+		-webkit-transform: scale3d(1.15, .85, 1);
+		transform: scale3d(1.15, .85, 1)
+	}
+
+	65% {
+		-webkit-transform: scale3d(.95, 1.05, 1);
+		transform: scale3d(.95, 1.05, 1)
+	}
+
+	75% {
+		-webkit-transform: scale3d(1.05, .95, 1);
+		transform: scale3d(1.05, .95, 1)
+	}
+
+	to {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+}
+
+@keyframes rubberBand {
+	from {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+
+	30% {
+		-webkit-transform: scale3d(1.25, .75, 1);
+		transform: scale3d(1.25, .75, 1)
+	}
+
+	40% {
+		-webkit-transform: scale3d(.75, 1.25, 1);
+		transform: scale3d(.75, 1.25, 1)
+	}
+
+	50% {
+		-webkit-transform: scale3d(1.15, .85, 1);
+		transform: scale3d(1.15, .85, 1)
+	}
+
+	65% {
+		-webkit-transform: scale3d(.95, 1.05, 1);
+		transform: scale3d(.95, 1.05, 1)
+	}
+
+	75% {
+		-webkit-transform: scale3d(1.05, .95, 1);
+		transform: scale3d(1.05, .95, 1)
+	}
+
+	to {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+}
+
+.rubberBand {
+	-webkit-animation-name: rubberBand;
+	animation-name: rubberBand
+}
+
+@-webkit-keyframes shake {
+
+	from,
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	10%,
+	30%,
+	50%,
+	70%,
+	90% {
+		-webkit-transform: translate3d(-10px, 0, 0);
+		transform: translate3d(-10px, 0, 0)
+	}
+
+	20%,
+	40%,
+	60%,
+	80% {
+		-webkit-transform: translate3d(10px, 0, 0);
+		transform: translate3d(10px, 0, 0)
+	}
+}
+
+@keyframes shake {
+
+	from,
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	10%,
+	30%,
+	50%,
+	70%,
+	90% {
+		-webkit-transform: translate3d(-10px, 0, 0);
+		transform: translate3d(-10px, 0, 0)
+	}
+
+	20%,
+	40%,
+	60%,
+	80% {
+		-webkit-transform: translate3d(10px, 0, 0);
+		transform: translate3d(10px, 0, 0)
+	}
+}
+
+.shake {
+	-webkit-animation-name: shake;
+	animation-name: shake
+}
+
+@-webkit-keyframes headShake {
+	0% {
+		-webkit-transform: translateX(0);
+		transform: translateX(0)
+	}
+
+	6.5% {
+		-webkit-transform: translateX(-6px) rotateY(-9deg);
+		transform: translateX(-6px) rotateY(-9deg)
+	}
+
+	18.5% {
+		-webkit-transform: translateX(5px) rotateY(7deg);
+		transform: translateX(5px) rotateY(7deg)
+	}
+
+	31.5% {
+		-webkit-transform: translateX(-3px) rotateY(-5deg);
+		transform: translateX(-3px) rotateY(-5deg)
+	}
+
+	43.5% {
+		-webkit-transform: translateX(2px) rotateY(3deg);
+		transform: translateX(2px) rotateY(3deg)
+	}
+
+	50% {
+		-webkit-transform: translateX(0);
+		transform: translateX(0)
+	}
+}
+
+@keyframes headShake {
+	0% {
+		-webkit-transform: translateX(0);
+		transform: translateX(0)
+	}
+
+	6.5% {
+		-webkit-transform: translateX(-6px) rotateY(-9deg);
+		transform: translateX(-6px) rotateY(-9deg)
+	}
+
+	18.5% {
+		-webkit-transform: translateX(5px) rotateY(7deg);
+		transform: translateX(5px) rotateY(7deg)
+	}
+
+	31.5% {
+		-webkit-transform: translateX(-3px) rotateY(-5deg);
+		transform: translateX(-3px) rotateY(-5deg)
+	}
+
+	43.5% {
+		-webkit-transform: translateX(2px) rotateY(3deg);
+		transform: translateX(2px) rotateY(3deg)
+	}
+
+	50% {
+		-webkit-transform: translateX(0);
+		transform: translateX(0)
+	}
+}
+
+.headShake {
+	-webkit-animation-timing-function: ease-in-out;
+	animation-timing-function: ease-in-out;
+	-webkit-animation-name: headShake;
+	animation-name: headShake
+}
+
+@-webkit-keyframes swing {
+	20% {
+		-webkit-transform: rotate3d(0, 0, 1, 15deg);
+		transform: rotate3d(0, 0, 1, 15deg)
+	}
+
+	40% {
+		-webkit-transform: rotate3d(0, 0, 1, -10deg);
+		transform: rotate3d(0, 0, 1, -10deg)
+	}
+
+	60% {
+		-webkit-transform: rotate3d(0, 0, 1, 5deg);
+		transform: rotate3d(0, 0, 1, 5deg)
+	}
+
+	80% {
+		-webkit-transform: rotate3d(0, 0, 1, -5deg);
+		transform: rotate3d(0, 0, 1, -5deg)
+	}
+
+	to {
+		-webkit-transform: rotate3d(0, 0, 1, 0deg);
+		transform: rotate3d(0, 0, 1, 0deg)
+	}
+}
+
+@keyframes swing {
+	20% {
+		-webkit-transform: rotate3d(0, 0, 1, 15deg);
+		transform: rotate3d(0, 0, 1, 15deg)
+	}
+
+	40% {
+		-webkit-transform: rotate3d(0, 0, 1, -10deg);
+		transform: rotate3d(0, 0, 1, -10deg)
+	}
+
+	60% {
+		-webkit-transform: rotate3d(0, 0, 1, 5deg);
+		transform: rotate3d(0, 0, 1, 5deg)
+	}
+
+	80% {
+		-webkit-transform: rotate3d(0, 0, 1, -5deg);
+		transform: rotate3d(0, 0, 1, -5deg)
+	}
+
+	to {
+		-webkit-transform: rotate3d(0, 0, 1, 0deg);
+		transform: rotate3d(0, 0, 1, 0deg)
+	}
+}
+
+.swing {
+	-webkit-transform-origin: top center;
+	transform-origin: top center;
+	-webkit-animation-name: swing;
+	animation-name: swing
+}
+
+@-webkit-keyframes tada {
+	from {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+
+	10%,
+	20% {
+		-webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
+		transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)
+	}
+
+	30%,
+	50%,
+	70%,
+	90% {
+		-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
+		transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)
+	}
+
+	40%,
+	60%,
+	80% {
+		-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
+		transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)
+	}
+
+	to {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+}
+
+@keyframes tada {
+	from {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+
+	10%,
+	20% {
+		-webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
+		transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)
+	}
+
+	30%,
+	50%,
+	70%,
+	90% {
+		-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
+		transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)
+	}
+
+	40%,
+	60%,
+	80% {
+		-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
+		transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)
+	}
+
+	to {
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+}
+
+.tada {
+	-webkit-animation-name: tada;
+	animation-name: tada
+}
+
+@-webkit-keyframes wobble {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	15% {
+		-webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
+		transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg)
+	}
+
+	30% {
+		-webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
+		transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg)
+	}
+
+	45% {
+		-webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
+		transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg)
+	}
+
+	60% {
+		-webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
+		transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg)
+	}
+
+	75% {
+		-webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
+		transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg)
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes wobble {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	15% {
+		-webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
+		transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg)
+	}
+
+	30% {
+		-webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
+		transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg)
+	}
+
+	45% {
+		-webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
+		transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg)
+	}
+
+	60% {
+		-webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
+		transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg)
+	}
+
+	75% {
+		-webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
+		transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg)
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.wobble {
+	-webkit-animation-name: wobble;
+	animation-name: wobble
+}
+
+@-webkit-keyframes jello {
+
+	from,
+	11.1%,
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	22.2% {
+		-webkit-transform: skewX(-12.5deg) skewY(-12.5deg);
+		transform: skewX(-12.5deg) skewY(-12.5deg)
+	}
+
+	33.3% {
+		-webkit-transform: skewX(6.25deg) skewY(6.25deg);
+		transform: skewX(6.25deg) skewY(6.25deg)
+	}
+
+	44.4% {
+		-webkit-transform: skewX(-3.125deg) skewY(-3.125deg);
+		transform: skewX(-3.125deg) skewY(-3.125deg)
+	}
+
+	55.5% {
+		-webkit-transform: skewX(1.5625deg) skewY(1.5625deg);
+		transform: skewX(1.5625deg) skewY(1.5625deg)
+	}
+
+	66.6% {
+		-webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg);
+		transform: skewX(-0.78125deg) skewY(-0.78125deg)
+	}
+
+	77.7% {
+		-webkit-transform: skewX(0.390625deg) skewY(0.390625deg);
+		transform: skewX(0.390625deg) skewY(0.390625deg)
+	}
+
+	88.8% {
+		-webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
+		transform: skewX(-0.1953125deg) skewY(-0.1953125deg)
+	}
+}
+
+@keyframes jello {
+
+	from,
+	11.1%,
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	22.2% {
+		-webkit-transform: skewX(-12.5deg) skewY(-12.5deg);
+		transform: skewX(-12.5deg) skewY(-12.5deg)
+	}
+
+	33.3% {
+		-webkit-transform: skewX(6.25deg) skewY(6.25deg);
+		transform: skewX(6.25deg) skewY(6.25deg)
+	}
+
+	44.4% {
+		-webkit-transform: skewX(-3.125deg) skewY(-3.125deg);
+		transform: skewX(-3.125deg) skewY(-3.125deg)
+	}
+
+	55.5% {
+		-webkit-transform: skewX(1.5625deg) skewY(1.5625deg);
+		transform: skewX(1.5625deg) skewY(1.5625deg)
+	}
+
+	66.6% {
+		-webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg);
+		transform: skewX(-0.78125deg) skewY(-0.78125deg)
+	}
+
+	77.7% {
+		-webkit-transform: skewX(0.390625deg) skewY(0.390625deg);
+		transform: skewX(0.390625deg) skewY(0.390625deg)
+	}
+
+	88.8% {
+		-webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
+		transform: skewX(-0.1953125deg) skewY(-0.1953125deg)
+	}
+}
+
+.jello {
+	-webkit-animation-name: jello;
+	animation-name: jello;
+	-webkit-transform-origin: center;
+	transform-origin: center
+}
+
+@-webkit-keyframes heartBeat {
+	0% {
+		-webkit-transform: scale(1);
+		transform: scale(1)
+	}
+
+	14% {
+		-webkit-transform: scale(1.3);
+		transform: scale(1.3)
+	}
+
+	28% {
+		-webkit-transform: scale(1);
+		transform: scale(1)
+	}
+
+	42% {
+		-webkit-transform: scale(1.3);
+		transform: scale(1.3)
+	}
+
+	70% {
+		-webkit-transform: scale(1);
+		transform: scale(1)
+	}
+}
+
+@keyframes heartBeat {
+	0% {
+		-webkit-transform: scale(1);
+		transform: scale(1)
+	}
+
+	14% {
+		-webkit-transform: scale(1.3);
+		transform: scale(1.3)
+	}
+
+	28% {
+		-webkit-transform: scale(1);
+		transform: scale(1)
+	}
+
+	42% {
+		-webkit-transform: scale(1.3);
+		transform: scale(1.3)
+	}
+
+	70% {
+		-webkit-transform: scale(1);
+		transform: scale(1)
+	}
+}
+
+.heartBeat {
+	-webkit-animation-name: heartBeat;
+	animation-name: heartBeat;
+	-webkit-animation-duration: 1.3s;
+	animation-duration: 1.3s;
+	-webkit-animation-timing-function: ease-in-out;
+	animation-timing-function: ease-in-out
+}
+
+@-webkit-keyframes bounceIn {
+
+	from,
+	20%,
+	40%,
+	60%,
+	80%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		animation-timing-function: cubic-bezier(.215, .61, .355, 1)
+	}
+
+	0% {
+		opacity: 0;
+		-webkit-transform: scale3d(.3, .3, .3);
+		transform: scale3d(.3, .3, .3)
+	}
+
+	20% {
+		-webkit-transform: scale3d(1.1, 1.1, 1.1);
+		transform: scale3d(1.1, 1.1, 1.1)
+	}
+
+	40% {
+		-webkit-transform: scale3d(.9, .9, .9);
+		transform: scale3d(.9, .9, .9)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(1.03, 1.03, 1.03);
+		transform: scale3d(1.03, 1.03, 1.03)
+	}
+
+	80% {
+		-webkit-transform: scale3d(.97, .97, .97);
+		transform: scale3d(.97, .97, .97)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+}
+
+@keyframes bounceIn {
+
+	from,
+	20%,
+	40%,
+	60%,
+	80%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		animation-timing-function: cubic-bezier(.215, .61, .355, 1)
+	}
+
+	0% {
+		opacity: 0;
+		-webkit-transform: scale3d(.3, .3, .3);
+		transform: scale3d(.3, .3, .3)
+	}
+
+	20% {
+		-webkit-transform: scale3d(1.1, 1.1, 1.1);
+		transform: scale3d(1.1, 1.1, 1.1)
+	}
+
+	40% {
+		-webkit-transform: scale3d(.9, .9, .9);
+		transform: scale3d(.9, .9, .9)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(1.03, 1.03, 1.03);
+		transform: scale3d(1.03, 1.03, 1.03)
+	}
+
+	80% {
+		-webkit-transform: scale3d(.97, .97, .97);
+		transform: scale3d(.97, .97, .97)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: scale3d(1, 1, 1);
+		transform: scale3d(1, 1, 1)
+	}
+}
+
+.bounceIn {
+	-webkit-animation-duration: 0.75s;
+	animation-duration: 0.75s;
+	-webkit-animation-name: bounceIn;
+	animation-name: bounceIn
+}
+
+@-webkit-keyframes bounceInDown {
+
+	from,
+	60%,
+	75%,
+	90%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		animation-timing-function: cubic-bezier(.215, .61, .355, 1)
+	}
+
+	0% {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -3000px, 0);
+		transform: translate3d(0, -3000px, 0)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 25px, 0);
+		transform: translate3d(0, 25px, 0)
+	}
+
+	75% {
+		-webkit-transform: translate3d(0, -10px, 0);
+		transform: translate3d(0, -10px, 0)
+	}
+
+	90% {
+		-webkit-transform: translate3d(0, 5px, 0);
+		transform: translate3d(0, 5px, 0)
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes bounceInDown {
+
+	from,
+	60%,
+	75%,
+	90%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		animation-timing-function: cubic-bezier(.215, .61, .355, 1)
+	}
+
+	0% {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -3000px, 0);
+		transform: translate3d(0, -3000px, 0)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 25px, 0);
+		transform: translate3d(0, 25px, 0)
+	}
+
+	75% {
+		-webkit-transform: translate3d(0, -10px, 0);
+		transform: translate3d(0, -10px, 0)
+	}
+
+	90% {
+		-webkit-transform: translate3d(0, 5px, 0);
+		transform: translate3d(0, 5px, 0)
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.bounceInDown {
+	-webkit-animation-name: bounceInDown;
+	animation-name: bounceInDown
+}
+
+@-webkit-keyframes bounceInLeft {
+
+	from,
+	60%,
+	75%,
+	90%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		animation-timing-function: cubic-bezier(.215, .61, .355, 1)
+	}
+
+	0% {
+		opacity: 0;
+		-webkit-transform: translate3d(-3000px, 0, 0);
+		transform: translate3d(-3000px, 0, 0)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: translate3d(25px, 0, 0);
+		transform: translate3d(25px, 0, 0)
+	}
+
+	75% {
+		-webkit-transform: translate3d(-10px, 0, 0);
+		transform: translate3d(-10px, 0, 0)
+	}
+
+	90% {
+		-webkit-transform: translate3d(5px, 0, 0);
+		transform: translate3d(5px, 0, 0)
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes bounceInLeft {
+
+	from,
+	60%,
+	75%,
+	90%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		animation-timing-function: cubic-bezier(.215, .61, .355, 1)
+	}
+
+	0% {
+		opacity: 0;
+		-webkit-transform: translate3d(-3000px, 0, 0);
+		transform: translate3d(-3000px, 0, 0)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: translate3d(25px, 0, 0);
+		transform: translate3d(25px, 0, 0)
+	}
+
+	75% {
+		-webkit-transform: translate3d(-10px, 0, 0);
+		transform: translate3d(-10px, 0, 0)
+	}
+
+	90% {
+		-webkit-transform: translate3d(5px, 0, 0);
+		transform: translate3d(5px, 0, 0)
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.bounceInLeft {
+	-webkit-animation-name: bounceInLeft;
+	animation-name: bounceInLeft
+}
+
+@-webkit-keyframes bounceInRight {
+
+	from,
+	60%,
+	75%,
+	90%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		animation-timing-function: cubic-bezier(.215, .61, .355, 1)
+	}
+
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(3000px, 0, 0);
+		transform: translate3d(3000px, 0, 0)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: translate3d(-25px, 0, 0);
+		transform: translate3d(-25px, 0, 0)
+	}
+
+	75% {
+		-webkit-transform: translate3d(10px, 0, 0);
+		transform: translate3d(10px, 0, 0)
+	}
+
+	90% {
+		-webkit-transform: translate3d(-5px, 0, 0);
+		transform: translate3d(-5px, 0, 0)
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes bounceInRight {
+
+	from,
+	60%,
+	75%,
+	90%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		animation-timing-function: cubic-bezier(.215, .61, .355, 1)
+	}
+
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(3000px, 0, 0);
+		transform: translate3d(3000px, 0, 0)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: translate3d(-25px, 0, 0);
+		transform: translate3d(-25px, 0, 0)
+	}
+
+	75% {
+		-webkit-transform: translate3d(10px, 0, 0);
+		transform: translate3d(10px, 0, 0)
+	}
+
+	90% {
+		-webkit-transform: translate3d(-5px, 0, 0);
+		transform: translate3d(-5px, 0, 0)
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.bounceInRight {
+	-webkit-animation-name: bounceInRight;
+	animation-name: bounceInRight
+}
+
+@-webkit-keyframes bounceInUp {
+
+	from,
+	60%,
+	75%,
+	90%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		animation-timing-function: cubic-bezier(.215, .61, .355, 1)
+	}
+
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 3000px, 0);
+		transform: translate3d(0, 3000px, 0)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: translate3d(0, -20px, 0);
+		transform: translate3d(0, -20px, 0)
+	}
+
+	75% {
+		-webkit-transform: translate3d(0, 10px, 0);
+		transform: translate3d(0, 10px, 0)
+	}
+
+	90% {
+		-webkit-transform: translate3d(0, -5px, 0);
+		transform: translate3d(0, -5px, 0)
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes bounceInUp {
+
+	from,
+	60%,
+	75%,
+	90%,
+	to {
+		-webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
+		animation-timing-function: cubic-bezier(.215, .61, .355, 1)
+	}
+
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 3000px, 0);
+		transform: translate3d(0, 3000px, 0)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: translate3d(0, -20px, 0);
+		transform: translate3d(0, -20px, 0)
+	}
+
+	75% {
+		-webkit-transform: translate3d(0, 10px, 0);
+		transform: translate3d(0, 10px, 0)
+	}
+
+	90% {
+		-webkit-transform: translate3d(0, -5px, 0);
+		transform: translate3d(0, -5px, 0)
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.bounceInUp {
+	-webkit-animation-name: bounceInUp;
+	animation-name: bounceInUp
+}
+
+@-webkit-keyframes bounceOut {
+	20% {
+		-webkit-transform: scale3d(.9, .9, .9);
+		transform: scale3d(.9, .9, .9)
+	}
+
+	50%,
+	55% {
+		opacity: 1;
+		-webkit-transform: scale3d(1.1, 1.1, 1.1);
+		transform: scale3d(1.1, 1.1, 1.1)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale3d(.3, .3, .3);
+		transform: scale3d(.3, .3, .3)
+	}
+}
+
+@keyframes bounceOut {
+	20% {
+		-webkit-transform: scale3d(.9, .9, .9);
+		transform: scale3d(.9, .9, .9)
+	}
+
+	50%,
+	55% {
+		opacity: 1;
+		-webkit-transform: scale3d(1.1, 1.1, 1.1);
+		transform: scale3d(1.1, 1.1, 1.1)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale3d(.3, .3, .3);
+		transform: scale3d(.3, .3, .3)
+	}
+}
+
+.bounceOut {
+	-webkit-animation-duration: 0.75s;
+	animation-duration: 0.75s;
+	-webkit-animation-name: bounceOut;
+	animation-name: bounceOut
+}
+
+@-webkit-keyframes bounceOutDown {
+	20% {
+		-webkit-transform: translate3d(0, 10px, 0);
+		transform: translate3d(0, 10px, 0)
+	}
+
+	40%,
+	45% {
+		opacity: 1;
+		-webkit-transform: translate3d(0, -20px, 0);
+		transform: translate3d(0, -20px, 0)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 2000px, 0);
+		transform: translate3d(0, 2000px, 0)
+	}
+}
+
+@keyframes bounceOutDown {
+	20% {
+		-webkit-transform: translate3d(0, 10px, 0);
+		transform: translate3d(0, 10px, 0)
+	}
+
+	40%,
+	45% {
+		opacity: 1;
+		-webkit-transform: translate3d(0, -20px, 0);
+		transform: translate3d(0, -20px, 0)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 2000px, 0);
+		transform: translate3d(0, 2000px, 0)
+	}
+}
+
+.bounceOutDown {
+	-webkit-animation-name: bounceOutDown;
+	animation-name: bounceOutDown
+}
+
+@-webkit-keyframes bounceOutLeft {
+	20% {
+		opacity: 1;
+		-webkit-transform: translate3d(20px, 0, 0);
+		transform: translate3d(20px, 0, 0)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(-2000px, 0, 0);
+		transform: translate3d(-2000px, 0, 0)
+	}
+}
+
+@keyframes bounceOutLeft {
+	20% {
+		opacity: 1;
+		-webkit-transform: translate3d(20px, 0, 0);
+		transform: translate3d(20px, 0, 0)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(-2000px, 0, 0);
+		transform: translate3d(-2000px, 0, 0)
+	}
+}
+
+.bounceOutLeft {
+	-webkit-animation-name: bounceOutLeft;
+	animation-name: bounceOutLeft
+}
+
+@-webkit-keyframes bounceOutRight {
+	20% {
+		opacity: 1;
+		-webkit-transform: translate3d(-20px, 0, 0);
+		transform: translate3d(-20px, 0, 0)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(2000px, 0, 0);
+		transform: translate3d(2000px, 0, 0)
+	}
+}
+
+@keyframes bounceOutRight {
+	20% {
+		opacity: 1;
+		-webkit-transform: translate3d(-20px, 0, 0);
+		transform: translate3d(-20px, 0, 0)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(2000px, 0, 0);
+		transform: translate3d(2000px, 0, 0)
+	}
+}
+
+.bounceOutRight {
+	-webkit-animation-name: bounceOutRight;
+	animation-name: bounceOutRight
+}
+
+@-webkit-keyframes bounceOutUp {
+	20% {
+		-webkit-transform: translate3d(0, -10px, 0);
+		transform: translate3d(0, -10px, 0)
+	}
+
+	40%,
+	45% {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 20px, 0);
+		transform: translate3d(0, 20px, 0)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -2000px, 0);
+		transform: translate3d(0, -2000px, 0)
+	}
+}
+
+@keyframes bounceOutUp {
+	20% {
+		-webkit-transform: translate3d(0, -10px, 0);
+		transform: translate3d(0, -10px, 0)
+	}
+
+	40%,
+	45% {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 20px, 0);
+		transform: translate3d(0, 20px, 0)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -2000px, 0);
+		transform: translate3d(0, -2000px, 0)
+	}
+}
+
+.bounceOutUp {
+	-webkit-animation-name: bounceOutUp;
+	animation-name: bounceOutUp
+}
+
+@-webkit-keyframes fadeIn {
+	from {
+		opacity: 0
+	}
+
+	to {
+		opacity: 1
+	}
+}
+
+@keyframes fadeIn {
+	from {
+		opacity: 0
+	}
+
+	to {
+		opacity: 1
+	}
+}
+
+.fadeIn {
+	-webkit-animation-name: fadeIn;
+	animation-name: fadeIn
+}
+
+@-webkit-keyframes fadeInDown {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -100%, 0);
+		transform: translate3d(0, -100%, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes fadeInDown {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -100%, 0);
+		transform: translate3d(0, -100%, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.fadeInDown {
+	-webkit-animation-name: fadeInDown;
+	animation-name: fadeInDown
+}
+
+@-webkit-keyframes fadeInDownBig {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -2000px, 0);
+		transform: translate3d(0, -2000px, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes fadeInDownBig {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -2000px, 0);
+		transform: translate3d(0, -2000px, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.fadeInDownBig {
+	-webkit-animation-name: fadeInDownBig;
+	animation-name: fadeInDownBig
+}
+
+@-webkit-keyframes fadeInLeft {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(-100%, 0, 0);
+		transform: translate3d(-100%, 0, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes fadeInLeft {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(-100%, 0, 0);
+		transform: translate3d(-100%, 0, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.fadeInLeft {
+	-webkit-animation-name: fadeInLeft;
+	animation-name: fadeInLeft
+}
+
+@-webkit-keyframes fadeInLeftBig {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(-2000px, 0, 0);
+		transform: translate3d(-2000px, 0, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes fadeInLeftBig {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(-2000px, 0, 0);
+		transform: translate3d(-2000px, 0, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.fadeInLeftBig {
+	-webkit-animation-name: fadeInLeftBig;
+	animation-name: fadeInLeftBig
+}
+
+@-webkit-keyframes fadeInRight {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(100%, 0, 0);
+		transform: translate3d(100%, 0, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes fadeInRight {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(100%, 0, 0);
+		transform: translate3d(100%, 0, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.fadeInRight {
+	-webkit-animation-name: fadeInRight;
+	animation-name: fadeInRight
+}
+
+@-webkit-keyframes fadeInRightBig {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(2000px, 0, 0);
+		transform: translate3d(2000px, 0, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes fadeInRightBig {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(2000px, 0, 0);
+		transform: translate3d(2000px, 0, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.fadeInRightBig {
+	-webkit-animation-name: fadeInRightBig;
+	animation-name: fadeInRightBig
+}
+
+@-webkit-keyframes fadeInUp {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 100%, 0);
+		transform: translate3d(0, 100%, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes fadeInUp {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 100%, 0);
+		transform: translate3d(0, 100%, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.fadeInUp {
+	-webkit-animation-name: fadeInUp;
+	animation-name: fadeInUp
+}
+
+@-webkit-keyframes fadeInUpBig {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 2000px, 0);
+		transform: translate3d(0, 2000px, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes fadeInUpBig {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 2000px, 0);
+		transform: translate3d(0, 2000px, 0)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.fadeInUpBig {
+	-webkit-animation-name: fadeInUpBig;
+	animation-name: fadeInUpBig
+}
+
+@-webkit-keyframes fadeOut {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0
+	}
+}
+
+@keyframes fadeOut {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0
+	}
+}
+
+.fadeOut {
+	-webkit-animation-name: fadeOut;
+	animation-name: fadeOut
+}
+
+@-webkit-keyframes fadeOutDown {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 100%, 0);
+		transform: translate3d(0, 100%, 0)
+	}
+}
+
+@keyframes fadeOutDown {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 100%, 0);
+		transform: translate3d(0, 100%, 0)
+	}
+}
+
+.fadeOutDown {
+	-webkit-animation-name: fadeOutDown;
+	animation-name: fadeOutDown
+}
+
+@-webkit-keyframes fadeOutDownBig {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 2000px, 0);
+		transform: translate3d(0, 2000px, 0)
+	}
+}
+
+@keyframes fadeOutDownBig {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, 2000px, 0);
+		transform: translate3d(0, 2000px, 0)
+	}
+}
+
+.fadeOutDownBig {
+	-webkit-animation-name: fadeOutDownBig;
+	animation-name: fadeOutDownBig
+}
+
+@-webkit-keyframes fadeOutLeft {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(-100%, 0, 0);
+		transform: translate3d(-100%, 0, 0)
+	}
+}
+
+@keyframes fadeOutLeft {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(-100%, 0, 0);
+		transform: translate3d(-100%, 0, 0)
+	}
+}
+
+.fadeOutLeft {
+	-webkit-animation-name: fadeOutLeft;
+	animation-name: fadeOutLeft
+}
+
+@-webkit-keyframes fadeOutLeftBig {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(-2000px, 0, 0);
+		transform: translate3d(-2000px, 0, 0)
+	}
+}
+
+@keyframes fadeOutLeftBig {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(-2000px, 0, 0);
+		transform: translate3d(-2000px, 0, 0)
+	}
+}
+
+.fadeOutLeftBig {
+	-webkit-animation-name: fadeOutLeftBig;
+	animation-name: fadeOutLeftBig
+}
+
+@-webkit-keyframes fadeOutRight {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(100%, 0, 0);
+		transform: translate3d(100%, 0, 0)
+	}
+}
+
+@keyframes fadeOutRight {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(100%, 0, 0);
+		transform: translate3d(100%, 0, 0)
+	}
+}
+
+.fadeOutRight {
+	-webkit-animation-name: fadeOutRight;
+	animation-name: fadeOutRight
+}
+
+@-webkit-keyframes fadeOutRightBig {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(2000px, 0, 0);
+		transform: translate3d(2000px, 0, 0)
+	}
+}
+
+@keyframes fadeOutRightBig {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(2000px, 0, 0);
+		transform: translate3d(2000px, 0, 0)
+	}
+}
+
+.fadeOutRightBig {
+	-webkit-animation-name: fadeOutRightBig;
+	animation-name: fadeOutRightBig
+}
+
+@-webkit-keyframes fadeOutUp {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -100%, 0);
+		transform: translate3d(0, -100%, 0)
+	}
+}
+
+@keyframes fadeOutUp {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -100%, 0);
+		transform: translate3d(0, -100%, 0)
+	}
+}
+
+.fadeOutUp {
+	-webkit-animation-name: fadeOutUp;
+	animation-name: fadeOutUp
+}
+
+@-webkit-keyframes fadeOutUpBig {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -2000px, 0);
+		transform: translate3d(0, -2000px, 0)
+	}
+}
+
+@keyframes fadeOutUpBig {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(0, -2000px, 0);
+		transform: translate3d(0, -2000px, 0)
+	}
+}
+
+.fadeOutUpBig {
+	-webkit-animation-name: fadeOutUpBig;
+	animation-name: fadeOutUpBig
+}
+
+@-webkit-keyframes flip {
+	from {
+		-webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg);
+		transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg);
+		-webkit-animation-timing-function: ease-out;
+		animation-timing-function: ease-out
+	}
+
+	40% {
+		-webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg);
+		transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg);
+		-webkit-animation-timing-function: ease-out;
+		animation-timing-function: ease-out
+	}
+
+	50% {
+		-webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg);
+		transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in
+	}
+
+	80% {
+		-webkit-transform: perspective(400px) scale3d(.95, .95, .95) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg);
+		transform: perspective(400px) scale3d(.95, .95, .95) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in
+	}
+
+	to {
+		-webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg);
+		transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in
+	}
+}
+
+@keyframes flip {
+	from {
+		-webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg);
+		transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg);
+		-webkit-animation-timing-function: ease-out;
+		animation-timing-function: ease-out
+	}
+
+	40% {
+		-webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg);
+		transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg);
+		-webkit-animation-timing-function: ease-out;
+		animation-timing-function: ease-out
+	}
+
+	50% {
+		-webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg);
+		transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in
+	}
+
+	80% {
+		-webkit-transform: perspective(400px) scale3d(.95, .95, .95) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg);
+		transform: perspective(400px) scale3d(.95, .95, .95) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in
+	}
+
+	to {
+		-webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg);
+		transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in
+	}
+}
+
+.animated.flip {
+	-webkit-backface-visibility: visible;
+	backface-visibility: visible;
+	-webkit-animation-name: flip;
+	animation-name: flip
+}
+
+@-webkit-keyframes flipInX {
+	from {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in;
+		opacity: 0
+	}
+
+	40% {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in
+	}
+
+	60% {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
+		opacity: 1
+	}
+
+	80% {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, -5deg)
+	}
+
+	to {
+		-webkit-transform: perspective(400px);
+		transform: perspective(400px)
+	}
+}
+
+@keyframes flipInX {
+	from {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in;
+		opacity: 0
+	}
+
+	40% {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in
+	}
+
+	60% {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
+		opacity: 1
+	}
+
+	80% {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, -5deg)
+	}
+
+	to {
+		-webkit-transform: perspective(400px);
+		transform: perspective(400px)
+	}
+}
+
+.flipInX {
+	-webkit-backface-visibility: visible !important;
+	backface-visibility: visible !important;
+	-webkit-animation-name: flipInX;
+	animation-name: flipInX
+}
+
+@-webkit-keyframes flipInY {
+	from {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in;
+		opacity: 0
+	}
+
+	40% {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in
+	}
+
+	60% {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
+		opacity: 1
+	}
+
+	80% {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, -5deg)
+	}
+
+	to {
+		-webkit-transform: perspective(400px);
+		transform: perspective(400px)
+	}
+}
+
+@keyframes flipInY {
+	from {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in;
+		opacity: 0
+	}
+
+	40% {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
+		-webkit-animation-timing-function: ease-in;
+		animation-timing-function: ease-in
+	}
+
+	60% {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
+		opacity: 1
+	}
+
+	80% {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, -5deg)
+	}
+
+	to {
+		-webkit-transform: perspective(400px);
+		transform: perspective(400px)
+	}
+}
+
+.flipInY {
+	-webkit-backface-visibility: visible !important;
+	backface-visibility: visible !important;
+	-webkit-animation-name: flipInY;
+	animation-name: flipInY
+}
+
+@-webkit-keyframes flipOutX {
+	from {
+		-webkit-transform: perspective(400px);
+		transform: perspective(400px)
+	}
+
+	30% {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
+		opacity: 0
+	}
+}
+
+@keyframes flipOutX {
+	from {
+		-webkit-transform: perspective(400px);
+		transform: perspective(400px)
+	}
+
+	30% {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
+		transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
+		opacity: 0
+	}
+}
+
+.flipOutX {
+	-webkit-animation-duration: 0.75s;
+	animation-duration: 0.75s;
+	-webkit-animation-name: flipOutX;
+	animation-name: flipOutX;
+	-webkit-backface-visibility: visible !important;
+	backface-visibility: visible !important
+}
+
+@-webkit-keyframes flipOutY {
+	from {
+		-webkit-transform: perspective(400px);
+		transform: perspective(400px)
+	}
+
+	30% {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
+		opacity: 0
+	}
+}
+
+@keyframes flipOutY {
+	from {
+		-webkit-transform: perspective(400px);
+		transform: perspective(400px)
+	}
+
+	30% {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
+		transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
+		opacity: 0
+	}
+}
+
+.flipOutY {
+	-webkit-animation-duration: 0.75s;
+	animation-duration: 0.75s;
+	-webkit-backface-visibility: visible !important;
+	backface-visibility: visible !important;
+	-webkit-animation-name: flipOutY;
+	animation-name: flipOutY
+}
+
+@-webkit-keyframes lightSpeedIn {
+	from {
+		-webkit-transform: translate3d(100%, 0, 0) skewX(-30deg);
+		transform: translate3d(100%, 0, 0) skewX(-30deg);
+		opacity: 0
+	}
+
+	60% {
+		-webkit-transform: skewX(20deg);
+		transform: skewX(20deg);
+		opacity: 1
+	}
+
+	80% {
+		-webkit-transform: skewX(-5deg);
+		transform: skewX(-5deg)
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes lightSpeedIn {
+	from {
+		-webkit-transform: translate3d(100%, 0, 0) skewX(-30deg);
+		transform: translate3d(100%, 0, 0) skewX(-30deg);
+		opacity: 0
+	}
+
+	60% {
+		-webkit-transform: skewX(20deg);
+		transform: skewX(20deg);
+		opacity: 1
+	}
+
+	80% {
+		-webkit-transform: skewX(-5deg);
+		transform: skewX(-5deg)
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.lightSpeedIn {
+	-webkit-animation-name: lightSpeedIn;
+	animation-name: lightSpeedIn;
+	-webkit-animation-timing-function: ease-out;
+	animation-timing-function: ease-out
+}
+
+@-webkit-keyframes lightSpeedOut {
+	from {
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform: translate3d(100%, 0, 0) skewX(30deg);
+		transform: translate3d(100%, 0, 0) skewX(30deg);
+		opacity: 0
+	}
+}
+
+@keyframes lightSpeedOut {
+	from {
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform: translate3d(100%, 0, 0) skewX(30deg);
+		transform: translate3d(100%, 0, 0) skewX(30deg);
+		opacity: 0
+	}
+}
+
+.lightSpeedOut {
+	-webkit-animation-name: lightSpeedOut;
+	animation-name: lightSpeedOut;
+	-webkit-animation-timing-function: ease-in;
+	animation-timing-function: ease-in
+}
+
+@-webkit-keyframes rotateIn {
+	from {
+		-webkit-transform-origin: center;
+		transform-origin: center;
+		-webkit-transform: rotate3d(0, 0, 1, -200deg);
+		transform: rotate3d(0, 0, 1, -200deg);
+		opacity: 0
+	}
+
+	to {
+		-webkit-transform-origin: center;
+		transform-origin: center;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1
+	}
+}
+
+@keyframes rotateIn {
+	from {
+		-webkit-transform-origin: center;
+		transform-origin: center;
+		-webkit-transform: rotate3d(0, 0, 1, -200deg);
+		transform: rotate3d(0, 0, 1, -200deg);
+		opacity: 0
+	}
+
+	to {
+		-webkit-transform-origin: center;
+		transform-origin: center;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1
+	}
+}
+
+.rotateIn {
+	-webkit-animation-name: rotateIn;
+	animation-name: rotateIn
+}
+
+@-webkit-keyframes rotateInDownLeft {
+	from {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: rotate3d(0, 0, 1, -45deg);
+		transform: rotate3d(0, 0, 1, -45deg);
+		opacity: 0
+	}
+
+	to {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1
+	}
+}
+
+@keyframes rotateInDownLeft {
+	from {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: rotate3d(0, 0, 1, -45deg);
+		transform: rotate3d(0, 0, 1, -45deg);
+		opacity: 0
+	}
+
+	to {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1
+	}
+}
+
+.rotateInDownLeft {
+	-webkit-animation-name: rotateInDownLeft;
+	animation-name: rotateInDownLeft
+}
+
+@-webkit-keyframes rotateInDownRight {
+	from {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: rotate3d(0, 0, 1, 45deg);
+		transform: rotate3d(0, 0, 1, 45deg);
+		opacity: 0
+	}
+
+	to {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1
+	}
+}
+
+@keyframes rotateInDownRight {
+	from {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: rotate3d(0, 0, 1, 45deg);
+		transform: rotate3d(0, 0, 1, 45deg);
+		opacity: 0
+	}
+
+	to {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1
+	}
+}
+
+.rotateInDownRight {
+	-webkit-animation-name: rotateInDownRight;
+	animation-name: rotateInDownRight
+}
+
+@-webkit-keyframes rotateInUpLeft {
+	from {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: rotate3d(0, 0, 1, 45deg);
+		transform: rotate3d(0, 0, 1, 45deg);
+		opacity: 0
+	}
+
+	to {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1
+	}
+}
+
+@keyframes rotateInUpLeft {
+	from {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: rotate3d(0, 0, 1, 45deg);
+		transform: rotate3d(0, 0, 1, 45deg);
+		opacity: 0
+	}
+
+	to {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1
+	}
+}
+
+.rotateInUpLeft {
+	-webkit-animation-name: rotateInUpLeft;
+	animation-name: rotateInUpLeft
+}
+
+@-webkit-keyframes rotateInUpRight {
+	from {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: rotate3d(0, 0, 1, -90deg);
+		transform: rotate3d(0, 0, 1, -90deg);
+		opacity: 0
+	}
+
+	to {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1
+	}
+}
+
+@keyframes rotateInUpRight {
+	from {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: rotate3d(0, 0, 1, -90deg);
+		transform: rotate3d(0, 0, 1, -90deg);
+		opacity: 0
+	}
+
+	to {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0);
+		opacity: 1
+	}
+}
+
+.rotateInUpRight {
+	-webkit-animation-name: rotateInUpRight;
+	animation-name: rotateInUpRight
+}
+
+@-webkit-keyframes rotateOut {
+	from {
+		-webkit-transform-origin: center;
+		transform-origin: center;
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform-origin: center;
+		transform-origin: center;
+		-webkit-transform: rotate3d(0, 0, 1, 200deg);
+		transform: rotate3d(0, 0, 1, 200deg);
+		opacity: 0
+	}
+}
+
+@keyframes rotateOut {
+	from {
+		-webkit-transform-origin: center;
+		transform-origin: center;
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform-origin: center;
+		transform-origin: center;
+		-webkit-transform: rotate3d(0, 0, 1, 200deg);
+		transform: rotate3d(0, 0, 1, 200deg);
+		opacity: 0
+	}
+}
+
+.rotateOut {
+	-webkit-animation-name: rotateOut;
+	animation-name: rotateOut
+}
+
+@-webkit-keyframes rotateOutDownLeft {
+	from {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: rotate3d(0, 0, 1, 45deg);
+		transform: rotate3d(0, 0, 1, 45deg);
+		opacity: 0
+	}
+}
+
+@keyframes rotateOutDownLeft {
+	from {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: rotate3d(0, 0, 1, 45deg);
+		transform: rotate3d(0, 0, 1, 45deg);
+		opacity: 0
+	}
+}
+
+.rotateOutDownLeft {
+	-webkit-animation-name: rotateOutDownLeft;
+	animation-name: rotateOutDownLeft
+}
+
+@-webkit-keyframes rotateOutDownRight {
+	from {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: rotate3d(0, 0, 1, -45deg);
+		transform: rotate3d(0, 0, 1, -45deg);
+		opacity: 0
+	}
+}
+
+@keyframes rotateOutDownRight {
+	from {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: rotate3d(0, 0, 1, -45deg);
+		transform: rotate3d(0, 0, 1, -45deg);
+		opacity: 0
+	}
+}
+
+.rotateOutDownRight {
+	-webkit-animation-name: rotateOutDownRight;
+	animation-name: rotateOutDownRight
+}
+
+@-webkit-keyframes rotateOutUpLeft {
+	from {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: rotate3d(0, 0, 1, -45deg);
+		transform: rotate3d(0, 0, 1, -45deg);
+		opacity: 0
+	}
+}
+
+@keyframes rotateOutUpLeft {
+	from {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform-origin: left bottom;
+		transform-origin: left bottom;
+		-webkit-transform: rotate3d(0, 0, 1, -45deg);
+		transform: rotate3d(0, 0, 1, -45deg);
+		opacity: 0
+	}
+}
+
+.rotateOutUpLeft {
+	-webkit-animation-name: rotateOutUpLeft;
+	animation-name: rotateOutUpLeft
+}
+
+@-webkit-keyframes rotateOutUpRight {
+	from {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: rotate3d(0, 0, 1, 90deg);
+		transform: rotate3d(0, 0, 1, 90deg);
+		opacity: 0
+	}
+}
+
+@keyframes rotateOutUpRight {
+	from {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform-origin: right bottom;
+		transform-origin: right bottom;
+		-webkit-transform: rotate3d(0, 0, 1, 90deg);
+		transform: rotate3d(0, 0, 1, 90deg);
+		opacity: 0
+	}
+}
+
+.rotateOutUpRight {
+	-webkit-animation-name: rotateOutUpRight;
+	animation-name: rotateOutUpRight
+}
+
+@-webkit-keyframes hinge {
+	0% {
+		-webkit-transform-origin: top left;
+		transform-origin: top left;
+		-webkit-animation-timing-function: ease-in-out;
+		animation-timing-function: ease-in-out
+	}
+
+	20%,
+	60% {
+		-webkit-transform: rotate3d(0, 0, 1, 80deg);
+		transform: rotate3d(0, 0, 1, 80deg);
+		-webkit-transform-origin: top left;
+		transform-origin: top left;
+		-webkit-animation-timing-function: ease-in-out;
+		animation-timing-function: ease-in-out
+	}
+
+	40%,
+	80% {
+		-webkit-transform: rotate3d(0, 0, 1, 60deg);
+		transform: rotate3d(0, 0, 1, 60deg);
+		-webkit-transform-origin: top left;
+		transform-origin: top left;
+		-webkit-animation-timing-function: ease-in-out;
+		animation-timing-function: ease-in-out;
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 700px, 0);
+		transform: translate3d(0, 700px, 0);
+		opacity: 0
+	}
+}
+
+@keyframes hinge {
+	0% {
+		-webkit-transform-origin: top left;
+		transform-origin: top left;
+		-webkit-animation-timing-function: ease-in-out;
+		animation-timing-function: ease-in-out
+	}
+
+	20%,
+	60% {
+		-webkit-transform: rotate3d(0, 0, 1, 80deg);
+		transform: rotate3d(0, 0, 1, 80deg);
+		-webkit-transform-origin: top left;
+		transform-origin: top left;
+		-webkit-animation-timing-function: ease-in-out;
+		animation-timing-function: ease-in-out
+	}
+
+	40%,
+	80% {
+		-webkit-transform: rotate3d(0, 0, 1, 60deg);
+		transform: rotate3d(0, 0, 1, 60deg);
+		-webkit-transform-origin: top left;
+		transform-origin: top left;
+		-webkit-animation-timing-function: ease-in-out;
+		animation-timing-function: ease-in-out;
+		opacity: 1
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 700px, 0);
+		transform: translate3d(0, 700px, 0);
+		opacity: 0
+	}
+}
+
+.hinge {
+	-webkit-animation-duration: 2s;
+	animation-duration: 2s;
+	-webkit-animation-name: hinge;
+	animation-name: hinge
+}
+
+@-webkit-keyframes jackInTheBox {
+	from {
+		opacity: 0;
+		-webkit-transform: scale(.1) rotate(30deg);
+		transform: scale(.1) rotate(30deg);
+		-webkit-transform-origin: center bottom;
+		transform-origin: center bottom
+	}
+
+	50% {
+		-webkit-transform: rotate(-10deg);
+		transform: rotate(-10deg)
+	}
+
+	70% {
+		-webkit-transform: rotate(3deg);
+		transform: rotate(3deg)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: scale(1);
+		transform: scale(1)
+	}
+}
+
+@keyframes jackInTheBox {
+	from {
+		opacity: 0;
+		-webkit-transform: scale(.1) rotate(30deg);
+		transform: scale(.1) rotate(30deg);
+		-webkit-transform-origin: center bottom;
+		transform-origin: center bottom
+	}
+
+	50% {
+		-webkit-transform: rotate(-10deg);
+		transform: rotate(-10deg)
+	}
+
+	70% {
+		-webkit-transform: rotate(3deg);
+		transform: rotate(3deg)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: scale(1);
+		transform: scale(1)
+	}
+}
+
+.jackInTheBox {
+	-webkit-animation-name: jackInTheBox;
+	animation-name: jackInTheBox
+}
+
+@-webkit-keyframes rollIn {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg);
+		transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes rollIn {
+	from {
+		opacity: 0;
+		-webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg);
+		transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg)
+	}
+
+	to {
+		opacity: 1;
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.rollIn {
+	-webkit-animation-name: rollIn;
+	animation-name: rollIn
+}
+
+@-webkit-keyframes rollOut {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg);
+		transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg)
+	}
+}
+
+@keyframes rollOut {
+	from {
+		opacity: 1
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg);
+		transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg)
+	}
+}
+
+.rollOut {
+	-webkit-animation-name: rollOut;
+	animation-name: rollOut
+}
+
+@-webkit-keyframes zoomIn {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(.3, .3, .3);
+		transform: scale3d(.3, .3, .3)
+	}
+
+	50% {
+		opacity: 1
+	}
+}
+
+@flipInX zoomIn {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(.3, .3, .3);
+		transform: scale3d(.3, .3, .3)
+	}
+
+	50% {
+		opacity: 1
+	}
+}
+
+.zoomIn {
+	-webkit-animation-name: zoomIn;
+	animation-name: zoomIn
+}
+
+@-webkit-keyframes zoomInDown {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
+		transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
+		-webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
+		animation-timing-function: cubic-bezier(.55, .055, .675, .19)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
+		transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
+		-webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
+		animation-timing-function: cubic-bezier(.175, .885, .32, 1)
+	}
+}
+
+@keyframes zoomInDown {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
+		transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
+		-webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
+		animation-timing-function: cubic-bezier(.55, .055, .675, .19)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
+		transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
+		-webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
+		animation-timing-function: cubic-bezier(.175, .885, .32, 1)
+	}
+}
+
+.zoomInDown {
+	-webkit-animation-name: zoomInDown;
+	animation-name: zoomInDown
+}
+
+@-webkit-keyframes zoomInLeft {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0);
+		transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0);
+		-webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
+		animation-timing-function: cubic-bezier(.55, .055, .675, .19)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0);
+		transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0);
+		-webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
+		animation-timing-function: cubic-bezier(.175, .885, .32, 1)
+	}
+}
+
+@keyframes zoomInLeft {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0);
+		transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0);
+		-webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
+		animation-timing-function: cubic-bezier(.55, .055, .675, .19)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0);
+		transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0);
+		-webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
+		animation-timing-function: cubic-bezier(.175, .885, .32, 1)
+	}
+}
+
+.zoomInLeft {
+	-webkit-animation-name: zoomInLeft;
+	animation-name: zoomInLeft
+}
+
+@-webkit-keyframes zoomInRight {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0);
+		transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0);
+		-webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
+		animation-timing-function: cubic-bezier(.55, .055, .675, .19)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0);
+		transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0);
+		-webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
+		animation-timing-function: cubic-bezier(.175, .885, .32, 1)
+	}
+}
+
+@keyframes zoomInRight {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0);
+		transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0);
+		-webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
+		animation-timing-function: cubic-bezier(.55, .055, .675, .19)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0);
+		transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0);
+		-webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
+		animation-timing-function: cubic-bezier(.175, .885, .32, 1)
+	}
+}
+
+.zoomInRight {
+	-webkit-animation-name: zoomInRight;
+	animation-name: zoomInRight
+}
+
+@-webkit-keyframes zoomInUp {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0);
+		transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0);
+		-webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
+		animation-timing-function: cubic-bezier(.55, .055, .675, .19)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
+		transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
+		-webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
+		animation-timing-function: cubic-bezier(.175, .885, .32, 1)
+	}
+}
+
+@keyframes zoomInUp {
+	from {
+		opacity: 0;
+		-webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0);
+		transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0);
+		-webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
+		animation-timing-function: cubic-bezier(.55, .055, .675, .19)
+	}
+
+	60% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
+		transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
+		-webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
+		animation-timing-function: cubic-bezier(.175, .885, .32, 1)
+	}
+}
+
+.zoomInUp {
+	-webkit-animation-name: zoomInUp;
+	animation-name: zoomInUp
+}
+
+@-webkit-keyframes zoomOut {
+	from {
+		opacity: 1
+	}
+
+	50% {
+		opacity: 0;
+		-webkit-transform: scale3d(.3, .3, .3);
+		transform: scale3d(.3, .3, .3)
+	}
+
+	to {
+		opacity: 0
+	}
+}
+
+@keyframes zoomOut {
+	from {
+		opacity: 1
+	}
+
+	50% {
+		opacity: 0;
+		-webkit-transform: scale3d(.3, .3, .3);
+		transform: scale3d(.3, .3, .3)
+	}
+
+	to {
+		opacity: 0
+	}
+}
+
+.zoomOut {
+	-webkit-animation-name: zoomOut;
+	animation-name: zoomOut
+}
+
+@-webkit-keyframes zoomOutDown {
+	40% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
+		transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
+		-webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
+		animation-timing-function: cubic-bezier(.55, .055, .675, .19)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0);
+		transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0);
+		-webkit-transform-origin: center bottom;
+		transform-origin: center bottom;
+		-webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
+		animation-timing-function: cubic-bezier(.175, .885, .32, 1)
+	}
+}
+
+@keyframes zoomOutDown {
+	40% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
+		transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
+		-webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
+		animation-timing-function: cubic-bezier(.55, .055, .675, .19)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0);
+		transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0);
+		-webkit-transform-origin: center bottom;
+		transform-origin: center bottom;
+		-webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
+		animation-timing-function: cubic-bezier(.175, .885, .32, 1)
+	}
+}
+
+.zoomOutDown {
+	-webkit-animation-name: zoomOutDown;
+	animation-name: zoomOutDown
+}
+
+@-webkit-keyframes zoomOutLeft {
+	40% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0);
+		transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale(.1) translate3d(-2000px, 0, 0);
+		transform: scale(.1) translate3d(-2000px, 0, 0);
+		-webkit-transform-origin: left center;
+		transform-origin: left center
+	}
+}
+
+@keyframes zoomOutLeft {
+	40% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0);
+		transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale(.1) translate3d(-2000px, 0, 0);
+		transform: scale(.1) translate3d(-2000px, 0, 0);
+		-webkit-transform-origin: left center;
+		transform-origin: left center
+	}
+}
+
+.zoomOutLeft {
+	-webkit-animation-name: zoomOutLeft;
+	animation-name: zoomOutLeft
+}
+
+@-webkit-keyframes zoomOutRight {
+	40% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0);
+		transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale(.1) translate3d(2000px, 0, 0);
+		transform: scale(.1) translate3d(2000px, 0, 0);
+		-webkit-transform-origin: right center;
+		transform-origin: right center
+	}
+}
+
+@keyframes zoomOutRight {
+	40% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0);
+		transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale(.1) translate3d(2000px, 0, 0);
+		transform: scale(.1) translate3d(2000px, 0, 0);
+		-webkit-transform-origin: right center;
+		transform-origin: right center
+	}
+}
+
+.zoomOutRight {
+	-webkit-animation-name: zoomOutRight;
+	animation-name: zoomOutRight
+}
+
+@-webkit-keyframes zoomOutUp {
+	40% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
+		transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
+		-webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
+		animation-timing-function: cubic-bezier(.55, .055, .675, .19)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0);
+		transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0);
+		-webkit-transform-origin: center bottom;
+		transform-origin: center bottom;
+		-webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
+		animation-timing-function: cubic-bezier(.175, .885, .32, 1)
+	}
+}
+
+@keyframes zoomOutUp {
+	40% {
+		opacity: 1;
+		-webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
+		transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
+		-webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
+		animation-timing-function: cubic-bezier(.55, .055, .675, .19)
+	}
+
+	to {
+		opacity: 0;
+		-webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0);
+		transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0);
+		-webkit-transform-origin: center bottom;
+		transform-origin: center bottom;
+		-webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
+		animation-timing-function: cubic-bezier(.175, .885, .32, 1)
+	}
+}
+
+.zoomOutUp {
+	-webkit-animation-name: zoomOutUp;
+	animation-name: zoomOutUp
+}
+
+@-webkit-keyframes slideInDown {
+	from {
+		-webkit-transform: translate3d(0, -100%, 0);
+		transform: translate3d(0, -100%, 0);
+		visibility: visible
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes slideInDown {
+	from {
+		-webkit-transform: translate3d(0, -100%, 0);
+		transform: translate3d(0, -100%, 0);
+		visibility: visible
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.slideInDown {
+	-webkit-animation-name: slideInDown;
+	animation-name: slideInDown
+}
+
+@-webkit-keyframes slideInLeft {
+	from {
+		-webkit-transform: translate3d(-100%, 0, 0);
+		transform: translate3d(-100%, 0, 0);
+		visibility: visible
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes slideInLeft {
+	from {
+		-webkit-transform: translate3d(-100%, 0, 0);
+		transform: translate3d(-100%, 0, 0);
+		visibility: visible
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.slideInLeft {
+	-webkit-animation-name: slideInLeft;
+	animation-name: slideInLeft
+}
+
+@-webkit-keyframes slideInRight {
+	from {
+		-webkit-transform: translate3d(100%, 0, 0);
+		transform: translate3d(100%, 0, 0);
+		visibility: visible
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes slideInRight {
+	from {
+		-webkit-transform: translate3d(100%, 0, 0);
+		transform: translate3d(100%, 0, 0);
+		visibility: visible
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.slideInRight {
+	-webkit-animation-name: slideInRight;
+	animation-name: slideInRight
+}
+
+@-webkit-keyframes slideInUp {
+	from {
+		-webkit-transform: translate3d(0, 100%, 0);
+		transform: translate3d(0, 100%, 0);
+		visibility: visible
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+@keyframes slideInUp {
+	from {
+		-webkit-transform: translate3d(0, 100%, 0);
+		transform: translate3d(0, 100%, 0);
+		visibility: visible
+	}
+
+	to {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+}
+
+.slideInUp {
+	-webkit-animation-name: slideInUp;
+	animation-name: slideInUp
+}
+
+@-webkit-keyframes slideOutDown {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	to {
+		visibility: hidden;
+		-webkit-transform: translate3d(0, 100%, 0);
+		transform: translate3d(0, 100%, 0)
+	}
+}
+
+@keyframes slideOutDown {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	to {
+		visibility: hidden;
+		-webkit-transform: translate3d(0, 100%, 0);
+		transform: translate3d(0, 100%, 0)
+	}
+}
+
+.slideOutDown {
+	-webkit-animation-name: slideOutDown;
+	animation-name: slideOutDown
+}
+
+@-webkit-keyframes slideOutLeft {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	to {
+		visibility: hidden;
+		-webkit-transform: translate3d(-100%, 0, 0);
+		transform: translate3d(-100%, 0, 0)
+	}
+}
+
+@keyframes slideOutLeft {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	to {
+		visibility: hidden;
+		-webkit-transform: translate3d(-100%, 0, 0);
+		transform: translate3d(-100%, 0, 0)
+	}
+}
+
+.slideOutLeft {
+	-webkit-animation-name: slideOutLeft;
+	animation-name: slideOutLeft
+}
+
+@-webkit-keyframes slideOutRight {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	to {
+		visibility: hidden;
+		-webkit-transform: translate3d(100%, 0, 0);
+		transform: translate3d(100%, 0, 0)
+	}
+}
+
+@keyframes slideOutRight {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	to {
+		visibility: hidden;
+		-webkit-transform: translate3d(100%, 0, 0);
+		transform: translate3d(100%, 0, 0)
+	}
+}
+
+.slideOutRight {
+	-webkit-animation-name: slideOutRight;
+	animation-name: slideOutRight
+}
+
+@-webkit-keyframes slideOutUp {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	to {
+		visibility: hidden;
+		-webkit-transform: translate3d(0, -100%, 0);
+		transform: translate3d(0, -100%, 0)
+	}
+}
+
+@keyframes slideOutUp {
+	from {
+		-webkit-transform: translate3d(0, 0, 0);
+		transform: translate3d(0, 0, 0)
+	}
+
+	to {
+		visibility: hidden;
+		-webkit-transform: translate3d(0, -100%, 0);
+		transform: translate3d(0, -100%, 0)
+	}
+}
+
+.slideOutUp {
+	-webkit-animation-name: slideOutUp;
+	animation-name: slideOutUp
+}
+
+.animated {
+	-webkit-animation-duration: 1s;
+	animation-duration: 1s;
+	-webkit-animation-fill-mode: both;
+	animation-fill-mode: both
+}
+
+.animated.infinite {
+	-webkit-animation-iteration-count: infinite;
+	animation-iteration-count: infinite
+}
+
+.animated.delay-1s {
+	-webkit-animation-delay: 1s;
+	animation-delay: 1s
+}
+
+.animated.delay-2s {
+	-webkit-animation-delay: 2s;
+	animation-delay: 2s
+}
+
+.animated.delay-3s {
+	-webkit-animation-delay: 3s;
+	animation-delay: 3s
+}
+
+.animated.delay-4s {
+	-webkit-animation-delay: 4s;
+	animation-delay: 4s
+}
+
+.animated.delay-5s {
+	-webkit-animation-delay: 5s;
+	animation-delay: 5s
+}
+
+.animated.fast {
+	-webkit-animation-duration: 800ms;
+	animation-duration: 800ms
+}
+
+.animated.faster {
+	-webkit-animation-duration: 500ms;
+	animation-duration: 500ms
+}
+
+.animated.slow {
+	-webkit-animation-duration: 2s;
+	animation-duration: 2s
+}
+
+.animated.slower {
+	-webkit-animation-duration: 3s;
+	animation-duration: 3s
+}
+
+@media (print),
+(prefers-reduced-motion:reduce) {
+	.animated {
+		-webkit-animation-duration: 1ms !important;
+		animation-duration: 1ms !important;
+		-webkit-transition-duration: 1ms !important;
+		transition-duration: 1ms !important;
+		-webkit-animation-iteration-count: 1 !important;
+		animation-iteration-count: 1 !important
+	}
+}

BIN
src/assets/perform/gs1.jpg


BIN
src/assets/perform/gs2.jpg


BIN
src/assets/perform/gs3.jpg


BIN
src/assets/perform/gs4.jpg


+ 1 - 1
src/components/EmployeeSelector.vue

@@ -18,7 +18,7 @@
             <div class="search flex-box"><el-input v-model="keyword" placeholder="请输入内容" clearable></el-input></div>
             <div class="search flex-box" style="margin-top: 7px;">
               <el-cascader
-                v-show="can_select_employee&&deptId==0"
+                v-show="can_select_employee && deptId==0"
                 style="width: 100%;"
                 v-model="dept_id"
                 ref="dept"

+ 1 - 1
src/components/LoadingAll.vue

@@ -72,7 +72,7 @@ export default {
   methods: {
     returnImg(){
       let arr=['static/images/pc-mubiao.jpg','static/images/pc-jifen.jpg','static/images/pc-jixiao.jpg','static/images/pc-xunjian.jpg']
-      return arr[this.isType-1]
+      return arr[this.isType - 1]
     },
     init(){
       let arr=['okr','integral','performance','sm'];

+ 6 - 6
src/components/PageHead.vue

@@ -1,12 +1,12 @@
 <template>
   <div class="PageHead">
-      <div class="flex-box-ce">
-         <span class="return" @click="routePush?$router.push(routePush):$router.go(-1)">返回</span>
-         <span v-if="phName!=''">{{phName}}</span>
-         <div v-else class="slot">
-           <slot name="headMent"></slot>
-         </div>
+    <div class="flex-box-ce">
+      <span class="return" @click=" routePush ? $router.push(routePush) : $router.go(-1) ">返回</span>
+      <span v-if="phName!=''" style="font-size: 16px; font-weight: 600;">{{phName}}</span>
+      <div v-else class="slot">
+        <slot name="headMent"></slot>
       </div>
+    </div>
   </div>
 </template>
 

+ 3 - 2
src/components/PageHeadTwo.vue

@@ -47,14 +47,15 @@ export default {
 .text {
   font-size: 16px;
   padding-left: 30px;
+  position: relative;
 }
 .text::before {
   position: absolute;
   content: '';
   width: 1px;
-  height: 60px;
+  height: 80px;
   background-color: #ebebeb;
-  left: 34px;
+  left: 20px;
   top: 50%;
   margin-top: -18px;
 }

+ 1 - 1
src/components/UserImage.vue

@@ -1,5 +1,5 @@
 <template>
-  <div style="text-align: center;">
+  <div style="text-align: center; overflow: hidden;">
     <el-image v-if="img_url" :src="img_url" :style="{ width: width, height: height ,'border-radius':radius}" style="margin: 0 auto;" fit="fill">
       <div slot="error" class="image-slot"><i class="el-icon-picture"></i></div>
     </el-image>

+ 10 - 9
src/components/organization/Post.vue

@@ -5,7 +5,7 @@
     </el-alert>
     <div class="box br-5 all boxMinHeight">
       <div class="main flex-box">
-        <div class="main-left" style="padding: 20px;padding-right: 0px;">
+        <div class="main-left" style="padding: 20px;  box-sizing: border-box; ">
           <div style="margin-bottom: 16px;" class="flex-box-v flex-center-center">
             <div style="margin-bottom: 6px;"><el-button type="primary" @click="openAdd(1)">新建分类</el-button></div>
             <div><el-button type="primary"  @click.stop="openV(1)">新增岗位</el-button></div>
@@ -268,7 +268,8 @@ export default {
     },
     getList(is) {
       this.item_loading=true;
-      this.tabs=[{name:'全部分类',id:-1}];
+      this.tabs = [{ name: '全部分类', id: -1 }];
+      // 岗位分类
       this.$axiosUser('get', '/api/pro/post/post_cate_list').then(res => {
           this.tabs.push(...res.data.data)
           if(is){
@@ -282,16 +283,16 @@ export default {
     getTableData() {
       this.table_loading=true;
       let data={
-        page:this.page,
-        page_size:this.page_size,
-        cate_id:this.tabItem.id,
-        name:this.name
+        page: this.page,
+        page_size: this.page_size,
+        cate_id: this.tabItem.id,
+        name: this.name
       }
       this.$axiosUser('get', '/api/pro/post/cate_post_list',data).then(res => {
-          this.list=res.data.data.list
-          this.total=res.data.data.total
+        this.list=res.data.data.list
+        this.total=res.data.data.total
       }).finally(()=>{
-           this.table_loading=false;
+        this.table_loading=false;
       });
     },
     openDetail(e){

+ 44 - 46
src/components/system/Jurisdiction.vue

@@ -7,38 +7,45 @@
           <div class="text fontColorC">默认拥有全部权限,更换后需另行设置各子系统的权限</div>
         </div>
         <div class="flex-box-ce" style="margin: 20px 0;">
-          <userImage :user_name="userMainData.name" :img_url="userMainData.img_url" :id="userMainData.id" fontSize="14"></userImage>
+          <userImage :user_name="userMainData.name" :img_url="userMainData.img_url" :id="userMainData.id" fontSize="14">
+          </userImage>
           <span class="name">{{ userMainData.name }}</span>
-          <div class="blue cursor" v-if="userInfo.id==userMainData.id" style="padding-left: 20px;" @click="setCreator=true">更换</div>
+          <div class="blue cursor" v-if="userInfo.id==userMainData.id" style="padding-left: 20px;"
+            @click="setCreator=true">更换</div>
         </div>
       </div>
-      <div class="flex-box-ce">
+      <div class="flex-box-ce" style="display: flex; align-items: center;">
         <div class="" style="width: 326px;">
           <div class="title">组织管理员</div>
           <div class="text fontColorC">组织管理员仅可操作组织和人员管理,并设置新增组织管理员(最多10位)</div>
         </div>
-        <el-button :disabled="AdministratorList.length>=10"  size="small" type="primary" @click="showSelectUser(1)" plain><i class="el-icon-plus"></i>添加</el-button>
+        <el-button :disabled="AdministratorList.length >= 10" size="small" type="primary" @click="showSelectUser(1)"
+          plain><i class="el-icon-plus"></i>添加</el-button>
       </div>
       <div class="flex-box" style="margin: 20px 0;width: 800px;flex-wrap: wrap;">
         <template v-if="AdministratorList.length>0">
           <div v-for="(item,index) in AdministratorList" :key="index" class="files-box">
             <userImage :user_name="item.name" :img_url="item.img_url" :id="item.id" fontSize="14"></userImage>
             <span class="name flex-1">{{ item.name }}</span>
-            <Tooltip preHtml="删除" v-if="userInfo.id==userMainData.id||userInfo.id!=item.id"><i class="el-icon-error cursor" @click="deleteUser(item.id)"></i></Tooltip>
+            <Tooltip preHtml="删除" v-if="userInfo.id==userMainData.id||userInfo.id!=item.id"><i
+                class="el-icon-error cursor" @click="deleteUser(item.id)"></i></Tooltip>
           </div>
         </template>
-        <noData isSolt v-else imgW="200px" imgH="140px" >
-            <div class="fontColorC" style="text-align: center;">暂无组织管理员,去<span class="blue cursor" @click="showSelectUser(1)">添加</span></div>
+        <noData isSolt v-else imgW="200px" imgH="140px">
+          <div class="fontColorC" style="text-align: center;">暂无组织管理员,去<span class="blue cursor"
+              @click="showSelectUser(1)">添加</span></div>
         </noData>
       </div>
     </div>
     <div class="title">各系统主管理员</div>
     <el-tabs v-model="tabName">
-      <el-tab-pane :label="item.label" :name="item.name" v-for="(item,index) in tabs" :key="index" v-show="item.isShow"></el-tab-pane>
+      <el-tab-pane :label="item.label" :name="item.name" v-for="(item,index) in tabs" :key="index"
+        v-show="item.isShow"></el-tab-pane>
     </el-tabs>
-    <div  class="flex-box">
+    <div class="flex-box">
       <noData isSolt imgW="200px" imgH="140px" v-if="tabs.length==0">
-          <div class="fontColorC" style="text-align: center;">系统未开通或已过期,请<span class="blue cursor" @click="innerVisible=true">联系客服</span></div>
+        <div class="fontColorC" style="text-align: center;">系统未开通或已过期,请<span class="blue cursor"
+            @click="innerVisible=true">联系客服</span></div>
       </noData>
     </div>
 
@@ -49,11 +56,12 @@
     <!-- 绩效 -->
     <div v-if="tabName=='User'" style="padding: 10px;">
       <div class="flex-box-ce">
-        <div  style="width: 326px;">
+        <div style="width: 326px;">
           <div class="title">绩效主管理员</div>
           <div class="text fontColorC">拥有全部权限,支持更换主管理员</div>
         </div>
-        <el-button plain :disabled="userMain.length>=10" @click="setJxCreator = true" size="small" class="primaryBtn" v-if="userInfo.per_permission.main"><i class="el-icon-plus"></i>添加</el-button>
+        <el-button plain :disabled="userMain.length>=10" @click="setJxCreator = true" size="small" class="primaryBtn"
+          v-if="userInfo.per_permission.main"><i class="el-icon-plus"></i>添加</el-button>
       </div>
       <div style="margin: 20px 0;">
         <el-table :data="userMain" style="width: 400px;">
@@ -67,7 +75,9 @@
           </el-table-column>
           <el-table-column label="操作">
             <template slot-scope="scope">
-              <el-button v-if="$store.getters.site_info.creator_id!=scope.row.id&&scope.row.id!=$userInfo().id&&userInfo.per_permission.main" @click="deleteUser2(scope.row.id)" type="text" class="red">删除</el-button>
+              <el-button
+                v-if="$store.getters.site_info.creator_id!=scope.row.id&&scope.row.id!=$userInfo().id&&userInfo.per_permission.main"
+                @click="deleteUser2(scope.row.id)" type="text" class="red">删除</el-button>
             </template>
           </el-table-column>
           <template slot="empty">
@@ -83,11 +93,12 @@
     <!-- 积分 -->
     <div v-if="tabName=='point'" style="padding: 10px;">
       <div class="flex-box-ce">
-        <div  style="width: 326px;">
+        <div style="width: 326px;">
           <div class="title">积分主管理员</div>
-          <div class="text fontColorC">积分主管理员为默认角色,拥有所有功能权限<br/><span class="red">(积分主管理员至少有一位,且不能删除自己)</span></div>
+          <div class="text fontColorC">积分主管理员为默认角色,拥有所有功能权限<br /><span class="red">(积分主管理员至少有一位,且不能删除自己)</span></div>
         </div>
-        <el-button plain :disabled="pointList.length>=10" @click="setPointCreator = true" size="small" class="primaryBtn" v-if="userInfo.is_creator"><i class="el-icon-plus"></i>添加</el-button>
+        <el-button plain :disabled="pointList.length>=10" @click="setPointCreator = true" size="small"
+          class="primaryBtn" v-if="userInfo.is_creator"><i class="el-icon-plus"></i>添加</el-button>
       </div>
       <div style="margin: 20px 0;">
         <el-table :data="pointList" style="width: 400px;">
@@ -101,7 +112,9 @@
           </el-table-column>
           <el-table-column label="操作">
             <template slot-scope="scope">
-              <el-button v-if="$store.getters.site_info.creator_id!=scope.row.id&&scope.row.id!=$userInfo().id&&userInfo.is_creator" @click="del_creator(scope.row.id)" type="text" class="red">删除</el-button>
+              <el-button
+                v-if="$store.getters.site_info.creator_id!=scope.row.id&&scope.row.id!=$userInfo().id&&userInfo.is_creator"
+                @click="del_creator(scope.row.id)" type="text" class="red">删除</el-button>
             </template>
           </el-table-column>
           <template slot="empty">
@@ -112,16 +125,8 @@
     </div>
 
     <!-- 组织管理员 -->
-    <EmployeeSelector
-      :selected="selected"
-      :is_filtration_creator="false"
-      :employee_not_select="employee_not_select"
-      :multi="false"
-      :isChecKedAll="false"
-       isRequired
-      :visible.sync="isAdministrator"
-      @confirm="confirmAdministrator"
-    />
+    <EmployeeSelector :selected="selected" :is_filtration_creator="false" :employee_not_select="employee_not_select"
+      :multi="false" :isChecKedAll="false" isRequired :visible.sync="isAdministrator" @confirm="confirmAdministrator" />
 
     <el-dialog :close-on-click-modal="false" title="更换创始人" :visible.sync="setCreator" width="550px">
       <el-form :model="formData" :rules="employee_info_rules" ref="formData" label-width="80px">
@@ -130,8 +135,10 @@
         </el-form-item>
         <el-form-item :inline="true" label="验证码" prop="verify">
           <div class="flex-box-ce">
-            <el-input autocomplete="off" style="width: 200px;" v-model="formData.verify" placeholder="请输入验证码"></el-input>
-            <el-button size="mini" @click="send_old_msg" :disabled="sended" style="line-height: 34px;padding: 0 22px;color:#606266;margin-left:5px">
+            <el-input autocomplete="off" style="width: 200px;" v-model="formData.verify"
+              placeholder="请输入验证码"></el-input>
+            <el-button size="mini" @click="send_old_msg" :disabled="sended"
+              style="line-height: 34px;padding: 0 22px;color:#606266;margin-left:5px">
               {{ sendBtnText }}
             </el-button>
           </div>
@@ -140,7 +147,8 @@
           <el-input style="width: 300px;" v-model="formData.target_id" v-show="false"></el-input>
           <div class="border flex-box-ce">
             <div class="flex-1">
-              <div class="fontColorB font-flex-word"  style="width: 230px;" v-if="formData.target_id">{{target.name}}</div>
+              <div class="fontColorB font-flex-word" style="width: 230px;" v-if="formData.target_id">{{target.name}}
+              </div>
               <span v-else>请选择人员</span>
             </div>
             <i class="el-icon-arrow-down icon-right"></i>
@@ -155,24 +163,14 @@
     </el-dialog>
 
     <!-- 更换绩效主管理员 -->
-    <EmployeeSelector
-      title="设置主管理员"
-      :is_filtration_creator="false"
-      :multi="false"
-      :isChecKedAll="false"
-      :visible.sync="setJxCreator"
-      @confirm="confirmCreator"
-    />
+    <EmployeeSelector title="设置主管理员" :is_filtration_creator="false" :multi="false" :isChecKedAll="false"
+      :visible.sync="setJxCreator" @confirm="confirmCreator" />
     <!-- 更换积分主管理员 -->
-    <EmployeeSelector
-      title="设置主管理员"
-      :multi="false"
-      :isChecKedAll="false"
-      :visible.sync="setPointCreator"
-      @confirm="confirmCreator2"
-    />
+    <EmployeeSelector title="设置主管理员" :multi="false" :isChecKedAll="false" :visible.sync="setPointCreator"
+      @confirm="confirmCreator2" />
 
-    <el-dialog :close-on-click-modal="false" title="客服" :visible.sync="innerVisible" width="400px" append-to-body class="innerVisible">
+    <el-dialog :close-on-click-modal="false" title="客服" :visible.sync="innerVisible" width="400px" append-to-body
+      class="innerVisible">
       <p style="margin:0;font-size:18px;">微信扫码添加功道云客服进行咨询</p>
       <img src="static/images/code2.png" style="width:100%" />
       <p style="font-size:20px;">电话咨询:400-6877-880</p>

+ 4 - 4
src/home.vue

@@ -17,10 +17,10 @@
                   <div class="fontColorC font-flex-word" style="max-width: 180px;">{{returnDeptName()}}</div>
                 </div>
                 <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>
-                    </div>
+                  <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>
+                  </div>
                 </template>
             </div>
 

+ 5 - 8
src/index.vue

@@ -23,7 +23,6 @@
               <SystemMessage></SystemMessage>
               <div slot="reference" class="count_max">{{ site_info.user_count_max }}用户</div>
             </el-popover>
-             <!-- <div v-else  class="count_max">{{ site_info.user_count_max }}用户</div> -->
           </div>
           <div class="flex-1"></div>
           <div class="right-menu">
@@ -830,14 +829,12 @@ export default {
   overflow-y: scroll;
   padding: 10px;
   min-width: 1100px;
+  box-sizing: border-box;
+}
+.el-main::-webkit-scrollbar {
+  display: none;
 }
-// .el-main::-webkit-scrollbar {
-//   width: 8px;
-//   background-color: #fff;
-// }
-// .el-main::-webkit-scrollbar-thumb {
-//   background-color: #;
-// }
+
 .logo-box {
   cursor: pointer;
   width: 70px;

+ 39 - 1
src/main.js

@@ -7,7 +7,26 @@ import moment from 'moment' // 时间库
 import App from './App'
 import router from './router'
 import store from './store'
-import {getDept,openUrl, getToken, getCourseId,getTyps,getLocal, getTypsName, supremeAuthority, getUserData, getEmployeeMap,getEmployeeMapAll,getEmployeeMapItem,getIsIdentity,getCache,setCache,removeCache,returnDeptName,getIsAdministrator} from '@/utils/auth'
+import {
+  getDept,
+  openUrl, 
+  getToken, 
+  getCourseId,
+  getTyps,
+  getLocal, 
+  getTypsName, 
+  supremeAuthority, 
+  getUserData, 
+  getEmployeeMap,
+  getEmployeeMapAll,
+  getEmployeeMapItem,
+  getIsIdentity,
+  getCache,
+  setCache,
+  removeCache,
+  returnDeptName,
+  getIsAdministrator
+} from '@/utils/auth'
 import './performanceSet' // 绩效系统相关配置
 import {onFilePreView } from '@/okr/utils/auth';
 // import { VueOkrTree } from "vue-okr-tree";
@@ -22,6 +41,7 @@ import * as filters from '@/utils/filters'
 import axiosKq from '@/utils/axiosKq'
 import axiosUser from '@/utils/axiosUser'
 import axios from '@/utils/axios'
+import http from '@/utils/http'
 import axiosKc from '@/utils/axiosKc'
 import * as socketApi from './api/websocket'
 import * as socketApiTow from './api/websocketTow'
@@ -45,6 +65,7 @@ Vue.prototype.$axios = axios
 Vue.prototype.$axiosUser = axiosUser
 Vue.prototype.$axiosKq = axiosKq
 Vue.prototype.$axiosKc = axiosKc
+Vue.prototype.$http = http
 Vue.prototype.$moment = moment
 Vue.prototype.$getToken = getToken
 Vue.prototype.$getCourseId = getCourseId
@@ -114,6 +135,23 @@ Object.keys(filters).forEach(key => {
   Vue.filter(key, filters[key])
 })
 
+
+import tableMove from "@/utils/tableMove";
+
+Vue.use(tableMove)
+
+
+// 族谱图组件
+import Vue2OrgTree from 'vue-tree-color'
+Vue.use(Vue2OrgTree)
+
+// 创建事件总线   就相当于创建了一个新的vue实例
+const bus = new Vue()
+// 把bus挂载到了Vue的原型上, 保证所有的组件都能通过 this.$bus访问到事件总线
+Vue.prototype.$bus = bus
+
+
+
 Vue.config.productionTip = false
 
 new Vue({

+ 127 - 0
src/newPerformance/components/CombinationTemplates/BatchTemplatePublish.vue

@@ -0,0 +1,127 @@
+<template>
+    <div>
+        <el-dialog title="请选择考核模板" center :visible.sync="chooseExamineDialog" width="500px"
+            :before-close="dialogBeforeClose">
+            <div class="dialog-content" v-loading="loading">
+                <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange"
+                    style="margin-bottom: 10px;">全选</el-checkbox>
+                <div class="examine-list scroll-bar">
+                    <div class="examine-item" v-for="item in examineList" :key="item.templateId">
+                        <el-checkbox-group v-model="selectExamineIds" @change="changeSelectExamineIds">
+                            <el-checkbox :label="item.templateId"
+                                style="width: 100%; padding: 5px 0; box-sizing: border-box; margin-bottom: 10px; border-bottom: 1px solid #f1f1f1;">
+                                {{ item.title || '默认标题' }}
+                            </el-checkbox>
+                        </el-checkbox-group>
+                    </div>
+                </div>
+
+            </div>
+            <div slot="footer">
+                <el-button @click="dialogBeforeClose()">取 消</el-button>
+                <el-button type="primary" @click="next()" :disable="!(selectExamineIds && selectExamineIds.length > 0)">下一步</el-button>
+            </div>
+        </el-dialog>
+
+    </div>
+
+</template>
+
+
+<script>
+import moment from 'moment';
+import { mapGetters } from 'vuex';
+import InterviewFlow from "../TemplateDetails/InterviewFlow" // 面谈弹框
+
+export default {
+    components: {
+        InterviewFlow
+    },
+    model: {
+        prop: 'chooseExamineDialog',
+        event: 'close-dialog'
+    },
+    props: {
+        chooseExamineDialog: {
+            type: Boolean,
+            default: false
+        },
+        examineList: {
+            type: Array,
+            default: () => []
+        }
+    },
+
+    data() {
+        return {
+            loading: false,
+            selectExamineIds: [],
+            isIndeterminate: false,
+            checkAll: false,
+            interview: false,
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+    },
+    watch: {
+    },
+
+    mounted() {
+        
+    },
+
+    methods: {
+
+        handleCheckAllChange(v) {
+            if (v) {
+                this.examineList.forEach(examine => {
+                    this.selectExamineIds.push(examine.templateId)
+                })
+
+                this.isIndeterminate = true;
+                this.checkAll = true;
+            } else {
+                this.selectExamineIds = [];
+                this.isIndeterminate = false;
+                this.checkAll = false;
+            }
+        },
+        changeSelectExamineIds(v) { },
+
+        dialogBeforeClose() {
+            this.$emit('close-dialog', false)
+        },
+
+        next() {
+            this.$emit("onConfirm", this.selectExamineIds)
+            this.$emit('close-dialog', false)
+        }
+    }
+}
+
+</script>
+
+
+
+<style scoped="scoped" lang="scss">
+.status-btn-box {
+    width: 120px;
+    height: 40px;
+    z-index: 10;
+    position: absolute;
+    top: 20px;
+    left: 20px;
+}
+
+.dialog-content {
+    width: 100%;
+    height: 450px;
+
+    .examine-list {
+        width: 100%;
+        height: 400px;
+        overflow-y: auto;
+    }
+}
+</style>

+ 171 - 0
src/newPerformance/components/CombinationTemplates/EmployeeChooseOkrs.vue

@@ -0,0 +1,171 @@
+<template>
+    <div>
+        <el-dialog title="考核人员绑定OKR" center :visible.sync="chooseOkrsDialog" width="500px"
+            :before-close="dialogBeforeClose">
+            <div class="flex-box-ce" style="flex-direction: column; justify-content: center; width: 100%;">
+
+                <div class="title">
+                    已绑定的考核人员
+                </div>
+
+                <el-select v-model="employeeIds" placeholder="请选择指定人员" multiple filterable
+                    style="width: 100%; margin: 10px 0;" @change="changeEmployeeIds">
+                    <el-option v-for="item in employeeMap" :key="item.id" :label="item.name"
+                        :value="item.id"></el-option>
+                </el-select>
+
+
+                <div style="width: 100%; margin-bottom: 10px;" v-for="(item, index) in showEmployeeInfos" :key="index"
+                    @click="openSelectedOkrs(item)">
+                    <div class="flex-box-ce" style="width: 100%; justify-content: space-between; margin-bottom: 10px;">
+                        <div class="flex-box-ce">
+                            <userImage :id="item.employeeId" :img_url="item.img_url" :user_name="item.employeeName"
+                                width="30px" height="30px" style="margin-right: 5px;"></userImage>
+                            {{ item.employeeName }}
+                        </div>
+
+                        <el-link type="primary" size="small">请选择关联OKR</el-link>
+                    </div>
+                    <div class="selected-list" v-if="item.okrInfos && item.okrInfos.length > 0">
+                        <div class="selected-item" v-for="info in item.okrInfos">
+                            {{ info.name }}
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </el-dialog>
+
+        <!-- 关联OKR -->
+        <TargetSearch :visible.sync="isShowProject" @confirm="confirmProject" :selectedCheckList="selectedOkrs"
+            :showSelectedData="[]"></TargetSearch>
+    </div>
+</template>
+
+
+<script>
+import _ from "lodash"
+
+import TargetSearch from '@/performance/views/assessManagement/TargetSearch'; // 对齐目标
+
+export default {
+    components: {
+        TargetSearch
+    },
+    model: {
+        prop: 'chooseOkrsDialog',
+        event: 'close-dialog'
+    },
+    props: {
+        chooseOkrsDialog: {
+            type: Boolean,
+            default: false
+        },
+        selectEmployeeIds: {
+            type: Array,
+            default: () => []
+        },
+        employeeInfos: {
+            type: Array,
+            default: () => []
+        }
+    },
+
+    watch: {
+        chooseOkrsDialog(v) {
+            this.employeeIds = _.cloneDeep(this.selectEmployeeIds)
+            this.interviewFlow = _.cloneDeep(this.employeeInfos[0].interviewFlow)
+            this.showEmployeeInfos = _.cloneDeep(this.employeeInfos)
+        }
+    },
+
+    data() {
+        return {
+            employeeIds: [],
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            showEmployeeInfos: [],
+            interviewFlow: {},
+            isShowProject: false,
+            selectedCheckList: [],
+            selectedOkrs: [],
+            chooseEmployee: null
+        }
+    },
+
+    mounted() {
+        this.employeeIds = this.selectEmployeeIds;
+        this.employeeIds = _.cloneDeep(this.selectEmployeeIds)
+        this.interviewFlow = _.cloneDeep(this.employeeInfos[0].interviewFlow)
+        this.showEmployeeInfos = _.cloneDeep(this.employeeInfos)
+    },
+
+    methods: {
+        dialogBeforeClose() {
+            this.$emit('close-dialog', false)
+        },
+
+        changeEmployeeIds(v) {
+            this.employeeIds = v
+            this.employeeIds = Array.from(new Set(this.employeeIds.map(JSON.stringify))).map(JSON.parse);
+            console.log(this.employeeIds)
+            if (this.employeeIds && this.employeeIds.length > 0) {
+                this.employeeIds.forEach(employee => {
+                    let obj = {
+                        employeeId: employee,
+                        employeeName: this.employeeMap[employee].name,
+                        ids: [],
+                        img_url: this.employeeMap[employee].img_url,
+                        interviewFlow: this.interviewFlow
+                    }
+                    this.showEmployeeInfos.push(obj)
+                })
+            } else {
+                this.employeeIds = []
+                this.showEmployeeInfos = []
+            }
+            this.showEmployeeInfos = Array.from(new Set(this.showEmployeeInfos.map(JSON.stringify))).map(JSON.parse);
+        },
+
+        openSelectedOkrs(item) {
+            console.log(item);
+            this.chooseEmployeeId = item.employeeId;
+            this.selectedOkrs = item.ids;
+            this.isShowProject = true
+        },
+
+        confirmProject(okrs, selectedData) {
+            console.log(okrs)
+            this.showEmployeeInfos.forEach(item => {
+                if (item.employeeId == this.chooseEmployeeId) {
+                    item.ids = okrs
+                    item.okrInfos = selectedData
+                }
+            })
+            console.log(this.showEmployeeInfos)
+
+            // this.selectedData = selectedData;
+        }
+    }
+}
+</script>
+
+
+<style scoped lang="scss">
+.title {
+    width: 100%;
+    text-align: left;
+    line-height: 40px;
+    color: #999;
+}
+
+.selected-item {
+    padding: 5px;
+    box-sizing: border-box;
+    border-radius: 4px;
+    background: #f7f7f7;
+    margin-bottom: 5px;
+
+    &:hover {
+        cursor: pointer;
+    }
+}
+</style>

+ 546 - 0
src/newPerformance/components/ExamineContrast copy 2.vue

@@ -0,0 +1,546 @@
+<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="title">已选择的考核列表:</div>
+            <el-button size="mini" @click="dialogVisible = true">查看更多</el-button>
+        </div>
+
+
+        <div v-if="selectExamineList && selectExamineList.length > 0" class="template-list"
+            style="background: #f7f7f7; padding: 10px 10px 0 0; margin: 0 20px; border-radius: 4px;">
+            <template size="small" v-for="(item, index) in selectExamineList">
+                <el-tag :key="item.reviewPackageId" style="margin: 0 0 10px 10px;" closable
+                    @close="handleTagDelete(index)">
+                    {{ 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>
+
+        <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"
+                    :data="userList" style="width: 100%; " border stripe :header-cell-style="{ background: '#f5f7fa' }"
+                    v-loading="loading">
+                    <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">
+                        <template slot-scope="scope">
+                            <div v-for="user in item.users">
+                                <span v-if="user.employeeId == scope.row.employeeId">{{ user.score }}</span>
+                            </div>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </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%;" />
+                </div>
+            </el-tab-pane>
+        </el-tabs>
+
+        <el-dialog title="请选择需要对比的考核列表" center :visible.sync="dialogVisible" width="600px"
+            :before-close="dialogBeforeClose">
+            <div>
+                <div class="search-box">
+                    <el-select v-model="cycleType" placeholder="请选择周期类型" @change="changeCircle" style="width: 100px;"
+                        size="mini">
+                        <el-option v-for="item in cycleOptions" :key="item.value" :label="item.label"
+                            :value="item.value">
+                        </el-option>
+                    </el-select>
+
+                    <el-date-picker v-model="date" type="daterange" align="right" unlink-panels range-separator="至"
+                        start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd"
+                        :picker-options="pickerOptions" @change="changeDate" style="width: 300px; margin: 0 10px;"
+                        size="mini">
+                    </el-date-picker>
+
+                    <el-select v-model="status" placeholder="请选择" style="width: 100px;" @change="changeStatus"
+                        size="mini">
+                        <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
+                        </el-option>
+                    </el-select>
+
+                    <el-button plain round size="mini" style="margin-left: 10px;" @click="reset">重 置</el-button>
+                </div>
+                <div class="package-list">
+                    <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>
+                        </template>
+                    </div>
+                    <div class="choose-template scroll-bar">
+                        <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>
+
+            </div>
+            <div slot="footer">
+                <el-button @click="dialogVisible = false">取 消</el-button>
+                <el-button type="primary" @click="confirm">确 定</el-button>
+            </div>
+        </el-dialog>
+
+        <div style="height: 50px;"></div>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+
+export default {
+    data() {
+        return {
+            checkAll: false,
+            isIndeterminate: true,
+            dialogVisible: false,
+            activeName: "0",
+            loading: false,
+            treeData: {},
+            // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+            cycleType: '-1',
+            cycleOptions: [
+                { label: "全部", value: '-1' },
+                { label: "未定义", value: '0' },
+                { label: "年度", value: '1' },
+                { label: "半年度", value: '2' },
+                { label: "季度", value: '3' },
+                { label: "月度", value: '4' },
+            ],
+            status: -1,
+            options: [{
+                value: -1,
+                label: '全部'
+            }, {
+                value: '0',
+                label: '未完成'
+            }, {
+                value: '1',
+                label: '已完成'
+            }],
+            params: {
+                startDate: '',
+                endDate: '',
+                status: -1, // 不传默认返回全部 0-未完成 1-已完成
+            },
+
+            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]);
+                    }
+                }]
+            },
+            date: [],
+            examineList: [], // 考核表列表
+            tableHeader: [],
+            tableData: [],
+            userList: [],
+            selectExamineIds: [],
+            selectExamineList: [],
+            chooseExamineList: []
+        }
+    },
+    // watch: {
+    //     chooseExamineList(v) {
+    //         this.selectExamineList = this.chooseExamineList
+    //     }
+    // },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    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 = []
+            }
+            
+        },
+        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))
+                }
+
+            }
+        },
+
+        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;
+            } else {
+                this.isIndeterminate = false;
+                this.checkAll = false;
+            }
+            if (this.activeName == '0') this.initTableData()
+            if (this.activeName == '1') this.initTreeData()
+        },
+
+        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 }
+            }
+            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;
+                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.treeData = {
+                    id: 0,
+                    label: "考核对比",
+                    children: []
+                }
+                // this.selectExamineIds = [];
+            })
+        },
+        initTableData() {
+            let userList = [];
+            let selectExamineList = [];
+
+            this.examineList && this.examineList.forEach(item => {
+                this.selectExamineIds.forEach(selectExamineId => {
+                    if (item.reviewPackageId == selectExamineId) {
+                        selectExamineList.push(item)
+                    }
+                })
+
+            })
+
+            selectExamineList.forEach(item => {
+                let { reviewPackageId, title, users } = item;
+                if (users && users.length > 0) {
+                    users.forEach(user => {
+                        let { employeeName, employeeId } = user
+                        userList.push({ employeeName, employeeId })
+                    })
+                }
+            })
+            this.userList = userList.reduce((acc, obj) => {
+                // 检查当前对象是否已经存在于结果数组中
+                if (!acc.find(item => item.employeeId === obj.employeeId)) { // 假设我们通过id来判断唯一性
+                    acc.push(obj);
+                }
+                return acc;
+            }, []);
+
+        },
+        initTreeData() {
+            this.selectExamineList.forEach(item => {
+                item.id = Date.now() + Math.floor(Math.random() * 10000);
+                item.label = item.title;
+                item.children = item.users;
+                item.users.forEach(user => {
+                    user.id = Date.now() + Math.floor(Math.random() * 10000);
+                    user.label = user.employeeName + ", " + (user.score ? user.score : 0);
+                })
+            })
+            this.treeData = {
+                id: 0,
+                label: "考核对比",
+                children: this.selectExamineList
+            }
+        },
+        handleClick(tab, event) {
+            if (this.activeName == '0') this.initTableData()
+            if (this.activeName == '1') this.initTreeData()
+        },
+        // 日期选择时间
+        changeDate(v) {
+            if (v && v.length > 0) {
+                this.params.startDate = v[0] || ''
+                this.params.endDate = v[1] || ''
+                this.getRecords();
+            }
+
+        },
+        //选择周期
+        changeCircle(v) {
+            this.getRecords();
+        },
+        // 选择状态
+        changeStatus(v) {
+            this.getRecords();
+        },
+        // 重置搜索条件
+        reset() {
+            this.cycleType = '-1'
+            this.status = -1
+            this.date = []
+            this.params = {
+                startDate: '',
+                endDate: '',
+                status: -1, // 不传默认返回全部 0-未完成 1-已完成
+            }
+            this.getRecords();
+        },
+
+        // 清空选择的考核列表
+        clear() {
+            this.selectExamineIds = [];
+            this.chooseExamineList = [];
+        },
+        dialogBeforeClose() {
+            this.dialogVisible = false;
+        },
+        confirm() {
+            this.selectExamineList = this.chooseExamineList;
+            if (this.activeName == '0') this.initTableData()
+            if (this.activeName == '1') this.initTreeData()
+            this.dialogVisible = false;
+        }
+    },
+
+}
+
+</script>
+
+
+
+<style scoped="scoped" lang="scss">
+.contrast-container {
+    width: 100%;
+    height: 100%;
+    background-color: #fff;
+
+    .el-icon-close {
+        transition: all 0.2s;
+
+        &:hover {
+            font-size: 16px;
+        }
+    }
+
+    .search-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        box-sizing: border-box;
+    }
+
+    .title-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 10px;
+        padding: 0 20px;
+
+        .title {
+            color: #999;
+            font-size: 14px;
+            font-weight: bold;
+        }
+    }
+
+    .package-list {
+        width: 100%;
+        height: 400px;
+        display: flex;
+        margin-top: 10px;
+        border: 1px solid #f7f7f7;
+
+        .template-list {
+            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;
+                }
+
+            }
+        }
+
+        .choose-template {
+            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;
+            }
+        }
+    }
+
+
+    .table-box {
+        width: 100%;
+        padding: 0 20px;
+        box-sizing: border-box;
+    }
+
+    /* 设置滚动条的宽度和背景色 */
+    ::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>

+ 549 - 0
src/newPerformance/components/ExamineContrast copy 3.vue

@@ -0,0 +1,549 @@
+<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="title">已选择的考核列表:</div>
+            <el-button size="mini" @click="dialogVisible = true">查看更多</el-button>
+        </div>
+
+
+        <div v-if="selectExamineList && selectExamineList.length > 0" class="template-list"
+            style="background: #f7f7f7; padding: 10px 10px 0 0; margin: 0 20px; border-radius: 4px;">
+            <template size="small" v-for="(item, index) in selectExamineList">
+                <el-tag :key="item.reviewPackageId" style="margin: 0 0 10px 10px;" closable
+                    @close="handleTagDelete(index)">
+                    {{ 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>
+
+        <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"
+                    :data="userList" style="width: 100%; " border stripe :header-cell-style="{ background: '#f5f7fa' }"
+                    v-loading="loading">
+                    <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">
+                        <template slot-scope="scope">
+                            <div v-for="user in item.users">
+                                <span v-if="user.employeeId == scope.row.employeeId">{{ user.score }}</span>
+                            </div>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </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%;" />
+                </div>
+            </el-tab-pane>
+        </el-tabs>
+
+        <el-dialog title="请选择需要对比的考核列表" center :visible.sync="dialogVisible" width="700px"
+            :before-close="dialogBeforeClose">
+            <div>
+                <div class="search-box">
+                    <el-select v-model="cycleType" placeholder="请选择周期类型" @change="changeCircle" style="width: 100px;"
+                        size="mini">
+                        <el-option v-for="item in cycleOptions" :key="item.value" :label="item.label"
+                            :value="item.value">
+                        </el-option>
+                    </el-select>
+
+                    <el-date-picker v-model="date" type="daterange" align="right" unlink-panels range-separator="至"
+                        start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd"
+                        :picker-options="pickerOptions" @change="changeDate" style="width: 300px; margin: 0 10px;"
+                        size="mini">
+                    </el-date-picker>
+
+                    <el-select v-model="status" placeholder="请选择" style="width: 100px;" @change="changeStatus"
+                        size="mini">
+                        <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
+                        </el-option>
+                    </el-select>
+
+                    <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>
+                        </template>
+                    </div>
+                    <div class="choose-template scroll-bar">
+                        <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>
+
+            </div>
+            <div slot="footer">
+                <el-button @click="dialogVisible = false">取 消</el-button>
+                <el-button type="primary" @click="confirm">确 定</el-button>
+            </div>
+        </el-dialog>
+
+        <div style="height: 50px;"></div>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+
+export default {
+    data() {
+        return {
+            checkAll: false,
+            isIndeterminate: true,
+            dialogVisible: false,
+            activeName: "0",
+            loading: false,
+            treeData: {},
+            // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+            cycleType: '-1',
+            cycleOptions: [
+                { label: "全部", value: '-1' },
+                { label: "未定义", value: '0' },
+                { label: "年度", value: '1' },
+                { label: "半年度", value: '2' },
+                { label: "季度", value: '3' },
+                { label: "月度", value: '4' },
+            ],
+            status: -1,
+            options: [{
+                value: -1,
+                label: '全部'
+            }, {
+                value: '0',
+                label: '未完成'
+            }, {
+                value: '1',
+                label: '已完成'
+            }],
+            params: {
+                startDate: '',
+                endDate: '',
+                status: -1, // 不传默认返回全部 0-未完成 1-已完成
+            },
+            transferData: [],
+            transferValue: [],
+            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]);
+                    }
+                }]
+            },
+            date: [],
+            examineList: [], // 考核表列表
+            tableHeader: [],
+            tableData: [],
+            userList: [],
+            selectExamineIds: [],
+            selectExamineList: [],
+            chooseExamineList: []
+        }
+    },
+    // watch: {
+    //     chooseExamineList(v) {
+    //         this.selectExamineList = this.chooseExamineList
+    //     }
+    // },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    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 = []
+            }
+            
+        },
+        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))
+                }
+
+            }
+        },
+
+        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;
+            } else {
+                this.isIndeterminate = false;
+                this.checkAll = false;
+            }
+            if (this.activeName == '0') this.initTableData()
+            if (this.activeName == '1') this.initTreeData()
+        },
+
+        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 }
+            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.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)
+                    }
+                })
+
+            })
+
+            selectExamineList.forEach(item => {
+                let { reviewPackageId, title, users } = item;
+                if (users && users.length > 0) {
+                    users.forEach(user => {
+                        let { employeeName, employeeId } = user
+                        userList.push({ employeeName, employeeId })
+                    })
+                }
+            })
+            this.userList = userList.reduce((acc, obj) => {
+                // 检查当前对象是否已经存在于结果数组中
+                if (!acc.find(item => item.employeeId === obj.employeeId)) { // 假设我们通过id来判断唯一性
+                    acc.push(obj);
+                }
+                return acc;
+            }, []);
+
+        },
+        initTreeData() {
+            this.selectExamineList.forEach(item => {
+                item.id = Date.now() + Math.floor(Math.random() * 10000);
+                item.label = item.title;
+                item.children = item.users;
+                item.users.forEach(user => {
+                    user.id = Date.now() + Math.floor(Math.random() * 10000);
+                    user.label = user.employeeName + ", " + (user.score ? user.score : 0);
+                })
+            })
+            this.treeData = {
+                id: 0,
+                label: "考核对比",
+                children: this.selectExamineList
+            }
+        },
+        handleClick(tab, event) {
+            if (this.activeName == '0') this.initTableData()
+            if (this.activeName == '1') this.initTreeData()
+        },
+        // 日期选择时间
+        changeDate(v) {
+            if (v && v.length > 0) {
+                this.params.startDate = v[0] || ''
+                this.params.endDate = v[1] || ''
+                this.getRecords();
+            }
+
+        },
+        //选择周期
+        changeCircle(v) {
+            console.log(v);
+            this.getRecords();
+        },
+        // 选择状态
+        changeStatus(v) {
+            this.getRecords();
+        },
+        // 重置搜索条件
+        reset() {
+            this.cycleType = '-1'
+            this.status = -1
+            this.date = []
+            this.params = {
+                startDate: '',
+                endDate: '',
+                status: -1, // 不传默认返回全部 0-未完成 1-已完成
+            }
+            this.getRecords();
+        },
+
+        // 清空选择的考核列表
+        clear() {
+            this.selectExamineIds = [];
+            this.chooseExamineList = [];
+        },
+        dialogBeforeClose() {
+            this.dialogVisible = false;
+        },
+        confirm() {
+            this.selectExamineList = this.examineList.filter(item => this.transferValue.indexOf(item.reviewPackageId) >= 0);
+            if (this.activeName == '0') this.initTableData()
+            if (this.activeName == '1') this.initTreeData()
+            this.dialogVisible = false;
+        }
+    },
+
+}
+
+</script>
+
+
+
+<style scoped="scoped" lang="scss">
+.contrast-container {
+    width: 100%;
+    height: 100%;
+    background-color: #fff;
+
+    .el-icon-close {
+        transition: all 0.2s;
+
+        &:hover {
+            font-size: 16px;
+        }
+    }
+
+    .search-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        box-sizing: border-box;
+    }
+
+    .title-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 10px;
+        padding: 0 20px;
+
+        .title {
+            color: #999;
+            font-size: 14px;
+            font-weight: bold;
+        }
+    }
+
+    .package-list {
+        width: 100%;
+        height: 400px;
+        margin-top: 10px;
+        border: 1px solid #f7f7f7;
+
+        .template-list {
+            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;
+                }
+
+            }
+        }
+
+        .choose-template {
+            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;
+            }
+        }
+    }
+
+
+    .table-box {
+        width: 100%;
+        padding: 0 20px;
+        box-sizing: border-box;
+    }
+
+    /* 设置滚动条的宽度和背景色 */
+    ::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>

+ 537 - 0
src/newPerformance/components/ExamineContrast copy.vue

@@ -0,0 +1,537 @@
+<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="title">已选择的考核列表:</div>
+            <el-button size="mini" @click="dialogVisible = true">查看更多</el-button>
+        </div>
+
+
+        <div v-if="selectExamineList && selectExamineList.length > 0" class="template-list"
+            style="background: #f7f7f7; padding: 10px 10px 0 0; margin: 0 20px; border-radius: 4px;">
+            <template size="small" v-for="(item, index) in selectExamineList">
+                <el-tag :key="item.reviewPackageId" style="margin: 0 0 10px 10px;" closable
+                    @close="handleTagDelete(index)">
+                    {{ 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>
+
+        <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"
+                    :data="userList" style="width: 100%; " border stripe :header-cell-style="{ background: '#f5f7fa' }"
+                    v-loading="loading">
+                    <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">
+                        <template slot-scope="scope">
+                            <div v-for="user in item.users">
+                                <span v-if="user.employeeId == scope.row.employeeId">{{ user.score }}</span>
+                            </div>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </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%;" />
+                </div>
+            </el-tab-pane>
+        </el-tabs>
+
+        <el-dialog title="请选择需要对比的考核列表" center :visible.sync="dialogVisible" width="600px"
+            :before-close="dialogBeforeClose">
+            <div>
+                <div class="search-box">
+                    <el-select v-model="cycleType" placeholder="请选择周期类型" @change="changeCircle" style="width: 100px;"
+                        size="mini">
+                        <el-option v-for="item in cycleOptions" :key="item.value" :label="item.label"
+                            :value="item.value">
+                        </el-option>
+                    </el-select>
+
+                    <el-date-picker v-model="date" type="daterange" align="right" unlink-panels range-separator="至"
+                        start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd"
+                        :picker-options="pickerOptions" @change="changeDate" style="width: 300px; margin: 0 10px;"
+                        size="mini">
+                    </el-date-picker>
+
+                    <el-select v-model="status" placeholder="请选择" style="width: 100px;" @change="changeStatus"
+                        size="mini">
+                        <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
+                        </el-option>
+                    </el-select>
+
+                    <el-button plain round size="mini" style="margin-left: 10px;" @click="reset">重 置</el-button>
+                </div>
+                <div class="package-list">
+                    <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>
+                        </template>
+                    </div>
+                    <div class="choose-template scroll-bar">
+                        <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>
+
+            </div>
+            <div slot="footer">
+                <el-button @click="dialogVisible = false">取 消</el-button>
+                <el-button type="primary" @click="confirm">确 定</el-button>
+            </div>
+        </el-dialog>
+
+        <div style="height: 50px;"></div>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+
+export default {
+    data() {
+        return {
+            checkAll: false,
+            isIndeterminate: true,
+            dialogVisible: false,
+            activeName: "0",
+            loading: false,
+            treeData: {},
+            // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+            cycleType: '-1',
+            cycleOptions: [
+                { label: "全部", value: '-1' },
+                { label: "未定义", value: '0' },
+                { label: "年度", value: '1' },
+                { label: "半年度", value: '2' },
+                { label: "季度", value: '3' },
+                { label: "月度", value: '4' },
+            ],
+            status: -1,
+            options: [{
+                value: -1,
+                label: '全部'
+            }, {
+                value: '0',
+                label: '未完成'
+            }, {
+                value: '1',
+                label: '已完成'
+            }],
+            params: {
+                startDate: '',
+                endDate: '',
+                status: -1, // 不传默认返回全部 0-未完成 1-已完成
+            },
+
+            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]);
+                    }
+                }]
+            },
+            date: [],
+            examineList: [], // 考核表列表
+            tableHeader: [],
+            tableData: [],
+            userList: [],
+            selectExamineIds: [],
+            selectExamineList: [],
+            chooseExamineList: []
+        }
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    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) {
+            if (v && v.length > 0) {
+                this.examineList.forEach(examine => {
+                    this.selectExamineIds.forEach(val => {
+                        if (val == examine.reviewPackageId) this.chooseExamineList.push(examine)
+                    })
+                })
+            }
+
+        },
+        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))
+                }
+
+            }
+        },
+
+        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;
+            } else {
+                this.isIndeterminate = false;
+                this.checkAll = false;
+            }
+            if (this.activeName == '0') this.initTableData()
+            if (this.activeName == '1') this.initTreeData()
+        },
+
+        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 }
+            }
+            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;
+                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.treeData = {
+                    id: 0,
+                    label: "考核对比",
+                    children: []
+                }
+                // this.selectExamineIds = [];
+            })
+        },
+        initTableData() {
+            let userList = [];
+            let selectExamineList = [];
+
+            this.examineList && this.examineList.forEach(item => {
+                this.selectExamineIds.forEach(selectExamineId => {
+                    if (item.reviewPackageId == selectExamineId) {
+                        selectExamineList.push(item)
+                    }
+                })
+
+            })
+
+            selectExamineList.forEach(item => {
+                let { reviewPackageId, title, users } = item;
+                if (users && users.length > 0) {
+                    users.forEach(user => {
+                        let { employeeName, employeeId } = user
+                        userList.push({ employeeName, employeeId })
+                    })
+                }
+            })
+            this.userList = userList.reduce((acc, obj) => {
+                // 检查当前对象是否已经存在于结果数组中
+                if (!acc.find(item => item.employeeId === obj.employeeId)) { // 假设我们通过id来判断唯一性
+                    acc.push(obj);
+                }
+                return acc;
+            }, []);
+
+        },
+        initTreeData() {
+            this.selectExamineList.forEach(item => {
+                item.id = Date.now() + Math.floor(Math.random() * 10000);
+                item.label = item.title;
+                item.children = item.users;
+                item.users.forEach(user => {
+                    user.id = Date.now() + Math.floor(Math.random() * 10000);
+                    user.label = user.employeeName + ", " + (user.score ? user.score : 0);
+                })
+            })
+            this.treeData = {
+                id: 0,
+                label: "考核对比",
+                children: this.selectExamineList
+            }
+        },
+        handleClick(tab, event) {
+            if (this.activeName == '0') this.initTableData()
+            if (this.activeName == '1') this.initTreeData()
+        },
+        // 日期选择时间
+        changeDate(v) {
+            if (v && v.length > 0) {
+                this.params.startDate = v[0] || ''
+                this.params.endDate = v[1] || ''
+                this.getRecords();
+            }
+
+        },
+        //选择周期
+        changeCircle(v) {
+            this.getRecords();
+        },
+        // 选择状态
+        changeStatus(v) {
+            this.getRecords();
+        },
+        // 重置搜索条件
+        reset() {
+            this.cycleType = '-1'
+            this.status = -1
+            this.date = []
+            this.params = {
+                startDate: '',
+                endDate: '',
+                status: -1, // 不传默认返回全部 0-未完成 1-已完成
+            }
+            this.getRecords();
+        },
+
+        // 清空选择的考核列表
+        clear() {
+            this.selectExamineIds = [];
+            this.chooseExamineList = [];
+        },
+        dialogBeforeClose() {
+            this.dialogVisible = false;
+        },
+        confirm() {
+            this.selectExamineList = this.examineList.filter(item => this.selectExamineIds.includes(item.reviewPackageId))
+            if (this.activeName == '0') this.initTableData()
+            if (this.activeName == '1') this.initTreeData()
+            this.dialogVisible = false;
+        }
+    },
+
+}
+
+</script>
+
+
+
+<style scoped="scoped" lang="scss">
+.contrast-container {
+    width: 100%;
+    height: 100%;
+    background-color: #fff;
+
+    .el-icon-close {
+        transition: all 0.2s;
+
+        &:hover {
+            font-size: 16px;
+        }
+    }
+
+    .search-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        box-sizing: border-box;
+    }
+
+    .title-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 10px;
+        padding: 0 20px;
+
+        .title {
+            color: #999;
+            font-size: 14px;
+            font-weight: bold;
+        }
+    }
+
+    .package-list {
+        width: 100%;
+        height: 400px;
+        display: flex;
+        margin-top: 10px;
+        border: 1px solid #f7f7f7;
+
+        .template-list {
+            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;
+                }
+
+            }
+        }
+
+        .choose-template {
+            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;
+            }
+        }
+    }
+
+
+    .table-box {
+        width: 100%;
+        padding: 0 20px;
+        box-sizing: border-box;
+    }
+
+    /* 设置滚动条的宽度和背景色 */
+    ::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>

+ 549 - 0
src/newPerformance/components/ExamineContrast.vue

@@ -0,0 +1,549 @@
+<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="title">已选择的考核列表:</div>
+            <el-button size="mini" @click="dialogVisible = true">查看更多</el-button>
+        </div>
+
+
+        <div v-if="selectExamineList && selectExamineList.length > 0" class="template-list"
+            style="background: #f7f7f7; padding: 10px 10px 0 0; margin: 0 20px; border-radius: 4px;">
+            <template size="small" v-for="(item, index) in selectExamineList">
+                <el-tag :key="item.reviewPackageId" style="margin: 0 0 10px 10px;" closable
+                    @close="handleTagDelete(index)">
+                    {{ 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>
+
+        <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"
+                    :data="userList" style="width: 100%; " border stripe :header-cell-style="{ background: '#f5f7fa' }"
+                    v-loading="loading">
+                    <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">
+                        <template slot-scope="scope">
+                            <div v-for="user in item.users">
+                                <span v-if="user.employeeId == scope.row.employeeId">{{ user.score }}</span>
+                            </div>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </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%;" />
+                </div>
+            </el-tab-pane>
+        </el-tabs>
+
+        <el-dialog title="请选择需要对比的考核列表" center :visible.sync="dialogVisible" width="700px"
+            :before-close="dialogBeforeClose">
+            <div>
+                <div class="search-box">
+                    <el-select v-model="cycleType" placeholder="请选择周期类型" @change="changeCircle" style="width: 100px;"
+                        size="mini">
+                        <el-option v-for="item in cycleOptions" :key="item.value" :label="item.label"
+                            :value="item.value">
+                        </el-option>
+                    </el-select>
+
+                    <el-date-picker v-model="date" type="daterange" align="right" unlink-panels range-separator="至"
+                        start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd"
+                        :picker-options="pickerOptions" @change="changeDate" style="width: 300px; margin: 0 10px;"
+                        size="mini">
+                    </el-date-picker>
+
+                    <el-select v-model="status" placeholder="请选择" style="width: 100px;" @change="changeStatus"
+                        size="mini">
+                        <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
+                        </el-option>
+                    </el-select>
+
+                    <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>
+                        </template>
+                    </div>
+                    <div class="choose-template scroll-bar">
+                        <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>
+
+            </div>
+            <div slot="footer">
+                <el-button @click="dialogVisible = false">取 消</el-button>
+                <el-button type="primary" @click="confirm">确 定</el-button>
+            </div>
+        </el-dialog>
+
+        <div style="height: 50px;"></div>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+
+export default {
+    data() {
+        return {
+            checkAll: false,
+            isIndeterminate: true,
+            dialogVisible: false,
+            activeName: "0",
+            loading: false,
+            treeData: {},
+            // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+            cycleType: '-1',
+            cycleOptions: [
+                { label: "全部", value: '-1' },
+                { label: "未定义", value: '0' },
+                { label: "年度", value: '1' },
+                { label: "半年度", value: '2' },
+                { label: "季度", value: '3' },
+                { label: "月度", value: '4' },
+            ],
+            status: -1,
+            options: [{
+                value: -1,
+                label: '全部'
+            }, {
+                value: '0',
+                label: '未完成'
+            }, {
+                value: '1',
+                label: '已完成'
+            }],
+            params: {
+                startDate: '',
+                endDate: '',
+                status: -1, // 不传默认返回全部 0-未完成 1-已完成
+            },
+            transferData: [],
+            transferValue: [],
+            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]);
+                    }
+                }]
+            },
+            date: [],
+            examineList: [], // 考核表列表
+            tableHeader: [],
+            tableData: [],
+            userList: [],
+            selectExamineIds: [],
+            selectExamineList: [],
+            chooseExamineList: []
+        }
+    },
+    // watch: {
+    //     chooseExamineList(v) {
+    //         this.selectExamineList = this.chooseExamineList
+    //     }
+    // },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    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 = []
+            }
+
+        },
+        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))
+                }
+
+            }
+        },
+
+        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;
+            } else {
+                this.isIndeterminate = false;
+                this.checkAll = false;
+            }
+            if (this.activeName == '0') this.initTableData()
+            if (this.activeName == '1') this.initTreeData()
+        },
+
+        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 }
+            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.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)
+                    }
+                })
+
+            })
+
+            selectExamineList.forEach(item => {
+                let { reviewPackageId, title, users } = item;
+                if (users && users.length > 0) {
+                    users.forEach(user => {
+                        let { employeeName, employeeId } = user
+                        userList.push({ employeeName, employeeId })
+                    })
+                }
+            })
+            this.userList = userList.reduce((acc, obj) => {
+                // 检查当前对象是否已经存在于结果数组中
+                if (!acc.find(item => item.employeeId === obj.employeeId)) { // 假设我们通过id来判断唯一性
+                    acc.push(obj);
+                }
+                return acc;
+            }, []);
+
+        },
+        initTreeData() {
+            this.selectExamineList.forEach(item => {
+                item.id = Date.now() + Math.floor(Math.random() * 10000);
+                item.label = item.title;
+                item.children = item.users;
+                item.users.forEach(user => {
+                    user.id = Date.now() + Math.floor(Math.random() * 10000);
+                    user.label = user.employeeName + ", " + (user.score ? user.score : 0);
+                })
+            })
+            this.treeData = {
+                id: 0,
+                label: "考核对比",
+                children: this.selectExamineList
+            }
+        },
+        handleClick(tab, event) {
+            if (this.activeName == '0') this.initTableData()
+            if (this.activeName == '1') this.initTreeData()
+        },
+        // 日期选择时间
+        changeDate(v) {
+            if (v && v.length > 0) {
+                this.params.startDate = v[0] || ''
+                this.params.endDate = v[1] || ''
+                this.getRecords();
+            }
+
+        },
+        //选择周期
+        changeCircle(v) {
+            console.log(v);
+            this.getRecords();
+        },
+        // 选择状态
+        changeStatus(v) {
+            this.getRecords();
+        },
+        // 重置搜索条件
+        reset() {
+            this.cycleType = '-1'
+            this.status = -1
+            this.date = []
+            this.params = {
+                startDate: '',
+                endDate: '',
+                status: -1, // 不传默认返回全部 0-未完成 1-已完成
+            }
+            this.getRecords();
+        },
+
+        // 清空选择的考核列表
+        clear() {
+            this.selectExamineIds = [];
+            this.chooseExamineList = [];
+        },
+        dialogBeforeClose() {
+            this.dialogVisible = false;
+        },
+        confirm() {
+            this.selectExamineList = this.examineList.filter(item => this.transferValue.indexOf(item.reviewPackageId) >= 0);
+            if (this.activeName == '0') this.initTableData()
+            if (this.activeName == '1') this.initTreeData()
+            this.dialogVisible = false;
+        }
+    },
+
+}
+
+</script>
+
+
+
+<style scoped="scoped" lang="scss">
+.contrast-container {
+    width: 100%;
+    height: 100%;
+    background-color: #fff;
+
+    .el-icon-close {
+        transition: all 0.2s;
+
+        &:hover {
+            font-size: 16px;
+        }
+    }
+
+    .search-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        box-sizing: border-box;
+    }
+
+    .title-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 10px;
+        padding: 0 20px;
+
+        .title {
+            color: #999;
+            font-size: 14px;
+            font-weight: bold;
+        }
+    }
+
+    .package-list {
+        width: 100%;
+        height: 400px;
+        margin-top: 10px;
+        border: 1px solid #f7f7f7;
+
+        .template-list {
+            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;
+                }
+
+            }
+        }
+
+        .choose-template {
+            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;
+            }
+        }
+    }
+
+
+    .table-box {
+        width: 100%;
+        padding: 0 20px;
+        box-sizing: border-box;
+    }
+
+    /* 设置滚动条的宽度和背景色 */
+    ::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>

+ 91 - 0
src/newPerformance/components/ExamineRecord.vue

@@ -0,0 +1,91 @@
+<template>
+    <div class="record-container">
+        <!-- 左边 考核列表组件 -->
+        <LeftExamineRecord @selectRow="handleSeleRow" />
+        <!-- 右边 考核明细组件 -->
+        <RightExamineRecord v-if="JSON.stringify(detailInfo) !== '{}'" :detailInfo="detailInfo" :cateList="cateList" />
+    </div>
+</template>
+
+
+<script>
+import { mapGetters } from 'vuex';
+import RightExamineRecord from "./ExamineRecord/RightExamineRecord" // 考核列表组件
+import LeftExamineRecord from "./ExamineRecord/LeftExamineRecord" // 考核列表组件
+export default {
+    components: {
+        RightExamineRecord,
+        LeftExamineRecord
+    },
+    data() {
+        return {
+            detailInfo: {},
+            cateList: [], // 考核分类
+        }
+    },
+    created() {
+        this.getCateList()
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+    },
+    methods: {
+        handleSeleRow(reviewPackageId) {
+            // 获取考核详情
+            let url = `/performance/statistics/package/info/${this.user_info.site_id}/${reviewPackageId}`
+            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 }
+            })
+        },
+        // 考核分类列表
+        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;
+                }
+
+            })
+        },
+    }
+};
+</script>
+
+
+<style scoped="scoped" lang="scss">
+.record-container {
+    width: 100%;
+    height: 100%;
+    background-color: #f0f4fa;
+    display: flex;
+    justify-content: space-between;
+
+    /* 设置滚动条的宽度和背景色 */
+    ::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>

+ 383 - 0
src/newPerformance/components/ExamineRecord/LeftExamineRecord.vue

@@ -0,0 +1,383 @@
+<template>
+    <div class="record-left">
+        <!-- 搜索条件 -->
+        <div class="search-box">
+            <div>
+                <el-checkbox v-model="checked" label="我发起的" border size="small"
+                    @change="changeSelfPublish"></el-checkbox>
+            </div>
+
+            <el-popover placement="bottom" trigger="click" ref="searchPopover">
+                <div class="searchBox" style="width: 300px;">
+                    <div
+                        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>
+                    <el-input placeholder="请输入考核表名称" v-model="params.keyword" prefix-icon="el-icon-search"
+                        style="width: 300px;" size="small"></el-input>
+
+                    <div style="margin: 10px 0;">考核周期</div>
+
+                    <el-date-picker v-model="date" type="daterange" align="right" unlink-panels
+                        value-format="yyyy-MM-dd" :picker-options="pickerOptions" @change="changeDate"
+                        style="width: 300px; " size="small">
+                    </el-date-picker>
+                    <div style="margin: 10px 0;">部门</div>
+                    <el-cascader ref="deptSelectRef" v-model="selected_dept_ids" size="small" style="width: 300px; "
+                        :options="deptList" @change="deptChange" filterable placeholder="请选择部门" clearable
+                        :show-all-levels="false" :props="{ checkStrictly: true }">
+                    </el-cascader>
+                    <div style="margin: 10px 0;">状态</div>
+                    <el-select v-model="status" placeholder="请选择" style="width: 100px;" @change="changeStatus"
+                        size="small">
+                        <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
+                        </el-option>
+                    </el-select>
+                    <div class="flex-box-end">
+                        <el-button size="small" plain round @click="close">取 消</el-button>
+                        <el-button size="small" type="primary" plain round @click="confirm">确 定</el-button>
+                    </div>
+                </div>
+                <template slot="reference">
+                    <div class="gd"><i class="el-icon-finished" style="margin-right: 5px; color: #999;"></i>筛选</div>
+                </template>
+            </el-popover>
+
+        </div>
+        <!-- 水平线 -->
+        <div class="line"></div>
+
+        <!-- 表格数据 -->
+        <div class="scroll-bar" style="width: 100%; flex: 1; ">
+            <el-table
+                :data="tableData"
+                v-loading="loading" style="width: 100%; height: 500px;" border
+                :header-cell-style="{ background: '#f5f7fa' }" 
+                @row-click="handleSeleRecord"
+                :row-style="rowStyle"
+                >
+                <el-table-column prop="title" label="考核名称" min-width="30%" align="center">
+                    <template slot-scope="scope">
+                        <PreBox :value="scope.row.title" />
+                    </template>
+                </el-table-column>
+                <el-table-column label="周期" align="center" min-width="30%">
+                    <template slot-scope="scope">
+                        <div>{{ scope.row.startTime | formatDate }} 至 {{ scope.row.endTime | formatDate }}</div>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="start" label="开始时间" min-width="20%" align="center">
+                    <template slot-scope="scope">
+                        <div>{{ scope.row.startTime | formatDate }}</div>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="users" label="进度" min-width="20%" align="center">
+                    <template slot-scope="scope">
+                        <el-progress :percentage="scope.row.users | filterProgress"></el-progress>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <div class="flex-box-ce" style="justify-content: center; margin-top: 20px;">
+                <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
+                    :current-page="params.page" :page-sizes="[10, 20, 30, 40, 50]" :page-size="params.pageSize"
+                    layout="total, sizes, prev, pager, next" :total="total">
+                </el-pagination>
+            </div>
+        </div>
+
+
+
+
+    </div>
+</template>
+
+
+<script>
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+export default {
+    components: {
+        EmployeeSelector
+    },
+    data() {
+        return {
+            isShow: false,
+            total: 0,
+            loading: false,
+            checked: [],
+            status: -1,
+            options: [
+                {
+                    value: -1,
+                    label: '全部'
+                }, {
+                    value: '0',
+                    label: '未完成'
+                }, {
+                    value: '1',
+                    label: '已完成'
+                }
+            ],
+            tableData: [],
+
+            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]);
+                    }
+                }]
+            },
+            date: [],
+            reviewPackageId: 0,
+            deptList: [], // 部门列表 - 树形结构
+            dept_list: [], // 部门列表
+            selected_dept_ids: [], // 选择的部门列表
+            selfPublish: false,
+            params: {
+                selfPublish: 0,
+                keyword: '',
+                page: 1,
+                pageSize: 10,
+                startDate: '',
+                endDate: '',
+                status: -1, // 不传默认返回全部 0-未完成 1-已完成 
+                deptId: ''
+            },
+            selectedReviewPackageId: '',
+        }
+    },
+    
+    created() {
+        this.getRecords();
+        this.get_dept_list(); // 获取部门列表
+        this.$bus.$on("finishEdit", (reviewPackageId) => {
+            this.selectedReviewPackageId = reviewPackageId
+            this.getRecords();
+        })
+    },
+    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: {
+        confirm() {
+            this.$refs.searchPopover && this.$refs.searchPopover.doClose();
+            this.getRecords();
+        },
+        close() {
+            this.$refs.searchPopover && this.$refs.searchPopover.doClose();
+        },
+        getRecords() {
+            this.loading = true
+            this.params.selfPublish = this.selfPublish ? 1 : 0
+            if (this.status === '-1') this.params.status = '' // 不传默认返回全部
+            else this.params.status = this.status
+            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.tableData = list;
+                this.total = total;
+                if (this.selectedReviewPackageId) {
+                    this.$emit('selectRow', this.selectedReviewPackageId);
+                } else {
+                    let { reviewPackageId } = this.tableData[0]
+                    this.reviewPackageId = reviewPackageId
+                    this.$emit('selectRow', reviewPackageId); 
+                }
+                
+            })
+        },
+        // 是否我发起的
+        changeSelfPublish(v) {
+            this.selfPublish = !this.selfPublish;
+            this.params.selfPublish = this.selfPublish ? 1 : 0;
+            this.getRecords();
+        },
+        
+        // 处理部门树状结构数据
+        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); // 处理成树状结构
+            });
+        },
+
+        deptChange(val) {
+            this.params.deptId = val.toString()
+        },
+        
+
+        // 日期选择时间
+        changeDate(v) {
+            this.params.startDate = v[0] || ''
+            this.params.endDate = v[1] || ''
+        },
+
+        handleSizeChange(v) {
+            this.params.pageSize = v
+            this.getRecords();
+        },
+        handleCurrentChange(v) {
+            this.params.page = v
+            this.getRecords();
+        },
+
+        changeStatus(v) {
+            // this.getRecords();
+        },
+
+        // 表格行单击事件
+        handleSeleRecord(data) {
+            let { reviewPackageId } = data;
+            if (this.reviewPackageId == reviewPackageId) return
+            this.reviewPackageId = reviewPackageId
+            this.$emit('selectRow', reviewPackageId)
+            // console.log(data)
+        },
+        // 更改选中行背景色
+        rowStyle({ row }) {
+            if (this.reviewPackageId === row.reviewPackageId) {
+                return {
+                    'background-color': 'rgb(236, 245, 255)', cursor: 'pointer' };
+            }
+            return { cursor: 'pointer' };
+        },
+    }
+}
+
+</script>
+
+<style lang="scss">
+    .record-left {
+        /* 取消表格鼠标进入高亮显示 */ 
+        .el-table__row:hover>td {
+            background-color: transparent;
+        }
+    }
+        
+</style>
+
+<style scoped="scoped" lang="scss">
+    .gd {
+        border-left: 1px solid #f1f1f1;
+        padding: 0 10px;
+        margin-left: 10px;
+        padding-right: 0px;
+        cursor: pointer;
+        position: relative;
+    }
+
+    .gd i {
+        font-size: 18px;
+    }
+    .w300 {
+        width: 300px;
+    }
+    .active {
+        color: #409eff;
+        background: #ecf5ff;
+        border-color: #b3d8ff;
+    }
+    .record-left {
+        width: 49.6%;
+        height: 100%;
+        border-radius: 5px;
+        background: #fff;
+        padding: 10px 20px;
+        box-sizing: border-box;
+        overflow: hidden;
+        overflow-y: auto;
+        display: flex;
+        flex-direction: column;
+        .search-box {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            width: 100%;
+            height: 40px;
+            .btn-box {
+                padding: 7px 5px;
+                border: 1px solid #DCDFE6;
+                border-radius: 4px;
+                &:hover {
+                    cursor: pointer;
+                }
+            }
+        }
+    
+        .line {
+            width: 100%;
+            height: 1px;
+            background: #f1f1f1;
+            margin: 20px 0;
+        }
+        ::-webkit-scrollbar {
+            width: 10px;
+            height: 10px;
+            background-color: #f9f9f9;
+        }
+        ::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
+        ::-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>

+ 131 - 0
src/newPerformance/components/ExamineRecord/RightEamineComp/EditCateType.vue

@@ -0,0 +1,131 @@
+<template>
+    <!-- 考核分类 -->
+    <div class="circle" style="margin-right: 20px;">
+        <el-popover ref="popoverRef2" placement="right" width="400" trigger="click">
+            <div class="popover-box">
+                <el-select v-model="chooseCateIds" placeholder="请选择考核分类" style="width: 280px; margin-bottom: 10px;"
+                    size="small" multiple @change="changeCateIds">
+                    <el-option v-for="item in cateList" :key="item.cateId" :label="item.name" :value="item.cateId">
+                    </el-option>
+                </el-select>
+                <div class="footer" style="margin-top: 20px;">
+                    <el-button size="mini" @click="cancelChange">取消</el-button>
+                    <el-button type="primary" size="mini" @click="confirmChange">确定</el-button>
+                </div>
+            </div>
+            <template slot="reference">
+                <div class="reference">
+                    <div class="cate-list" v-if="cateIds && cateIds.length > 0">
+                        <template v-for="item in cateList">
+                            <div v-if="cateIds.includes(item.cateId)" class="cate-item" :key="item.cateId">
+                                {{ item.name }}
+                            </div>
+                        </template>
+                    </div>
+                    <div v-else class="fontColorC flex-box-ce">
+                        未绑定考核分类 <i class="el-icon-edit" style="margin-left: 10px; color: #999;"></i>
+                    </div>
+                </div>
+            </template>
+        </el-popover>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+
+export default {
+    name: "EditCateType",
+    props: {
+        reviewPackageId: {
+            type: String | Number,
+            default: ""
+        },
+        cateIds: {
+            type: Array,
+            default: () => []
+        },
+        cateList: {
+            type: Array,
+            default: () => []
+        },
+    },
+    data() {
+        return {
+            chooseCateIds: []
+        }
+    },
+    watch: {
+        cateIds(v) {
+            this.chooseCateIds = this.cateIds;
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+    },
+
+    mounted() {
+        this.chooseCateIds = this.cateIds;
+    },
+    methods: {
+        changeCateIds(v) {
+            this.chooseCateIds = v
+        },
+
+        cancelChange() {
+            this.chooseCateIds = []
+            this.$refs.popoverRef2 && this.$refs.popoverRef2.doClose();
+        },
+
+        confirmChange() {
+            let url = `/performance/statistics/bind/cate/${this.user_info.site_id}`
+            let reviewPackageId = this.reviewPackageId
+            let data = {
+                reviewPackageId,
+                cateIds: this.chooseCateIds
+            }
+            this.$http.post(url, data).then(res => {
+                let { code } = res
+                if (code == 1) {
+                    this.$emit('comfirmChanged', this.chooseCateIds)
+                    this.cancelChange()
+                } else {
+                    this.$message.error(res.msg || "操作失败")
+                }
+            })
+        },
+    }
+}
+
+</script>
+<style scoped lang="scss">
+.popover-box {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+}
+
+.reference {
+    width: 100%;
+    border: 1px solid #DCDFE6;
+    border-radius: 4px;
+    padding: 7px 10px 0 10px;
+    box-sizing: border-box;
+    .cate-list {
+        display: flex;
+        flex-wrap: wrap;
+        .cate-item {
+            padding: 5px;
+            border-radius: 4px;
+            background-color: #f7f7f7;
+            color: #999;
+            margin-right: 5px;
+            margin-bottom: 7px;
+            &:hover {
+                cursor: pointer;
+            }
+        }
+    }
+}
+</style>

+ 259 - 0
src/newPerformance/components/ExamineRecord/RightEamineComp/EditCircle.vue

@@ -0,0 +1,259 @@
+<template>
+    <!-- 考核周期 -->
+    <div class="circle" style="margin-right: 20px;">
+        <el-popover ref="popoverRef2" placement="right" width="400" trigger="click">
+            <div class="popover-box">
+                <div class="selectBox">
+                    <SelectPeriod :dateParameter="dateParameter" @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>
+                    </SelectPeriod>
+                </div>
+
+                <el-date-picker v-if="params.cycleType == 0" v-model="date" type="daterange" align="right" unlink-panels
+                    range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd"
+                    :picker-options="pickerOptions" @change="changeDate">
+                </el-date-picker>
+
+                <div class="footer" style="margin-top: 20px;">
+                    <el-button size="mini" @click="cancelChange">取消</el-button>
+                    <el-button type="primary" size="mini" @click="confirmChange">确定</el-button>
+                </div>
+            </div>
+            <template slot="reference">
+                <div
+                    style="width: 100%; border: 1px solid #DCDFE6; border-radius: 4px; padding: 7px 10px; box-sizing: border-box; ">
+                    {{ startTime | formatDate }} 至 {{ endTime | formatDate }}
+                    <i class="el-icon-edit" style="margin-left: 10px; color: #999;"></i>
+                </div>
+            </template>
+        </el-popover>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+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 SelectPeriod from '@/okr/components/public/SelectPeriod'; //选择周期
+
+export default {
+    name: "EditCircle",
+    components: {
+        SelectPeriod
+    },
+    props: {
+        reviewPackageId: {
+            type: String | Number,
+            default: ""
+        },
+        startTime: {
+            type: String,
+            default: "--"
+        },
+        endTime: {
+            type: String,
+            default: "--"
+        }
+    },
+    data() {
+        return {
+            params: {
+                cycleType: 0, // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+                startDate: '',
+                endDate: ''
+            },
+            dateParameter: {
+                year: moment().format('YYYY'),
+                cycle_type: 0,
+                dateId: 1,
+                name: '全部周期'
+            },
+            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]);
+                    }
+                }]
+            },
+            date: [],
+        }
+    },
+    watch: {
+        startTime(v) {
+            this.params.startDate = v
+        },
+        endTime(v) {
+            this.params.endDate = v
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    methods: {
+        // 日期选择时间
+        changeDate(v) {
+            this.params.startDate = v[0] || ''
+            this.params.endDate = v[1] || ''
+        },
+        //选择周期
+        dateConfirm(item) {
+            this.dateParameter = item;
+            let year = item.year;
+            if (item.name == '全部周期') this.params.cycleType = 0
+
+            if (item.name == '年度') {
+                this.params.cycleType = 1
+                // 计算年份的开始时间(即1月1日)
+                const startOfYear = moment().year(year).startOf('year');
+                // 计算年份的结束时间(即12月31日)
+                const endOfYear = moment().year(year).endOf('year');
+                this.params.startDate = startOfYear.format('YYYY-MM-DD');
+                // 年度结束时间
+                this.params.endDate = endOfYear.format('YYYY-MM-DD');
+            }
+            if (item.name == '上半年') {
+                this.params.cycleType = 2
+                // 获取年初
+                let startOfYear = moment().year(year).startOf('year');
+                // 获取上半年结束日(6月30日)
+                let endOfFirstHalfYear = moment().year(year).startOf('year').add(6, 'months').subtract(1, 'days');
+                this.params.startDate = startOfYear.format('YYYY-MM-DD');
+                this.params.endDate = endOfFirstHalfYear.format('YYYY-MM-DD');
+
+            }
+            if (item.name == '下半年') {
+                this.params.cycleType = 2
+                let startOfSecondHalfYear = moment().year(year).startOf('year').add(6, 'months'); // 获取下半年开始日(7月1日)
+                let endOfYear = moment().year(year).endOf('year'); // 获取年末
+                this.params.startDate = startOfSecondHalfYear.format('YYYY-MM-DD');
+                this.params.endDate = endOfYear.format('YYYY-MM-DD');
+            }
+
+            if (item.name.includes('季度')) {
+
+                this.params.cycleType = 3;
+                // 第一季度
+                if (item.name === "第一季度") {
+                    const dates = getQuarterDates(year, 1);
+                    this.params.startDate = dates.start.format('YYYY-MM-DD');
+                    this.params.endDate = dates.end.format('YYYY-MM-DD');
+                }
+
+                // 第一季度
+                if (item.name === "第二季度") {
+                    const dates = getQuarterDates(year, 2);
+                    this.params.startDate = dates.start.format('YYYY-MM-DD');
+                    this.params.endDate = dates.end.format('YYYY-MM-DD');
+                }
+
+                // 第三季度
+                if (item.name === "第三季度") {
+                    const dates = getQuarterDates(year, 3);
+                    this.params.startDate = dates.start.format('YYYY-MM-DD');
+                    this.params.endDate = dates.end.format('YYYY-MM-DD');
+                }
+
+                // 第四季度
+                if (item.name === "第四季度") {
+                    const dates = getQuarterDates(year, 4);
+                    this.params.startDate = dates.start.format('YYYY-MM-DD');
+                    this.params.endDate = dates.end.format('YYYY-MM-DD');
+                }
+            }
+            if (item.name.includes('月')) {
+                this.params.cycleType = 4
+                let month = item.name.substring(0, item.name.length - 1); // 获取月份
+                // 计算开始时间(该月的第1天)
+                const startOfMonth = moment(`${year}-${month}-01`);
+                this.params.startDate = startOfMonth.format('YYYY-MM-DD');
+
+                // 计算结束时间(该月的最后一天)
+                const endOfMonth = moment(startOfMonth).endOf('month');
+                this.params.endDate = endOfMonth.format('YYYY-MM-DD');
+            }
+            // this.getTemplateList()
+        },
+        cancelChange() {
+            this.$refs.popoverRef2 && this.$refs.popoverRef2.doClose();
+        },
+
+        confirmChange() {
+            let url = `/performance/statistics/package/time/${this.user_info.site_id}`
+            let reviewPackageId = this.reviewPackageId
+            let data = {
+                reviewPackageId,
+                ...this.params
+            }
+            this.$http.post(url, data).then(res => {
+
+                let { code, data: { startTime, endTime } } = res
+                if (code == 1) {
+                    this.$refs.popoverRef2 && this.$refs.popoverRef2.doClose();
+                    this.$emit('comfirmChanged', { startTime, endTime })
+                } else {
+                    this.$message.error(res.msg || "操作失败")
+                }
+                
+            })
+        },
+    }
+}
+
+</script>
+<style scoped lang="scss">
+.popover-box {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+
+    .selectBox {
+        border: 1px solid #ccc;
+        border-radius: 25px;
+        padding: 8px 20px;
+        width: 100%;
+        margin: 0 20px 20px 20px;
+    }
+}
+</style>

+ 145 - 0
src/newPerformance/components/ExamineRecord/RightEamineComp/EditScoreList.vue

@@ -0,0 +1,145 @@
+<template>
+    <!-- 正态规则 -->
+    <el-popover ref="popoverRef" placement="right" trigger="click" width="300">
+        <div class="setting">
+            <div class="setting-title-box flex-box-ce" style="justify-content: space-between;">
+                <div class="setting-title">
+                    正态规则
+                </div>
+                <el-button class="plus-button" icon="el-icon-plus" circle size="mini" @click="addData()"></el-button>
+            </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-popover>
+</template>
+
+
+<script>
+import { mapGetters } from 'vuex';
+export default {
+    name: "EditScoreList",
+    props: {
+        reviewPackageId: {
+            type: String | Number,
+            default: ""
+        },
+        scoreList: {
+            type: Array,
+            default: () => []
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+    },
+    methods: {
+        addData() {
+            this.scoreList.push({ id: this.distributionId, name: "", scale: 0 })
+        },
+        deleteData(index) {
+            this.scoreList.splice(index, 1)
+        },
+        cancelChangeScore() {
+            this.$refs.popoverRef && this.$refs.popoverRef.doClose();
+        },
+        confirmChangeScore() {
+            let url = `/performance/statistics/package/distribution/${this.user_info.site_id}`
+            let reviewPackageId = this.reviewPackageId
+            let items = this.scoreList;
+            let sum = 0;
+            for (let i = 0; i < items.length - 1; i++) {
+                if (!items[i].name) return this.$message.error('请输入正太规则名称')
+                sum += Number(items[i].scale)
+            }
+            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();
+            })
+        }
+    }
+}
+
+</script>
+
+
+<style scoped lang="scss">
+.setting {
+    width: 300px;
+    height: 200px;
+    .setting-title-box {
+        width: 100%;
+        padding-right: 20px;
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        font-size: 18px;
+        color: #000;
+
+        .setting-title {
+            width: 60%;
+        }
+        .plus-button {
+        }
+
+    }
+    // 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>

+ 78 - 0
src/newPerformance/components/ExamineRecord/RightEamineComp/EditTitle.vue

@@ -0,0 +1,78 @@
+<template>
+    <!-- 考核标题 -->
+    <div class="title">
+        <!-- {{ title }}
+        <el-popover ref="popoverRef" placement="right" width="400" trigger="click">
+            <div class="flex-box-ce">
+                <el-input v-model="editTitle" placeholder="请输入模板名称" size="small"></el-input>
+                <el-button type="primary" size="small" style="margin-left: 10px;" @click="handleEditTitle()">确定</el-button>
+            </div>
+            <i class="el-icon-edit" slot="reference" style="margin-left: 10px; color: #999;"></i>
+        </el-popover> -->
+        <el-input v-model="editTitle" placeholder="请输入模板名称" size="small" @blur="handleEditTitle()"></el-input>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+export default {
+    name: "EditTitle",
+    props: {
+        reviewPackageId: {
+            type: String | Number,
+            default: ""
+        },
+        title: {
+            type: String,
+            default: "默认标题"
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+    },
+    watch: {
+        title(v) {
+            this.editTitle = this.title
+        }
+    },
+    data() {
+        return {
+            editTitle: ""
+        }
+    },
+    mounted() {
+        this.editTitle = this.title
+    },
+    methods: {
+        // 编辑模板标题
+        handleEditTitle() {
+            let title = this.editTitle
+            let reviewPackageId = this.reviewPackageId
+            if (title !== null && title !== '') {
+                let url = `/performance/statistics/package/title/${this.user_info.site_id}`
+                this.$http.post(url, { reviewPackageId, title }).then(res => {
+                    if (res.code == 1) {
+                        this.$refs.popoverRef && this.$refs.popoverRef.doClose();
+                        this.$emit("handleEditTile", res.data.title);
+                        // this.$bus.$emit("finishEditTitle")
+                    } else {
+                        this.$message.error(res.msg || "操作失败")
+                    }
+                })
+            }
+        },
+    }
+}
+
+</script>
+<style scoped lang="scss">
+
+
+
+    .title {
+        font-size: 16px;
+        font-weight: 600;
+        margin-right: 20px;
+        display: flex;
+    }
+</style>

+ 660 - 0
src/newPerformance/components/ExamineRecord/RightExamineRecord.vue

@@ -0,0 +1,660 @@
+<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="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>
+            <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>
+
+        <table>
+            <tr>
+                <td v-for="item in users" :key="item.employeeId">{{ item.employeeName }}</td>
+            </tr>
+
+            <tr>
+                <td v-for="item in users" :key="item.employeeId">{{ item.score || '--' }}</td>
+            </tr>
+
+            <tr>
+                <td v-for="item in users" :key="item.employeeId">
+                    <el-tag v-if="item.status" type="success">
+                        已完成
+                    </el-tag>
+                    <el-tag v-else="item.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">
+                        未评分
+                    </el-tag>
+                    <el-tag v-else>
+                        {{ item.level }}
+                    </el-tag>
+                </td>
+            </tr>
+
+            <tr>
+                <td v-for="item in users" :key="item.employeeId">
+                    <el-tag>
+                        {{ item.scoreResult }}
+                    </el-tag>
+                </td>
+            </tr>
+        </table>
+
+
+        <!-- 指标列表 -->
+        <div class="title-container" style="margin: 20px 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-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>
+            </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>
+
+    </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';
+
+export default {
+    components: {
+        EditTitle,
+        EditScoreList,
+        EmployeeSelector,
+        EditCircle,
+        EditCateType
+    },
+    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: [] // 选择的考核分类
+        };
+    },
+    watch: {
+        detailInfo(v) {
+            this.loading = true
+            this.activeName = '1'
+            let { data: { data: { cateIds, indicators, startTime, endTime, title, distribution: { items }, users }, code }, reviewPackageId } = v
+            this.getAllSet();
+            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 "未评分"; // 如果分数不在任何范围内
+        },
+
+        // 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) {
+            // 降序排序并去重(假设分数不重复,可省略去重)
+            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 => {
+                        console.log(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;
+                    });
+                    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);
+                            group.standardResultRate = standardResultRate !== 0 ? `${standardResultRate}%` : '--';
+                        }
+                    }
+                    this.tableData2.push(group);
+                    console.log(this.tableData2);
+                })
+            }
+        }
+    }
+}
+
+</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: 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: 20px 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;
+            }
+        }
+    }
+
+    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;
+            /* 奇数行背景色 */
+        }
+    }
+}
+</style>

+ 136 - 0
src/newPerformance/components/ExamineSetting.vue

@@ -0,0 +1,136 @@
+<template>
+    <div class="all">
+        <div>
+            指标设计
+        </div>
+        <div>
+            <div>按管理员</div>
+            <div>按个人</div>
+            <div>按岗位</div>
+            <div>按部门</div>
+            <div>自己</div>
+        </div>
+        <div>
+            流程设计
+        </div>
+
+        <ol class="steps">
+            <li class="title">
+                1.确认目标</li>
+            <li class="info-box">
+                <div class="line"></div>
+                <div class="content"></div>
+            </li>
+            <li class="title">2.确认目标</li>
+            <li class="info-box">
+                <div class="line"></div>
+                <div class="content"></div>
+            </li>
+            <li class="title">3.确认目标</li>
+            <li class="info-box">
+                <div class="line"></div>
+                <div class="content"></div>
+            </li>
+            <li class="title">4.确认目标</li>
+            <li class="info-box">
+                <div class="line"></div>
+                <div class="content"></div>
+            </li>
+            <li class="title">5.确认目标</li>
+            <li class="info-box">
+                <div class="line"></div>
+                <div class="content"></div>
+            </li>
+            <li class="title">6.确认目标</li>
+            <li class="info-box">
+                <div class="line"></div>
+                <div class="content"></div>
+            </li>
+            <li class="title">7.确认目标</li>
+            <li class="info-box">
+                <div class="line"></div>
+                <div class="content"></div>
+            </li>
+        </ol>
+    </div>
+</template>
+
+<style scoped="scoped" lang="scss">
+.all {
+    width: 100%;
+    height: 100%;
+    font-size: 14px;
+    border-radius: 4px;
+    background: #fff;
+    padding: 10px;
+    box-sizing: border-box;
+    .steps {
+            display: flex;
+            flex-direction: column;
+            margin: 0;
+            padding: 0;
+    
+            .title {
+                width: 100%;
+                height: 20px;
+                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;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+    
+        }
+}
+</style>

+ 531 - 0
src/newPerformance/components/MyPerformance.vue

@@ -0,0 +1,531 @@
+<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>
+
+                <div class="flex-1 fontColorB item">
+                    <div>{{ score || '暂无评分' }}</div>
+                    <div class="fontColorC">考核评分</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>
+
+                <div class="flex-1 fontColorB item">
+                    <div>{{ finish_status }}</div>
+                    <div class="fontColorC">考核完成的指标</div>
+                </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>
+            <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>
+
+        </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">
+                </el-table-column>
+                <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="target" label="目标" align="center" min-width="100">
+                    <template slot-scope="scope">
+                        {{ scope.row.target + scope.row.unit }}
+                    </template>
+                </el-table-column>
+
+                <el-table-column prop="result" label="结果" align="center" min-width="100">
+                    <template slot-scope="scope">
+                        {{ scope.row.result + scope.row.unit }}
+                    </template>
+                </el-table-column>
+                <el-table-column prop="weight" label="权重" align="center" min-width="100">
+                </el-table-column>
+
+                <el-table-column prop="formulae" label="计算公式" align="center" min-width="120">
+                    <template slot-scope="scope">
+                        <el-button v-if="scope.row.expression && 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>
+                        <div v-else style="color: #999;">暂无公式</div>
+                    </template>
+                </el-table-column>
+
+                <el-table-column prop="score" label="最终评分" align="center" min-width="100">
+                    <template slot-scope="scope">
+                        <div>{{ scope.row.score || '--' }}</div>
+                    </template>
+                </el-table-column>
+
+                <el-table-column prop="businessStatus" label="考核状态" align="center" min-width="120">
+                    <template slot-scope="scope">
+                        <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="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">
+                    <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]" />
+                    </template>
+                </el-table-column>
+                <el-table-column v-if="isShow" prop="carbon_copy" label="抄送" align="center" width="120" fixed="right">
+                    <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]" />
+                    </template>
+                </el-table-column>
+            </el-table>
+        </div>
+
+
+        <!-- 编辑计算公式 -->
+        <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" />
+
+        <!-- 选择考核列表弹框 -->
+        <SelectExamineComp v-if="dialogVisible" v-model="dialogVisible" @chooseExamine="choosePerformItem" />
+    </div>
+</template>
+
+<script>
+import moment from 'moment';
+import ShowHandlerComp from '@/newPerformance/components/PublicComp/ShowHandler'; // 显示节点数据组件
+import FormulaComp from '@/newPerformance/components/TemplateDetails/FormulaComp'; // 计算公式弹框
+import SelectExamineComp from '@/newPerformance/components/MyPerformance/SelectExamine'; // 选择考核列表弹框
+import { mapGetters } from 'vuex';
+
+export default {
+    name: "MyPerformance",
+    components: {
+        ShowHandlerComp,
+        FormulaComp,
+        SelectExamineComp
+    },
+    data() {
+        return {
+            isShow: false,
+            dialogVisible: false,
+            loading: false,
+            value: [],
+            title: "",
+            startTime: "",
+            endTime: "",
+            cycleType: "",
+            score: 0,
+            status: 0,
+            total: 0,
+            levelName: '', // 考核详情信息
+            currentIndicator: null,
+            showFormula: false, // 公式弹框
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            employeeId: '',
+            params: {
+                keyword: '',
+                page: 1,
+                pageSize: 15,
+                cateId: '',
+                startDate: '',
+                endDate: ''
+            },
+            // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+            cycleType: '-1',
+            cycleOptions: [
+                { label: "全部", value: '-1' },
+                { label: "未定义", value: '0' },
+                { label: "年度", value: '1' },
+                { label: "半年度", value: '2' },
+                { label: "季度", value: '3' },
+                { label: "月度", value: '4' },
+            ],
+            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]);
+                    }
+                }]
+            },
+            date: [],
+            performList: [],
+            tableData: [],
+        };
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        create_status() {
+            if (this.tableData && this.tableData.length > 0) {
+                return this.tableData.filter(data => data.businessStatus !== 'end').length 
+            }
+            return '--'
+        },
+        finish_status() {
+            if (this.tableData && this.tableData.length > 0) {
+                return this.tableData.filter(data => data.businessStatus === 'end').length
+            }
+            return '--'
+        }
+    },
+
+    created() {
+        this.getTemplateList();
+    },
+
+    methods: {
+
+        handleChange(value) {
+            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 requestdata;
+            if (that.cycleType == '-1') requestdata = { ...that.params }
+            else {
+                requestdata = { ...that.params, cycleType: that.cycleType }
+            }
+            that.$axiosUser('get', url, requestdata).then(res => {
+                let { data: { data: { list, total }, code } } = res;
+                if (code == 1) {
+                    if (list && list.length > 0) {
+                        that.performList = list;
+                        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.title = title || ''
+                    this.cycleType = cycleType || ''
+                    this.score = score || ''
+                    this.status = status || '0'
+                    this.startTime = startTime || ''
+                    this.endTime = endTime || ''
+                    this.levelName = levelName || ''
+                    this.tableData = indicators || [];
+                    this.loading = false
+                }
+                
+            });
+        },
+        
+
+        dialogBeforeClose() {
+            this.dialogVisible = false;
+        },
+        
+
+        // 打开计算公式弹框
+        openFormula(row, index) {
+            this.currentIndicator = row;
+            this.showFormula = true;
+        },
+        
+        onFormulaConfirm() { },
+
+        changeShow() {
+            this.$nextTick(() => {
+                if (this.$refs.fmeaTableRef) this.$refs.fmeaTableRef.doLayout();
+            })
+        }
+    }
+};
+</script>
+
+<style lang="scss">
+.performance {
+    .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;
+    }
+}
+
+.oneLine {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+
+</style>
+
+<style scoped="scoped" lang="scss">
+.green-color {
+    color: #67C23A;
+}
+
+
+.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;
+
+        .status-btn {
+            width: 100%;
+            height: 100%;
+            background: transparent;
+            border: 2px dashed #67C23A;
+            color: #67C23A;
+            text-align: center;
+            font-size: 16px;
+            line-height: 30px;
+        }
+    }
+}
+.performance {
+    width: 100%;
+    height: 100%;
+    background-color: #f0f4fa;
+    display: flex;
+    flex-direction: column;
+
+    .more {
+        &:hover {
+            cursor: pointer;
+        }
+    }
+    
+
+    .main {
+        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;
+            }
+        }
+        .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;
+            }
+        }
+    }
+    
+}
+
+
+/* 设置滚动条的宽度和背景色 */
+::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;
+}
+
+
+
+.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;
+}
+
+
+</style>

+ 317 - 0
src/newPerformance/components/MyPerformance/SelectExamine.vue

@@ -0,0 +1,317 @@
+<template>
+    <el-dialog :visible.sync="dialogVisible" width="800px" :before-close="dialogBeforeClose">
+        <div>
+            <!-- 搜索框 -->
+            <div class="search-box">
+                <div class="flex-box-ce">
+                    <el-input v-model="params.keyword" placeholder="考核表名称" prefix-icon="el-icon-search"
+                        style="width: 200px; " size="small"></el-input>
+
+                    <el-select v-model="cycleType" placeholder="周期类型" @change="changeCircle"
+                        style="width: 100px; margin: 0 10px;" size="small">
+                        <el-option v-for="item in cycleOptions" :key="item.value" :label="item.label"
+                            :value="item.value">
+                        </el-option>
+                    </el-select>
+
+                    <el-date-picker v-model="date" type="daterange" align="right" unlink-panels range-separator="至"
+                        start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd"
+                        :picker-options="pickerOptions" @change="changeDate" style="width: 300px; margin-right: 10px;"
+                        size="small">
+                    </el-date-picker>
+
+                    <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-button type="primary" size="mini" @click="getTemplateList()">查询</el-button>
+                    <el-button size="mini" @click="resetSearch()">重置</el-button>
+                </div>
+            </div>
+
+            <!-- 考核模板列表 -->
+            <div class="perform-list scroll-bar" style="margin-top: 10px; height: 450px; overflow-y: auto;">
+                <template v-if="performList && performList.length > 0">
+                    <div class="perform-item" v-for="item in performList" :key="item.reviewId"
+                        @click="choosePerformItem(item)">
+                        <div class="perform-item-title">{{ item.title }}</div>
+                        <div class="perform-item-date">
+                            {{ item.startTime | formatDate }} 至 {{ item.endTime | formatDate }}
+                        </div>
+                        <div class="perform-item-status" :class="item.status == 1 ? 'green-color' : 'orange-color'">
+                            {{
+                            item.status == 1 ? '考核结束' : '考核中' }}</div>
+                        <div class="perform-item-progress">{{ item.progress }}</div>
+                        <div class="perform-item-score">{{ item.score ? item.score + '分' : '--' }} </div>
+                    </div>
+                </template>
+                <noData v-else content="暂无数据" imgW="200px" imgH="200px"></noData>
+            </div>
+            <div v-if="performList && performList.length > 0" class="flex-box-ce"
+                style="justify-content: center; margin-top: 10px;">
+                <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>
+    </el-dialog>
+
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+import _ from 'lodash';
+export default {
+    model: {
+        prop: 'dialogVisible',
+        event: 'close-dialog'
+    },
+    props: {
+        dialogVisible: {
+            type: Boolean,
+            default: false
+        },
+        searchOptions: {
+            type: Object,
+            default: () => {}
+        }
+    },
+
+    watch: {
+        dialogVisible(v) {
+            if (v) {
+                if (!_.isEmpty(this.searchOptions)) {
+                    let { employeeId, cycleType, cateId } = this.searchOptions
+                    this.params.employeeId = employeeId
+                    this.params.cycleType = cycleType
+                    this.params.cateId = cateId + ''
+                }
+                
+                this.getCateList()
+                this.getTemplateList()
+            }
+        }
+    },
+
+    data() {
+        return {
+            params: {
+                keyword: '',
+                page: 1,
+                pageSize: 15,
+                cateId: '',
+                startDate: '',
+                endDate: ''
+            },
+            employeeId: '',
+            // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+            cycleType: '-1',
+            cycleOptions: [
+                { label: "全部", value: '-1' },
+                { label: "未定义", value: '0' },
+                { label: "年度", value: '1' },
+                { label: "半年度", value: '2' },
+                { label: "季度", value: '3' },
+                { label: "月度", value: '4' },
+            ],
+            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]);
+                    }
+                }]
+            },
+            date: [],
+            performList: [],
+            cateList: [],
+            total: 0
+        }
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    created() {
+        if (!_.isEmpty(this.searchOptions)) {
+            let { employeeId, cycleType, cateId } = this.searchOptions
+            this.employeeId = employeeId
+            this.params.cycleType = cycleType
+            this.cycleType = cycleType
+            this.params.cateId = cateId
+        } else {
+            this.employeeId = this.user_info.id
+        }
+        this.getCateList()
+        this.getTemplateList()
+    },
+    methods: {
+        // 考核分类列表
+        getCateList() {
+            let url = `/performance/cate/list/${this.user_info.site_id}`;
+            this.$axiosUser('get', url).then(res => {
+                let { data: { code, data: { list, total } } } = res
+                if (code == 1) {
+                    this.cateList = list
+                }
+
+            })
+        },
+
+        getTemplateList() {
+            let that = this
+            let url = `/performance/statistics/reviews/${that.user_info.site_id}/${that.employeeId}`
+            let requestdata;
+            if (that.cycleType == '-1') requestdata = { ...that.params }
+            else requestdata = { ...that.params, cycleType: that.cycleType } 
+            that.$axiosUser('get', url, requestdata).then(res => {
+                let { data: { data: { list, total }, code } } = res;
+                if (code == 1) {
+                    that.performList = list;
+                    that.total = total
+                } else {
+                    that.performList = [];
+                }
+            });
+        },
+
+        
+
+        choosePerformItem(item) {
+            this.$emit('chooseExamine', item)
+            this.$emit('close-dialog', false)
+        },
+
+        dialogBeforeClose() {
+            this.$emit('close-dialog', false)
+        },
+
+        changeCateId(v) {
+            this.getTemplateList();
+        },
+
+        resetSearch() {
+            this.params = {
+                keyword: '',
+                page: 1,
+                pageSize: 10,
+                startDate: '',
+                endDate: ''
+            };
+            this.cycleType = ''
+            this.getTemplateList();
+        },
+
+        // 日期选择时间
+        changeDate(v) {
+            if (this.date[0]) this.params.startDate = this.date[0] || ''
+            if (this.date[1]) this.params.endDate = this.date[1] || ''
+        },
+
+
+        changeCircle(v) {
+            this.params.cycleType = v;
+        },
+
+        handleSizeChange(v) {
+            this.params.pageSize = v
+            this.getTemplateList();
+        },
+        handleCurrentChange(v) {
+            this.params.page = v
+            this.getTemplateList();
+        },
+    }
+}
+</script>
+
+
+<style scoped lang="scss">
+.green-color {
+    color: #67c23a;
+}
+
+.orange-color {
+    color: #e6a23c;
+}
+
+.perform-list {
+    width: 100%;
+    padding: 0 20px;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    position: relative;
+    background-color: #fff;
+
+
+    .perform-item {
+        width: 100%;
+        height: 50px;
+        padding: 10px;
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+        justify-content: space-around;
+        font-size: 14px;
+        margin-bottom: 10px;
+        border-bottom: 1px solid #ddd;
+
+        &:hover {
+            background-color: #f7f7f7;
+            cursor: pointer;
+        }
+
+        &-title {
+            width: 240px;
+            font-weight: 600;
+        }
+
+        &-name {
+            width: 80px;
+        }
+
+        &-date {
+            width: 200px;
+        }
+
+        &-score {
+            width: 60px;
+        }
+
+        &-status {
+            width: 80px;
+        }
+
+    }
+}
+</style>

+ 476 - 0
src/newPerformance/components/OrganizationExamine.vue

@@ -0,0 +1,476 @@
+<template>
+    <div class="all">
+        <div class="flex-box-ce" style="justify-content: space-between;">
+            <div class="flex-box-ce">
+                <el-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 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="danger" :disabled="!(multipleSelection && multipleSelection.length > 0)"
+                    @click="confirmDelete()" size="small">批量删除</el-button>
+                <el-button type="warning" size="small" @click="reset()">重置表格</el-button>
+                <el-button type="default" :disabled="!(tableData && tableData.length > 0)"
+                    @click="exportToExcel('组织考核', '#myTable')" size="small" :loading="downloadLoading">导出表格</el-button>
+            </div>
+
+        </div>
+
+        <el-alert class="bounce animated" type="warning" :title="alertTilte" :closable="false" show-icon
+            style="width: 100%; margin-top: 10px;"></el-alert>
+
+        <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" />
+
+        <!-- 选择考核列表组件 -->
+        <SelectExamineComp v-if="dialogVisible" v-model="dialogVisible" @chooseExamine="chooseEaxmineItem"
+            :search-options="searchOptions" />
+
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+import XLSX from 'xlsx';
+import FileSaver from 'file-saver';
+import EmployeeSelector from '@/components/EmployeeSelector';
+import SelectExamineComp from '@/newPerformance/components/MyPerformance/SelectExamine'; // 选择考核列表弹框
+
+export default {
+    components: {
+        EmployeeSelector,
+        SelectExamineComp
+    },
+
+    data() {
+        return {
+            selected_employee_ids: [],
+            selected_dept_ids: [],
+            deptList: [], // 部门列表 - 树形结构
+            dept_list: [], // 部门列表
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            cateId: '',
+            cateList: [],
+            loading: false,
+            downloadLoading: false,
+            total: 0,
+            tableData: [],
+            tableIndex: -1,
+            // 添加指标
+            selected: { employee: [], dept: [] },//已经选择人员
+            choosePerson: false,
+            // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+            cycleType: '-1',
+            cycleOptions: [
+                { label: "全部", value: '-1' },
+                { label: "未定义", value: '0' },
+                { label: "年度", value: '1' },
+                { label: "半年度", value: '2' },
+                { label: "季度", value: '3' },
+                { label: "月度", value: '4' },
+            ],
+            params: {
+                deptIds: '',
+                employeeIds: '',
+                cateId: '',
+                pages: 1,
+                pageSize: 10
+            },
+            multipleSelection: [],
+            alertTilte: "默认显示每个人的最新考核数据,可选择人员添加考核数据,也可以移除某个人员的考核数据,或点击人员姓名替换某条考核数据",
+            dialogVisible: false
+        }
+    },
+    created() {
+        this.getList();
+        this.get_dept_list();
+        this.get_cate_list();
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        },
+        formatCycleType(val) {
+            if (val == 0) return '未定义'
+            if (val == 1) return '年度'
+            if (val == 2) return '半年'
+            if (val == 3) return '季度'
+            if (val == 4) return '月度'
+            else return '--'
+        },
+        formatStatus(val) {
+            if (val == 0) return '考核中'
+            if (val == 1) return '已结束'
+            if (val == 2) return '面谈'
+            else return '--'
+        }
+    },
+    
+    methods: {
+        // 表格数据 - 最新考核数据
+        getList() {
+            this.loading = true;
+            let url = `/performance/statistics/review/last/${this.user_info.site_id}`
+            let requestdata
+            if (this.cycleType == '-1') requestdata = { ...this.params }
+            else {
+                requestdata = { ...this.params, cycleType: this.cycleType }
+            }
+            delete requestdata['employeeIds'] // 删除部门字段
+            this.$axiosUser('get', url, requestdata).then(res => {
+                this.loading = false;
+                let { data: { data: { list, total }, code } } = res
+                if (code == 1) {
+                    this.tableData = list;
+                    this.total = total 
+                }
+            });
+        },
+
+        reset() {
+            this.loading = true;
+            let url = `/performance/statistics/review/last/${this.user_info.site_id}`
+            let requestdata
+            this.cycleType = -1;
+            this.params = {
+                deptIds: '',
+                employeeIds: '',
+                cateId: '',
+                pages: 1,
+                pageSize: 10
+            }
+            if (this.cycleType == '-1') requestdata = { ...this.params }
+            else {
+                requestdata = { ...this.params, cycleType: this.cycleType }
+            }
+            delete requestdata['employeeIds'] // 删除部门字段
+            this.$axiosUser('get', url, requestdata).then(res => {
+                this.loading = false;
+                let { data: { data: { list, total }, code } } = res
+                if (code == 1) {
+                    this.tableData = list;
+                    this.total = total
+                }
+            });
+        },
+
+        // 表格数据 - 选择人员获取数据
+        getListByPerson() {
+            this.loading = true;
+            let url = `/performance/statistics/review/last/${this.user_info.site_id}`
+            this.$axiosUser('get', url, this.params).then(res => {
+                this.loading = false;
+                let { data: { data: { list, total }, code } } = res
+                if (code == 1) {
+                    let contactList = [...this.tableData, ...list];
+                    // 去重
+                    this.tableData = Array.from(
+                        new Set(contactList.map(item => JSON.stringify(item)))
+                    ).map(item => JSON.parse(item));
+                    this.total = this.tableData.length
+                }
+            });
+        },
+
+        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, code } = res.data
+                if (code == 1) {
+                    this.loading = false;
+                    if (data) {
+                        delete data['deptList'] // 删除部门字段
+                        let replaceData = {
+                            ...this.tableData[this.tableIndex],
+                            ...data
+                        }
+                        this.tableData.splice(this.tableIndex, 1, replaceData);
+                    }
+                }
+            });
+        },
+
+        
+        // 处理部门树状结构数据
+        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) {
+            if (v && v.length > 0) {
+                v.forEach(item => {
+                    this.selected_employee_ids.push(item)
+                })
+            }
+            this.selected_employee_ids = Array.from(new Set(this.selected_employee_ids)); // 去重
+            this.params.employeeIds = this.selected_employee_ids.toString()
+            this.getList();
+        },
+
+        // 选择考核分类
+        changeCateId(v) {
+            this.params.cateId = v;
+            this.getList();
+        },
+
+        //选择人员弹框 -- 保存人员
+        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 : []
+        },
+
+        handleSelectChange(val) {
+            this.multipleSelection = val;
+            // this.tableData = this.tableData.filter(data => this.multipleSelection.)
+        },
+
+        confirmDelete() {
+            this.tableData = this.tableData.filter(item => !this.multipleSelection.includes(item));
+            this.$message.success("删除成功!");
+            this.$refs.multipleTable.clearSelection(); // 清空选中状态
+        },
+
+        chooseExamine(row, index) {
+            this.searchOptions = {
+                employeeId: row.employeeId,
+                cycleType: this.params.cycleType,
+                cateId: this.params.cateId
+            }
+            
+            this.tableIndex = index
+            this.dialogVisible = true
+        },
+
+        chooseEaxmineItem(item) {
+            this.getDetails(item.reviewId)
+        },
+
+        exportToExcel(tableName, elementName) {
+            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;
+        },
+    }
+}
+</script>
+
+<style scoped="scoped" lang="scss">
+.all {
+    width: 100%;
+    height: 100%;
+    font-size: 14px;
+    border-radius: 4px;
+    background: #fff;
+    padding: 10px;
+    box-sizing: border-box;
+    /* 设置滚动条的宽度和背景色 */
+    ::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>

+ 347 - 0
src/newPerformance/components/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/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>

+ 104 - 0
src/newPerformance/components/PerEmployeeSelector.vue

@@ -0,0 +1,104 @@
+<template>
+  <el-dialog
+    :visible="innerVisible"
+    @close="handleClose"
+    @open="initData"
+    append-to-body
+    width="650px"
+    center
+    title="选择用户"
+  >
+    <el-transfer
+      v-model="selectedValues"
+      :data="employees"
+      :titles="['未选用户', '已选用户']"
+      style="margin-bottom: 20px;"
+      @change="onChange"
+    ></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>
+</template>
+
+<script>
+export default {
+  name: 'PerEmployeeSelector',
+  props:{
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+    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.selectedTemplate,
+    }
+  },
+  watch: {
+    showVisible(val) {
+      this.innerVisible = val;
+    },
+  },
+  computed:{
+    employees(){
+      return this.employeeList.map(e => {
+        return {
+          key:Number.parseInt(e.id),
+          label:e.name
+        }
+      })
+    },
+    employeeMap(){
+      const map = {};
+      this.employeeList.forEach(e => {
+        if (!map[e.id]) map[e.id] = e;
+      });
+      return map;
+    }
+  },
+  methods: {
+    handleClose(){
+      this.$emit('update:showVisible',false)
+    },
+    initData(){
+      this.selectedValues = this.selectedEmployees.filter(id => Number.isFinite(id)).map(id => Number.parseInt(id));
+    },
+    onConfirm(){
+      let res = this.selectedValues.filter(id => !!this.employeeMap[id]).map(id => {
+        return {
+          employeeId:Number.parseInt(this.employeeMap[id].id),
+          name:this.employeeMap[id].name,
+          imgUrl:this.employeeMap[id].img_url,
+        }
+      });
+      this.$emit('confirm',res);
+      this.handleClose();
+    },
+    onChange(values){
+      if(!this.multiple && values && values.length > 0) this.selectedValues = [values[values.length - 1]];
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 758 - 0
src/newPerformance/components/PerInterview.vue

@@ -0,0 +1,758 @@
+<template>
+  <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
+              v-if="canJoinMeeting"
+              type="text"
+              :disabled="loading"
+              @click="initMeeting"
+            >进入会议模式</el-button>
+            <el-button
+              v-if="canExitMeeting"
+              type="warning"
+              :disabled="loading"
+              @click="leaveInterview"
+            >
+              退出会议模式
+            </el-button>
+            <el-button
+              v-if="canCloseMeeting"
+              :disabled="loading"
+              type="primary"
+              @click="closeMeeting"
+            >
+              结束会议
+            </el-button>
+          </el-col>
+          <el-col align="end">
+            <el-rate
+              v-model="interviewInfo.satisfactionLevel"
+              :colors="rateColors"
+              :disabled="!canSatisfaction"
+              @change="sendSatisfaction"
+            />
+          </el-col>
+        </el-row>
+      </el-card>
+      <el-card style="height: 30%; overflow-y: auto; margin-bottom: 10px;">
+        <el-descriptions
+          title="考核信息"
+          :column="3"
+          border
+        >
+          <template
+            v-if="interviewInfo.reviewInfo.indicators && interviewInfo.reviewInfo.indicators.length > 0"
+            #extra
+          >
+            <el-button
+              type="text"
+              @click="showIndicator = true"
+            >
+              查看考核指标
+            </el-button>
+          </template>
+          <el-descriptions-item label="考核">
+            {{interviewInfo.reviewInfo.title}}
+          </el-descriptions-item>
+          <el-descriptions-item
+            v-if="interviewInfo.reviewInfo.cateIds && interviewInfo.reviewInfo.cateIds.length > 0"
+            label="考核分类">
+            <template
+              v-for="cateId in interviewInfo.reviewInfo.cateIds"
+            >
+              <el-link
+                v-if="cateMap[cateId]"
+                :key="cateId"
+                type="primary"
+              >
+                {{ cateMap[cateId] }}
+              </el-link>
+            </template>
+          </el-descriptions-item>
+          <el-descriptions-item label="考核人">
+            {{ interviewInfo.reviewInfo.employeeName }}
+          </el-descriptions-item>
+          <el-descriptions-item label="考核周期">
+            {{ $moment(interviewInfo.reviewInfo.startTime).format("YYYY/MM/DD") }} - {{ $moment(interviewInfo.reviewInfo.endTime).format("YYYY/MM/DD") }}
+            <span v-if="interviewInfo.reviewInfo.cycleType > 0">
+              {{ cycleMap[interviewInfo.reviewInfo.cycleType] || '' }}
+            </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="考核状态">
+            {{ statusMap[interviewInfo.reviewInfo.status] || '--'}}
+          </el-descriptions-item>
+          <el-descriptions-item label="评分">
+            {{ interviewInfo.reviewInfo.score }}
+          </el-descriptions-item>
+          <el-descriptions-item v-if="interviewInfo.reviewInfo.levelName" label="评级">
+            {{ interviewInfo.reviewInfo.levelName }}
+          </el-descriptions-item>
+        </el-descriptions>
+      </el-card>
+      <el-row
+        type="flex"
+        justify="space-between"
+        style="height: 55%;"
+      >
+        <el-col :span="17" style="height: 100%; overflow-y: auto;">
+          <el-row style="height: 100%;" type="flex" justify="space-around">
+            <el-col :span="12" style="height: 100%;">
+              <el-card style="height: 100%;">
+                <template #header>
+                  主持人记录
+                </template>
+                <template #default>
+                  <el-input
+                    type="textarea"
+                    v-model="interviewInfo.assigneeComment"
+                    :rows="canAssigneeComment ? 10 : 11"
+                    :readonly="!canAssigneeComment"
+                  />
+                  <div style="margin-top: 5px; position: relative;">
+                    <el-button
+                      v-if="canAssigneeComment"
+                      type="primary"
+                      style="position: absolute; right: 5px;"
+                      @click="sendAssigneeComment"
+                    >
+                      保存
+                    </el-button>
+                  </div>
+                </template>
+              </el-card>
+            </el-col>
+            <el-col :span="11" style="height: 100%;">
+              <el-card style="height: 100%;">
+                <template #header>
+                  考核人记录
+                </template>
+                <template #default>
+                  <el-input
+                    type="textarea"
+                    v-model="interviewInfo.employeeComment"
+                    :rows="canEmployeeComment ? 10 : 11"
+                    :readonly="!canEmployeeComment"
+                  />
+                  <div style="margin-top: 5px; position: relative;">
+                    <el-button
+                      v-if="canEmployeeComment"
+                      type="primary"
+                      style="position: absolute; right: 5px;"
+                      @click="sendEmployeeComment"
+                    >
+                      保存
+                    </el-button>
+                  </div>
+                </template>
+              </el-card>
+            </el-col>
+          </el-row>
+        </el-col>
+        <el-col :span="7" style="height: 100%;">
+          <el-card style="height: 100%;">
+            <template #header>
+              <div style="height: 15px; line-height: 15px;">
+                <el-dropdown
+                  v-if="hasJoinInterview && userList.length > 0"
+                >
+                  <el-link
+                    type="primary"
+                  >
+                    {{ `${userList.length} 个参与者` }} <i class="el-icon-arrow-down el-icon--right"/>
+                  </el-link>
+                  <el-dropdown-menu slot="dropdown">
+                    <el-dropdown-item
+                      v-for="user in userList"
+                      :key="user.id"
+                    >
+                      {{ user.name }}
+                    </el-dropdown-item>
+                  </el-dropdown-menu>
+                </el-dropdown>
+                <span v-else>聊天记录</span>
+                <el-button
+                  v-if="canInterviewLog"
+                  size="mini"
+                  type="text"
+                  style="float: right;"
+                  @click="showChat = true"
+                >
+                  发送
+                </el-button>
+              </div>
+            </template>
+            <template #default>
+              <div style="height: 300px; overflow-y: auto;">
+                <el-card
+                  v-for="log in interviewLogs"
+                  :key="log.logId"
+                >
+                  <el-row type="flex" justify="start" align="center">
+                    <el-col :span="3">
+                      <el-avatar size="small" :src="employeeMap[log.employeeId].imgUrl">
+                        <el-avatar
+                          icon="el-icon-user-solid"
+                          size="small"
+                        />
+                      </el-avatar>
+                    </el-col>
+                    <el-col :span="11" style="line-height: 28px;">
+                      {{ employeeMap[log.employeeId].name }}
+                    </el-col>
+                    <el-col :span="10" style="line-height: 28px;">
+                      {{ log.ct }}
+                    </el-col>
+                  </el-row>
+                  <el-link type="primary" style="margin-top: 5px;">
+                    {{ log.content }}
+                  </el-link>
+                </el-card>
+                <div ref="chatLogRef"></div>
+              </div>
+            </template>
+          </el-card>
+        </el-col>
+      </el-row>
+    </el-main>
+    <NoData v-else content="暂无面谈记录"></NoData>
+    <el-dialog
+      :visible.sync="showChat"
+      width="600"
+      title="聊天记录"
+      center
+    >
+      <el-card>
+        <el-input
+          v-model="chatContent"
+          type="textarea"
+          autosize
+          style="margin-bottom: 10px;"
+        />
+        <el-button
+          type="primary"
+          style="float: right;margin-bottom: 10px;"
+          @click="sendChatLog"
+        >
+          发送
+        </el-button>
+      </el-card>
+    </el-dialog>
+    <el-dialog
+      v-if="interviewInfo && interviewInfo.reviewInfo.indicators && interviewInfo.reviewInfo.indicators.length > 0"
+      :visible.sync="showIndicator"
+      title="考核指标"
+      center
+    >
+      <el-table
+        :data="interviewInfo.reviewInfo.indicators"
+        max-height="800"
+      >
+        <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) }}
+          </template>
+        </el-table-column>
+        <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) }}
+          </template>
+        </el-table-column>
+        <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"
+        >
+          <template slot-scope="scope">
+            <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>
+    </el-dialog>
+  </el-container>
+</template>
+
+
+<script>
+
+import Template from "../../examine/components/Template.vue";
+import Stomp from 'stompjs';
+import {getToken} from '@/utils/auth';
+
+export default {
+  name: "PerInterview",
+  components: {Template},
+  props:{
+    reviewId:{
+      type: Number,
+      required: true
+    },
+    interviewId:{
+      type: Number,
+      require: true,
+    }
+  },
+  watch:{
+    interviewId(v){
+      if (v) this.initData();
+    },
+  },
+  data(){
+    return {
+      userInfo:this.$userInfo(),
+      innerVisible:this.showVisible,
+      loading:false,
+      interviewInfo:null,
+      interviewLogs:[],
+      cateList:[],
+      employeeList:[],
+      cycleMap:{
+        0:'未定义',
+        1:'年度',
+        2:'半年度',
+        3:'季度',
+        4:'月度',
+      },
+      statusMap:{
+        0:'考核中',
+        1:'已结束',
+        2:'面谈',
+      },
+      indicatorStatusMap:{
+        start:'进入考核',
+        target_confirm:'目标确认中',
+        result_input:'结果录入中',
+        score_self:'自评中',
+        score_each_other:'互评中',
+        score:'评分中',
+        review:'审批中',
+        cc:'抄送',
+        end:'已结束',
+      },
+      rateColors:['#99A9BF', '#F7BA2A', '#FF9900'],
+      chatContent:'',
+      interviewConnect:false,
+      hasJoinInterview:false,
+      stompClient:null,
+      userList:[],
+      showChat:false,
+      showIndicator:false,
+    }
+  },
+  computed: {
+    cateMap(){
+      const map = {};
+      this.cateList.forEach(cate => {
+        map[cate.cateId] = cate.name;
+      });
+      return map;
+    },
+    employeeMap(){
+      const map = {};
+      this.employeeList.forEach(e => {
+        map[e.id] = e;
+      });
+      return map;
+    },
+    canSatisfaction(){
+      return this.interviewInfo && this.interviewInfo.assessor;
+    },
+    canAssigneeComment(){
+      return this.interviewInfo && this.interviewInfo.host && this.hasJoinInterview;
+    },
+    canEmployeeComment(){
+      return this.canSatisfaction;
+    },
+    canInterviewLog(){
+      return this.interviewConnect && this.hasJoinInterview;
+    },
+    canExitMeeting(){
+      return this.interviewConnect
+    },
+    canCloseMeeting(){
+      return this.interviewConnect && this.hasJoinInterview && this.interviewInfo.host;
+    },
+    canJoinMeeting(){
+      return this.interviewInfo && this.interviewInfo.businessStatus === 'interview' && !this.interviewConnect;
+    }
+  },
+  methods:{
+    initData(){
+      if (this.loading || !this.reviewId || !this.interviewId || !this.userInfo) return;
+      this.loading = true;
+
+      Promise.all([
+        this.$axiosUser('get',`/performance/interview/info/${this.userInfo.site_id}/${this.interviewId}`),
+        this.$axiosUser('get',`/performance/interview/chat/logs/${this.userInfo.site_id}/${this.interviewId}`),
+        this.$axiosUser('get',`/performance/cate/list/${this.userInfo.site_id}`),
+        this.$axiosUser('get',`/org/employees`),
+      ])
+        .then(([interviewRes,logRes,cateRes,employeeRes]) => {
+          if (interviewRes.data.code !== 1) throw new Error(interviewRes.data.message);
+          if (logRes.data.code !== 1) throw new Error(logRes.data.message);
+          if (cateRes.data.code !== 1) throw new Error(cateRes.data.message);
+          if (employeeRes.data.code !== 1) throw new Error(employeeRes.data.message);
+
+          this.interviewInfo = interviewRes.data.data || null;
+          this.interviewLogs = logRes.data.data.list || [];
+          this.cateList = (cateRes.data.data.list || []).map(cate => {
+            return {
+              cateId:cate.cateId,
+              name:cate.name,
+            }
+          });
+          this.employeeList = employeeRes.data.data || [];
+          this.$nextTick(() => {
+            if (this.$refs.chatLogRef) this.$refs.chatLogRef.scrollIntoView(true);
+          })
+        })
+        .catch(err => {
+          console.warn(err);
+        })
+        .finally(() => {
+          this.loading = false;
+        })
+    },
+    interviewUsers(){
+      if (this.loading) return;
+      this.loading = true;
+
+      Promise.all([
+        this.$axiosUser('get',`/performance/interview/users/${this.userInfo.site_id}/${this.interviewInfo.interviewId}`)
+      ])
+        .then(([userRes]) => {
+          if (userRes.data.code !== 1) throw new Error(userRes.data.message);
+
+          this.userList = userRes.data.data.list || [];
+        })
+        .catch(err => {
+          console.warn(err);
+        })
+        .finally(() => {
+          this.loading = false;
+        })
+    },
+    initMeeting(){
+      if (this.loading || (this.stompClient && this.interviewConnect) || !this.userInfo) return;
+      this.loading = true;
+
+      this.initMeetingState();
+
+      this.stompClient = Stomp.client(process.env.VUE_APP_WEBSOCKET_PERFORMANCE);
+      this.stompClient.connect(
+        {
+          at:getToken()
+        },
+        (res) => {
+          this.loading = false;
+          this.interviewConnect = true;
+
+          this.subscribe();
+
+          this.joinInterview();
+        },
+        (err) => {
+          console.warn('meeting err',err);
+          this.loading = false;
+        }
+      )
+    },
+    initMeetingState(){
+      this.stompClient = null;
+      this.interviewConnect = false;
+      this.hasJoinInterview = false;
+      this.userList = [];
+    },
+    subscribe(){
+      if (!this.stompClient || !this.interviewConnect) return;
+
+      this.stompClient.subscribe(
+        `/topic/interview/join/${this.interviewInfo.interviewId}`,
+        (message) => {
+          const user = JSON.parse(message.body);
+          if (!user) return;
+
+          if (user.id !== this.userInfo.id){
+            this.$notify({
+              title:user.name,
+              message:'加入会议',
+              type:'success',
+              duration:1000,
+            });
+            if(!this.userList.find(u => u.id === user.id)) this.userList.push(user);
+          } else {
+            this.interviewUsers();
+            this.hasJoinInterview = true;
+          }
+        }
+      );
+
+      this.stompClient.subscribe(
+        `/topic/interview/leave/${this.interviewInfo.interviewId}`,
+        (message) => {
+          const user = JSON.parse(message.body);
+          if (!user) return;
+
+          if (this.userInfo.id !== user.id){
+            this.$notify({
+              title:user.name,
+              message: '离开会议',
+              type:'success',
+              duration:1000,
+            });
+          } else {
+            this.disconnect();
+          }
+        }
+      );
+      this.stompClient.subscribe(
+        `/topic/interview/comment/assignee/${this.interviewInfo.interviewId}`,
+        (message) => {
+          if (!this.interviewInfo) return;
+          if (this.interviewInfo.assigneeComment !== message.body) {
+            this.interviewInfo.assigneeComment = message.body;
+          } else {
+            this.$notify({
+              message:'更新完毕',
+              type:'success',
+              duration:1000,
+            });
+          }
+        }
+      );
+      this.stompClient.subscribe(
+        `/topic/interview/comment/employee/${this.interviewInfo.interviewId}`,
+        (message) => {
+
+          if (!this.interviewInfo) return;
+          if (this.interviewInfo.employeeComment !== message.body){
+            this.interviewInfo.employeeComment = message.body;
+          } else {
+            this.$notify({
+              message:'更新完毕',
+              type:'success',
+              duration:1000,
+            });
+          }
+        }
+      );
+      this.stompClient.subscribe(
+        `/topic/interview/chat/log/${this.interviewInfo.interviewId}`,
+        (message) => {
+          let log = JSON.parse(message.body);
+          if (log) this.interviewLogs.push(log);
+          this.$nextTick(() => {
+            if (this.$refs.chatLogRef) this.$refs.chatLogRef.scrollIntoView(false);
+          })
+        }
+      );
+      this.stompClient.subscribe(
+        `/topic/interview/satisfaction/${this.interviewInfo.interviewId}`,
+        (message) => {
+          const tmp = JSON.parse(message.body);
+          if (tmp) this.interviewInfo.satisfactionLevel = tmp.satisfactionLevel || this.interviewInfo.satisfactionLevel;
+        }
+      );
+
+
+      this.stompClient.subscribe(
+        `/topic/interview/close/${this.interviewInfo.interviewId}`,
+        (message) => {
+          console.log('interview close',message);
+
+          const tmp = JSON.parse(message.body);
+          if (!tmp) return;
+
+          this.disconnect();
+          this.$notify({
+            message:'面谈已结束,退出会议模式',
+            type:'success',
+            duration:1000,
+          });
+
+          this.interviewInfo = tmp;
+        }
+      );
+    },
+    joinInterview(){
+      if (!this.stompClient || !this.interviewConnect) return;
+      this.stompClient.send(`/app/interview/join/${this.interviewInfo.interviewId}`);
+
+      this.interviewUsers();
+    },
+    leaveInterview(){
+      if (!this.stompClient || !this.interviewConnect) return;
+      this.stompClient.send(`/app/interview/leave/${this.interviewInfo.interviewId}`);
+    },
+    disconnect(){
+      if (!this.stompClient || !this.interviewConnect) return;
+
+      this.stompClient.disconnect();
+      this.initMeetingState();
+    },
+    apiSatisfaction(){
+      if (!this.canSatisfaction || this.loading) return;
+
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$http.post(`performance/interview/satisfaction/${this.userInfo.site_id}`,{interviewId:this.interviewInfo.interviewId,level:this.interviewInfo.satisfactionLevel}),
+      ])
+        .then(([levelRes]) => {
+          if (levelRes.code !== 1) throw new Error(levelRes.message);
+
+          this.interviewInfo = levelRes.data || null;
+
+          if (!this.interviewInfo) result = false;
+        })
+        .catch(err => {
+          result = false;
+          console.warn(err);
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) this.leaveInterview();
+        })
+    },
+    sendSatisfaction(){
+      if (!this.canSatisfaction) return;
+      if (!this.stompClient || !this.hasJoinInterview){
+        //普通面谈详情模式
+        this.apiSatisfaction();
+      } else {
+        //面谈会议模式
+        this.stompClient.send(
+          `/app/interview/satisfaction/${this.interviewInfo.interviewId}`,
+          {},
+          this.interviewInfo.satisfactionLevel
+        );
+      }
+    },
+    apiEmployeeComment(){
+      if (!this.canEmployeeComment || this.loading) return;
+
+      this.loading = true;
+
+      let result = true;
+      Promise.all([
+        this.$http.post(`performance/interview/comment/employee/${this.userInfo.site_id}`,{interviewId:this.interviewInfo.interviewId,comment:this.interviewInfo.employeeComment}),
+      ])
+        .then(([commentRes]) => {
+          if (commentRes.code !== 1) throw new Error(commentRes.message);
+
+          this.interviewInfo = commentRes.data || null;
+
+          if (!this.interviewInfo) result = false;
+        })
+        .catch(err => {
+          result = false;
+          console.warn(err);
+        })
+        .finally(() => {
+          this.loading = false;
+          if (!result) {
+            this.leaveInterview();
+          } else {
+            this.$notify({
+              message:'更新完毕',
+              type:'success',
+              duration:1000,
+            });
+          }
+        })
+    },
+    sendEmployeeComment(){
+      if (!this.canEmployeeComment) return;
+
+      if (!this.stompClient || !this.hasJoinInterview){
+        //普通面谈模式
+        this.apiEmployeeComment();
+      } else {
+        //面谈会议模式
+        this.stompClient.send(
+          `/app/interview/comment/employee/${this.interviewInfo.interviewId}`,
+          {},
+          this.interviewInfo.employeeComment
+        );
+      }
+    },
+    sendAssigneeComment(){
+      if (!this.canAssigneeComment) return;
+
+      this.stompClient.send(
+        `/app/interview/comment/assignee/${this.interviewInfo.interviewId}`,
+        {},
+        this.interviewInfo.assigneeComment
+      );
+
+    },
+    sendChatLog(){
+      if (!this.stompClient || !this.hasJoinInterview || !this.canInterviewLog || !this.chatContent) return;
+
+      this.stompClient.send(
+        `/app/interview/chat/log/${this.interviewInfo.interviewId}`,
+        {},
+        this.chatContent,
+      );
+
+      this.chatContent = '';
+    },
+    closeMeeting(){
+      if (!this.canCloseMeeting || !this.stompClient) return;
+
+      this.stompClient.send(`/app/interview/close/${this.interviewInfo.interviewId}`);
+    },
+  },
+  mounted(){
+    this.initData();
+  }
+
+}
+</script>
+
+
+<style scoped lang="scss">
+
+</style>

+ 367 - 0
src/newPerformance/components/PerResultInputSelector.vue

@@ -0,0 +1,367 @@
+<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-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>
+  </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>

+ 382 - 0
src/newPerformance/components/PerResultInputSelectorOnly.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"
+        />
+      </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-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>
+    <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>

+ 722 - 0
src/newPerformance/components/PerReviewDetail.vue

@@ -0,0 +1,722 @@
+<template>
+  <el-container style="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-col>
+            <WaStstistics
+              title="考核表"
+              :value="reviewInfo.title"
+            />
+          </el-col>
+          <el-col>
+            <WaStstistics
+              title="周期种类"
+              :value="cycleMap[reviewInfo.cycleType] || '--'"
+            />
+          </el-col>
+          <el-col>
+            <WaStstistics
+              title="考核时间"
+              :value="timeScope"
+            />
+          </el-col>
+          <el-col>
+            <WaStstistics
+              title="考核状态"
+              :value="statusMap[reviewInfo.status] || '--'"
+            />
+          </el-col>
+          <el-col>
+            <WaStstistics
+              title="评分"
+              :value="reviewInfo.score === null ? '--' : reviewInfo.score"
+            />
+          </el-col>
+          <el-col>
+            <WaStstistics
+              title="考核中的指标"
+              :value="reviewInfo.indicators.length > 0 ? (reviewInfo.indicators.length - indicatorEnd) : '--'"
+            />
+          </el-col>
+          <el-col>
+            <WaStstistics
+              title="考核完成的指标"
+              :value="indicatorEnd"
+            />
+          </el-col>
+          <el-col
+            v-if="reviewInfo.interviews && reviewInfo.interviews.length > 0"
+          >
+            <el-dropdown trigger="click">
+              <el-button
+                type="text"
+              >
+                面谈记录
+              </el-button>
+              <el-dropdown-menu>
+                <el-dropdown-item
+                  v-for="interview in reviewInfo.interviews"
+                  :key="interview.interviewId"
+                >
+                  <span @click="openInterview(interview.interviewId)">
+                    {{ interview.createTime }}
+                  </span>
+                </el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+          </el-col>
+        </el-row>
+      </el-card>
+      <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="隐藏考核节点"
+          />
+        </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"
+          >
+            <template slot-scope="scope">
+              {{ 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"
+          >
+            <template slot-scope="scope">
+              {{ 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"
+          >
+            <template slot-scope="scope">
+              {{ scope.row.weight ? `${scope.row.weight} %` : '--' }}
+            </template>
+          </el-table-column>
+          <el-table-column
+            prop="businessStatus"
+            label="考核状态"
+            align="center"
+          >
+            <template slot-scope="scope">
+              <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"
+          >
+            <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>
+              <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"
+                  >
+                    {{ scope.row.targetConfirms.finishCount }}
+                  </el-link>
+                  <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="结果录入"
+          >
+            <template slot-scope="scope">
+              <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>
+              <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"
+                  >
+                    {{ scope.row.resultInput.finishCount }}
+                  </el-link>
+                  <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="自评"
+          >
+            <template slot-scope="scope">
+              <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>
+              <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"
+                  >
+                    {{ scope.row.scoreSelf.finishCount }}
+                  </el-link>
+                  <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="互评"
+          >
+            <template slot-scope="scope">
+              <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>
+              <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"
+                  >
+                    {{ scope.row.scoreEachOther.finishCount }}
+                  </el-link>
+                  <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="评分"
+          >
+            <template slot-scope="scope">
+              <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>
+              <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"
+                  >
+                    {{ scope.row.scores.finishCount }}
+                  </el-link>
+                  <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="审批"
+          >
+            <template slot-scope="scope">
+              <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>
+              <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"
+                  >
+                    {{ scope.row.reviews.finishCount }}
+                  </el-link>
+                  <el-link
+                    v-if="scope.row.reviews.runningCount > 0"
+                    type="warning"
+                    icon="el-icon-warning"
+                  >
+                    {{ scope.row.reviews.runningCount }}
+                  </el-link>
+                </div>
+              </template>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-card>
+    </el-main>
+    <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-descriptions-item label="任务状态">{{ task.state === 'completed' ? '已处理' : '待处理' }}</el-descriptions-item>
+            <el-descriptions-item
+              v-if="task.score !== null"
+              label="评分"
+            >
+              {{ task.score }}
+            </el-descriptions-item>
+            <el-descriptions-item
+              v-if="task.result !== null"
+              label="结果"
+            >
+              {{ tasksInfo.unit ? `${task.result} ${tasksInfo.unit}` : task.result }}
+            </el-descriptions-item>
+            <el-descriptions-item
+              label="评论"
+            >
+              {{ task.comment }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-card>
+      </div>
+
+    </el-dialog>
+  </el-container>
+</template>
+
+
+<script>
+import WaStstistics from "./tool/WaStstistics.vue";
+import moment from "moment";
+import Template from "../../examine/components/Template.vue";
+import Seal from "./tool/Seal.vue";
+import PerInterview from "./PerInterview.vue";
+
+export default {
+  name: "PerReviewDetail",
+  components: {PerInterview, Seal, Template, WaStstistics},
+  props: {
+    reviewId:{
+      type: Number,
+      required: true
+    }
+  },
+  data(){
+    return {
+      userInfo:this.$userInfo(),
+      loading:false,
+      reviewInfo:null,
+      cycleMap:{
+        0:'未定义',
+        1:'年度',
+        2:'半年度',
+        3:'季度',
+        4:'月度',
+      },
+      statusMap:{
+        0:'考核中',
+        1:'已结束',
+        2:'面谈',
+      },
+      indicatorStatusMap:{
+        start:'进入考核',
+        target_confirm:'目标确认中',
+        result_input:'结果录入中',
+        score_self:'自评中',
+        score_each_other:'互评中',
+        score:'评分中',
+        review:'审批中',
+        cc:'抄送',
+        end:'已结束',
+      },
+      showNode:false,
+      showTasks:false,
+      tasks:[],
+      tasksInfo:{
+        title:'',
+        unit:'',
+        tasks:[]
+      },
+    }
+  },
+  computed:{
+    timeScope(){
+      if (!this.reviewInfo || !this.reviewInfo.startTime || !this.reviewInfo.endTime) return '--';
+      return moment(this.reviewInfo.startTime).format('YY/MM/DD') + ' ~ ' + moment(this.reviewInfo.endTime).format('YY/MM/DD');
+    },
+    indicatorEnd(){
+      if (!this.reviewInfo || this.reviewInfo.indicators.length <= 0) return '--';
+      return this.reviewInfo.indicators.filter(indicator => indicator.businessStatus === 'end').length;
+    },
+    indicators(){
+      return !this.reviewInfo ? [] : this.reviewInfo.indicators.map(item => {
+        const indicator = {
+          reviewIndicatorId:item.reviewIndicatorId,
+          reviewTitle:item.reviewTitle,
+          title:item.title,
+          content:item.content,
+          target:item.target,
+          result:item.result,
+          score:item.score,
+          unit:item.unit,
+          weight:item.weight,
+          businessStatus:item.businessStatus,
+          targetConfirms:null,
+          resultInput:null,
+          scoreSelf:null,
+          scoreEachOther:null,
+          scores:null,
+          reviews:null,
+          cc:null,
+        };
+        item.flow.nodes.forEach(node => {
+          switch (node.type){
+            case 'targetConfirms':
+              const targetConfirms = {
+                enable:node.enable,
+                tasks:[],
+                runningCount:0,
+                finishCount:0,
+              }
+              if (node.children && node.children.length > 0){
+                node.children.forEach(child => {
+                  if (child.tasks && child.tasks.length > 0) {
+                    child.tasks.forEach(task => {
+                      targetConfirms.tasks.push(task);
+                      if (task.state === 'completed'){
+                        targetConfirms.finishCount ++;
+                      }else {
+                        targetConfirms.runningCount ++;
+                      }
+                    })
+                  }
+                })
+              }
+              indicator.targetConfirms = targetConfirms;
+              break;
+            case 'resultInput':
+              const resultInput = {
+                enable:node.enable,
+                tasks:[],
+                runningCount:0,
+                finishCount:0,
+              }
+
+              if (node.tasks && node.tasks.length > 0){
+                node.tasks.forEach(task => {
+                  resultInput.tasks.push(task);
+                  if (task.state === 'completed'){
+                    resultInput.finishCount ++;
+                  }else {
+                    resultInput.runningCount ++;
+                  }
+                })
+              }
+
+              indicator.resultInput = resultInput;
+              break;
+            case 'scoreSelf':
+              const scoreSelf = {
+                enable:node.enable,
+                tasks:[],
+                runningCount:0,
+                finishCount:0,
+              }
+
+              if (node.tasks && node.tasks.length > 0){
+                node.tasks.forEach(task => {
+                  scoreSelf.tasks.push(task);
+                  if (task.state === 'completed'){
+                    scoreSelf.finishCount ++;
+                  }else {
+                    scoreSelf.runningCount ++;
+                  }
+                })
+              }
+              indicator.scoreSelf = scoreSelf;
+              break;
+            case 'scoreEachOther':
+              const scoreEachOther = {
+                enable:node.enable,
+                tasks:[],
+                runningCount:0,
+                finishCount:0,
+              }
+
+              if (node.tasks && node.tasks.length > 0){
+                node.tasks.forEach(task => {
+                  scoreEachOther.tasks.push(task);
+                  if (task.state === 'completed'){
+                    scoreEachOther.finishCount ++;
+                  }else {
+                    scoreEachOther.runningCount ++;
+                  }
+                })
+              }
+              indicator.scoreEachOther = scoreEachOther;
+              break;
+            case 'scores':
+              const scores = {
+                enable:node.enable,
+                tasks:[],
+                runningCount:0,
+                finishCount:0,
+              }
+
+              if (node.children && node.children.length > 0){
+                node.children.forEach(child => {
+                  if (child.tasks && child.tasks.length > 0) {
+                    child.tasks.forEach(task => {
+                      scores.tasks.push(task);
+                      if (task.state === 'completed'){
+                        scores.finishCount ++;
+                      }else {
+                        scores.runningCount ++;
+                      }
+                    })
+                  }
+                })
+              }
+              indicator.scores = scores;
+              break;
+            case 'reviews':
+              const reviews = {
+                enable:node.enable,
+                tasks:[],
+                runningCount:0,
+                finishCount:0,
+              }
+
+              if (node.children && node.children.length > 0){
+                node.children.forEach(child => {
+                  if (child.tasks && child.tasks.length > 0) {
+                    child.tasks.forEach(task => {
+                      reviews.tasks.push(task);
+                      if (task.state === 'completed'){
+                        reviews.finishCount ++;
+                      }else {
+                        reviews.runningCount ++;
+                      }
+                    })
+                  }
+                })
+              }
+              indicator.reviews = reviews;
+              break;
+            case 'cc':
+              const cc = {
+                enable:node.enable,
+                tasks:[],
+                runningCount:0,
+                finishCount:0,
+              }
+              indicator.cc = cc;
+              break;
+          }
+        });
+
+        return indicator;
+      });
+
+    }
+  },
+  watch:{
+    reviewId(v){
+      if (v) this.initData();
+    }
+  },
+  methods:{
+    initData(){
+      if (this.loading || !this.reviewId) return;
+      this.loading = true;
+
+      this.showNode = false;
+      this.reviewInfo = null;
+
+      Promise.all([
+        this.$axiosUser('get',`/performance/statistics/review/info/${this.userInfo.site_id}/${this.reviewId}`)
+      ])
+        .then(([reviewRes]) =>{
+          if (reviewRes.data.code !== 1) throw new Error(reviewRes.data.message);
+
+          this.reviewInfo = reviewRes.data.data || null;
+        })
+        .catch(err => {
+          console.warn(err);
+        })
+        .finally(() => {
+          this.loading = false;
+        })
+    },
+    openTasks(tasks,title,unit){
+      if (!tasks || tasks.length <= 0) return;
+      this.tasksInfo.tasks = tasks;
+      this.tasksInfo.title = title || '任务信息';
+      this.tasksInfo.unit = unit || '';
+      this.showTasks = true;
+    },
+    openInterview(interviewId){
+      if (!interviewId || !this.reviewInfo) return;
+      window.open(`#/per/interview/${this.reviewId}/${interviewId}`);
+    },
+  },
+  mounted() {
+    this.initData();
+  }
+
+}
+</script>
+
+<style scoped lang="scss">
+
+
+</style>

+ 424 - 0
src/newPerformance/components/PerReviewsSelector.vue

@@ -0,0 +1,424 @@
+<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>
+      <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>
+        </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>
+        </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>
+        </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>
+        </template>
+      </el-form-item>
+      <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>
+      </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>

+ 442 - 0
src/newPerformance/components/PerReviewsSelectorOnly.vue

@@ -0,0 +1,442 @@
+<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>
+      <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>
+        </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>
+        </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>
+        </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>
+        </template>
+      </el-form-item>
+      <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>
+      </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>

+ 428 - 0
src/newPerformance/components/PerScoresSelector.vue

@@ -0,0 +1,428 @@
+<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>
+
+      <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>
+        </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>
+        </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>
+        </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>
+        </template>
+      </el-form-item>
+
+      <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>
+      </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>

+ 446 - 0
src/newPerformance/components/PerScoresSelectorOnly.vue

@@ -0,0 +1,446 @@
+<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>
+
+      <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>
+        </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>
+        </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>
+        </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>
+        </template>
+      </el-form-item>
+
+      <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>
+      </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>

+ 426 - 0
src/newPerformance/components/PerTargetConfirmSelector.vue

@@ -0,0 +1,426 @@
+<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>
+
+      <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>
+        </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>
+        </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>
+        </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>
+        </template>
+      </el-form-item>
+
+      <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>
+      </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>

+ 447 - 0
src/newPerformance/components/PerTargetConfirmSelectorOnly.vue

@@ -0,0 +1,447 @@
+<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>
+
+      <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>
+        </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>
+        </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>
+        </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>
+        </template>
+      </el-form-item>
+
+      <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>
+      </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>

+ 164 - 0
src/newPerformance/components/PerformanceInterview copy.vue

@@ -0,0 +1,164 @@
+<template>
+    <div class="all">
+        <el-card>
+            <el-descriptions :column="3" border style="margin-bottom: 10px;">
+                <el-descriptions-item>
+                    <template slot="label">
+                        用户名
+                    </template>
+                    kooriookami
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        用户名
+                    </template>
+                    kooriookami
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        用户名
+                    </template>
+                    kooriookami
+                </el-descriptions-item>
+
+                <el-descriptions-item>
+                    <template slot="label">
+                        用户名
+                    </template>
+                    kooriookami
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        用户名
+                    </template>
+                    kooriookami
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        用户名
+                    </template>
+                    kooriookami
+                </el-descriptions-item>
+
+                <el-descriptions-item>
+                    <template slot="label">
+                        用户名
+                    </template>
+                    kooriookami
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        用户名
+                    </template>
+                    kooriookami
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        用户名
+                    </template>
+                    kooriookami
+                </el-descriptions-item>
+            </el-descriptions>
+        </el-card>
+
+        <el-row :gutter="10" class="flex-1">
+            <el-col :span="20" style="height: 100%;">
+                <el-row :gutter="10" style="height: 70%; margin-bottom: 10px;">
+                    <el-col :span="12" style="height: 100%;">
+                        <el-card style="height: 100%;">
+                            <div slot="header">主持人记录</div>
+                            <div></div>
+                        </el-card>
+                    </el-col>
+                    <el-col :span="12" style="height: 100%;">
+                        <el-card style="height: 100%;">
+                            <div slot="header">考核人记录</div>
+                            <div></div>
+                        </el-card>
+                    </el-col>
+                </el-row>
+                <el-row :gutter="10" style="height: 30%;">
+                    <el-col :span="24" style="height: 100%;">
+                        <el-card style="height: 100%;">
+                            <div style="height: 80%;">
+                                <el-input type="textarea" v-model="value" placeholder="" style="height: 100%;" rows="5"
+                                    cols="4"></el-input>
+                            </div>
+                            <div style="height: 20%;"><el-button>发送</el-button></div>
+                        </el-card>
+                    </el-col>
+                </el-row>
+            </el-col>
+
+
+            <el-col :span="4" style="height: 100%;">
+                <el-card style="height: 100%;" >
+                    <div slot="header">聊天记录</div>
+                    <div>
+                        <div class="chat-item flex-box-ce" v-for="item in chatList" :key="item.logId">
+                            <userImage :id="item.employeeId" :img_url="item.imgUrl" :user_name="item.employeeName"
+                                width="30px" height="30px"></userImage>
+
+                            {{ item.employeeName }} {{ item.ct }}
+                        </div>
+                    </div>
+                </el-card>
+            </el-col>
+        </el-row>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+
+export default {
+    data() {
+        return {
+            value: '',
+            chatList: []
+        }
+    },
+
+    created() {
+        this.getChatList()
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    methods: {
+        getChatList() {
+            let url = `/performance/interview/chat/logs/${this.user_info.site_id}/7`
+            this.$axiosUser('get', url).then(res => {
+                this.chatList = res.data.data.list
+                console.log(res.data.data.list)
+                // let { data: { title, levelName, indicators, startTime, endTime }, code } = res.data
+                // this.title = title || "默认标题";
+                // this.startTime = startTime || '';
+                // this.endTime = endTime || '';
+                // this.levelName = levelName || '';
+                // this.tableData = indicators || [];
+            });
+        }
+    }
+}
+</script>
+
+<style scoped lang="scss">
+.all {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    box-sizing: border-box;
+    background: #f0f4fa;
+    display: flex;
+    flex-direction: column;
+
+    .chat-item {
+        margin-bottom: 10px;
+        background: #f7f7f7;
+        border-radius: 4px;
+        padding: 5px;
+        box-sizing: border-box;
+    }
+}
+</style>

+ 786 - 0
src/newPerformance/components/PerformanceInterview.vue

@@ -0,0 +1,786 @@
+<template>
+    <div class="all">
+        <div class="flex-box-ce header" style="">
+            <div class="btn-box">
+                <el-button v-if="businessStatus === 'interview' && !interviewConnect" type="primary" size="small"
+                    @click="initMeeting()">加入面谈</el-button>
+                <el-button v-if="interviewConnect" type="warning" size="small" @click="leaveInterview()">离开面谈</el-button>
+                <el-button v-if="interviewConnect" type="danger" size="small" @click="finishInterview()">结束面谈</el-button>
+            </div>
+            <div>
+                <el-rate v-model="value" @change="sendSatisfaction()" :disable="canSatisfaction"></el-rate>
+            </div>
+        </div>
+
+        <div class="examine-info">
+            <div class="flex-box-ce" style="justify-content: space-between; padding: 5px ; box-sizing: border-box;">
+                <div>考核信息</div>
+                <div @click="detailsDialog = true">查看更多</div>
+            </div>
+            <el-descriptions :column="3" border>
+                <el-descriptions-item>
+                    <template slot="label">
+                        姓名
+                    </template>
+                    <div class="flex-box-ce" v-if="interviewInfo && interviewInfo.employee">
+                        <userImage :id="interviewInfo.employee.id" :img_url="interviewInfo.employee.imgUrl"
+                            :user_name="interviewInfo.employee.employeeName" width="30px" height="30px"
+                            style="margin-right: 5px;"></userImage>
+                        {{ interviewInfo && interviewInfo.employee && interviewInfo.employee.name || '--' }}
+                    </div>
+
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        考核名称
+                    </template>
+                    {{ interviewInfo && interviewInfo.reviewInfo && interviewInfo.reviewInfo.title || '--'}}
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        考核周期
+                    </template>
+                    <el-tag size="small">{{ interviewInfo && interviewInfo.reviewInfo &&
+                        interviewInfo.reviewInfo.cycleType | formatCycleType }}</el-tag>
+                </el-descriptions-item>
+
+                <el-descriptions-item>
+                    <template slot="label">
+                        考核时间
+                    </template>
+                    {{ interviewInfo && interviewInfo.reviewInfo && interviewInfo.reviewInfo.startTime | formatDate }}
+                    至
+                    {{ interviewInfo && interviewInfo.reviewInfo && interviewInfo.reviewInfo.endTime | formatDate }}
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        评分
+                    </template>
+                    {{ interviewInfo && interviewInfo.reviewInfo && interviewInfo.reviewInfo.score }}
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        评级
+                    </template>
+                    {{ interviewInfo && interviewInfo.reviewInfo && interviewInfo.reviewInfo.levelName }}
+                </el-descriptions-item>
+
+            </el-descriptions>
+        </div>
+
+        <div class="flex-box-ce flex-1" style="height: 100%; ">
+            <div class="left-box">
+                <div class="flex-box-ce" style="margin-bottom: 10px;">
+                    <!-- 主持人记录 -->
+                    <div class="emcee-record">
+                        <div class="title">主持人记录</div>
+                        <div class="content" v-if="interviewInfo && interviewInfo.assigneeComment">
+                            <el-input type="textarea" v-model="interviewInfo.assigneeComment" placeholder="输入内容"
+                                rows="5" @keydown.native="sendAssigneeComment($event)"
+                                :disable="canAssigneeComment"></el-input>
+                        </div>
+                    </div>
+                    <!-- 考核人记录 -->
+                    <div class="examinee-record">
+                        <div class="title">考核人记录</div>
+                        <div class="content" v-if="interviewInfo && interviewInfo.employeeComment">
+                            <el-input type="textarea" v-model="interviewInfo.employeeComment" placeholder="输入内容"
+                                rows="5" @keyup.enter.native="sendEmployeeComment($event)"></el-input>
+                        </div>
+                    </div>
+                </div>
+                <div class="input-box flex-1">
+                    <el-input type="textarea" v-model="chatLog" placeholder="输入内容" rows="5"
+                        @keyup.enter.native="sendChatLog($event)"></el-input>
+                    <div class="flex-box-ce" style="margin-top: 10px;">
+                        <el-button type="primary" size="small" style="margin-left: auto;" @click="sendChatLog2()">
+                            发 送
+                        </el-button>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 聊天记录 -->
+            <div class="chat-record">
+                <div class="chat-record-title">聊天记录</div>
+                <div class="scroll-bar"
+                    style="height: 500px; overflow-y: auto; padding-bottom: 50px; box-sizing: border-box;">
+                    <div class="chat-item" v-for="item in chatList" :key="item.logId">
+                        <div class="flex-box-ce" style="justify-content: space-between;">
+                            <div class="flex-box-ce">
+                                <userImage :id="item.employeeId" :img_url="item.imgUrl" :user_name="item.employeeName"
+                                    width="30px" height="30px" style="margin-right: 5px;"></userImage>
+                                {{ item.employeeName }}
+                            </div>
+                            <div class="fontColorC">
+                                {{ item.ct }}
+                            </div>
+                        </div>
+
+                        <div class="content fontColorC">
+                            {{ item.content }}
+                        </div>
+                    </div>
+                    <div ref="chatLogRef"></div>
+                </div>
+            </div>
+        </div>
+
+        <el-drawer title="考核信息" :visible.sync="detailsDialog" direction="rtl" :before-close="handleClose" size="70%">
+            <!-- <el-descriptions :column="1" border style="padding: 0 5px; box-sizing: border-box;">
+                <el-descriptions-item>
+                    <template slot="label">
+                        姓名
+                    </template>
+                </el-descriptions-item>
+            </el-descriptions> -->
+            <!-- <el-table :data="tableData" style="width: 100%">
+                <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"
+                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">
+                </el-table-column>
+                <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="target" label="目标" align="center" min-width="100">
+                </el-table-column>
+                <el-table-column prop="unit" label="单位" align="center" min-width="100">
+                </el-table-column>
+                <el-table-column prop="weight" label="权重" align="center" min-width="100">
+                </el-table-column>
+                <el-table-column prop="result" label="结果值" align="center" min-width="100">
+                </el-table-column>
+                <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"
+                            @click="openFormula(scope.row, scope.$index)">
+                            {{ scope.row.expression && scope.row.expression.formulas.length > 0 ? `公式
+                            ${scope.row.expression.formulas.length} 条` : '公式' }}
+                        </el-button>
+                        <div v-else style="color: #999;">暂无公式</div>
+                    </template>
+                </el-table-column>
+
+
+                <el-table-column prop="businessStatus" label="指标考核状态" align="center" min-width="120">
+                    <template slot-scope="scope">
+                        <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 prop="confirm_target" label="确认目标" align="center" width="80" 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 prop="input_result" label="录入结果" align="center" width="80" 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 prop="self_assessment" label="自评" align="center" width="80" 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 prop="peer_assessmen" label="互评" align="center" width="80" 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 prop="grade" label="评分" align="center" width="80" fixed="right">
+                    <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 prop="approval" label="审批" align="center" width="80" 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]" />
+                    </template>
+                </el-table-column>
+                <el-table-column prop="carbon_copy" label="抄送" align="center" width="80" fixed="right">
+                    <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]" />
+                    </template>
+                </el-table-column>
+            </el-table>
+        </el-drawer>
+
+        <!-- 编辑计算公式 -->
+        <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" />
+
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+import Stomp from 'stompjs'
+import ShowHandlerComp from '@/newPerformance/components/PublicComp/ShowHandler'; // 显示节点数据组件
+import FormulaComp from '@/newPerformance/components/TemplateDetails/FormulaComp'; // 计算公式弹框
+
+export default {
+    components: {
+        ShowHandlerComp,
+        // FormulaComp
+    },
+    data() {
+        return {
+            chatLog: '',
+            value: 0,
+            isShow: false,
+            chatList: [],
+            interviewInfo: {},
+            stompClient: null, // 实例
+            interviewConnect: false, // 面试是否连接
+            interviewId: 7,
+            hasJoinInterview: false, // 是否已加入面谈会议
+            users: [], // 当前面谈会议人员
+            detailsDialog: false,
+            showFormula: false,
+            currentIndicator: null
+        }
+    },
+
+    created() {
+        this.getChatList();
+        this.getInterviewDetails();
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        businessStatus() {
+            return this.interviewInfo && this.interviewInfo.businessStatus
+        },
+        tableData() {
+            return this.interviewInfo && this.interviewInfo.reviewInfo && this.interviewInfo.reviewInfo.indicators
+        },
+        canInterviewLog() {
+            return this.interviewConnect && this.hasJoinInterview;
+        },
+        //是否可以给予满意度,被考核人不管会议是否开始或者面谈状态是否已结束都可以
+        canSatisfaction() {
+            return this.interviewInfo && this.interviewInfo.assessor;
+        },
+
+        //是否可以填写主持人记录,主持人并且只有在进入面谈会议状态下可以填写
+        canAssigneeComment() {
+            return this.interviewInfo && this.interviewInfo.host && this.hasJoinInterview;
+        },
+
+        //是否可以填写被考核人记录,与填写满意度一样
+        canEmployeeComment() {
+            return this.canSatisfaction;
+        }
+    },
+    filters: {
+        formatCycleType(val) {
+            if (val == 0) return '未定义'
+            if (val == 1) return '年度'
+            if (val == 2) return '半年'
+            if (val == 3) return '季度'
+            if (val == 4) return '月度'
+            else return '--'
+        },
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    methods: {
+        handleClose() {
+            this.detailsDialog = false
+        },
+        // 打开计算公式弹框
+        openFormula(row, index) {
+            this.currentIndicator = row;
+            this.showFormula = true;
+        },
+
+        onFormulaConfirm() { },
+        initMeetingState() {
+            this.stompClient = null;
+            this.interviewConnect = false;
+            this.hasJoinInterview = false;
+            this.users = [];
+        },
+        // 聊天列表
+        getChatList() {
+            let url = `/performance/interview/chat/logs/${this.user_info.site_id}/${this.interviewId}`
+            this.$axiosUser('get', url).then(res => {
+                this.chatList = res.data.data.list;
+                this.$nextTick(() => {
+                    if (this.$refs.chatLogRef) this.$refs.chatLogRef.scrollIntoView(true);
+                })
+            });
+        },
+        // 面谈详情
+        getInterviewDetails() {
+            let url = `/performance/interview/info/${this.user_info.site_id}/${this.interviewId}`
+            this.$axiosUser('get', url).then(res => {
+                this.interviewInfo = res.data.data;
+                this.value = this.interviewInfo.satisfactionLevel
+            });
+        },
+
+        // 加入面谈
+        initMeeting() {
+            console.log("加入面谈");
+            if (this.stompClient && this.interviewConnect) return;
+            this.initMeetingState();
+            // 连接到 STOMP 服务器
+            this.stompClient = Stomp.client('ws://new.gdy.g107.com/performance/ws') // 创建stomp对象
+            this.stompClient.connect(
+                {
+                    at: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOm51bGwsImlhdCI6MTc0MTgyNjk5OCwiZXhwIjotMSwibmJmIjoxNzQxODI2OTk4LCJqdGkiOiJDWk1PQnVoa0lFV2RndzNvIiwic3ViIjoxOTc3LCJwcnYiOiJjYTY0ODlkNTBmMjQwN2E2NzgzMGU4MDkwZDAxNDg4MzU2ODU5NjJiIiwicm9sZSI6ImVtcGxveWVlIn0.GfSKlOCdSUDSPSebuT7CswEhJtmOFWLTfzeOU1-QcVk'
+                },
+                (res) => { // 连接成功,此时可以在network中看到建立了ws连接
+                    console.log("连接成功,此时可以在network中看到建立了ws连接")
+                    this.interviewConnect = true;
+                    // 在这里进行订阅 主题由后端提供
+                    // 发起订阅
+                    this.subscribe()
+
+                    // 加入面谈
+                    this.joinInterview()
+                },
+                (err) => { // 连接失败
+                    console.log(err)
+                }
+            )
+            this.stompClient.heartbeat.outgoing = 10000 // 10秒发一次
+            this.stompClient.heartbeat.incoming = 10000 // 10秒发一次
+            this.stompClient.debug = function (str) {
+                console.log(str)
+            };
+        },
+
+        subscribe() {
+            if (!this.stompClient || !this.interviewConnect) return;
+            //订阅参加面谈
+            this.stompClient.subscribe(`/topic/interview/join/${this.interviewId}`, (message) => {
+                console.log('received interview join message', message);
+                let user = JSON.parse(message.body);
+                if (!user) return;
+                if (this.user_info.id !== user.id) {
+                    // 别人进入面谈
+                    this.$notify({
+                        title: `${user.name}`,
+                        message: '加入了会议',
+                        type: 'success'
+                    });
+
+                } else {
+                    // 自己进入面谈
+                    this.getChatList();
+                    this.getInterviewDetails();
+                    this.getInterviewUsers();
+                    this.hasJoinInterview = true;
+                }
+            });
+            // 订阅离开面谈
+            this.stompClient.subscribe(`/topic/interview/leave/${this.interviewId}`, (message) => {
+                console.log('received interview leave message', message);
+                let user = JSON.parse(message.body);
+
+                if (this.user_info.id !== user.id) {
+
+                    //别人离开面谈
+
+                    this.$notify({
+                        title: `${user.name}`,
+                        message: '离开了会议',
+                        type: 'error'
+                    });
+                } else {
+                    //自己离开面谈
+                    this.disconnect();
+                }
+            });
+            // 订阅主持人记录更新
+            this.stompClient.subscribe(`/topic/interview/comment/assignee/${this.interviewId}`, (message) => {
+                console.log('received comment assignee message', message);
+                if (!this.interviewInfo) return;
+                this.interviewInfo.assigneeComment = message.body;
+                this.$message.success("操作成功")
+            });
+
+            //订阅被考核人记录更新
+            this.stompClient.subscribe(`/topic/interview/comment/employee/${this.interviewId}`, (message) => {
+                console.log('received comment employee message', message);
+                if (!this.interviewInfo) return;
+                this.interviewInfo.employeeComment = message.body;
+                this.$message.success("操作成功")
+            });
+
+            //订阅聊天记录更新
+            this.stompClient.subscribe(`/topic/interview/chat/log/${this.interviewId}`, (message) => {
+                console.log('received chat log message', message);
+                let log = JSON.parse(message.body);
+                if (log) this.chatList.push(log);
+                this.$nextTick(() => {
+                    if (this.$refs.chatLogRef) this.$refs.chatLogRef.scrollIntoView(false);
+                })
+            });
+
+            // 订阅面谈满意度更新
+            this.stompClient.subscribe(`/topic/interview/satisfaction/${this.interviewId}`, (message) => {
+                console.log('订阅面谈满意度更新', message);
+                let tmp = JSON.parse(message.body);
+                if (tmp) this.interviewInfo.satisfactionLevel = tmp.satisfactionLevel || this.interviewInfo.satisfactionLevel;
+                this.$message.success("操作成功")
+            })
+        },
+
+        // 加入会议
+        joinInterview() {
+            if (!this.stompClient || !this.interviewConnect) return;
+
+            this.stompClient.send(`/app/interview/join/${this.interviewId}`);
+            // 获取当前会议的人员列表
+            this.getInterviewUsers();
+        },
+
+        // 获取当前会议的人员列表
+        getInterviewUsers() {
+            let url = `/performance/interview/users/${this.user_info.site_id}/${this.interviewId}`
+            this.$axiosUser('get', url).then(res => {
+                if(res.data.code) this.users = res.data.data
+            });
+        },
+
+        // 离开会议
+        leaveInterview() {
+            if (!this.stompClient || !this.interviewConnect) return;
+
+            this.stompClient.send(`/app/interview/leave/${this.interviewId}`)
+        },
+
+        // 发送聊天记录
+        sendChatLog(event) {
+            // 去掉换行符
+            if (event.keyCode == 13) {
+                if (!event.ctrlKey) {
+                    event.preventDefault();
+                    if (!this.stompClient || !this.hasJoinInterview || !this.canInterviewLog || !this.chatLog) return;
+                    this.stompClient.send(`/app/interview/chat/log/${this.interviewId}`,
+                        {},
+                        this.chatLog
+                    );
+                    this.chatLog = '';
+                    // alt + enter换行
+                } else {
+                    message += "\n";
+                }
+            }
+
+        },
+
+        sendChatLog2() {
+            if (!this.stompClient || !this.hasJoinInterview || !this.canInterviewLog || !this.chatLog) return;
+            this.stompClient.send(`/app/interview/chat/log/${this.interviewId}`,
+                {},
+                this.chatLog
+            );
+            this.chatLog = '';
+        },
+
+        // 主持人发布记录
+        sendAssigneeComment(event) {
+            // 去掉换行符
+            if (event.keyCode == 13) {
+                if (!event.ctrlKey) {
+                    event.preventDefault();
+                    if (!this.stompClient || !this.hasJoinInterview || !this.interviewInfo.host) return;
+                    this.stompClient.send(`/app/interview/comment/assignee/${this.interviewId}`,
+                        {},
+                        this.interviewInfo.assigneeComment
+                    )
+                // alt + enter换行
+                } else {
+                    message += "\n";
+                }
+            }
+        },
+
+        // 被考核人发布记录
+        sendEmployeeComment(event) {
+            // 去掉换行符
+            if (event.keyCode == 13) {
+                if (!event.ctrlKey) {
+                    event.preventDefault();
+                    if (this.canEmployeeComment) return;
+                    if (!this.stompClient || !this.hasJoinInterview || !this.interviewInfo.assessor) {
+                        //普通面谈详情模式
+                        this.apiEmployeeComment();
+                    } else {
+                        //面谈会议模式
+                        stompClient.value.send(`/app/interview/comment/employee/${interviewId.value}`,
+                            {},
+                            interview.value.employeeComment)
+                    }
+                    // alt + enter换行
+                } else {
+                    message += "\n";
+                }
+            }
+
+        },
+
+        // 普通面谈详情模式 - 提交被考核人记录
+        apiEmployeeComment() {
+            if (!this.interviewInfo || !this.canEmployeeComment) return;
+            let url = `/performance/interview/comment/employee/${this.user_info.siteId}`
+            let data = {
+                interviewId: this.interviewId,
+                comment: this.interviewInfo.employeeComment
+            }
+            this.$http.post(url, data).then(res => {
+                if (res.data.code == 1) this.interviewInfo = res.data.data;
+                if (!this.interviewInfo) this.leaveInterview();
+            })
+        },
+
+        // 提交满意度
+        sendSatisfaction() {
+            if (!this.canSatisfaction) return;
+            if (!this.stompClient || !this.hasJoinInterview || !this.interviewInfo.assessor) {
+                //普通面谈详情模式
+                this.apiSatisfaction();
+            } else {
+                //面谈会议模式
+                this.stompClient.send(`/app/interview/satisfaction/${this.interviewId}`,
+                    {},
+                    this.interviewInfo.satisfactionLevel
+                )
+            }
+        },
+
+        apiSatisfaction() {
+            if (!this.interviewInfo || !this.canSatisfaction) return;
+            let url = `/performance/interview/satisfaction/${this.user_info.site_id}`
+            let data = {
+                interviewId: this.interviewId,
+                level: this.value
+            }
+            this.$http.post(url, data).then(res => {
+                if (res.data.code == 1) this.interviewInfo = res.data.data;
+                if (!this.interviewInfo) this.leaveInterview();
+            })
+        },
+
+        disconnect(){
+            if (!this.stompClient || !this.interviewConnect) return;
+            this.leaveInterview();
+            this.stompClient.disconnect();
+            this.initMeetingState();
+
+        },
+
+        // 结束面谈
+        finishInterview() {
+            if (this.stompClient || this.interviewConnect) return;
+            let url = `/performance/interview/close/${this.user_info.site_id}/${this.interviewId}`
+            this.$http.post(url, {}).then(res => {
+                if (res.data.code == 1) this.interviewInfo = res.data.data;
+                if (!this.interviewInfo) this.leaveInterview();
+            })
+        },
+
+
+        // onAssigneeFocused(focused) {
+        //     if (!focused) this.sendAssigneeComment();
+        // },
+
+        // onEmployeeFocused(focused) {
+        //     if (!focused) this.sendEmployeeComment();
+        // }
+    },
+    beforeDestroy() {
+        if(this.stompClient) this.stompClient.disconnect()
+    }
+}
+</script>
+
+<style lang="scss">
+.all {
+    .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;
+    }
+}
+
+.oneLine {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+</style>
+
+<style scoped lang="scss">
+.all {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    box-sizing: border-box;
+    background: #f0f4fa;
+    display: flex;
+    flex-direction: column;
+    .header {
+        background: #fff;
+        padding: 10px;
+        margin-bottom: 10px;
+        box-sizing: border-box;
+        justify-content: space-between;
+    }
+    .examine-info {
+        background: #fff;
+        padding: 10px;
+        margin-bottom: 10px;
+        box-sizing: border-box;
+    }
+
+
+    .left-box {
+        width: 76%;
+        height: 100%;
+        margin-right: 10px;
+        display: flex;
+        flex-direction: column;
+
+        .emcee-record, .examinee-record {
+            width: 100%;
+            height: 450px;
+            margin-right: 10px;
+            background-color: #fff;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            .title {
+                width: 100%;
+                line-height: 40px;
+                font-size: 16px;
+                font-weight: 600;
+                padding: 0 10px;
+                box-sizing: border-box;
+                border-bottom: 1px solid #f7f7f7;
+            }
+            .content {
+                width: 95%;
+                height: 85%;
+                padding: 10px;
+                box-sizing: border-box;
+                background: #F7F8FA;
+                margin-top: 10px;
+            }
+        }
+
+        .examinee-record {
+            margin-right: 0;
+        }
+
+        .input-box {
+            width: 100%;
+            background-color: #fff;
+            padding: 10px;
+            box-sizing: border-box;
+            margin-right: 10px;
+        }
+    }
+
+
+
+    .chat-record {
+        width: 24%;
+        height: 100%;
+        background-color: #fff;
+        // overflow-y: auto;
+        .chat-record-title {
+            width: 100%;
+            line-height: 50px;
+            font-size: 16px;
+            font-weight: 600;
+            padding: 0 10px;
+            box-sizing: border-box;
+            border-bottom: 1px solid #f7f7f7;
+        }
+        .chat-item {
+            margin-bottom: 10px;
+            border-radius: 4px;
+            padding: 5px;
+            box-sizing: border-box;
+            margin-top: 10px;
+            .content {
+                padding: 5px;
+                box-sizing: border-box;
+                background: #F7F8FA;
+                margin: 5px 0 0 20px;
+            }
+        }
+    }
+
+    /* 设置滚动条的宽度和背景色 */
+    ::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>

+ 355 - 0
src/newPerformance/components/PersonalExamine.vue

@@ -0,0 +1,355 @@
+<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>
+                    <div>
+                        <PieChart1 key="PieChart1" v-if="tableData && tableData.length > 0" :table-data="tableData" />
+                        <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>
+                        <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>
+                    </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>
+            </div>
+        </div>
+        <ExamineDetails v-if="dialogVisible" v-model="dialogVisible" :table-data="tableData" :cycle-list="cycleOptions"
+            :cate-list="cateList" :examine-status="examineStatus"/>
+    </div>
+</template>
+
+<script>
+
+import { mapGetters } from 'vuex';
+import _ from "lodash"
+import EmployeeSelector from '@/components/EmployeeSelector';
+import SelectExamineComp from '@/newPerformance/components/MyPerformance/SelectExamine'; // 选择考核列表弹框
+import PieChart1 from '@/newPerformance/components/PersonalExamine/PieChart1'; // 考核状态分布 - 饼图
+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'; // 考核详情
+
+export default {
+    components: {
+        EmployeeSelector,
+        SelectExamineComp,
+        PieChart1,
+        PieChart2,
+        PieChart3,
+        PieChart4,
+        ExamineDetails
+    },
+
+    data() {
+        return {
+            selected_employee_ids: '',
+            selected_dept_ids: [],
+            deptList: [], // 部门列表 - 树形结构
+            dept_list: [], // 部门列表
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            cateId: '',
+            cateList: [],
+            loading: false,
+            total: 0,
+            tableData: [],
+            tableIndex: -1,
+            // 添加指标
+            selected: { employee: [], dept: [] },//已经选择人员
+            choosePerson: false,
+            // 周期类型 0-未定义 1-年度 2-半年度 3-季度 4-月度
+            cycleType: '-1',
+            cycleOptions: [
+                { label: "全部", value: '-1' },
+                { label: "未定义", value: '0' },
+                { label: "年度", value: '1' },
+                { label: "半年度", value: '2' },
+                { label: "季度", value: '3' },
+                { label: "月度", value: '4' },
+            ],
+            params: {
+                cycleType: '',
+                cateId: '',
+            },
+            multipleSelection: [],
+            alertTilte: "默认显示每个人的最新考核数据,可选择人员添加考核数据,也可以移除某个人员的考核数据,或点击人员姓名替换某条考核数据",
+            dialogVisible: false,
+            gradeLevels: [],
+            examineStatus: [
+                { label: "考核中", value: "0" },
+                { label: "已结束", value: "1" },
+                { label: "面谈", value: "2" }
+            ]
+        }
+    },
+    
+    
+    created() {
+        this.getAllSet();
+        this.getList();
+        this.get_dept_list();
+        this.get_cate_list();
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+    },
+   
+    methods: {
+
+        // 表格数据 - 最新考核数据
+        getList(employeeId = '') {
+            let that = this
+            that.loading = true;
+            let url
+            if (employeeId) url = `/performance/statistics/reviews/${that.user_info.site_id}/${employeeId}`
+            else url = `/performance/statistics/reviews/${that.user_info.site_id}/${that.user_info.id}`
+            let requestdata;
+            if (that.cycleType == '-1') requestdata = { ...that.params }
+            else requestdata = { ...that.params, cycleType: that.cycleType }
+            that.$axiosUser('get', url, requestdata).then(res => {
+                let { data: { data: { list, total }, code } } = res;
+                if (code == 1) {
+                    that.tableData = [];
+                    that.tableData = list;
+                }
+            });
+        },
+
+        // 处理部门树状结构数据
+        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')
+            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
+            }
+        },
+
+    }
+}
+</script>
+
+<style scoped="scoped" lang="scss">
+.boxMinHeight {
+
+    .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;
+        }
+    }
+
+    .circular_item {
+        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;
+        }
+        
+
+        .grey_font {
+            font-weight: normal;
+            font-size: 12px;
+            color: grey;
+        }
+
+    }
+
+    .main {
+        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;
+            }
+        }
+
+        .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;
+            }
+        }
+    }
+}
+</style>

+ 297 - 0
src/newPerformance/components/PersonalExamine/ExamineDetails copy.vue

@@ -0,0 +1,297 @@
+<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 @filter-change="handleFilterChange">
+                    <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">
+                        <template slot-scope="scope">
+                            <div>
+                                {{ scope.row.startTime | formatDate }} 至 {{ scope.row.endTime | formatDate }}
+                            </div>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column prop="cycleType" label="周期类型" min-width="80" align="center"
+                        :filters="cycle_type_list" :filter-method="filterMethods">
+                        <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">
+                        <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}}
+                                    </el-tag>
+                                </template>
+                            </div>
+
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="levelName" label="得分" min-width="100" align="center" :filters="gradeLevels"
+                        :filter-method="filterMethods">
+                        <template slot-scope="scope">
+                            <div>
+                                {{ scope.row.score || '--' }}
+                                {{ scope.row.levelName ? "(" + scope.row.levelName + ")" : '--' }}
+                            </div>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="status" label="考核状态" min-width="100" align="center"
+                        :filters="examine_status_list" :filter-method="filterMethods">
+                        <template slot-scope="scope">
+                            <div v-if="scope.row.status == 0" class="orange-color">
+                                {{ scope.row.status | formatStatus }}
+                            </div>
+                            <div v-if="scope.row.status == 1" class="green-color">
+                                {{ scope.row.status | formatStatus }}
+                            </div>
+                            <div v-if="scope.row.status == 2" class="blue-color">
+                                {{ scope.row.status | formatStatus }}
+                            </div>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </div>
+            <div class="flex-box-ce" style="justify-content: center; margin-top: 10px;">
+                <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>
+    </el-dialog>
+
+</template>
+
+<script>
+import moment from 'moment';
+import TableHeaderRender from "./TableHeaderRender.vue"
+export default {
+    model: {
+        prop: 'dialogVisible',
+        event: 'close-dialog'
+    },
+    props: {
+        dialogVisible: {
+            type: Boolean,
+            default: false
+        },
+        tableData: {
+            type: Array,
+            default: () => []
+        },
+        cycleList: {
+            type: Array,
+            default: () => []
+        },
+        cateList: {
+            type: Array,
+            default: () => []
+        },
+        examineStatus: {
+            type: Array,
+            default: () => []
+        },
+    },
+
+
+    data() {
+        return {
+            loading: false,
+            params: {
+                page: 1,
+                pageSize: 15
+            }
+        }
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        },
+        formatCycleType(val) {
+            if (val == 0) return '未定义'
+            if (val == 1) return '年度'
+            if (val == 2) return '半年'
+            if (val == 3) return '季度'
+            if (val == 4) return '月度'
+            else return '--'
+        },
+        formatStatus(val) {
+            if (val == 0) return '考核中'
+            if (val == 1) return '已结束'
+            if (val == 2) return '面谈'
+            else return '--'
+        }
+    },
+    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() {
+    },
+
+    methods: {
+
+        dialogBeforeClose() {
+            this.$emit('close-dialog', false)
+        },
+        
+        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]; // 只取第一个筛选值
+            // }
+        },
+    }
+}
+</script>
+
+
+<style scoped lang="scss">
+
+
+.perform-list {
+    width: 100%;
+    padding: 0 20px;
+    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>

+ 341 - 0
src/newPerformance/components/PersonalExamine/ExamineDetails.vue

@@ -0,0 +1,341 @@
+<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>
+                    <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">
+                        <template slot-scope="scope">
+                            <div>
+                                {{ scope.row.startTime | formatDate }} 至 {{ scope.row.endTime | formatDate }}
+                            </div>
+                        </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-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">
+                        <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}}
+                                    </el-tag>
+                                </template>
+                            </div>
+
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="levelName" label="得分" min-width="100" align="center" :filters="gradeLevels"
+                        :filter-method="filterMethods">
+                        <template slot-scope="scope">
+                            <div>
+                                {{ scope.row.score || '--' }}
+                                {{ scope.row.levelName ? "(" + scope.row.levelName + ")" : '--' }}
+                            </div>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="status" label="考核状态" min-width="100" align="center"
+                        :filters="examine_status_list" :filter-method="filterMethods">
+                        <template slot-scope="scope">
+                            <div v-if="scope.row.status == 0" class="orange-color">
+                                {{ scope.row.status | formatStatus }}
+                            </div>
+                            <div v-if="scope.row.status == 1" class="green-color">
+                                {{ scope.row.status | formatStatus }}
+                            </div>
+                            <div v-if="scope.row.status == 2" class="blue-color">
+                                {{ scope.row.status | formatStatus }}
+                            </div>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </div>
+            <div class="flex-box-ce" style="justify-content: center; margin-top: 10px;">
+                <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>
+    </el-dialog>
+
+</template>
+
+<script>
+import moment from 'moment';
+import TableHeaderRender from "./TableHeaderRender.vue"
+export default {
+    model: {
+        prop: 'dialogVisible',
+        event: 'close-dialog'
+    },
+    props: {
+        dialogVisible: {
+            type: Boolean,
+            default: false
+        },
+        tableData: {
+            type: Array,
+            default: () => []
+        },
+        cycleList: {
+            type: Array,
+            default: () => []
+        },
+        cateList: {
+            type: Array,
+            default: () => []
+        },
+        examineStatus: {
+            type: Array,
+            default: () => []
+        },
+    },
+
+
+    data() {
+        return {
+            loading: false,
+            visible: false,
+            value: '',
+            iconColor: false,
+            params: {
+                page: 1,
+                pageSize: 15
+            }
+        }
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        },
+        formatCycleType(val) {
+            if (val == 0) return '未定义'
+            if (val == 1) return '年度'
+            if (val == 2) return '半年'
+            if (val == 3) return '季度'
+            if (val == 4) return '月度'
+            else return '--'
+        },
+        formatStatus(val) {
+            if (val == 0) return '考核中'
+            if (val == 1) return '已结束'
+            if (val == 2) return '面谈'
+            else return '--'
+        }
+    },
+    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() {
+    },
+
+    methods: {
+
+        dialogBeforeClose() {
+            this.$emit('close-dialog', false)
+        },
+        
+        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]; // 只取第一个筛选值
+            // }
+        },
+
+        showPopover() {
+            this.$nextTick(() => {
+                this.$refs.sInput.focus();
+            });
+        },
+        closeOver() {
+            this.visible = false;
+        },
+        popClick(e) {
+            this.visible = !this.visible;
+        },
+        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;
+            }
+        },
+        
+    }
+}
+</script>
+
+
+<style scoped lang="scss">
+
+
+.perform-list {
+    width: 100%;
+    padding: 0 20px;
+    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>

+ 108 - 0
src/newPerformance/components/PersonalExamine/PieChart1.vue

@@ -0,0 +1,108 @@
+<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: "300px" },
+            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: {
+        // 考核表状态
+        examineStatus() {
+            let examineStatus = [
+                { name: "进行中", value: 0 },
+                { name: "已结束", value: 0 },
+                { name: "面谈中", value: 0 },
+            ]
+            examineStatus[0].value = this.tableData.filter(item => item.status === 0).length;
+            examineStatus[1].value = this.tableData.filter(item => item.status === 1).length;
+            examineStatus[2].value = this.tableData.filter(item => item.status === 2).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 = {
+                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>

+ 109 - 0
src/newPerformance/components/PersonalExamine/PieChart2.vue

@@ -0,0 +1,109 @@
+<template>
+    <!-- 按等级分布 -->
+    <div ref="chart2" :style="myChartStyle"></div>
+</template>
+
+<script>
+
+import echarts from 'echarts';
+
+export default {
+    props: {
+        tableData: {
+            type: Array,
+            default: () => []
+        },
+        gradeLevels: {
+            type: Array,
+            default: () => []
+        },
+    },
+    data() {
+        return {
+            myChart: null,
+            myChartStyle: { width: "100%", height: "300px" },
+            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' },
+            ],
+        }
+    },
+
+    watch: {  //此处为关键,监听tableData值的变化,进行echarts渲染
+        levelStatus(v) {
+            this.initEcharts2(); //值发生改变则渲染一次echarts
+        }
+    },
+    
+    computed: {
+        // 考核等级
+        levelStatus() {
+            let levelStatus = []
+            levelStatus = this.gradeLevels.map(item => ({
+                name: item.name, value: this.tableData.filter(data => data.levelName === item.name).length,
+            }))
+            levelStatus.unshift({ name: "未评分", value: this.tableData.filter(data => !data.levelName).length })
+            
+            return levelStatus
+        },
+    },
+    mounted() {
+        this.initEcharts2();
+    },
+    beforeDestroy() {
+        if (this.myChart) {
+            this.myChart.clear();//清空当前实例,会移除实例中所有的组件和图表
+            this.myChart.dispose();//销毁实例,实例销毁后无法再被使用
+        }
+    },
+    methods: {
+        initEcharts2() {
+            const colorList = this.colorList;
+            const option = {
+                tooltip: {//悬浮提示组件
+                    trigger: 'item',
+                },
+                legend: {
+                    show: true,
+                    x: 'center',
+                    y: 'bottom',
+                    icon: "circle",
+                    itemWidth: 10,  // 设置宽度
+                    itemHeight: 10, // 设置高度
+                    itemGap: 30
+                },
+
+                series: [
+                    {
+                        type: "pie",
+                        radius: ['40%', '65%'],//小的是内径,大的是外径
+                        data: this.levelStatus,
+                        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.chart2);
+            this.myChart.setOption(option);
+            window.addEventListener("resize", () => {
+                this.myChart.resize();
+            });
+        },
+    }
+}
+</script>

+ 111 - 0
src/newPerformance/components/PersonalExamine/PieChart3.vue

@@ -0,0 +1,111 @@
+<template>
+    <!-- 按考核周期分布 -->
+    <div ref="chart3" :style="myChartStyle"></div>
+</template>
+
+<script>
+
+import echarts from 'echarts';
+import _ from "lodash"
+export default {
+    props: {
+        tableData: {
+            type: Array,
+            default: () => []
+        },
+        cycleOptions: {
+            type: Array,
+            default: () => []
+        },
+    },
+    data() {
+        return {
+            myChart: null,
+            myChartStyle: { width: "100%", height: "300px" },
+            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' },
+                { c1: '#ede737', c2: '#FF9600' },
+            ],
+        }
+    },
+
+    watch: {  //此处为关键,监听tableData值的变化,进行echarts渲染
+        tableData(v) {
+            this.initEcharts3(); //值发生改变则渲染一次echarts
+        }
+    },
+    
+    computed: {
+        // 考核周期分布
+        cycleTypeList() {
+            let cycleTypeList = _.cloneDeep(this.cycleOptions)
+            cycleTypeList = cycleTypeList.filter(item => item.label !== "全部");
+            cycleTypeList = cycleTypeList.map(item => ({
+                    name: item.label,
+                    value: this.tableData.filter(data => data.cycleType == item.value).length,
+                })
+            )
+            return cycleTypeList
+        }
+    },
+    mounted() {
+        this.initEcharts3();
+    },
+    beforeDestroy() {
+        if (this.myChart) {
+            this.myChart.clear();//清空当前实例,会移除实例中所有的组件和图表
+            this.myChart.dispose();//销毁实例,实例销毁后无法再被使用
+        }
+    },
+    methods: {
+        initEcharts3() {
+            const colorList = this.colorList;
+            const option = {
+                tooltip: {//悬浮提示组件
+                    trigger: 'item',
+                },
+                legend: {
+                    show: true,
+                    x: 'center',
+                    y: 'bottom',
+                    icon: "circle",
+                    itemWidth: 10,  // 设置宽度
+                    itemHeight: 10, // 设置高度
+                    itemGap: 30
+                },
+
+                series: [
+                    {
+                        type: "pie",
+                        radius: ['40%', '65%'],//小的是内径,大的是外径
+                        data: this.cycleTypeList,
+                        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.chart3);
+            this.myChart.setOption(option);
+            window.addEventListener("resize", () => {
+                this.myChart.resize();
+            });
+        },
+    }
+}
+</script>

+ 111 - 0
src/newPerformance/components/PersonalExamine/PieChart4.vue

@@ -0,0 +1,111 @@
+<template>
+    <!-- 按考核分类分布 -->
+    <div ref="chart4" :style="myChartStyle"></div>
+</template>
+
+<script>
+
+import echarts from 'echarts';
+
+export default {
+    props: {
+        tableData: {
+            type: Array,
+            default: () => []
+        },
+        cateList: {
+            type: Array,
+            default: () => []
+        },
+    },
+    data() {
+        return {
+            myChart: null,
+            myChartStyle: { width: "100%", height: "300px" },
+            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' },
+                { c1: '#ede737', c2: '#FF9600' }, { c1: '#ede737', c2: '#FF9600' }, { c1: '#ede737', c2: '#FF9600' },
+                { c1: '#ede737', c2: '#FF9600' }, { c1: '#ede737', c2: '#FF9600' }, { c1: '#ede737', c2: '#FF9600' },
+                { c1: '#ede737', c2: '#FF9600' }, { c1: '#ede737', c2: '#FF9600' }, { c1: '#ede737', c2: '#FF9600' },
+            ],
+        }
+    },
+    
+    computed: {
+        // 考核周期分布
+        chartCateList() {
+            let cateList = []
+            cateList = this.cateList.map(item => ({
+                name: item.name, value: this.tableData.filter(data => data.cateIds.length > 0 && data.cateIds.includes(item.cateId)).length,
+            }))
+            return cateList
+        }
+    },
+    mounted() {
+        this.initEcharts4();
+    },
+    
+    watch: {
+        chartCateList(v) {
+            this.initEcharts4()
+        }
+    },
+
+    beforeDestroy() {
+        if (this.myChart) {
+            this.myChart.clear();//清空当前实例,会移除实例中所有的组件和图表
+            this.myChart.dispose();//销毁实例,实例销毁后无法再被使用
+        }
+    },
+    methods: {
+        initEcharts4() {
+            const colorList = this.colorList;
+            const option = {
+                tooltip: {//悬浮提示组件
+                    trigger: 'item',
+                },
+                legend: {
+                    show: true,
+                    x: 'center',
+                    y: 'bottom',
+                    icon: "circle",
+                    itemWidth: 10,  // 设置宽度
+                    itemHeight: 10, // 设置高度
+                    itemGap: 30
+                },
+
+                series: [
+                    {
+                        type: "pie",
+                        radius: ['40%', '65%'],//小的是内径,大的是外径
+                        data: this.chartCateList,
+                        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.chart4);
+            this.myChart.setOption(option);
+            window.addEventListener("resize", () => {
+                this.myChart.resize();
+            });
+        },
+    }
+}
+</script>

+ 138 - 0
src/newPerformance/components/PersonalExamine/TableHeaderRender.vue

@@ -0,0 +1,138 @@
+<template>
+    <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>
+            <!-- <el-select >
+                    <el-option v-for="item in options" :label="item.label" :value="item.value"></el-option>
+                </el-select> -->
+            <el-checkbox-group v-model="selectedOptions">
+                <el-checkbox v-for="item in filteredOptions" :key="item.value" :label="item.value">
+                    {{ item.label }}
+                </el-checkbox>
+            </el-checkbox-group>
+        </div>
+        <div class="el-table-filter__bottom">
+            <button @click="confirm">筛选</button>
+            <button @click="resetData">重置</button>
+        </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>
+
+<script>
+export default {
+    name: "tableHeaderRender",
+    data() {
+        return {
+            value: "",
+            visible: false,
+            iconColor: false,
+        };
+    },
+    props: {
+        tableColumn: {
+            type: Object,
+            default: () => { },
+        },
+        columnProp: {
+            type: String,
+            default: "",
+        },
+        defaultValue: {
+            type: String,
+            default: "",
+        },
+        inputFilteredMap: {
+            type: Object,
+            default: () => { },
+        },
+        filteredOptions: {
+            type: Array,
+            default: () => [],
+        }
+    },
+    created() {
+        this.value = this.defaultValue;
+        this.iconColor = this.value.trim() ? true : false;
+    },
+    methods: {
+        showPopover() {
+            this.$nextTick(() => {
+                this.$refs.sInput.focus();
+            });
+        },
+        resetData() {
+            this.value = "";
+            this.visible = false;
+            this.iconColor = false;
+            const self = this;
+            if (this.inputFilteredMap[self.columnProp]) {
+                delete this.inputFilteredMap[self.columnProp];
+            }
+            self.$emit("resetChangeMethod", this.tableColumn, self.columnProp);
+        },
+        closeOver() {
+            this.visible = false;
+        },
+        popClick(e) {
+            this.visible = !this.visible;
+        },
+        confirm() {
+            this.visible = false;
+            if (this.value.trim()) {
+                this.iconColor = true;
+                this.inputFilteredMap[this.columnProp] = this.value;
+                this.$emit(
+                    "filterInputMethod",
+                    this.tableColumn,
+                    this.inputFilteredMap
+                );
+            } else {
+                this.resetData();
+            }
+        },
+    },
+    directives: {
+        clickOutside: {
+            bind(el, binding, vnode) {
+                function clickHandler(e) {
+                    if (el.contains(e.target)) {
+                        return false;
+                    }
+                    if (binding.expression) {
+                        binding.value(e);
+                    }
+                }
+                el.__vueClickOutside__ = clickHandler;
+                document.addEventListener("click", clickHandler);
+            },
+            update() { },
+            unbind(el, binding) {
+                document.removeEventListener("click", el.__vueClickOutside__);
+                delete el.__vueClickOutside__;
+            },
+        },
+    },
+};
+</script>
+
+<style scoped>
+.filter-icon {
+    font-size: 14px;
+    color: #909399;
+    cursor: pointer;
+    font-weight: 400;
+}
+
+.table-header-popover {
+    padding: 0;
+}
+
+.table-header-popover .table-header-popover-template {
+    margin: 10px;
+}
+</style>

+ 203 - 0
src/newPerformance/components/PublicComp/ShowData.vue

@@ -0,0 +1,203 @@
+<template>
+    <div class="show-data-box" v-if="selectNodes">
+        <div v-if="showData.enable">
+            <template v-if="showData.children && showData.children.length > 0">
+
+                <div class="item" v-for="(item, index) in showData.children" :key="item.id">
+                    <!-- 指定人员 -->
+                    <el-button v-if="item.assigneeType == 'user'" style="margin-bottom: 5px;" type="primary" plain round
+                        size="mini" @click="btnClick(index)">
+                        <el-popover placement="right" trigger="hover" :content="'[ ' + filterEmployeeNames(item.assigneeIds) + ' ]'">
+                            <div slot="reference">指定人员</div>
+                        </el-popover>
+                    </el-button>
+
+                    <!-- 被考核人 -->
+                    <el-button v-if="item.assigneeType == 'self'" style="margin-bottom: 5px;" type="primary" plain round
+                        size="mini" @click="btnClick(index)">被考核人</el-button>
+
+                    <!-- 岗位 -->
+                    <el-button v-if="item.assigneeType == 'post'" style="margin-bottom: 5px;" type="primary" plain round
+                        size="mini" @click="btnClick(index)">
+                        <el-popover placement="right" trigger="hover" :content="'[ ' + filterPostNames(item.assigneeIds) + ' ]'">
+                            <div slot="reference">岗位</div>
+                        </el-popover>
+                    </el-button>
+
+                    <!-- 部门管理者 -->
+                    <el-button v-if="item.assigneeType == 'deptLeader'" style="margin-bottom: 5px;" type="primary" plain
+                        round size="mini" @click="btnClick(index)">
+                        <el-popover placement="right" trigger="hover" :content="'[ ' + filterDeptNames(item.assigneeIds) + ' ]'">
+                            <div slot="reference">部门管理者</div>
+                        </el-popover>
+                    </el-button>
+
+                    <!-- 管理员 -->
+                    <el-button v-if="item.assigneeType == 'leader'" style="margin-bottom: 5px;" type="primary" plain
+                        round size="mini" @click="btnClick(index)">
+                        <div v-if="item.leaderLevel == 1">直接管理员</div>
+                        <div v-if="item.leaderLevel == 2">二级管理员</div>
+                        <div v-if="item.leaderLevel == 3">三级管理员</div>
+                        <div v-if="item.leaderLevel == 4">四级管理员</div>
+                        <div v-if="item.leaderLevel == 5">五级管理员</div>
+                        <div v-if="item.leaderLevel == 6">六级管理员</div>
+                    </el-button>
+                </div>
+            </template>
+            <template v-else>
+                <div class="item">
+                    <!-- 指定人员 -->
+                    <el-button v-if="showData.assigneeType == 'user'" style="margin-bottom: 5px;" type="primary" plain
+                        round size="mini" @click="btnClick('-1')">
+                        <el-popover placement="right" trigger="hover" :content="'[ ' + filterEmployeeNames(showData.assigneeIds) + ' ]'">
+                            <div slot="reference">指定人员</div>
+                        </el-popover>
+                    </el-button>
+
+                    <!-- 被考核人 -->
+                    <el-button v-if="showData.assigneeType == 'self'" style="margin-bottom: 5px;" type="primary" plain
+                        round size="mini" @click="btnClick('-1')">被考核人</el-button>
+
+                    <!-- 岗位 -->
+                    <el-button v-if="showData.assigneeType == 'post'" style="margin-bottom: 5px;" type="primary" plain
+                        round size="mini" @click="btnClick('-1')">
+                        <el-popover placement="right" trigger="hover" :content="'[ ' + filterPostNames(showData.assigneeIds) + ' ]'">
+                            <div slot="reference">岗位</div>
+                        </el-popover>
+                    </el-button>
+
+                    <!-- 部门管理者 -->
+                    <el-button v-if="showData.assigneeType == 'deptLeader'" style="margin-bottom: 5px;" type="primary"
+                        plain round size="mini" @click="btnClick('-1')">
+                        <el-popover placement="right" trigger="hover" :content="'[ ' + filterdeptNames(showData.assigneeIds) + ' ]'">
+                            <div slot="reference">部门管理者</div>
+                        </el-popover>
+                    </el-button>
+
+                    <el-button v-if="showData.assigneeType == 'leader'" style="margin-bottom: 5px;" type="primary" plain
+                        round size="mini" @click="btnClick('-1')">
+                        <div v-if="showData.leaderLevel == 1">直接管理员</div>
+                        <div v-if="showData.leaderLevel == 2">二级管理员</div>
+                        <div v-if="showData.leaderLevel == 3">三级管理员</div>
+                        <div v-if="showData.leaderLevel == 4">四级管理员</div>
+                        <div v-if="showData.leaderLevel == 5">五级管理员</div>
+                        <div v-if="showData.leaderLevel == 6">六级管理员</div>
+                    </el-button>
+                </div>
+            </template>
+        </div>
+        <!-- <el-switch v-else></el-switch> -->
+    </div>
+</template>
+
+<script>
+export default {
+    props: {
+        selectNodes: {
+            type: Object,
+            default: () => {}
+        },
+        showData: {
+            type: Object,
+            default: () => {}
+        },
+        readonly: {
+            type: Boolean,
+            default: false
+        }
+    },
+    data() {
+        return {
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            postList: JSON.parse(localStorage.getItem("postList")), // 岗位列表
+            deptList: JSON.parse(localStorage.getItem("deptList")), // 部门列表 - 树形结构
+            dept_list: JSON.parse(localStorage.getItem("dept_list")), // 部门列表
+        }
+    },
+    mounted() {
+    },
+    
+    methods: {
+        btnClick(index) {
+            if (this.readonly) return
+            this.$emit('btnClick', index, this.showData, this.selectNodes)
+        },
+
+        filterEmployeeNames(assigneeIds) {
+            let employeeNames = []
+            if (assigneeIds && assigneeIds.length > 0) {
+                Object.keys(this.employeeMap).forEach(key => {
+                    assigneeIds.forEach(assigneeId => {
+                        if (key == assigneeId) employeeNames.push(this.employeeMap[key].name)
+                    })
+                })
+            }
+            return employeeNames
+        },
+
+        filterPostNames(assigneeIds) {
+            let select_post_name = []
+            if (assigneeIds && assigneeIds.length > 0) {
+                if (this.postList && this.postList.length > 0) {
+                    this.postList.forEach(post => {
+                        assigneeIds.forEach(assignee => {
+                            if (post.id == assignee) select_post_name.push(post.name)
+                        })
+                    })
+                }
+                
+            }
+            return select_post_name
+        },
+
+        filterDeptNames(assigneeIds) {
+            let select_dept_name = []
+            if (assigneeIds && assigneeIds.length > 0) {
+                if (this.dept_list && this.dept_list.length > 0) {
+                    this.dept_list.forEach(dept => {
+                        assigneeIds.forEach(assignee => {
+                            if (dept.id == assignee) {
+                                select_dept_name.push(dept.name)
+                            }
+                        })
+                    })
+                }
+            }
+            return select_dept_name
+        },
+    }
+}
+</script>
+
+<style lang="scss">
+.show-data-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 lang="scss">
+    .show-data-box {
+        // width: 100%;
+        // height: 100%;
+        .item {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+        }
+    }
+</style>

+ 208 - 0
src/newPerformance/components/PublicComp/ShowHanderDialog.vue

@@ -0,0 +1,208 @@
+<template>
+    <div>
+        <el-dialog :title="dialogTitle" center :visible.sync="dialogVisible" width="600px"
+            :before-close="handleCloseDialog" :append-to-body="true">
+            <div class="show-data-box">
+
+                <div v-if="showData.enable">
+                    <div class="item-list">
+                        <!-- 有子节点 -->
+                        <template v-if="showData.children && showData.children.length > 0">
+                            <div class="item" v-for="item in showData.children" :key="item.id">
+                                <div v-if="item.tasks && item.tasks.length > 0" class="title-box">
+                                    {{ showData.assigneeType | filterType }}
+                                </div>
+                                <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 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>
+
+                                </template>
+
+                            </div>
+                        </template>
+
+                        <!-- 无子节点 -->
+                        <template v-else>
+                            <div class="item">
+                                <template v-if="showData.tasks && showData.tasks.length > 0">
+                                    <div class="title-box" v-if="showData.tasks && showData.tasks.length > 0">
+                                        {{ showData.assigneeType | filterType }}
+                                    </div>
+                                    <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>
+
+                                                <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>
+
+                                    </template>
+
+                                </template>
+
+                            </div>
+                        </template>
+                    </div>
+
+                </div>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+export default {
+    model: {
+        prop: 'dialogVisible',
+        event: 'close-dialog'
+    },
+    props: {
+        dialogVisible: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        status: {
+            type: String,
+            default: 'completed'
+        },
+        showData: {
+            type: Object,
+            default: () => { }
+        }
+    },
+    computed: {
+
+    },
+    data() {
+        return {
+
+        }
+    },
+    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 = "部门"
+        }
+    },
+    watch: {
+        'dialogVisible'(v) {
+            if (v) this.initData()
+        },
+
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+        },
+        handleCloseDialog() {
+            this.$emit('close-dialog', false)
+        }
+    }
+}
+
+</script>
+
+<style scoped lang="scss">
+.green-color {
+    color: #67c23a;
+}
+
+
+
+.red-color {
+    color: #f56c6c;
+}
+
+.orange-color {
+    color: #e6a23c;
+}
+
+.gray-color {
+    color: rgb(144, 147, 153);
+}
+
+.show-data-box {
+
+    .item-list {
+        .item {
+            display: flex;
+            align-items: center;
+            width: 100%;
+            background: #f7f7f7;
+            box-sizing: border-box;
+            .title-box {
+                width: 120px;
+                text-align: center;
+            }
+            .item-box {
+                background: #fff;
+                padding: 10px;
+                box-sizing: border-box;
+                flex: 1;
+                display: flex;
+                flex-direction: column;
+                border: 1px solid #f7f7f7;
+                .value-box {
+                    display: flex;
+                    align-items: center;
+                    justify-content: space-between;
+                    width: 100%;
+                }
+            }
+            
+            
+        }
+    }
+}
+</style>

+ 555 - 0
src/newPerformance/components/PublicComp/ShowHanderDialog2.vue

@@ -0,0 +1,555 @@
+<template>
+    <el-dialog :title="dialogTitle" center :visible.sync="dialogVisible" 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>
+                                    </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>
+                                    </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>
+                                    </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 === 'score'">
+                        <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">
+                                            <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 }}
+                                                </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 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">
+                                            <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 }}
+                                                </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 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: 'dialogVisible',
+        event: 'close-dialog'
+    },
+    props: {
+        dialogVisible: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        status: {
+            type: String,
+            default: 'completed'
+        },
+        showData: {
+            type: [],
+            default: () => []
+        }
+    },
+    computed: {
+
+    },
+    data() {
+        return {
+
+        }
+    },
+    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 = "部门"
+        }
+    },
+    watch: {
+        'dialogVisible'(v) {
+            if (v) this.initData()
+        },
+
+    },
+    computed: {
+        // targetComfirms() {
+        //     return this.showData.filters(item => item.type === "targetComfirms")
+        // }
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+        },
+        handleCloseDialog() {
+            this.$emit('close-dialog', false)
+        }
+    }
+}
+
+</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;
+}
+
+
+</style>

+ 180 - 0
src/newPerformance/components/PublicComp/ShowHandler.vue

@@ -0,0 +1,180 @@
+<template>
+    <div class="show-data-box">
+
+        <div v-if="showData.enable">
+            <div class="data-box" style="display: flex; align-items: center; justify-content: center;">
+                <div v-if="taskStatusList.completedTaskList.length > 0" class="data-complated green-color"
+                    style="margin-right: 5px;" @click="openDialog('completed')">
+                    <el-tooltip :content="'已完成:' + taskStatusList.completedTaskList.length + '人'" effect="dark"
+                        placement="right">
+                        <div>
+                            <i class="el-icon-check"></i> {{ taskStatusList.completedTaskList.length }}人
+                        </div>
+                    </el-tooltip>
+                </div>
+                <div v-if="taskStatusList.createdTaskList.length > 0" class="orange-color"
+                    @click="openDialog('created')">
+                    <el-tooltip :content="'进行中:' + taskStatusList.createdTaskList.length + '人'" effect="dark"
+                        placement="right">
+                        <div>
+                            <i class="el-icon-reading"></i> {{ taskStatusList.createdTaskList.length }}人
+                        </div>
+                    </el-tooltip>
+                </div>
+                <div v-if="!taskStatusList.completedTaskList.length > 0 && !taskStatusList.createdTaskList.length > 0"
+                    class="gray-color">
+                    未开始
+                </div>
+            </div>
+        </div>
+        <el-tooltip v-else content="未开启" effect="dark" placement="right">
+            <el-switch v-model="showData.enable" disabled></el-switch>
+        </el-tooltip>
+        <ShowHanderDialog v-if="dialogVisible" v-model="dialogVisible" :dialogTitle="dialogTitle" :showData="showData" :status="status" />
+    </div>
+</template>
+
+<script>
+import ShowHanderDialog from "./ShowHanderDialog.vue"
+export default {
+    components: {
+        ShowHanderDialog
+    },
+    props: {
+        showData: {
+            type: Object,
+            default: () => { }
+        }
+    },
+    watch: {
+        showData(v) {
+        }
+    },
+    data() {
+        return {
+            dialogVisible: false,
+            dialogTitle: "已完成",
+            status: "completed"
+        }
+    },
+    computed: {
+        taskStatusList() {
+            let completedTaskList = [], createdTaskList = [];
+            if (this.showData.children && this.showData.children.length > 0) {
+                this.showData.children.forEach(child => {
+                    if (child.tasks && child.tasks.length > 0) {
+                        child.tasks.forEach(task => {
+                            if (task.state === 'completed') {
+                                completedTaskList.push(task)
+                            } else {
+                                createdTaskList.push(task)
+                            }
+                        })
+                    }
+                })
+            }
+            else {
+                let { tasks } = this.showData
+                if (tasks && tasks.length > 0) {
+                    tasks.forEach(task => {
+                        if (task.state === 'completed') {
+                            completedTaskList.push(task)
+                        } else {
+                            createdTaskList.push(task)
+                        }
+                    })
+                }
+            }
+            return {
+                completedTaskList,
+                createdTaskList
+            }
+        },
+        handleShowData() {
+            if (this.showData.children && this.showData.children.length > 0) {
+                this.showData.children.forEach(children => {
+                    // 指定人员
+                    if (children.assigneeType === 'user') {
+                        let tasks = children.tasks;
+                        let assigneeIds = children.assigneeIds;
+                        let diff = []; // 找出未处理的指定人
+                        if (assigneeIds && assigneeIds.length > 0) {
+                            if (tasks && tasks.length > 0) {
+                                assigneeIds.forEach(assigneeId => {
+                                    tasks.forEach(task => {
+                                        if (assigneeId !== task.assignee) {
+                                            diff.push(assigneeId)
+                                        }
+                                    })
+                                })
+                            }
+                            return diff
+                        } else {
+                            return diff
+                        }
+
+                    }
+                })
+            }
+            else {
+
+            }
+        }
+    },
+    filters: {
+    },
+    methods: {
+        openDialog(status) {
+            if (status === 'created') {
+                this.status = 'created'
+                this.dialogTitle = '进行中'
+            } else {
+                this.status = 'completed'
+                this.dialogTitle = '已完成'
+            }
+            this.dialogVisible = true
+        }
+    }
+}
+</script>
+
+<style scoped lang="scss">
+.green-color {
+    color: #67c23a;
+}
+
+.orange-color {
+    color: #e6a23c;
+}
+
+.gray-color {
+    color: #ccc;
+}
+
+.show-data-box {
+
+    // width: 100%;
+    // height: 100%;
+    .item-list {
+        .item {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+
+            .value-box {
+                margin-bottom: 5px;
+                display: flex;
+
+                span {
+                    padding: 3px 6px;
+                    border-radius: 20px;
+                    white-space: nowrap;
+                    box-sizing: border-box;
+                }
+            }
+        }
+    }
+
+}
+</style>

+ 292 - 0
src/newPerformance/components/RoleSetting.vue

@@ -0,0 +1,292 @@
+<template>
+    <div class="all">
+        <div class="alert-box bounce animated">
+            <div class="alert-content flex-box-ce">
+                <i class="el-icon-warning"></i>
+                如需设置绩效主管理员,绩效子管理员,部门管理员,普通员工及其权限,请前往旧系统设置!
+            </div>
+            <div class="alert-btn" @click="goSetting()">
+                <el-link type="primary">立即设置</el-link>
+            </div>
+        </div>
+            <div class="flex-box-ce">
+                <div style="height: 40px; display: flex; align-items: center;">
+                    <div class="title">绩效主管理员</div>
+                    <div class="text fontColorC">拥有全部权限,支持更换主管理员</div>
+                </div>
+            </div>
+            <div style="margin: 10px 0;">
+                <div class="flex-box-ce" style="margin-bottom: 10px; width: 360px;" v-for="(item, index) in userMain"
+                    :key="index">
+                    <userImage :user_name="item.name" :img_url="item.img_url" :id="item.id" fontSize="14"></userImage>
+                    <span class="name flex-1">{{ item.name }}</span>
+                </div>
+            </div>
+            <div class="flex-box-ce">
+                <div style="height: 40px; display: flex; align-items: center;">
+                    <div class="title">绩效子管理员</div>
+                    <div class="text fontColorB">负责绩效考核日常的管理工作,由主管理员分配权限</div>
+                </div>
+            </div>
+            <div class="table-box">
+                <el-table :data="AdministratorList" style="width: 100%;" v-loading="loading" size="mini"
+                    :header-cell-style="{ background: '#f5f7fa' }">
+                    <el-table-column prop="date" label="姓名">
+                        <template slot-scope="scope">
+                            <div class="flex-box-ce">
+                                <userImage :user_name="scope.row.name" fontSize="14" :img_url="scope.row.img_url">
+                                </userImage>
+                                <span class="name">{{ scope.row.name }}</span>
+                            </div>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="range" label="管理范围">
+                        <template slot-scope="scope">
+                            <span>{{ scope.row.range == '全公司' ? '全公司' : '其管辖部门及子部门的人员' }}</span>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="menu" label="权限菜单">
+                        <template slot-scope="scope">
+                            <span>{{ filtrationStr(scope.row.menu) }}</span>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="group" label="管理考核表">
+                        <template slot-scope="scope">
+                            <span>{{ filtrationStr(scope.row.group) }}</span>
+                        </template>
+                    </el-table-column>
+                    <!-- <el-table-column label="操作">
+                    <template slot-scope="scope">
+                        <el-button @click="compile(scope.row)" type="text">编辑</el-button>
+                        <el-button @click="deleteUser(scope.row.id)" type="text" class="red">删除</el-button>
+                    </template>
+                </el-table-column> -->
+                </el-table>
+                <Pagination :page="page" :page_size="page_size" :page_sizes="[5]" :total="total"
+                    @handleSizeChange="handleSizeChange" @handleCurrentChange="handleCurrentChange"></Pagination>
+            </div>
+            <div>
+                <div class="title-f flex-box-ce" style="height: 40px; display: flex; align-items: center;">
+                    <div class="title">部门管理员</div>
+                    <div class="text fontColorB">部门管理员在【组织架构】设置,可以对管理范围下的员工进行沟通反馈,评分,查看考核结果</div>
+                </div>
+                <div class="title-f flex-box-ce" style="height: 40px; display: flex; align-items: center;">
+                    <div class="title">普通员工</div>
+                    <div class="text fontColorB">员工只能看到自己的绩效,编写执行计划并进行沟通反馈</div>
+                </div>
+            </div>
+
+
+    </div>
+</template>
+
+<script>
+import EmployeeSelector from '@/components/EmployeeSelector';
+import BrawerBox from '@/performance/components/public/BrawerBox';
+export default {
+    components: { EmployeeSelector, BrawerBox },
+    name: 'JurisdictionSet',
+    data() {
+        return {
+            total: 0,
+            page: 1,
+            page_size: 5,
+            loading: false,
+            isAdministrator: false,
+            isChecks: false,
+            setAdministrator: false,
+
+            // 编辑添加管理员
+            checkboxs: [
+                { label: '全部权限', id: 'all', check: true },
+                {
+                    label: '绩效管理',
+                    id: 'kh',
+                    check: true,
+                    child: [
+                        { label: '发起考核', id: '7', check: true },
+                        { label: '开始评分', id: '8', check: true },
+                        { label: '调整结果', id: '9', check: true },
+                        { label: '等级配置', id: '10', check: true },
+                        { label: '导出报表', id: '11', check: true }
+                    ]
+                },
+                // { label: '考核指标库', id: '12', check: true },
+                { label: '绩效等级配置', id: '15', check: true },
+                { label: '绩效报表', id: '16', check: true },
+                { label: '考核表', id: 'kp', check: true }
+            ],
+            selected: { employee: [], dept: [] },
+            // 考核模板
+            searchWords: '',
+            groupList: [],
+            userMain: [], //主管理员
+            AdministratorList: [], //子管理员列表
+            setCreator: false, //更换主管理员
+            employee_not_select: [],//过滤的管理员
+
+            // 添加编辑子管理员
+            userData: {},
+            scope: 3, //管理范围
+            isKjJg: true, //是否可见考核结果
+            review: 14, //考核模板
+            isReviewShow: true, //控制考核模板是否能显示
+            isReviewShow2: false, //控制考核模板是否能显示
+            parameter: {
+                id: '',
+                permission: '',
+                group: ''
+            },
+            selectGroup: [], //选择的考核模板
+            isDisabled: false,
+            id: ''
+        };
+    },
+    watch: {
+        review(val) {
+            this.isReviewShow2 = val == 14 ? false : true;
+        },
+    },
+    created() {
+        this.getList();
+        this.getList2();
+        // this.getGroups();
+    },
+    mounted() {
+        this.$nextTick(() => { });
+    },
+    methods: {
+        
+        // 过滤
+        filtrationStr(str, is) {
+            if (is) {
+                var text = [...str].toString();
+                return str.length > 0 ? text : '--';
+            } else {
+                return str ? str : '--';
+            }
+        },
+        qkSelect() {
+            // 初始化数据
+            this.selectGroup = [];
+            for (let i in this.groupList) {
+                this.$set(this.groupList[i], 'check', false);
+            }
+            for (let i in this.checkboxs) {
+                this.$set(this.checkboxs[i], 'check', true);
+                if (this.checkboxs[i].child) {
+                    for (let j in this.checkboxs[i].child) {
+                        this.$set(this.checkboxs[i].child[j], 'check', true);
+                    }
+                }
+            }
+        },
+        
+        // 获取主管理员
+        getList2() {
+            this.$axiosUser('get', '/api/pro/per/user/manager_info', { range: 1 }).then(res => {
+                this.userMain = res.data.data.list || [];
+            });
+        },
+        // 获取子管理员
+        getList() {
+            this.loading = true;
+            this.$axiosUser('get', '/api/pro/per/user/manager_info', { page: this.page, page_size: this.page_size, range: 0 })
+                .then(res => {
+                    let data = res.data.data.list || [];
+                    this.total = res.data.data.total;
+                    this.AdministratorList = data.filter(item => {
+                        this.employee_not_select.push(item.id);
+                        return item.main == 0;
+                    });
+                })
+                .finally(() => {
+                    this.loading = false;
+                });
+        },
+        
+        
+       goSetting() {
+        this.$router.push("/jurisdictionSet")
+       },
+        // 页面变更
+        handleCurrentChange(val) {
+            this.page = val;
+            this.getList();
+        },
+        // 页面跳转
+        handleSizeChange(val) {
+            this.page_size = val;
+            this.getList();
+        }
+    }
+};
+</script>
+
+<style scoped="scoped" lang="scss">
+.all {
+    min-height: calc(100vh - 210px);
+    overflow: auto;
+    padding: 20px;
+    background-color: #fff;
+}
+
+.title {
+    font-size: 18px;
+    font-weight: 500;
+    margin-right: 10px;
+}
+
+.name {
+    padding-left: 10px;
+}
+
+.title-f {
+    margin-bottom: 20px;
+}
+
+.inputDc {
+    position: absolute;
+    top: 0;
+    right: 0;
+    left: 0;
+    bottom: 0;
+    z-index: 9;
+}
+
+.checkChild {
+    background-color: #fbfdff;
+    margin-left: 20px;
+}
+
+.li {
+    border-bottom: 1px solid #f1f1f1;
+}
+
+.li:hover {
+    background-color: #f5f7fa;
+}
+
+.ul {
+    margin: 20px 0;
+    margin-top: 10px;
+}
+
+.alert-box {
+    width: 100%;
+    height: 40px;
+    background-color: #fdf6ec;
+    color: #E6A23C;
+    border-radius: 4px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 8px 16px;
+    box-sizing: border-box;
+    .el-icon-warning {
+        font-size: 16px;
+        width: 16px;
+        margin-right: 3px;
+    }
+}
+</style>

+ 516 - 0
src/newPerformance/components/TemplateDetails/BatchHandleNode.vue

@@ -0,0 +1,516 @@
+<template>
+    <div>
+        <el-dialog :title="dialogTitle" center :visible.sync="batchHandleDialog" width="600px"
+            :before-close="handleCloseDialog">
+            <div v-if="currentNode">
+                <el-alert class="bounce animated" type="warning" :title="alertTilte" :closable="false" show-icon
+                    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-form-item>
+
+                    <div v-if="hasChildren" class="handler-list">
+                        <el-tag v-for="(item, index) in currentNode.children" :key="index"
+                            :type="childIndex === index ? '' : 'info'" closable @close="handleTagDelete(index)"
+                            @click="handleTagClick(item, index)">
+                            {{ item.assigneeType | filterType }}
+                        </el-tag>
+                        <el-button v-show="currentNode.enable" icon="el-icon-plus" size="mini"
+                            style="margin-bottom: 10px;" @click="addTagList()"></el-button>
+                    </div>
+
+                    <el-form-item v-if="['确认目标', '评分', '审批'].includes(dialogTitle)" :label="formLabel">
+                        <el-radio-group v-model="currentNode.children[childIndex].assigneeType"
+                            :disabled="!currentNode.enable" @change="changeAssigneeType">
+                            <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="['录入结果', '抄送'].includes(dialogTitle)" :label="formLabel">
+                        <el-radio-group v-model="currentNode.assigneeType" :disabled="!currentNode.enable"
+                            @change="changeAssigneeType">
+                            <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="['确认目标', '评分', '审批'].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-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="['确认目标', '评分', '审批'].includes(dialogTitle) && currentNode.children[childIndex].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="['确认目标', '评分', '审批'].includes(dialogTitle) && currentNode.children[childIndex].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="['确认目标', '评分', '审批'].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-form-item>
+
+
+                    <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-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="['录入结果', '抄送'].includes(dialogTitle) && currentNode.assigneeType === 'user'">
+                        <el-select 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>
+                    </el-form-item>
+
+                    <el-form-item v-if="['录入结果', '抄送'].includes(dialogTitle) && currentNode.assigneeType === 'post'">
+                        <el-select 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>
+                    </el-form-item>
+
+                    <el-form-item
+                        v-if="['录入结果', '抄送'].includes(dialogTitle) && currentNode.assigneeType === 'deptLeader'">
+                        <el-cascader 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>
+                    </el-form-item>
+
+                    <el-form-item v-if="['确认目标', '评分', '审批'].includes(dialogTitle)" 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>
+                    </el-form-item>
+
+                    <el-form-item v-if="['录入结果', '抄送'].includes(dialogTitle)" 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 v-if="['评分'].includes(dialogTitle)" label="权重">
+                        <el-input v-model="currentNode.children[childIndex].weight" :disabled="!currentNode.enable"
+                            style="width: 200px;">
+                            <template slot="append">%</template>
+                        </el-input>
+                    </el-form-item>
+
+                    <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-group>
+                    </el-form-item>
+
+                    <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>
+            </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>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+// 引入lodash库
+import _ from 'lodash';
+export default {
+    components: {
+        EmployeeSelector,
+    },
+    model: {
+        prop: 'batchHandleDialog',
+        event: 'close-dialog'
+    },
+    props: {
+        batchHandleDialog: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        nodeType: {
+            type: String,
+            default: ''
+        },
+        formLabel: {
+            type: String,
+            default: ''
+        },
+        templateId: {
+            type: String | Number,
+            default: ''
+        },
+        indicatorId: {
+            type: String | Number,
+            default: ''
+        },
+        handleNode: {
+            type: Object,
+            default: () => {}
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        hasChildren() {
+            return this.currentNode && this.currentNode.children && this.currentNode.children.length > 0
+        }
+    },
+    data() {
+        return {
+            alertTilte: "批量设置,所有指标都会共用同一个流程节点",
+            currentNode: null,
+            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: [], // 选择的员工列表
+            childIndex: 0,
+        }
+    },
+    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 = "部门"
+        }
+    },
+    watch: {
+        'dialogVisible'(v) {
+            if (v) this.initData()
+        },
+
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+            this.selected_manager_ids = ''
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [], // 选择的部门列表
+            this.childIndex = 0;
+            this.currentNode = this.handleNode;
+            
+            switch (this.nodeType) {
+                case 'targetConfirms': // 确认目标
+                case 'scores': // 评分
+                case 'reviews': // 审批
+                    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 : [];
+                    });
+                    this.childIndex = this.currentNode.children && this.currentNode.children.length > 0 ? 0 : null;
+                    break;
+                case 'resultInput': // 录入结果
+                case 'scoreSelf': // 自评
+                case 'scoreEachOther': // 互评
+                case 'cc':
+                    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 : [];
+                    break;
+            }
+        },
+
+
+        // 选择管理员
+        changeManagerIds(v) {
+            if (this.hasChildren) {
+                this.currentNode.children[this.childIndex].assigneeIds = []
+                this.currentNode.children[this.childIndex].leaderLevel = v || 1
+            } else {
+                this.currentNode.assigneeIds = []
+                this.currentNode.leaderLevel = v || 1
+            }
+        },
+
+
+        // 选择员工
+        changeEmployeeIds(v) {
+            if (this.hasChildren) {
+                this.currentNode.children[this.childIndex].assigneeIds = []
+                this.currentNode.children[this.childIndex].assigneeIds = v ? v : [];
+            }
+            else {
+                this.currentNode.assigneeIds = []
+                this.currentNode.assigneeIds = v ? v : [];
+            }
+        },
+
+        // 选择岗位
+        changePostIds(v) {
+            if (this.hasChildren) {
+                this.currentNode.children[this.childIndex].assigneeIds = []
+                this.currentNode.children[this.childIndex].assigneeIds = v ? v : [];
+            }
+            else {
+                this.currentNode.assigneeIds = []
+                this.currentNode.assigneeIds = v ? v : [];
+            }
+        },
+
+        // 选择部门
+        deptChange(val) {
+            // 获取当前选中的节点
+            const checkedNodes = that.$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 : []
+            } else {
+                this.currentNode.assigneeIds = []
+                this.currentNode.assigneeIds = this.selected_dept_ids.length ? this.selected_dept_ids : []
+            }
+        },
+
+
+        changeAssigneeType(v) {
+            if (this.hasChildren) {
+                this.currentNode.children[this.childIndex].assigneeIds = []
+                this.currentNode.children[this.childIndex].leaderLevel = 1
+                this.selected_manager_ids = 1;
+                this.selected_employee_ids = [];
+                this.selected_post_ids = [];
+                this.selected_dept_ids = [];
+                this.currentNode.children.forEach((child, index) => {
+                    if (index == this.childIndex) {
+                        this.selected_manager_ids = child.assigneeType === 'leader' ? child.leaderLevel : 1;
+                        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 : [];
+                    }
+                });
+            } else {
+                this.currentNode.assigneeIds = []
+                this.currentNode.leaderLevel = 1
+                this.selected_manager_ids = 1;
+                this.selected_employee_ids = [];
+                this.selected_post_ids = [];
+                this.selected_dept_ids = [];
+                this.selected_manager_ids = this.currentNode.assigneeType === 'leader' ? this.currentNode.leaderLevel : 1;
+                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 : [];
+            }
+            
+        },
+
+
+        changeFormEnable(v) {
+            // this.currentNode.enable = v;
+        },
+
+        // 添加子节点
+        addTagList() {
+            let id, type;
+            if (this.nodeType == 'targetConfirms') {
+                id = "TC_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "targetConfirm"
+            }
+            if (this.nodeType == 'scores') {
+                id = "S_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "score"
+            }
+            if (this.nodeType == 'reviews') {
+                id = "R_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "review"
+            }
+
+            let obj = {
+                allows: [],
+                assigneeIds: [],
+                assigneeType: 'self',
+                children: [],
+                enable: true,
+                id,
+                leaderLevel: 1,
+                multipleType: "or",
+                type,
+                weight: 0,
+            }
+            this.currentNode.children.push(obj);
+            this.childIndex = this.currentNode.children.length - 1;
+        },
+
+        handleTagClick(item, index) {
+            this.childIndex = index;
+        },
+
+        handleTagDelete(index) {
+            // 最后一个不能删除
+            if (this.currentNode.children && this.currentNode.children.length <= 1) return
+            let selectChildId = this.currentNode.children[this.childIndex].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;
+                })
+            }
+        },
+        
+        
+        handleCloseDialog() {
+            this.$emit('close-dialog', false)
+        },
+        submitForm() {
+            // return
+            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)
+                    this.$emit("onConfirm", res.data.flow)
+                } else {
+                    this.$message.error(res.message || '操作失败');
+                }
+            })
+        },
+        handleFormData() {
+
+        },
+
+        parseNode(node) {
+            let result = {
+                id: node.id,
+                type: node.type,
+                enable: node.enable,
+                assigneeType: node.assigneeType,
+                leaderLevel: node.leaderLevel,
+                assigneeIds: node.assigneeIds,
+                multipleType: node.multipleType,
+                allows: node.allows,
+                weight: node.weight,
+                children: node.children.map(child => this.parseNode(child)),
+            };
+            if (['targetConfirm', 'score', 'review', 'resultInput', 'cc'].includes(node.type)) {
+                const options = {
+                    'user': node.assigneeIds,
+                    'post': node.assigneeIds,
+                    'deptLeader': node.assigneeIds,
+                    'self': [this.user_info.id]
+                }
+                result.assigneeIds = options[node.assigneeType]
+            }
+
+            return result;
+        }
+    }
+}
+
+</script>
+
+<style lang="scss" scoped>
+.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;
+    }
+}
+</style>

+ 319 - 0
src/newPerformance/components/TemplateDetails/CC.vue

@@ -0,0 +1,319 @@
+<template>
+    <div>
+        <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="启用">
+                        <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>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+// 引入lodash库
+import _ from 'lodash';
+export default {
+    components: {
+        EmployeeSelector,
+    },
+    model: {
+        prop: 'reviews',
+        event: 'close-dialog'
+    },
+    props: {
+        reviews: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        nodeType: {
+            type: String,
+            default: ''
+        },
+        formLabel: {
+            type: String,
+            default: ''
+        },
+        templateId: {
+            type: String | Number,
+            default: ''
+        },
+        indicatorId: {
+            type: String | Number,
+            default: ''
+        },
+        tagIndex: {
+            type: String | Number,
+            default: 0
+        },
+        selectNodes: {
+            type: Array,
+            default: () => []
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    data() {
+        return {
+            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: [], // 选择的员工列表
+            childIndex: 0,
+            currentNode: null,
+        }
+    },
+    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 = "部门"
+        }
+    },
+    watch: {
+        'reviews'(v) {
+            if (v) this.initData()
+        },
+
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+            this.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) this.currentNode = _.cloneDeep(select)
+            })
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            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);
+        },
+
+
+        // 选择管理员
+        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)); // 去重
+            }
+
+        },
+
+
+        handleCloseDialog() {
+            this.currentNode = null
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.$emit('close-dialog', false)
+        },
+
+        submitForm() {
+            // 验证表单数据
+            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.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) {
+                    select = this.currentNode
+                    console.log(select)
+                }
+                nodes.push(select)
+            })
+            let data = {
+                indicatorId: this.indicatorId, // 指标ID
+                nodes
+            }
+            console.log(data)
+            this.$emit("onConfirm", data);
+            this.handleCloseDialog();
+        },
+    }
+}
+
+</script>
+
+<style lang="scss" scoped>
+.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;
+    }
+}
+</style>

+ 172 - 0
src/newPerformance/components/TemplateDetails/CateDetails.vue

@@ -0,0 +1,172 @@
+<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>
+
+            <el-button type="danger" :disabled="!(multipleSelection && multipleSelection.length > 0)" @click="delData()"
+                size="mini">批量删除</el-button>
+        </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>
+
+            </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>
+                </template>
+            </el-table-column>
+        </el-table>
+
+        <div class="flex-box-ce" style="justify-content: center; margin-top: 10px;">
+            <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
+                :current-page="params.page" :page-sizes="[10, 20, 30, 40, 50]" :page-size="params.pageSize"
+                layout="total, sizes, prev, pager, next" :total="total">
+            </el-pagination>
+        </div>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+export default {
+    data() {
+        return {
+            total: 0,
+            params: {
+                page: 1,
+                pageSize: 10
+            },
+            cateList: [],
+            multipleSelection: [],
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+    },
+    watch: {
+        cateDetailsDialog(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, this.params).then(res => {
+                let { data: { code, data: { list, total } } } = res
+                if (code == 1) {
+                    this.cateList = list;
+                    this.total = total
+                }
+
+            })
+        },
+
+        handleEdit(prop, value, cateId) {
+            let url = '', data = {};
+            // 分类名称必填
+            if (prop === 'name')
+                if (!value) return this.$message.error("请输入分类名称")
+                else data[prop] = value;
+            if (prop === 'desc') data['description'] = value;
+            console.log(data)
+            // 有ID修改
+            if (cateId) {
+                data['catId'] = cateId;
+                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 || '操作失败')
+                })
+            // 无ID新增
+            } else {
+                url = `/performance/cate/create/${this.user_info.site_id}`;
+                this.$http.post(url, data).then(res => {
+                    if(res.code == 1) this.getCateList()
+                    if (res.code == 0) return this.$message.error(res.msg || '操作失败')
+                })
+            }
+            
+        },
+
+        handleSelectChange(val) {
+            this.multipleSelection = val;
+        },
+
+        // 创建分类
+        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)
+        },
+
+        delData() {
+            let that = this
+            if (that.multipleSelection && that.multipleSelection.length > 0) {
+                that.$confirm('确定删除选中的分类吗?', '提示', {
+                    type: 'warning'
+                }).then(() => {
+                    // 创建一个包含异步任务的数组,每个任务都是一个 axios 请求
+                    const arrays = []
+                    that.multipleSelection.forEach(item => {
+                        arrays.push(that.delCateApi(item))
+                    })
+
+                    // 当所有请求都成功完成时,responses 是一个包含所有响应的数组
+                    Promise.all(arrays).then(responses => {
+                        // console.log(responses)
+                        that.getCateList();
+                    }).catch(error => {
+                        // 如果任何一个请求失败,将会进入这个 catch 块
+                        console.log(error)
+                    })
+                }).catch(() => { });
+            }
+        },
+
+        // 删除指标
+        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
+            })
+        },
+
+        handleSizeChange(v) {
+            this.params.pageSize = v
+            this.getCateList();
+        },
+        handleCurrentChange(v) {
+            this.params.page = v
+            this.getCateList();
+        },
+        // 关闭弹窗
+        handleCloseDialog() {
+            this.$emit('close-dialog', false);
+        },
+
+    }
+}
+</script>

+ 100 - 0
src/newPerformance/components/TemplateDetails/EditContent.vue

@@ -0,0 +1,100 @@
+<template>
+    <div>
+        <el-dialog title="编辑规则" center :visible.sync="showEditContent" width="600px" :before-close="handleCloseDialog">
+            <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: 'showEditContent',
+        event: 'close-dialog'
+    },
+    props: {
+        showEditContent: {
+            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() {
+            let url = '', data = {};
+            url = `/performance/indicator/content/${this.user_info.site_id}/${this.templateId}`;
+            data['content'] = this.editContent;
+            data['indicatorId'] = this.indicatorId;
+            this.$http.post(url, data).then(res => {
+                if (res.code == 1) {
+                    this.$emit('close-dialog', false);
+                    this.$emit('finishEdit', res.data.content);
+                }
+                if (res.code == 0) return this.$message.error(res.msg || '操作失败')
+            })
+        },
+        // 关闭弹窗
+        handleCloseDialog() {
+            this.$emit('close-dialog', false);
+        },
+
+        // 确定按钮
+        submit() {
+            
+        },
+        // 重置表单
+        cancel() {
+            this.handleCloseDialog()
+        }
+    }
+}
+</script>
+
+<style scoped="scoped" lang="scss"></style>

+ 448 - 0
src/newPerformance/components/TemplateDetails/FormulaComp.vue

@@ -0,0 +1,448 @@
+<template>
+    <div>
+        <el-dialog v-if="showFormula" title="计算公式" center :visible.sync="showFormula" width="60%" :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"
+                        @close="handleTagDelete(index)" @click="handleTagClick(index)">
+                        {{ '公式' + (index + 1) }}
+                    </el-tag>
+                    <el-button icon="el-icon-plus" size="mini" style="margin-bottom: 10px;"
+                        @click="addTagList()" v-if="isEdit"></el-button>
+                </div>
+
+                <!-- 编辑公式 -->
+                <el-row :gutter="10">
+                    <el-col :span="12">
+                        <el-card style="height: 460px;">
+                            <div class="container">
+                                <div class="title">
+                                    触发条件
+                                </div>
+                                <div class="content" @click="type = '触发条件'"
+                                    :class="type === '触发条件' ? 'black-border' : 'gray-border' ">
+                                    {{ tagList[currentIndex].conditionRemark }}
+                                </div>
+                            </div>
+                            <div class="container">
+                                <div class="title">
+                                    计算公式
+                                </div>
+                                <div class="content" @click="type = '计算公式'"
+                                    :class="type === '计算公式' ? 'black-border' : 'gray-border'  ">
+                                    {{ tagList[currentIndex].expressionRemark }}
+                                </div>
+                            </div>
+                        </el-card>
+                    </el-col>
+                    <el-col :span="12" style="height: 460px; display: flex; flex-direction: column;">
+                        <el-card>
+                            <div>
+                                <el-radio-group v-model="type" @change="changeType">
+                                    <el-radio label="触发条件"></el-radio>
+                                    <el-radio label="计算公式"></el-radio>
+                                </el-radio-group>
+                            </div>
+                            <div class="btn-container">
+                                <el-button v-for="item in fixedProps" @click="isEdit && inputText(item.key)" :key="item.key"
+                                    type="primary">{{ item.name }}</el-button>
+                            </div>
+
+                            <div class="keyboard">
+                                <div class="keyboard-left">
+                                    <div class="item" v-for="item in leftList" :key="item.id"
+                                        :style="{width: item.id == 0 ? '50%' : '25%'}" @click="isEdit && inputText(item.text)">
+                                        <div :style="{ width: item.id == 0 ? '90%' : '50px' }">
+                                            {{ item.text }}
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="keyboard-right">
+                                    <div class="item" v-for="item in rightList" :key="item.id"
+                                        @click="isEdit && inputText(item.text)">
+                                        <div v-if="item.id == 'delete' || item.id == 'clear'" class="no-bg">
+                                            <span v-if="item.id == 'delete'" class="el-icon-delete"
+                                                style="color: #FB8C00; font-size: 24px; font-weight: 600;">
+                                            </span>
+                                            <span v-if="item.id == 'clear'" class="el-icon-error"
+                                                style="color: #B00020; font-size: 24px; font-weight: 600;">
+                                            </span>
+                                        </div>
+                                        <div v-else :style="{ display: item.text ? 'block' : 'none' }" class="blue-bg">
+                                            {{ item.text }}
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </el-card>
+                    </el-col>
+                </el-row>
+            </div>
+            <div slot="footer">
+                <el-button v-if="isEdit" type="primary" @click="submitForm" size="small">确 定</el-button>
+                <el-button v-if="isEdit" @click="handleCloseDialog" size="small">取 消</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+// id生成器
+function idGenerator() {
+    return Date.now() + Math.floor(Math.random() * 10000);
+}
+
+export default {
+    model: {
+        prop: 'showFormula',
+        event: 'close-dialog'
+    },
+    props: {
+        showFormula: {
+            type: Boolean,
+            default: false
+        },
+        isEdit: {
+            type: Boolean,
+            default: true
+        },
+        fixedProps: {
+            type: Array,
+            default: () => []
+        },
+        dynamicProps: {
+            type: Array,
+            default: () => [],
+        },
+        expressionsProps: {
+            type: Array,
+            default: () => []
+        }
+    },
+    data() {
+        return {
+            type: "触发条件", // 单选
+            currentIndex: 0,
+            tagList: [
+                {
+                    id: idGenerator(),
+                    condition: [],
+                    expression: [],
+                    conditionRemark: '',
+                    expressionRemark: ''
+                }
+            ], // 公式列表
+            panels: [],
+            keyMap: {}, // 键盘输入的内容,键值对
+            // 键盘左边内容
+            leftList: [
+                { id: 7, text: 7 },
+                { id: 8, text: 8 },
+                { id: 9, text: 9 },
+                { id: "/", text: "/" },
+                { id: 4, text: 4 },
+                { id: 5, text: 5 },
+                { id: 6, text: 6 },
+                { id: "*", text: "*" },
+                { id: 1, text: 1 },
+                { id: 2, text: 2 },
+                { id: 3, text: 3 },
+                { id: "+", text: "+" },
+                { id: 0, text: 0 },
+                { id: ".", text: "." },
+                { id: "-", text: "-" },
+            ],
+            // 键盘右边内容
+            rightList: [
+                { id: "<", text: "<" },
+                { id: ">", text: ">" },
+                { id: "<=", text: "<=" },
+                { id: ">=", text: ">=" },
+                { id: "||", text: "||" },
+                { id: "&&", text: "&&" },
+                { id: "==", text: "==" },
+                {id: "1_1", text: ""},
+                { id: "(", text: "(" },
+                { id: ")", text: ")" },
+                { id: "2_2", text: "" },
+                { id: "3_3", text: "" },
+                { id: "delete", text: "delete" },
+                { id: "clear", text: "clear" },
+            ]
+        }
+    },
+    watch: {
+        showFormula(v) {
+            if (v) this.initData();
+        }
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+            let index = [];
+            // 固定属性 目标,权重,结果值
+            if (this.fixedProps.length > 0) {
+                index.push(0);
+                this.fixedProps.forEach((prop) => {
+                    this.keyMap[`#${prop.key}`] = prop.name;
+                });
+            }
+            // 动态属性
+            if (this.dynamicProps.length > 0) {
+                index.push(1);
+                this.dynamicProps.forEach((prop) => {
+                    this.keyMap[`#${prop.key}`] = prop.name;
+                });
+            }
+            index.push(2);
+            this.panels = Object.keys(index);
+            console.log(this.expressionsProps);
+            this.tagList = this.expressionsProps.map(prop => {
+                return {
+                    id: idGenerator(),
+                    condition: prop.condition || [],
+                    expression: prop.expression || [],
+                    conditionRemark: this.expRemark(prop.condition || []),
+                    expressionRemark: this.expRemark(prop.expression || []),
+                };
+            });
+            if (this.tagList.length === 0) this.tagList.push({ id: idGenerator(), condition: [], expression: [], conditionRemark: '', expressionRemark: '' });
+            this.currentIndex = 0;
+        },
+
+        // 单选改变事件
+        changeType(v) {
+            // console.log(v)
+        },
+        // 键盘键入内容
+        inputText(content) {
+            if (!['delete', 'clear'].includes(content)) {
+                switch (this.type) {
+                    case '触发条件':
+                        this.tagList[this.currentIndex].condition.push(this.keyMap[`#${content}`] ? `#${content}` : content);
+                        this.tagList[this.currentIndex].conditionRemark = this.expRemark(this.tagList[this.currentIndex].condition);
+                        break;
+                    case '计算公式':
+                        this.tagList[this.currentIndex].expression.push(this.keyMap[`#${content}`] ? `#${content}` : content);
+                        this.tagList[this.currentIndex].expressionRemark = this.expRemark(this.tagList[this.currentIndex].expression);
+                        break;
+                }
+                // 删除
+            } else if (content === 'delete') {
+                switch (this.type) {
+                    case '触发条件':
+                        this.tagList[this.currentIndex].condition = this.tagList[this.currentIndex].condition.slice(0, this.tagList[this.currentIndex].condition.length - 1);
+                        this.tagList[this.currentIndex].conditionRemark = this.expRemark(this.tagList[this.currentIndex].condition);
+                        break;
+                    case '计算公式':
+                        this.tagList[this.currentIndex].expression = this.tagList[this.currentIndex].expression.slice(0, this.tagList[this.currentIndex].expression.length - 1);
+                        this.tagList[this.currentIndex].expressionRemark = this.expRemark(this.tagList[this.currentIndex].expression);
+                        break;
+                }
+                // 清空
+            }else if (content === 'clear') {
+                switch (this.type) {
+                    case '触发条件':
+                        this.tagList[this.currentIndex].condition = [];
+                        this.tagList[this.currentIndex].conditionRemark = this.expRemark(this.tagList[this.currentIndex].condition);
+                        break;
+                    case '计算公式':
+                        this.tagList[this.currentIndex].expression = [];
+                        this.tagList[this.currentIndex].expressionRemark = this.expRemark(this.tagList[this.currentIndex].expression);
+                        break;
+                }
+            }
+        },
+
+        // 键盘键入的内容转成页面显示
+        expRemark(exp) {
+            let result = '';
+            if (!exp) return result;
+            exp.forEach(item => {
+                result += (!this.keyMap[item] ? item : this.keyMap[item]) + ' ';
+            });
+            return result;
+        },
+
+        // 切换公式
+        handleTagClick(index) {
+            this.currentIndex = index
+        },
+
+        // 添加公式
+        addTagList() {
+            this.tagList.push({ id: idGenerator(), condition: [], expression: [], conditionRemark: '', expressionRemark: '' });
+            this.currentIndex = this.tagList.length - 1;
+        },
+        // 删除公式
+        handleTagDelete(index) {
+            if (this.tagList.length <= 1) return;
+            let selectChildId = this.tagList[this.currentIndex].id;
+            this.currentIndex = 0;
+            this.tagList = this.tagList.filter((_, i) => i !== index);
+            if (selectChildId) {
+                this.tagList.forEach((item, i) => {
+                    if (item.id === selectChildId) this.currentIndex = i;
+                });
+            }
+        },
+
+        // 关闭弹窗
+        handleCloseDialog() {
+            this.type = "触发条件";
+            this.$emit('close-dialog', false);
+        },
+
+        // 确定按钮
+        submitForm() {
+            this.$emit('onConfirm', this.tagList);
+            this.handleCloseDialog();
+        }
+    }
+}
+</script>
+
+<style scoped="scoped" lang="scss">
+.no-bg {
+    background-color: transparent;
+}
+
+.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);
+}
+
+.black-border {
+    border: 1px #000 solid;
+}
+
+.gray-border {
+    border: 1px #ccc solid;
+}
+.handler-list {
+    width: 100%;
+    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;
+    }
+}
+.container {
+    width: 100%;
+    height: 100%;
+    .title {
+        font-weight: 600;
+        color: #999;
+        height: 40px;
+        line-height: 40px;
+    }
+    .content {
+        width: 100%;
+        height: 170px;
+        overflow-y: auto;
+        padding: 10px;
+        font-size: 18px;
+        letter-spacing: 2px;
+        box-sizing: box-sizing;
+        border-radius: 6px;
+        &::-webkit-scrollbar {
+            width: 6px;
+            height: 6px;
+            background-color: #f9f9f9;
+        }
+        &::-webkit-scrollbar-thumb {
+            border-radius: 6px;
+            background-color: #c1c1c1;
+        }
+        &::-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;
+        }
+    }
+}
+.btn-container {
+    display: flex;
+    margin: 40px 0;
+}
+
+.keyboard {
+    width: 100%;
+    height: 325px;
+    // padding: 10px;
+    box-sizing: box-sizing;
+    display: flex;
+    .keyboard-left {
+        width: 50%;
+        height: 100%;
+        display: flex;
+        flex-wrap: wrap;
+        .item {
+            width: 25%;
+            height: 50px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            margin-bottom: 20px;
+            box-sizing: border-box;
+            div {
+                width: 50px;
+                height: 50px;
+                border-radius: 6px;
+                text-align: center;
+                line-height: 50px;
+                background-color: #48A9A6;
+                color: white;
+                font-size: 18px;
+                box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
+                transition: all 0.3s linear;
+                &:hover {
+                    font-size: 20px;
+                }
+            }
+        }
+        
+    }
+    .keyboard-right {
+        width: 50%;
+        height: 100%;
+        display: flex;
+        flex-wrap: wrap;
+        .item {
+            width: 25%;
+            height: 50px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            margin-bottom: 20px;
+            box-sizing: border-box;
+    
+            div {
+                width: 50px;
+                height: 50px;
+                border-radius: 6px;
+                text-align: center;
+                line-height: 50px;
+                font-size: 18px;
+                transition: all 0.3s linear;
+                &:hover {
+                    font-size: 20px;
+                }
+            }
+        }
+    }
+}
+</style>

+ 640 - 0
src/newPerformance/components/TemplateDetails/HandleNode copy.vue

@@ -0,0 +1,640 @@
+<template>
+    <div>
+        <el-dialog :title="dialogTitle" center :visible.sync="dialogVisible" width="600px"
+            :before-close="handleCloseDialog">
+            <div>
+                <el-form ref="form" :model="form" label-width="80px" size="small">
+                    <el-form-item label="启用">
+                        <el-switch v-model="isDisable" :disabled="dialogTitle == '评分' || dialogTitle == '审批'"></el-switch>
+                    </el-form-item>
+
+                    <div v-if="tagList && tagList.length > 0" class="handler-list">
+                        <el-tag v-for="(item, index) in tagList" :key="index"
+                            :type="currentIndex === index ? '' : 'info'" closable @close="handleTagDelete(index)"
+                            @click="handleTagClick(item, index)">
+                            {{ item.assigneeType | filterType }}
+                        </el-tag>
+                        <el-button v-show="isDisable" icon="el-icon-plus" size="mini" style="margin-bottom: 10px;"
+                            @click="addTagList()"></el-button>
+                    </div>
+
+                    <el-form-item v-if="['确认目标', '录入结果', '评分', '审批', '抄送'].includes(dialogTitle)" :label="formLabel">
+                        <el-radio-group v-model="form && form.assigneeType" :disabled="!isDisable">
+                            <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-show="form && form.assigneeType === 'leader'">
+                        <el-select v-model="selected_manager_ids" placeholder="请选择管理员" :disabled="!isDisable" 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-show="form && form.assigneeType === 'user'">
+                        <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple :disabled="!isDisable"
+                            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-show="form && form.assigneeType === 'post'">
+                        <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple :disabled="!isDisable"
+                            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-show="form && form.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"></el-cascader>
+                    </el-form-item>
+
+                    <el-form-item v-if="['确认目标', '录入结果', '评分', '审批', '互评'].includes(dialogTitle)" label="多人时">
+                        <el-radio-group v-model="form && form.multipleType" :disabled="!isDisable">
+                            <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 v-if="['评分'].includes(dialogTitle)" label="权重">
+                        <el-input v-model="form && form.weight" :disabled="!isDisable" style="width: 200px;">
+                            <template slot="append">%</template>
+                        </el-input>
+                    </el-form-item>
+
+                    <el-form-item v-if="['确认目标', '录入结果', '评分', '审批'].includes(dialogTitle)" label="允许">
+                        <el-checkbox-group v-model="form && form.allows" :disabled="!isDisable">
+                            <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-group>
+                    </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>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+// 引入lodash库
+import _ from 'lodash';
+export default {
+    components: {
+        EmployeeSelector,
+    },
+    model: {
+        prop: 'dialogVisible',
+        event: 'close-dialog'
+    },
+    props: {
+        dialogVisible: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        nodeType: {
+            type: String,
+            default: ''
+        },
+        formLabel: {
+            type: String,
+            default: ''
+        },
+        templateId: {
+            type: String | Number,
+            default: ''
+        },
+        indicatorId: {
+            type: String | Number,
+            default: ''
+        },
+        tagIndex: {
+            type: String | Number,
+            default: 0
+        },
+        selectNodes: {
+            type: Array,
+            default: () => []
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        hasChildren() {
+            return this.currentNode && this.currentNode.children && this.currentNode.children.length > 0
+        }
+    },
+    data() {
+        return {
+            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")), // 部门列表
+            managerList: [], // 管理员列表
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            postList: JSON.parse(localStorage.getItem("postList")), // 岗位列表
+            selected_dept_ids: [], // 选择的部门列表
+            selected_manager_ids: '', // 选择的管理员列表
+            selected_post_ids: [], // 选择的岗位列表
+            selected_employee_ids: [], // 选择的员工列表
+            currentIndex: 0,
+            tagList: [],
+            currentNode: null,
+            isDisable: false,
+            form: {
+                id: "", // 指标ID
+                enable: false,
+                assigneeIds: [],// 处理人列表
+                assigneeType: "", // leader-上级 self-本人 post-岗位 user-指定员工 deptLeader-部门管理者,
+                multipleType: "", // or- 或签 parallel- 会签(同时处理) sequence - 会签(按顺序处理)
+                allows: [], // transfer-转交给别人处理 edit-修改指标(只有子目标确认节点有效) refuse-驳回上一个主节点	
+                type: "", // 节点类型
+                weight: 0,
+                children: []
+            }
+        }
+    },
+    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 = "部门"
+        }
+    },
+    watch: {
+        'dialogVisible'(v) {
+            if (v) this.initData()
+        },
+
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+            this.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) this.currentNode = _.cloneDeep(select)
+            })
+            this.isDisable = this.currentNode.enable;
+            this.selected_manager_ids = '';
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.tagList = [];
+            this.currentIndex = this.tagIndex;
+            switch (this.nodeType) {
+                case 'targetConfirms': // 确认目标
+                case 'scores': // 评分
+                case 'reviews': // 审批
+                    if (this.currentNode.children && this.currentNode.children.length > 0) {
+                        this.form = this.currentNode.children[this.currentIndex];
+                        this.tagList = this.currentNode.children;
+                    }
+                    break;
+                case 'resultInput': // 录入结果
+                case 'scoreSelf': // 自评
+                case 'scoreEachOther': // 互评
+                case 'cc':
+                    this.form = this.currentNode;
+                    break;
+            }
+            console.log("表单信息");
+            console.log(this.form);
+            // 回显选中的管理者
+            if (this.form.assigneeType === 'leader') {
+                if (this.form.leaderLevel) {
+                    this.selected_manager_ids = this.form.leaderLevel
+                }
+            }
+            // 回显选中的被考核人
+            if (this.form.assigneeType === 'self') {
+                // this.form.assigneeIds.push(this.user_info.id);
+            }
+
+            // 回显选中的岗位
+            if (this.form.assigneeType === 'post') {
+                if (this.form.assigneeIds && this.form.assigneeIds.length > 0) {
+                    this.postList.forEach(manager => {
+                        this.form.assigneeIds.forEach(assignee => {
+                            if (manager.id == assignee) this.selected_post_ids.push(manager.id)
+                        })
+                    })
+                }
+            }
+
+            // 回显选中的指定人员
+            if (this.form.assigneeType === 'user') {
+                if (this.form.assigneeIds && this.form.assigneeIds.length > 0) {
+                    Object.keys(this.employeeMap).forEach(key => {
+                        this.form.assigneeIds.forEach(assignee => {
+                            if (key == assignee) this.selected_employee_ids.push(key * 1)
+                        })
+                    })
+                }
+            }
+
+            // 回显选中的部门
+            if (this.form.assigneeType === 'deptLeader') {
+                if (this.form.assigneeIds && this.form.assigneeIds.length > 0) {
+                    this.dept_list.forEach(dept => {
+                        this.form.assigneeIds.forEach(assignee => {
+                            if (dept.id == assignee) {
+                                this.selected_dept_ids.push(dept.id)
+                            }
+                        })
+                    })
+                }
+            }
+        },
+
+
+
+
+        // 选择管理员
+        changeManagerIds(v) {
+            if (this.form.assigneeType === 'leader') {
+                if (this.hasChildren) {
+                    this.tagList[this.currentIndex].leaderLevel = ''
+                    this.tagList[this.currentIndex].leaderLevel = v
+                } else {
+                    this.form.leaderLevel = ''
+                    this.form.leaderLevel = v;
+                }
+            }
+        },
+
+
+        // 选择员工
+        changeEmployeeIds(v) {
+            if (this.form.assigneeType === 'user') {
+                if (this.hasChildren) {
+                    this.tagList[this.currentIndex].assigneeIds = [];
+                    if (v && v.length > 0) {
+                        v.forEach(item => {
+                            this.tagList[this.currentIndex].assigneeIds.push(item)
+                        })
+                        this.tagList[this.currentIndex].assigneeIds = Array.from(new Set(this.tagList[this.currentIndex].assigneeIds)); // 去重
+                    }
+                }
+                else {
+                    if (v && v.length > 0) {
+                        this.form.assigneeIds = []
+                        v.forEach(item => {
+                            this.form.assigneeIds.push(item)
+                        })
+                        this.form.assigneeIds = Array.from(new Set(this.form.assigneeIds)); // 去重
+                    }
+                }
+            }
+        },
+
+        // 选择岗位
+        changePostIds(v) {
+            if (this.form.assigneeType === 'post') {
+                if (this.hasChildren) {
+                    if (v && v.length > 0) {
+                        this.tagList[this.currentIndex].assigneeIds = [];
+                        v.forEach(item => {
+                            this.tagList[this.currentIndex].assigneeIds.push(item)
+                        })
+                        this.tagList[this.currentIndex].assigneeIds = Array.from(new Set(this.tagList[this.currentIndex].assigneeIds)); // 去重
+                    }
+                }
+                else {
+                    if (v && v.length > 0) {
+                        this.form.assigneeIds = []
+                        v.forEach(item => {
+                            this.form.assigneeIds.push(item)
+                        })
+                        this.form.assigneeIds = Array.from(new Set(this.form.assigneeIds)); // 去重
+                    }
+                }
+            }
+        },
+
+        // 选择部门
+        deptChange(val) {
+            if (val && val.length > 0) {
+                // 获取当前选中的节点
+                const checkedNodes = this.$refs.deptSelectRef.getCheckedNodes();
+                this.selected_dept_ids = checkedNodes.map((node) => node.value);
+                if (this.form.assigneeType === 'deptLeader') {
+                    if (this.hasChildren) {
+                        this.tagList[this.currentIndex].assigneeIds = this.selected_dept_ids
+                        this.tagList[this.currentIndex].assigneeIds = Array.from(new Set(this.tagList[this.currentIndex].assigneeIds)); // 去重
+                    }
+                    else {
+                        this.form.assigneeIds = this.selected_dept_ids
+                        this.form.assigneeIds = Array.from(new Set(this.form.assigneeIds)); // 去重
+                    }
+                }
+            } 
+            
+        },
+
+
+        changeFormEnable(v) {
+            this.isDisable = v;
+            this.currentNode.enable = v;
+        },
+
+        // 添加子节点
+        addTagList() {
+            let id, type;
+            if (this.nodeType == 'targetConfirms') {
+                id = "TC_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "targetConfirm"
+            }
+            if (this.nodeType == 'scores') {
+                id = "S_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "score"
+            }
+            if (this.nodeType == 'reviews') {
+                id = "R_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "review"
+            }
+
+            let obj = {
+                allows: [],
+                assigneeIds: [],
+                assigneeType: this.form.assigneeType,
+                children: [],
+                enable: true,
+                id,
+                leaderLevel: 1,
+                multipleType: "",
+                type,
+                weight: 0,
+            }
+            this.tagList.push(obj);
+            this.currentIndex = this.tagList.length - 1;
+            this.form = this.tagList[this.currentIndex];
+            this.selected_manager_ids = ''; // 选择的管理员列表
+            this.selected_dept_ids = []; // 选择的部门列表
+            this.selected_post_ids = [] // 选择的岗位列表
+            this.selected_employee_ids = [] // 选择的员工列表
+        },
+
+        handleTagClick(item, index) {
+            this.currentIndex = index;
+            this.form = item;
+            this.selected_manager_ids = '';
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = []; // 选择的部门列表
+
+            // 回显选中的管理者
+            if (this.form.assigneeType == 'leader') {
+                if (this.form.leaderLevel) {
+                    this.selected_manager_ids = this.form.leaderLevel
+                }
+            }
+
+            // 回显选中的被考核人
+            if (this.form.assigneeType === 'self') {
+                // this.form.assigneeIds.push(this.user_info.id);
+            }
+
+            // 回显选中的岗位
+            if (this.form.assigneeType == 'post') {
+                if (this.form.assigneeIds && this.form.assigneeIds.length > 0) {
+                    this.postList.forEach(manager => {
+                        this.form.assigneeIds.forEach(assignee => {
+                            if (manager.id == assignee) this.selected_post_ids.push(manager.id)
+                        })
+                    })
+                }
+            }
+
+            // 回显选中的指定人员
+            if (this.form.assigneeType == 'user') {
+                if (this.form.assigneeIds && this.form.assigneeIds.length > 0) {
+                    Object.keys(this.employeeMap).forEach(key => {
+                        this.form.assigneeIds.forEach(assignee => {
+                            if (key == assignee) this.selected_employee_ids.push(key * 1)
+                        })
+                    })
+                }
+            }
+
+            // 回显选中的部门列表
+            if (this.form.assigneeType == 'deptLeader') {
+                if (this.form.assigneeIds && this.form.assigneeIds.length > 0) {
+                    this.dept_list.forEach(dept => {
+                        this.form.assigneeIds.forEach(assignee => {
+                            if (dept.id == assignee) {
+                                this.selected_dept_ids.push(dept.id);
+                            }
+                        })
+                    })
+                }
+            }
+
+            // 去重
+            // this.form.assigneeIds = Array.from(new Set(this.form.assigneeIds))
+        },
+
+        handleTagDelete(index) {
+            // 最后一个不能删除
+            if (this.tagList && this.tagList.length > 1) {
+                this.tagList.splice(index, 1)
+                
+            }
+            if (this.tagList && this.tagList.length == 1) {
+                this.form = this.tagList[0]
+                this.currentIndex = 0
+            }
+
+        },
+        handleCloseDialog() {
+            this.form = {
+                id: "", // 指标ID
+                enable: false,
+                assigneeIds: [],// 处理人列表
+                assigneeType: "", // leader-上级 self-本人 post-岗位 user-指定员工 deptLeader-部门管理者,
+                multipleType: "", // or- 或签 parallel- 会签(同时处理) sequence - 会签(按顺序处理)
+                allows: [], // transfer-转交给别人处理 edit-修改指标(只有子目标确认节点有效) refuse-驳回上一个主节点	
+                type: "", // 节点类型
+                weight: 0,
+                children: []
+            };
+            this.form.assigneeIds = [];
+            this.selected_manager_ids = "";
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.$emit('close-dialog', false)
+        },
+        submitForm() {
+            let data = {
+                indicatorId: this.indicatorId, // 指标ID
+                nodes: []
+            }
+            data.nodes = this.selectNodes;
+            // 遍历所有节点,找到操作的节点
+            for (let i = 0; i < data.nodes.length; i++) {
+                // 正在操作的节点
+                if (data.nodes[i].type == this.nodeType) {
+                    data.nodes[i].enable = this.isDisable;
+                    // 有子节点
+                    if (data.nodes[i].children && data.nodes[i].children.length > 0) {
+                        data.nodes[i].children = this.tagList;
+                        // 无子节点
+                    } else {
+                        this.form.assigneeIds = []
+                        // 回显选中的管理者
+                        if (this.form.assigneeType == 'leader') {
+                            if (this.selected_manager_ids) this.form.leaderLevel = this.selected_manager_ids
+                            else return this.$message.error("请选择管理员")
+                        }
+
+                        // 回显选中的被考核人
+                        if (this.form.assigneeType == 'self') {
+                            this.form.assigneeIds.push(this.user_info.id);
+                        }
+
+                        // 回显选中的岗位
+                        if (this.form.assigneeType == 'post') {
+                            if (this.selected_post_ids && this.selected_post_ids.length > 0)
+                                this.selected_post_ids.forEach(postId => {
+                                    this.form.assigneeIds.push(postId);
+                                })
+                            else return this.$message.error("请选择岗位")
+                        }
+
+                        // 回显选中的指定人员
+                        if (this.form.assigneeType == 'user') {
+                            if (this.selected_employee_ids && this.selected_employee_ids.length > 0)
+                                this.selected_employee_ids.forEach(employeeId => {
+                                    this.form.assigneeIds.push(employeeId);
+                                })
+                            else return this.$message.error("请选择指定人员")
+                        }
+
+                        // 回显选中的部门列表
+                        if (this.form.assigneeType == 'deptLeader') {
+                            if (this.selected_dept_ids && this.selected_dept_ids.length > 0)
+                                this.selected_dept_ids.forEach(dept_id => {
+                                    this.form.assigneeIds.push(dept_id);
+                                })
+                            else return this.$message.error("请选择部门")
+                        }
+
+                        // 去重
+                        this.form.assigneeIds = Array.from(new Set(this.form.assigneeIds))
+                        data.nodes[i] = this.form;
+                        this.form.enable = this.isDisable;
+                    }
+                }
+            }
+
+            if (this.tagList && this.tagList.length > 0) {
+                for (let i = 0; i < this.tagList.length; i++) {
+
+                    let flag = this.tagList[i].assigneeIds && this.tagList[i].assigneeIds.length > 0
+
+                    if (this.tagList[i].assigneeType === 'self') {
+                        this.tagList[i].assigneeIds.push(this.user_info.id);
+                        // 去重
+                        this.tagList[i].assigneeIds = Array.from(new Set(this.tagList[i].assigneeIds))
+                    }
+
+                    if (this.tagList[i].assigneeType === 'leader') {
+                        if (!this.tagList[i].leaderLevel) return this.$message.error("请选择管理员")
+                    }
+
+                    if (this.tagList[i].assigneeType === 'deptLeader') {
+                        if (!flag) return this.$message.error("请选择部门")
+                    }
+
+                    if (this.tagList[i].assigneeType === 'user') {
+                        if (!flag) return this.$message.error("请选择指定人员")
+                    }
+
+                    if (this.tagList[i].assigneeType === 'post') {
+                        if (!flag) return this.$message.error("请选择岗位")
+                    }
+
+                }
+
+            }
+            console.log(data)
+            this.$emit("onConfirm", data);
+            this.handleCloseDialog();
+        },
+        handleFormData() {
+
+        }
+    }
+}
+
+</script>
+
+<style lang="scss" scoped>
+.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;
+    }
+}
+</style>

+ 640 - 0
src/newPerformance/components/TemplateDetails/HandleNode.vue

@@ -0,0 +1,640 @@
+<template>
+    <div>
+        <el-dialog :title="dialogTitle" center :visible.sync="dialogVisible" width="600px"
+            :before-close="handleCloseDialog">
+            <div>
+                <el-form ref="form" :model="form" label-width="80px" size="small">
+                    <el-form-item label="启用">
+                        <el-switch v-model="isDisable" :disabled="dialogTitle == '评分' || dialogTitle == '审批'"></el-switch>
+                    </el-form-item>
+
+                    <div v-if="tagList && tagList.length > 0" class="handler-list">
+                        <el-tag v-for="(item, index) in tagList" :key="index"
+                            :type="currentIndex === index ? '' : 'info'" closable @close="handleTagDelete(index)"
+                            @click="handleTagClick(item, index)">
+                            {{ item.assigneeType | filterType }}
+                        </el-tag>
+                        <el-button v-show="isDisable" icon="el-icon-plus" size="mini" style="margin-bottom: 10px;"
+                            @click="addTagList()"></el-button>
+                    </div>
+
+                    <el-form-item v-if="['确认目标', '录入结果', '评分', '审批', '抄送'].includes(dialogTitle)" :label="formLabel">
+                        <el-radio-group v-model="form && form.assigneeType" :disabled="!isDisable">
+                            <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-show="form && form.assigneeType === 'leader'">
+                        <el-select v-model="selected_manager_ids" placeholder="请选择管理员" :disabled="!isDisable" 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-show="form && form.assigneeType === 'user'">
+                        <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple :disabled="!isDisable"
+                            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-show="form && form.assigneeType === 'post'">
+                        <el-select v-model="selected_post_ids" placeholder="请选择岗位" multiple :disabled="!isDisable"
+                            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-show="form && form.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"></el-cascader>
+                    </el-form-item>
+
+                    <el-form-item v-if="['确认目标', '录入结果', '评分', '审批', '互评'].includes(dialogTitle)" label="多人时">
+                        <el-radio-group v-model="form && form.multipleType" :disabled="!isDisable">
+                            <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 v-if="['评分'].includes(dialogTitle)" label="权重">
+                        <el-input v-model="form && form.weight" :disabled="!isDisable" style="width: 200px;">
+                            <template slot="append">%</template>
+                        </el-input>
+                    </el-form-item>
+
+                    <el-form-item v-if="['确认目标', '录入结果', '评分', '审批'].includes(dialogTitle)" label="允许">
+                        <el-checkbox-group v-model="form && form.allows" :disabled="!isDisable">
+                            <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-group>
+                    </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>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+// 引入lodash库
+import _ from 'lodash';
+export default {
+    components: {
+        EmployeeSelector,
+    },
+    model: {
+        prop: 'dialogVisible',
+        event: 'close-dialog'
+    },
+    props: {
+        dialogVisible: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        nodeType: {
+            type: String,
+            default: ''
+        },
+        formLabel: {
+            type: String,
+            default: ''
+        },
+        templateId: {
+            type: String | Number,
+            default: ''
+        },
+        indicatorId: {
+            type: String | Number,
+            default: ''
+        },
+        tagIndex: {
+            type: String | Number,
+            default: 0
+        },
+        selectNodes: {
+            type: Array,
+            default: () => []
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        hasChildren() {
+            return this.currentNode && this.currentNode.children && this.currentNode.children.length > 0
+        }
+    },
+    data() {
+        return {
+            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")), // 部门列表
+            managerList: [], // 管理员列表
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            postList: JSON.parse(localStorage.getItem("postList")), // 岗位列表
+            selected_dept_ids: [], // 选择的部门列表
+            selected_manager_ids: '', // 选择的管理员列表
+            selected_post_ids: [], // 选择的岗位列表
+            selected_employee_ids: [], // 选择的员工列表
+            currentIndex: 0,
+            tagList: [],
+            currentNode: null,
+            isDisable: false,
+            form: {
+                id: "", // 指标ID
+                enable: false,
+                assigneeIds: [],// 处理人列表
+                assigneeType: "", // leader-上级 self-本人 post-岗位 user-指定员工 deptLeader-部门管理者,
+                multipleType: "", // or- 或签 parallel- 会签(同时处理) sequence - 会签(按顺序处理)
+                allows: [], // transfer-转交给别人处理 edit-修改指标(只有子目标确认节点有效) refuse-驳回上一个主节点	
+                type: "", // 节点类型
+                weight: 0,
+                children: []
+            }
+        }
+    },
+    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 = "部门"
+        }
+    },
+    watch: {
+        'dialogVisible'(v) {
+            if (v) this.initData()
+        },
+
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+            this.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) this.currentNode = _.cloneDeep(select)
+            })
+            this.isDisable = this.currentNode.enable;
+            this.selected_manager_ids = '';
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.tagList = [];
+            this.currentIndex = this.tagIndex;
+            switch (this.nodeType) {
+                case 'targetConfirms': // 确认目标
+                case 'scores': // 评分
+                case 'reviews': // 审批
+                    if (this.currentNode.children && this.currentNode.children.length > 0) {
+                        this.form = this.currentNode.children[this.currentIndex];
+                        this.tagList = this.currentNode.children;
+                    }
+                    break;
+                case 'resultInput': // 录入结果
+                case 'scoreSelf': // 自评
+                case 'scoreEachOther': // 互评
+                case 'cc':
+                    this.form = this.currentNode;
+                    break;
+            }
+            console.log("表单信息");
+            console.log(this.form);
+            // 回显选中的管理者
+            if (this.form.assigneeType === 'leader') {
+                if (this.form.leaderLevel) {
+                    this.selected_manager_ids = this.form.leaderLevel
+                }
+            }
+            // 回显选中的被考核人
+            if (this.form.assigneeType === 'self') {
+                // this.form.assigneeIds.push(this.user_info.id);
+            }
+
+            // 回显选中的岗位
+            if (this.form.assigneeType === 'post') {
+                if (this.form.assigneeIds && this.form.assigneeIds.length > 0) {
+                    this.postList.forEach(manager => {
+                        this.form.assigneeIds.forEach(assignee => {
+                            if (manager.id == assignee) this.selected_post_ids.push(manager.id)
+                        })
+                    })
+                }
+            }
+
+            // 回显选中的指定人员
+            if (this.form.assigneeType === 'user') {
+                if (this.form.assigneeIds && this.form.assigneeIds.length > 0) {
+                    Object.keys(this.employeeMap).forEach(key => {
+                        this.form.assigneeIds.forEach(assignee => {
+                            if (key == assignee) this.selected_employee_ids.push(key * 1)
+                        })
+                    })
+                }
+            }
+
+            // 回显选中的部门
+            if (this.form.assigneeType === 'deptLeader') {
+                if (this.form.assigneeIds && this.form.assigneeIds.length > 0) {
+                    this.dept_list.forEach(dept => {
+                        this.form.assigneeIds.forEach(assignee => {
+                            if (dept.id == assignee) {
+                                this.selected_dept_ids.push(dept.id)
+                            }
+                        })
+                    })
+                }
+            }
+        },
+
+
+
+
+        // 选择管理员
+        changeManagerIds(v) {
+            if (this.form.assigneeType === 'leader') {
+                if (this.hasChildren) {
+                    this.tagList[this.currentIndex].leaderLevel = ''
+                    this.tagList[this.currentIndex].leaderLevel = v
+                } else {
+                    this.form.leaderLevel = ''
+                    this.form.leaderLevel = v;
+                }
+            }
+        },
+
+
+        // 选择员工
+        changeEmployeeIds(v) {
+            if (this.form.assigneeType === 'user') {
+                if (this.hasChildren) {
+                    this.tagList[this.currentIndex].assigneeIds = [];
+                    if (v && v.length > 0) {
+                        v.forEach(item => {
+                            this.tagList[this.currentIndex].assigneeIds.push(item)
+                        })
+                        this.tagList[this.currentIndex].assigneeIds = Array.from(new Set(this.tagList[this.currentIndex].assigneeIds)); // 去重
+                    }
+                }
+                else {
+                    if (v && v.length > 0) {
+                        this.form.assigneeIds = []
+                        v.forEach(item => {
+                            this.form.assigneeIds.push(item)
+                        })
+                        this.form.assigneeIds = Array.from(new Set(this.form.assigneeIds)); // 去重
+                    }
+                }
+            }
+        },
+
+        // 选择岗位
+        changePostIds(v) {
+            if (this.form.assigneeType === 'post') {
+                if (this.hasChildren) {
+                    if (v && v.length > 0) {
+                        this.tagList[this.currentIndex].assigneeIds = [];
+                        v.forEach(item => {
+                            this.tagList[this.currentIndex].assigneeIds.push(item)
+                        })
+                        this.tagList[this.currentIndex].assigneeIds = Array.from(new Set(this.tagList[this.currentIndex].assigneeIds)); // 去重
+                    }
+                }
+                else {
+                    if (v && v.length > 0) {
+                        this.form.assigneeIds = []
+                        v.forEach(item => {
+                            this.form.assigneeIds.push(item)
+                        })
+                        this.form.assigneeIds = Array.from(new Set(this.form.assigneeIds)); // 去重
+                    }
+                }
+            }
+        },
+
+        // 选择部门
+        deptChange(val) {
+            if (val && val.length > 0) {
+                // 获取当前选中的节点
+                const checkedNodes = this.$refs.deptSelectRef.getCheckedNodes();
+                this.selected_dept_ids = checkedNodes.map((node) => node.value);
+                if (this.form.assigneeType === 'deptLeader') {
+                    if (this.hasChildren) {
+                        this.tagList[this.currentIndex].assigneeIds = this.selected_dept_ids
+                        this.tagList[this.currentIndex].assigneeIds = Array.from(new Set(this.tagList[this.currentIndex].assigneeIds)); // 去重
+                    }
+                    else {
+                        this.form.assigneeIds = this.selected_dept_ids
+                        this.form.assigneeIds = Array.from(new Set(this.form.assigneeIds)); // 去重
+                    }
+                }
+            } 
+            
+        },
+
+
+        changeFormEnable(v) {
+            this.isDisable = v;
+            this.currentNode.enable = v;
+        },
+
+        // 添加子节点
+        addTagList() {
+            let id, type;
+            if (this.nodeType == 'targetConfirms') {
+                id = "TC_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "targetConfirm"
+            }
+            if (this.nodeType == 'scores') {
+                id = "S_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "score"
+            }
+            if (this.nodeType == 'reviews') {
+                id = "R_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "review"
+            }
+
+            let obj = {
+                allows: [],
+                assigneeIds: [],
+                assigneeType: this.form.assigneeType,
+                children: [],
+                enable: true,
+                id,
+                leaderLevel: 1,
+                multipleType: "",
+                type,
+                weight: 0,
+            }
+            this.tagList.push(obj);
+            this.currentIndex = this.tagList.length - 1;
+            this.form = this.tagList[this.currentIndex];
+            this.selected_manager_ids = ''; // 选择的管理员列表
+            this.selected_dept_ids = []; // 选择的部门列表
+            this.selected_post_ids = [] // 选择的岗位列表
+            this.selected_employee_ids = [] // 选择的员工列表
+        },
+
+        handleTagClick(item, index) {
+            this.currentIndex = index;
+            this.form = item;
+            this.selected_manager_ids = '';
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = []; // 选择的部门列表
+
+            // 回显选中的管理者
+            if (this.form.assigneeType == 'leader') {
+                if (this.form.leaderLevel) {
+                    this.selected_manager_ids = this.form.leaderLevel
+                }
+            }
+
+            // 回显选中的被考核人
+            if (this.form.assigneeType === 'self') {
+                // this.form.assigneeIds.push(this.user_info.id);
+            }
+
+            // 回显选中的岗位
+            if (this.form.assigneeType == 'post') {
+                if (this.form.assigneeIds && this.form.assigneeIds.length > 0) {
+                    this.postList.forEach(manager => {
+                        this.form.assigneeIds.forEach(assignee => {
+                            if (manager.id == assignee) this.selected_post_ids.push(manager.id)
+                        })
+                    })
+                }
+            }
+
+            // 回显选中的指定人员
+            if (this.form.assigneeType == 'user') {
+                if (this.form.assigneeIds && this.form.assigneeIds.length > 0) {
+                    Object.keys(this.employeeMap).forEach(key => {
+                        this.form.assigneeIds.forEach(assignee => {
+                            if (key == assignee) this.selected_employee_ids.push(key * 1)
+                        })
+                    })
+                }
+            }
+
+            // 回显选中的部门列表
+            if (this.form.assigneeType == 'deptLeader') {
+                if (this.form.assigneeIds && this.form.assigneeIds.length > 0) {
+                    this.dept_list.forEach(dept => {
+                        this.form.assigneeIds.forEach(assignee => {
+                            if (dept.id == assignee) {
+                                this.selected_dept_ids.push(dept.id);
+                            }
+                        })
+                    })
+                }
+            }
+
+            // 去重
+            // this.form.assigneeIds = Array.from(new Set(this.form.assigneeIds))
+        },
+
+        handleTagDelete(index) {
+            // 最后一个不能删除
+            if (this.tagList && this.tagList.length > 1) {
+                this.tagList.splice(index, 1)
+                
+            }
+            if (this.tagList && this.tagList.length == 1) {
+                this.form = this.tagList[0]
+                this.currentIndex = 0
+            }
+
+        },
+        handleCloseDialog() {
+            this.form = {
+                id: "", // 指标ID
+                enable: false,
+                assigneeIds: [],// 处理人列表
+                assigneeType: "", // leader-上级 self-本人 post-岗位 user-指定员工 deptLeader-部门管理者,
+                multipleType: "", // or- 或签 parallel- 会签(同时处理) sequence - 会签(按顺序处理)
+                allows: [], // transfer-转交给别人处理 edit-修改指标(只有子目标确认节点有效) refuse-驳回上一个主节点	
+                type: "", // 节点类型
+                weight: 0,
+                children: []
+            };
+            this.form.assigneeIds = [];
+            this.selected_manager_ids = "";
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.$emit('close-dialog', false)
+        },
+        submitForm() {
+            let data = {
+                indicatorId: this.indicatorId, // 指标ID
+                nodes: []
+            }
+            data.nodes = this.selectNodes;
+            // 遍历所有节点,找到操作的节点
+            for (let i = 0; i < data.nodes.length; i++) {
+                // 正在操作的节点
+                if (data.nodes[i].type == this.nodeType) {
+                    data.nodes[i].enable = this.isDisable;
+                    // 有子节点
+                    if (data.nodes[i].children && data.nodes[i].children.length > 0) {
+                        data.nodes[i].children = this.tagList;
+                        // 无子节点
+                    } else {
+                        this.form.assigneeIds = []
+                        // 回显选中的管理者
+                        if (this.form.assigneeType == 'leader') {
+                            if (this.selected_manager_ids) this.form.leaderLevel = this.selected_manager_ids
+                            else return this.$message.error("请选择管理员")
+                        }
+
+                        // 回显选中的被考核人
+                        if (this.form.assigneeType == 'self') {
+                            this.form.assigneeIds.push(this.user_info.id);
+                        }
+
+                        // 回显选中的岗位
+                        if (this.form.assigneeType == 'post') {
+                            if (this.selected_post_ids && this.selected_post_ids.length > 0)
+                                this.selected_post_ids.forEach(postId => {
+                                    this.form.assigneeIds.push(postId);
+                                })
+                            else return this.$message.error("请选择岗位")
+                        }
+
+                        // 回显选中的指定人员
+                        if (this.form.assigneeType == 'user') {
+                            if (this.selected_employee_ids && this.selected_employee_ids.length > 0)
+                                this.selected_employee_ids.forEach(employeeId => {
+                                    this.form.assigneeIds.push(employeeId);
+                                })
+                            else return this.$message.error("请选择指定人员")
+                        }
+
+                        // 回显选中的部门列表
+                        if (this.form.assigneeType == 'deptLeader') {
+                            if (this.selected_dept_ids && this.selected_dept_ids.length > 0)
+                                this.selected_dept_ids.forEach(dept_id => {
+                                    this.form.assigneeIds.push(dept_id);
+                                })
+                            else return this.$message.error("请选择部门")
+                        }
+
+                        // 去重
+                        this.form.assigneeIds = Array.from(new Set(this.form.assigneeIds))
+                        data.nodes[i] = this.form;
+                        this.form.enable = this.isDisable;
+                    }
+                }
+            }
+
+            if (this.tagList && this.tagList.length > 0) {
+                for (let i = 0; i < this.tagList.length; i++) {
+
+                    let flag = this.tagList[i].assigneeIds && this.tagList[i].assigneeIds.length > 0
+
+                    if (this.tagList[i].assigneeType === 'self') {
+                        this.tagList[i].assigneeIds.push(this.user_info.id);
+                        // 去重
+                        this.tagList[i].assigneeIds = Array.from(new Set(this.tagList[i].assigneeIds))
+                    }
+
+                    if (this.tagList[i].assigneeType === 'leader') {
+                        if (!this.tagList[i].leaderLevel) return this.$message.error("请选择管理员")
+                    }
+
+                    if (this.tagList[i].assigneeType === 'deptLeader') {
+                        if (!flag) return this.$message.error("请选择部门")
+                    }
+
+                    if (this.tagList[i].assigneeType === 'user') {
+                        if (!flag) return this.$message.error("请选择指定人员")
+                    }
+
+                    if (this.tagList[i].assigneeType === 'post') {
+                        if (!flag) return this.$message.error("请选择岗位")
+                    }
+
+                }
+
+            }
+            console.log(data)
+            this.$emit("onConfirm", data);
+            this.handleCloseDialog();
+        },
+        handleFormData() {
+
+        }
+    }
+}
+
+</script>
+
+<style lang="scss" scoped>
+.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;
+    }
+}
+</style>

+ 278 - 0
src/newPerformance/components/TemplateDetails/InterviewFlow.vue

@@ -0,0 +1,278 @@
+<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>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+// 引入lodash库
+import _ from 'lodash';
+export default {
+    components: {
+        EmployeeSelector,
+    },
+    model: {
+        prop: 'interview',
+        event: 'close-dialog'
+    },
+    props: {
+        interview: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        nodeType: {
+            type: String,
+            default: ''
+        },
+        formLabel: {
+            type: String,
+            default: ''
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    data() {
+        return {
+            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: null
+        }
+    },
+
+    watch: {
+        'interview'(v) {
+            if (v) this.initData()
+        },
+
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+            
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.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"
+            }
+            console.log("表单信息");
+            console.log(this.currentNode);
+        },
+
+
+        // 选择管理员
+        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)); // 去重
+            }
+
+        },
+
+
+        handleCloseDialog() {
+            this.currentNode = null
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.$emit('close-dialog', false)
+        },
+        submitForm() {
+
+            // 验证表单数据
+            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];
+            this.$emit("onConfirm", nodes);
+            this.handleCloseDialog();
+        },
+        handleFormData() {
+
+        }
+    }
+}
+
+</script>
+
+<style lang="scss" scoped></style>

+ 427 - 0
src/newPerformance/components/TemplateDetails/PublishComp.vue

@@ -0,0 +1,427 @@
+<template>
+    <div>
+        <el-dialog title="发布考核" center :visible.sync="showPublishDialog" width="600px"
+            :before-close="handleCloseDialog">
+            <div class="status-btn-box fadeInDown animated">
+                <el-link type="primary" @click="cateDetailsDialog = true">查看考核分类</el-link>
+            </div>
+            <div class="flex-box-ce" style="justify-content: center;">
+                <el-form :model="form" :rules="rules" ref="ruleForm" label-width="120px" size="small"
+                    label-position="left">
+                    <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>
+
+                    <!-- <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> -->
+
+                    <template 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>
+                    </template>
+
+
+                    <el-link v-else type="primary" @click="isShowProject = true">请选择关联OKR</el-link>
+
+                </el-form>
+            </div>
+            <div slot="footer">
+                <el-button type="primary" @click="submitForm('ruleForm')" size="small">下一步</el-button>
+                <el-button @click="resetForm('ruleForm')" size="small">取 消</el-button>
+            </div>
+        </el-dialog>
+
+
+        <el-drawer title="考核分类" :visible.sync="cateDetailsDialog" direction="rtl" :before-close="handleClose">
+            <CateDetails v-if="cateDetailsDialog"></CateDetails>
+        </el-drawer>
+
+        <InterviewFlow v-if="interview" v-model="interview" form-label="主持人" dialog-title="面谈" node-type="interview"
+            @onConfirm="finishHandle" />
+
+        <!-- 关联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 "./CateDetails.vue" // 考核分类明细抽屉
+import InterviewFlow from "./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 {
+            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: [],
+        }
+    },
+
+    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);
+        },
+
+        // 确定按钮
+        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.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">
+.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>

+ 314 - 0
src/newPerformance/components/TemplateDetails/ResultInput.vue

@@ -0,0 +1,314 @@
+<template>
+    <div>
+        <el-dialog :title="dialogTitle" center :visible.sync="resultInput" width="600px"
+            :before-close="handleCloseDialog">
+            <div v-if="currentNode">
+                <el-form ref="form" label-width="80px" size="small">
+                    <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-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>
+            </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>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+// 引入lodash库
+import _ from 'lodash';
+export default {
+    components: {
+        EmployeeSelector,
+    },
+    model: {
+        prop: 'resultInput',
+        event: 'close-dialog'
+    },
+    props: {
+        resultInput: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        nodeType: {
+            type: String,
+            default: ''
+        },
+        formLabel: {
+            type: String,
+            default: ''
+        },
+        templateId: {
+            type: String | Number,
+            default: ''
+        },
+        indicatorId: {
+            type: String | Number,
+            default: ''
+        },
+        tagIndex: {
+            type: String | Number,
+            default: 0
+        },
+        selectNodes: {
+            type: Array,
+            default: () => []
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    data() {
+        return {
+            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: [], // 选择的员工列表
+            childIndex: 0,
+            currentNode: null
+        }
+    },
+    
+    watch: {
+        'resultInput'(v) {
+            if (v) this.initData()
+        },
+
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+            this.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) this.currentNode = _.cloneDeep(select)
+            })
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            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);
+        },
+
+
+        // 选择管理员
+        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)); // 去重
+            }
+
+        },
+
+       
+        handleCloseDialog() {
+            this.currentNode = null
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.$emit('close-dialog', false)
+        },
+        submitForm() {
+            
+            // 验证表单数据
+            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.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) {
+                    select = this.currentNode
+                    console.log(select)
+                }
+                nodes.push(select)
+            })
+            let data = {
+                indicatorId: this.indicatorId, // 指标ID
+                nodes
+            }
+            console.log(data)
+            this.$emit("onConfirm", data);
+            this.handleCloseDialog();
+        },
+        handleFormData() {
+
+        }
+    }
+}
+
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 416 - 0
src/newPerformance/components/TemplateDetails/Reviews.vue

@@ -0,0 +1,416 @@
+<template>
+    <div>
+        <el-dialog :title="dialogTitle" center :visible.sync="reviews" width="600px" :before-close="handleCloseDialog">
+            <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-form-item>
+
+                    <div class="handler-list">
+                        <el-tag v-for="(item, index) in currentNode.children" :key="index"
+                            :type="childIndex === index ? '' : 'info'" closable @close="handleTagDelete(index)"
+                            @click="handleTagClick(item, index)">
+                            {{ item.assigneeType | filterType }}
+                        </el-tag>
+                        <el-button v-show="currentNode.enable" icon="el-icon-plus" size="mini"
+                            style="margin-bottom: 10px;" @click="addTagList()"></el-button>
+                    </div>
+
+                    <el-form-item :label="formLabel">
+                        <el-radio-group v-model="currentNode.children[childIndex].assigneeType"
+                            :disabled="!currentNode.enable" @change="changeAssigneeType">
+                            <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.children[childIndex].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.children[childIndex].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.children[childIndex].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.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-form-item>
+
+                    <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>
+                    </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>
+            </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>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+// 引入lodash库
+import _ from 'lodash';
+export default {
+    components: {
+        EmployeeSelector,
+    },
+    model: {
+        prop: 'reviews',
+        event: 'close-dialog'
+    },
+    props: {
+        reviews: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        nodeType: {
+            type: String,
+            default: ''
+        },
+        formLabel: {
+            type: String,
+            default: ''
+        },
+        templateId: {
+            type: String | Number,
+            default: ''
+        },
+        indicatorId: {
+            type: String | Number,
+            default: ''
+        },
+        tagIndex: {
+            type: String | Number,
+            default: 0
+        },
+        selectNodes: {
+            type: Array,
+            default: () => []
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    data() {
+        return {
+            isShow: false,
+            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: [], // 选择的员工列表
+            childIndex: 0,
+            currentNode: null,
+        }
+    },
+    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 = "部门"
+        }
+    },
+    watch: {
+        'reviews'(v) {
+            if (v) this.initData()
+        },
+
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+            this.isShow = false
+            this.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) this.currentNode = _.cloneDeep(select)
+            })
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.childIndex = this.tagIndex;
+            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);
+            this.isShow = true
+        },
+
+
+        // 选择管理员
+        changeManagerIds(v) {
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].leaderLevel = v ? v : 1
+        },
+
+
+        // 选择员工
+        changeEmployeeIds(v) {
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].assigneeIds = v ? v : []
+        },
+
+        // 选择岗位
+        changePostIds(v) {
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].assigneeIds = v ? v : []
+        },
+
+        // 选择部门
+        deptChange(val) {
+
+            // 获取当前选中的节点
+            const checkedNodes = this.$refs.deptSelectRef.getCheckedNodes();
+            this.selected_dept_ids = checkedNodes.map((node) => node.value);
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].assigneeIds = this.selected_dept_ids.length ? this.selected_dept_ids : []
+        },
+
+        changeAssigneeType(v) {
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].leaderLevel = 1
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.currentNode.children.forEach((child, index) => {
+                if (index == this.childIndex) {
+                    this.selected_manager_ids = child.assigneeType === 'leader' ? child.leaderLevel : 1;
+                    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 : [];
+                }
+            });
+        },
+
+
+
+        changeFormEnable(v) {
+            this.currentNode.enable = v;
+        },
+
+        // 添加子节点
+        addTagList() {
+            let id, type;
+            if (this.nodeType == 'targetConfirms') {
+                id = "TC_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "targetConfirm"
+            }
+            if (this.nodeType == 'scores') {
+                id = "S_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "score"
+            }
+            if (this.nodeType == 'reviews') {
+                id = "R_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "review"
+            }
+
+            let obj = {
+                allows: [],
+                assigneeIds: [],
+                assigneeType: 'self',
+                children: [],
+                enable: true,
+                id,
+                leaderLevel: 1,
+                multipleType: "or",
+                type,
+                weight: 0,
+            }
+            this.currentNode.children.push(obj);
+            this.childIndex = this.currentNode.children.length - 1;
+            this.selected_manager_ids = 1; // 选择的管理员
+            this.selected_dept_ids = []; // 选择的部门列表
+            this.selected_post_ids = [] // 选择的岗位列表
+            this.selected_employee_ids = [] // 选择的员工列表
+        },
+
+        handleTagClick(item, index) {
+            this.childIndex = index;
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = []; // 选择的部门列表
+            // 回显下拉框数据
+            this.selected_employee_ids = item.assigneeType === 'user' ? item.assigneeIds : [];
+            this.selected_post_ids = item.assigneeType === 'post' ? item.assigneeIds : [];
+            this.selected_dept_ids = item.assigneeType === 'deptLeader' ? item.assigneeIds : [];
+        },
+
+        handleTagDelete(index) {
+            // 最后一个不能删除
+            if (this.currentNode && this.currentNode.children.length > 1) {
+                let selectChildId = this.currentNode.children[this.childIndex].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;
+                    })
+                }
+
+            }
+
+        },
+        handleCloseDialog() {
+            this.currentNode = null
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.$emit('close-dialog', false)
+        },
+        submitForm() {
+            
+
+            // 验证表单数据
+            if (this.currentNode.children && this.currentNode.children.length > 0) {
+                for (let i = 0; i < this.currentNode.children.length; i++) {
+                    let flag = this.currentNode.children[i].assigneeIds && this.currentNode.children[i].assigneeIds.length > 0
+                    if (this.currentNode.children[i].assigneeType === 'self') {
+                        this.currentNode.children[i].assigneeIds.push(this.user_info.id);
+                        // 去重
+                        this.currentNode.children[i].assigneeIds = Array.from(new Set(this.currentNode.children[i].assigneeIds))
+                    }
+                    if (this.currentNode.children[i].assigneeType === 'leader') {
+                        if (!this.currentNode.children[i].leaderLevel) return this.$message.error("请选择管理员")
+                    }
+                    if (this.currentNode.children[i].assigneeType === 'deptLeader') {
+                        if (!flag) return this.$message.error("请选择部门")
+                    }
+                    if (this.currentNode.children[i].assigneeType === 'user') {
+                        if (!flag) return this.$message.error("请选择指定人员")
+                    }
+                    if (this.currentNode.children[i].assigneeType === 'post') {
+                        if (!flag) return this.$message.error("请选择岗位")
+                    }
+                }
+            }
+
+            let nodes = []            
+            this.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) { 
+                    select = this.currentNode
+                    console.log(select)
+                } 
+                nodes.push(select)
+            })
+            let data = {
+                indicatorId: this.indicatorId, // 指标ID
+                nodes
+            }
+            this.$emit("onConfirm", data);
+            this.handleCloseDialog();
+        },
+        handleFormData() {
+
+        }
+    }
+}
+
+</script>
+
+<style lang="scss" scoped>
+.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;
+    }
+}
+</style>

+ 186 - 0
src/newPerformance/components/TemplateDetails/ScoreEachOther.vue

@@ -0,0 +1,186 @@
+<template>
+    <div>
+        <el-dialog :title="dialogTitle" center :visible.sync="scoreEachOther" width="600px"
+            :before-close="handleCloseDialog">
+            <div v-if="currentNode">
+                <el-form ref="form" label-width="80px" size="small">
+                    <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-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>
+            </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>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+// 引入lodash库
+import _ from 'lodash';
+export default {
+    components: {
+        EmployeeSelector,
+    },
+    model: {
+        prop: 'scoreEachOther',
+        event: 'close-dialog'
+    },
+    props: {
+        scoreEachOther: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        nodeType: {
+            type: String,
+            default: ''
+        },
+        formLabel: {
+            type: String,
+            default: ''
+        },
+        templateId: {
+            type: String | Number,
+            default: ''
+        },
+        indicatorId: {
+            type: String | Number,
+            default: ''
+        },
+        tagIndex: {
+            type: String | Number,
+            default: 0
+        },
+        selectNodes: {
+            type: Array,
+            default: () => []
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    data() {
+        return {
+            currentNode: null
+        }
+    },
+    
+    watch: {
+        'scoreEachOther'(v) {
+            if (v) this.initData()
+        },
+
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+            this.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) this.currentNode = _.cloneDeep(select)
+            })
+            console.log("表单信息");
+            console.log(this.currentNode);
+        },
+
+       
+        handleCloseDialog() {
+            this.currentNode = null;
+            this.$emit('close-dialog', false)
+        },
+        submitForm() {
+            
+            let nodes = []
+            this.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) {
+                    select = this.currentNode
+                }
+                nodes.push(select)
+            })
+            let data = {
+                indicatorId: this.indicatorId, // 指标ID
+                nodes
+            }
+            console.log(data)
+            this.$emit("onConfirm", data);
+            this.handleCloseDialog();
+        },
+        handleFormData() {
+
+        }
+    }
+}
+
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 421 - 0
src/newPerformance/components/TemplateDetails/Scores.vue

@@ -0,0 +1,421 @@
+<template>
+    <div>
+        <el-dialog :title="dialogTitle" center :visible.sync="scores" width="600px" :before-close="handleCloseDialog">
+            <div v-if="currentNode">
+                <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-form-item>
+
+                    <div class="handler-list">
+                        <el-tag v-for="(item, index) in currentNode.children" :key="index"
+                            :type="childIndex === index ? '' : 'info'" closable @close="handleTagDelete(index)"
+                            @click="handleTagClick(item, index)">
+                            {{ item.assigneeType | filterType }}
+                        </el-tag>
+                        <el-button v-show="currentNode.enable" icon="el-icon-plus" size="mini"
+                            style="margin-bottom: 10px;" @click="addTagList()"></el-button>
+                    </div>
+
+                    <el-form-item :label="formLabel">
+                        <el-radio-group v-model="currentNode.children[childIndex].assigneeType"
+                            :disabled="!currentNode.enable" @change="changeAssigneeType">
+                            <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.children[childIndex].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.children[childIndex].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.children[childIndex].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.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-form-item>
+
+                    <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>
+                    </el-form-item>
+
+                    <el-form-item v-if="['评分'].includes(dialogTitle)" label="权重">
+                        <el-input v-model="currentNode.children[childIndex].weight" :disabled="!currentNode.enable"
+                            style="width: 200px;">
+                            <template slot="append">%</template>
+                        </el-input>
+                    </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>
+            </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>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+// 引入lodash库
+import _ from 'lodash';
+export default {
+    components: {
+        EmployeeSelector,
+    },
+    model: {
+        prop: 'scores',
+        event: 'close-dialog'
+    },
+    props: {
+        scores: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        nodeType: {
+            type: String,
+            default: ''
+        },
+        formLabel: {
+            type: String,
+            default: ''
+        },
+        templateId: {
+            type: String | Number,
+            default: ''
+        },
+        indicatorId: {
+            type: String | Number,
+            default: ''
+        },
+        tagIndex: {
+            type: String | Number,
+            default: 0
+        },
+        selectNodes: {
+            type: Array,
+            default: () => []
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    data() {
+        return {
+            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: [], // 选择的员工列表
+            childIndex: 0,
+            currentNode: null,
+        }
+    },
+    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 = "部门"
+        }
+    },
+    watch: {
+        'scores'(v) {
+            if (v) this.initData()
+        },
+
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+            this.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) this.currentNode = _.cloneDeep(select)
+            })
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.childIndex = this.tagIndex;
+            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);
+        },
+
+
+
+
+        // 选择管理员
+        changeManagerIds(v) {
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].leaderLevel = v ? v : 1
+        },
+
+
+        // 选择员工
+        changeEmployeeIds(v) {
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].assigneeIds = v ? v : []
+        },
+
+        // 选择岗位
+        changePostIds(v) {
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].assigneeIds = v ? v : []
+        },
+
+        // 选择部门
+        deptChange(val) {
+            // 获取当前选中的节点
+            const checkedNodes = this.$refs.deptSelectRef.getCheckedNodes();
+            this.selected_dept_ids = checkedNodes.map((node) => node.value);
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].assigneeIds = this.selected_dept_ids.length ? this.selected_dept_ids : []
+
+        },
+
+        changeAssigneeType(v) {
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].leaderLevel = 1
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.currentNode.children.forEach((child, index) => {
+                if (index == this.childIndex) {
+                    this.selected_manager_ids = child.assigneeType === 'leader' ? child.leaderLevel : 1;
+                    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 : [];
+                }
+            });
+        },
+
+
+        changeFormEnable(v) {
+            this.currentNode.enable = v;
+        },
+
+        // 添加子节点
+        addTagList() {
+            let id, type;
+            if (this.nodeType == 'targetConfirms') {
+                id = "TC_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "targetConfirm"
+            }
+            if (this.nodeType == 'scores') {
+                id = "S_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "score"
+            }
+            if (this.nodeType == 'reviews') {
+                id = "R_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "review"
+            }
+
+            let obj = {
+                allows: [],
+                assigneeIds: [],
+                assigneeType: 'self',
+                children: [],
+                enable: true,
+                id,
+                leaderLevel: 1,
+                multipleType: "or",
+                type,
+                weight: 0,
+            }
+            this.currentNode.children.push(obj);
+            this.childIndex = this.currentNode.children.length - 1;
+            this.selected_manager_ids = 1; // 选择的管理员
+            this.selected_dept_ids = []; // 选择的部门列表
+            this.selected_post_ids = [] // 选择的岗位列表
+            this.selected_employee_ids = [] // 选择的员工列表
+        },
+
+        handleTagClick(item, index) {
+            this.childIndex = index;
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = []; // 选择的部门列表
+            // 回显下拉框数据
+            this.selected_employee_ids = item.assigneeType === 'user' ? item.assigneeIds : [];
+            this.selected_post_ids = item.assigneeType === 'post' ? item.assigneeIds : [];
+            this.selected_dept_ids = item.assigneeType === 'deptLeader' ? item.assigneeIds : [];
+        },
+
+        handleTagDelete(index) {
+            // 最后一个不能删除
+            if (this.currentNode && this.currentNode.children.length > 1) {
+                let selectChildId = this.currentNode.children[this.childIndex].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;
+                    })
+                }
+
+            }
+
+        },
+        handleCloseDialog() {
+            this.currentNode = null
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.$emit('close-dialog', false)
+        },
+        submitForm() {
+            
+
+            // 验证表单数据
+            if (this.currentNode.children && this.currentNode.children.length > 0) {
+                for (let i = 0; i < this.currentNode.children.length; i++) {
+                    let flag = this.currentNode.children[i].assigneeIds && this.currentNode.children[i].assigneeIds.length > 0
+                    if (this.currentNode.children[i].assigneeType === 'self') {
+                        this.currentNode.children[i].assigneeIds.push(this.user_info.id);
+                        // 去重
+                        this.currentNode.children[i].assigneeIds = Array.from(new Set(this.currentNode.children[i].assigneeIds))
+                    }
+                    if (this.currentNode.children[i].assigneeType === 'leader') {
+                        if (!this.currentNode.children[i].leaderLevel) return this.$message.error("请选择管理员")
+                    }
+                    if (this.currentNode.children[i].assigneeType === 'deptLeader') {
+                        if (!flag) return this.$message.error("请选择部门")
+                    }
+                    if (this.currentNode.children[i].assigneeType === 'user') {
+                        if (!flag) return this.$message.error("请选择指定人员")
+                    }
+                    if (this.currentNode.children[i].assigneeType === 'post') {
+                        if (!flag) return this.$message.error("请选择岗位")
+                    }
+                }
+            }
+
+            let nodes = []            
+            this.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) { 
+                    select = this.currentNode
+                    console.log(select)
+                } 
+                nodes.push(select)
+            })
+            let data = {
+                indicatorId: this.indicatorId, // 指标ID
+                nodes
+            }
+            this.$emit("onConfirm", data);
+            this.handleCloseDialog();
+        },
+        handleFormData() {
+
+        }
+    }
+}
+
+</script>
+
+<style lang="scss" scoped>
+.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;
+    }
+}
+</style>

+ 157 - 0
src/newPerformance/components/TemplateDetails/SelectCircle.vue

@@ -0,0 +1,157 @@
+<template>
+    <el-popover placement="bottom-start" :trigger="trigger" v-model="isShow" @show="showPop">
+        <div class="searchBox">
+            <div class="flex-box-ce search">
+                <i class="el-icon-arrow-left flex-1" @click="num(false)"></i>
+                <div class="flex-2">{{ year }}</div>
+                <i class="el-icon-arrow-right flex-1" @click="num(true)"></i>
+            </div>
+            <div class="flex-box-ce flex-d-wrap">
+                <div class="dateVal fontColorB"
+                    :class="dateParameter.name == item.name ? 'active' : ''"
+                    v-for="(item, index) in dates" :key="index" @click="activeDate(item)">{{ item.name }}</div>
+            </div>
+            
+        </div>
+        <!-- 内容 -->
+        <template slot="reference">
+            <div>
+                <slot></slot>
+            </div>
+        </template>
+    </el-popover>
+</template>
+
+<script>
+export default {
+    name: 'SelectCircle',
+    props: {
+        id: { //用于辨识是哪个地方调用了周期
+            type: Number,
+            default: 0,
+        },
+        
+        visible: {
+            // 是否显示组件
+            type: Boolean,
+            default: false
+        },
+        dateOptions: {
+            type: Array,
+            default: () => []
+        },
+        dateParameter: {
+            type: Object,
+            default: () => { return {} }
+        },
+        isShowAll: {//是否显示全部周期
+            type: Boolean,
+            default: true
+        },
+        trigger: {
+            type: String,
+            default: 'click'
+        }
+
+    },
+    data() {
+        return {
+            year: this.$moment().format('YYYY'),
+            dates: [],
+            cycle_type: 0,//周期ID
+            dateId: 1,
+            isShow: false
+        };
+    },
+    watch: {
+        visible(val) {
+            if (val) { this.isShow = true }
+        },
+        isShow(val) {
+            if (!val) { this.$emit('update:visible', false) }
+        }
+    },
+    created() { },
+    mounted() { },
+    methods: {
+        showPop() {
+            this.dates = this.dateOptions;
+            if (!this.isShowAll) {
+                let dates = JSON.parse(JSON.stringify(this.dates));
+                dates.shift()
+                this.dates = JSON.parse(JSON.stringify(dates));
+            }
+            if (this.dateParameter.year) {
+                this.year = this.dateParameter.year;
+                this.cycle_type = this.dateParameter.cycle_type;
+                this.dateId = this.dateParameter.dateId;
+            }
+        },
+        activeDate(item) {
+            this.$emit('confirm', {
+                year: this.year,
+                name: item.name,
+            });
+            this.isShow = false;
+        },
+        num(is) {
+            if (is) {
+                if (this.year == 2050) {
+                    return false;
+                }
+                this.year++;
+            } else {
+                if (this.year == 2020) {
+                    return false;
+                }
+                this.year--;
+            }
+        }
+    }
+};
+</script>
+
+<style scoped="scoped" lang="scss">
+.dateVal {
+    flex: 1;
+    text-align: center;
+    cursor: pointer;
+    padding: 5px 0;
+    margin-top: 10px;
+    font-size: 12px;
+}
+
+
+.dateVal:hover {
+    color: #409EFF !important;
+    border-radius: 2px;
+    background-color: #f1f1f1;
+}
+
+.searchBox .active {
+    color: #409EFF !important;
+    border-radius: 2px;
+    background-color: #f1f1f1;
+}
+
+.search i {
+    cursor: pointer;
+}
+
+.search i:hover {
+    color: #409EFF;
+}
+
+.search {
+    border-radius: 10px;
+    background-color: #f1f1f1;
+    padding: 10px;
+    text-align: center;
+}
+
+.searchBox {
+    width: 240px;
+    background-color: #fff;
+    border-radius: 25px;
+}
+</style>

+ 417 - 0
src/newPerformance/components/TemplateDetails/TargetConfirms.vue

@@ -0,0 +1,417 @@
+<template>
+    <div>
+        <el-dialog :title="dialogTitle" center :visible.sync="targetConfirms" width="600px"
+            :before-close="handleCloseDialog">
+            <div v-if="currentNode">
+                <el-form ref="form" label-width="80px" size="small">
+                    <el-form-item label="启用">
+                        <el-switch v-model="currentNode.enable"></el-switch>
+                    </el-form-item>
+
+                    <div class="handler-list">
+                        <el-tag v-for="(item, index) in currentNode.children" :key="index"
+                            :type="childIndex === index ? '' : 'info'" closable @close="handleTagDelete(index)"
+                            @click="handleTagClick(item, index)">
+                            {{ item.assigneeType | filterType }}
+                        </el-tag>
+                        <el-button v-show="currentNode.enable" icon="el-icon-plus" size="mini"
+                            style="margin-bottom: 10px;" @click="addTagList()"></el-button>
+                    </div>
+
+                    <el-form-item :label="formLabel">
+                        <el-radio-group v-model="currentNode.children[childIndex].assigneeType"
+                            :disabled="!currentNode.enable" @change="changeAssigneeType">
+                            <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.children[childIndex].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.children[childIndex].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.children[childIndex].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.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-form-item>
+
+                    <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>
+                    </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>
+            </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>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+// 引入lodash库
+import _ from 'lodash';
+export default {
+    components: {
+        EmployeeSelector,
+    },
+    model: {
+        prop: 'targetConfirms',
+        event: 'close-dialog'
+    },
+    props: {
+        targetConfirms: {
+            type: Boolean,
+            default: false
+        },
+        dialogTitle: {
+            type: String,
+            default: ''
+        },
+        nodeType: {
+            type: String,
+            default: ''
+        },
+        formLabel: {
+            type: String,
+            default: ''
+        },
+        templateId: {
+            type: String | Number,
+            default: ''
+        },
+        indicatorId: {
+            type: String | Number,
+            default: ''
+        },
+        tagIndex: {
+            type: String | Number,
+            default: 0
+        },
+        selectNodes: {
+            type: Array,
+            default: () => []
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    data() {
+        return {
+            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: [], // 选择的员工列表
+            childIndex: 0,
+            currentNode: null,
+        }
+    },
+    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 = "部门"
+        }
+    },
+    watch: {
+        'targetConfirms'(v) {
+            if (v) this.initData()
+        },
+
+    },
+    mounted() {
+        this.initData();
+    },
+    methods: {
+        initData() {
+            this.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) this.currentNode = _.cloneDeep(select)
+            })
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.childIndex = this.tagIndex;
+            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);
+        },
+
+
+
+
+        // 选择管理员
+        changeManagerIds(v) {
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].leaderLevel = v ? v : 1
+        },
+
+
+        // 选择员工
+        changeEmployeeIds(v) {
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].assigneeIds = v ? v : []
+        },
+
+        // 选择岗位
+        changePostIds(v) {
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].assigneeIds = v ? v : []
+        },
+
+        // 选择部门
+        deptChange(val) {
+
+            // 获取当前选中的节点
+            const checkedNodes = this.$refs.deptSelectRef.getCheckedNodes();
+            this.selected_dept_ids = checkedNodes.map((node) => node.value);
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].assigneeIds = this.selected_dept_ids.length ? this.selected_dept_ids : []
+        },
+
+        changeAssigneeType(v) {
+            this.currentNode.children[this.childIndex].assigneeIds = []
+            this.currentNode.children[this.childIndex].leaderLevel = 1
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.currentNode.children.forEach((child, index) => {
+                if (index == this.childIndex) {
+                    this.selected_manager_ids = child.assigneeType === 'leader' ? child.leaderLevel : 1;
+                    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 : [];
+                }
+            });
+        },
+
+
+        changeFormEnable(v) {
+            this.currentNode.enable = v;
+        },
+
+        // 添加子节点
+        addTagList() {
+            let id, type;
+            if (this.nodeType == 'targetConfirms') {
+                id = "TC_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "targetConfirm"
+            }
+            if (this.nodeType == 'scores') {
+                id = "S_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "score"
+            }
+            if (this.nodeType == 'reviews') {
+                id = "R_" + Date.now() + Math.floor(Math.random() * 10000);
+                type = "review"
+            }
+
+            let obj = {
+                allows: [],
+                assigneeIds: [],
+                assigneeType: 'self',
+                children: [],
+                enable: true,
+                id,
+                leaderLevel: 1,
+                multipleType: "or",
+                type,
+                weight: 0,
+            }
+            this.currentNode.children.push(obj);
+            this.childIndex = this.currentNode.children.length - 1;
+            this.selected_manager_ids = 1; // 选择的管理员
+            this.selected_dept_ids = []; // 选择的部门列表
+            this.selected_post_ids = [] // 选择的岗位列表
+            this.selected_employee_ids = [] // 选择的员工列表
+        },
+
+        handleTagClick(item, index) {
+            this.childIndex = index;
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = []; // 选择的部门列表
+            // 回显下拉框数据
+            this.selected_employee_ids = item.assigneeType === 'user' ? item.assigneeIds : [];
+            this.selected_post_ids = item.assigneeType === 'post' ? item.assigneeIds : [];
+            this.selected_dept_ids = item.assigneeType === 'deptLeader' ? item.assigneeIds : [];
+        },
+
+        handleTagDelete(index) {
+            // 最后一个不能删除
+            if (this.currentNode && this.currentNode.children.length > 1) {
+                let selectChildId = this.currentNode.children[this.childIndex].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;
+                    })
+                }
+
+            }
+
+        },
+        handleCloseDialog() {
+            this.currentNode = null
+            this.selected_manager_ids = 1;
+            this.selected_employee_ids = [];
+            this.selected_post_ids = [];
+            this.selected_dept_ids = [];
+            this.$emit('close-dialog', false)
+        },
+        submitForm() {
+            
+
+            // 验证表单数据
+            if (this.currentNode.children && this.currentNode.children.length > 0) {
+                for (let i = 0; i < this.currentNode.children.length; i++) {
+                    if (this.currentNode.children[i].assigneeType === 'self') {
+                        this.currentNode.children[i].assigneeIds.push(this.user_info.id);
+                        // 去重
+                        this.currentNode.children[i].assigneeIds = Array.from(new Set(this.currentNode.children[i].assigneeIds))
+                    }
+
+                    let flag = this.currentNode.children[i].assigneeIds && this.currentNode.children[i].assigneeIds.length > 0
+                    
+                    if (this.currentNode.children[i].assigneeType === 'leader') {
+                        if (!this.currentNode.children[i].leaderLevel) return this.$message.error("请选择管理员")
+                    }
+                    if (this.currentNode.children[i].assigneeType === 'deptLeader') {
+                        if (!flag) return this.$message.error("请选择部门")
+                    }
+                    if (this.currentNode.children[i].assigneeType === 'user') {
+                        if (!flag) return this.$message.error("请选择指定人员")
+                    }
+                    if (this.currentNode.children[i].assigneeType === 'post') {
+                        if (!flag) return this.$message.error("请选择岗位")
+                    }
+                }
+            }
+
+            let nodes = []            
+            this.selectNodes.forEach(select => {
+                // 正在操作的节点
+                if (select.type == this.nodeType) { 
+                    select = this.currentNode
+                    console.log(select)
+                } 
+                nodes.push(select)
+            })
+            let data = {
+                indicatorId: this.indicatorId, // 指标ID
+                nodes
+            }
+            this.$emit("onConfirm", data);
+            this.handleCloseDialog();
+        },
+        handleFormData() {
+
+        }
+    }
+}
+
+</script>
+
+<style lang="scss" scoped>
+.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;
+    }
+}
+</style>

+ 888 - 0
src/newPerformance/components/TemplateMixedPublish.vue

@@ -0,0 +1,888 @@
+<template>
+  <el-dialog
+    :visible="innerVisible"
+    @close="handleClose"
+    @open="initData"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    center
+    title="通过模板发布考核"
+    width="600px"
+  >
+
+    <el-carousel
+      v-loading="loading"
+      :autoplay="false"
+      :loop="false"
+      :initial-index="0"
+      height="500px"
+      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;"
+        />
+        <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-card class="card-cycle">
+            <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 :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>
+            </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-card>
+
+
+
+        </el-form>
+      </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"
+            />
+          </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="end"
+          align="middle"
+          style="height: 30px;"
+        >
+          <el-col :span="2">
+            <el-button
+              type="text"
+              icon="el-icon-plus"
+              @click.stop="showTemplateSearch = true"
+            />
+          </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="面谈流程可以为每个人考核进行复盘跟进,添加跟踪任务等。对个人提升很有帮助"
+        />
+        <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>
+      </el-carousel-item>
+    </el-carousel>
+    <template #footer>
+      <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>
+    </template>
+
+    <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"
+    />
+  </el-dialog>
+</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 "./TemplateSelector.vue";
+import PerEmployeeSelector from "./PerEmployeeSelector.vue";
+
+export default {
+  name: 'TemplateMixedPublish',
+  components:{
+    PerEmployeeSelector,
+    TemplateSelector,
+    TargetSearch,
+    Template,
+    SelectCircle,
+  },
+  props: {
+    showVisible:{
+      type:Boolean,
+      default:false
+    }
+  },
+  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:[],
+      },
+      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
+    },
+    '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;
+      }
+    }
+  },
+  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.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.$refs['carousel'].prev();
+    },
+    nextStep(){
+      if (this.step >= 3) return;
+      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;
+    },
+    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>

+ 89 - 0
src/newPerformance/components/TemplateSelector.vue

@@ -0,0 +1,89 @@
+<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>
+</template>
+
+
+<script>
+export default {
+  name: "TemplateSelector",
+  props:{
+    showVisible: {
+      type: Boolean,
+      default: false
+    },
+    selectedTemplate:{
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      userInfo: this.$userInfo(),
+      innerVisible: this.showVisible,
+      templateList:[],
+      selectedValues:this.selectedTemplate,
+    }
+  },
+  watch: {
+    showVisible(val) {
+      this.innerVisible = val;
+    },
+  },
+  computed:{
+    templates(){
+      return this.templateList.map(item => {
+        return {
+          key:item.templateId,
+          label:item.title
+        }
+      });
+    }
+  },
+  methods: {
+    handleClose(){
+      this.$emit('update:showVisible',false)
+    },
+    initData(){
+      this.templateList = [];
+      this.selectedValues = this.selectedTemplate;
+      this.$axiosUser("get", `/performance/template/list/self/${this.userInfo.site_id}`).then(res => {
+        this.templateList = res.data.data.list;
+      })
+    },
+    onConfirm(){
+      let res = this.templateList.filter(item => this.selectedValues.includes(item.templateId)).map(item => {
+        return {
+          templateId:item.templateId,
+          title:item.title
+        }
+      });
+      this.$emit('confirm',res);
+      this.handleClose();
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+
+</style>

ファイルの差分が大きいため隠しています
+ 1453 - 0
src/newPerformance/components/UploadPublish.vue


+ 990 - 0
src/newPerformance/components/Workbench.vue

@@ -0,0 +1,990 @@
+<template>
+    <div class="all">
+        <!-- 待办 -->
+        <div class="flex-box">
+            <div class="dispose-left" style="width: 70%;">
+                <div class="title">
+                    待办列表
+                </div>
+                <el-tabs v-model="activeName" class="tabs">
+                    <el-tab-pane v-loading="loading" :title="tab.title" :name="tab.name"
+                        style="max-height: 240px; overflow-y: auto;" class="scroll-bar" :key="tab.name"
+                        v-for="(tab, index) in editableTabs">
+                        <span slot="label" v-if="tab.num > 0">
+                            <el-badge :value="tab.num" :max="999">
+                                <span>{{ tab.title }}</span>
+                            </el-badge>
+                        </span>
+                        <span slot="label" v-else>
+                            <div class="tab-title">
+                                <span>{{ tab.title }}</span>
+                            </div>
+                        </span>
+
+                        <div style="height: 220px;">
+
+                            <template v-if="activeName == 0">
+                                <template v-if="agencyList && agencyList.length > 0">
+                                    <div class="flex-box-ce item" v-for="(item, index) in agencyList" :key="index">
+                                        <div class="flex-1 font-flex-word content" @click="openDetail(item)">
+                                            <span class="flag-old">旧</span>
+                                            <div style="max-width: 700px;" class="font-flex-word">{{ item.content }}
+                                            </div>
+                                        </div>
+                                        <div class="fontColorB" @click="openDetail(item)">
+                                            {{ item.update_time }}
+                                        </div>
+                                    </div>
+                                    <div class="flex-box-ce" style="justify-content: center; margin-top: 20px;">
+                                        <el-pagination @size-change="handleSizeChange"
+                                            @current-change="handleCurrentChange" :current-page.sync="params.page"
+                                            :page-size="params.pageSize" layout="total, prev, pager, next"
+                                            :total="total">
+                                        </el-pagination>
+                                    </div>
+                                </template>
+                                <noData v-else content="暂无内容" imgW="120px" imgH="80px"></noData>
+                            </template>
+                            <template v-else-if="activeName == 10">
+                                <template v-if="plcList && plcList.length > 0">
+                                    <div class="flex-box-ce item" v-for="(item, index) in plcList" :key="index"
+                                        @click="openAffirm(item)">
+                                        <div class="flex-1 font-flex-word content">
+                                            <span class="flag-old">旧</span>
+                                            <div style="max-width: 700px;" class="font-flex-word">
+                                                {{ item.package_name }}【{{ item.level_name }}】的绩效评分和等级,需要你检查确认
+                                            </div>
+                                        </div>
+                                        <div class="fontColorB">{{ item.ct }}</div>
+                                    </div>
+                                </template>
+                                <noData v-else content="暂无内容" imgW="120px" imgH="80px"></noData>
+                            </template>
+                            <template v-else>
+                                <template v-if="agencyList && agencyList.length > 0">
+                                    <div class="flex-box-ce item" v-for="(item, index) in agencyList" :key="index">
+
+                                        <div v-if="item.type == 1" class="flex-1 font-flex-word content"
+                                            @click="openDetail(item)">
+                                            <span class="flag-new">新</span>
+                                            <div style="max-width: 700px;" class="font-flex-word">
+                                                考核表&quot;
+                                                {{ item.jobNew && item.jobNew.reviewTitle + " ”中, " || '-- ' }}{{
+                                                    item.jobNew
+                                                    &&
+                                                    item.jobNew.employeeName + '的' || '-- ' }}{{ item.jobNew &&
+                                                    item.jobNew.title ||
+                                                    '-- ' }},需要您确认!
+                                            </div>
+                                        </div>
+                                        <div v-if="item.type == 1" class="fontColorB" @click="openDetail(item)">{{
+                                            item.jobNew &&
+                                            item.jobNew.timeRemark
+                                            || '--' }}
+                                        </div>
+
+                                        <div v-if="item.type == 2" class="flex-1 font-flex-word content"
+                                            @click="openDetail(item)">
+                                            <span class="flag-old">旧</span>
+                                            <div style="max-width: 700px;" class="font-flex-word">{{ item.jobOld &&
+                                                item.jobOld.content || '--' }}
+                                            </div>
+                                        </div>
+                                        <div v-if="item.type == 2" class="fontColorB" @click="openDetail(item)">
+                                            {{ item.jobOld && item.jobOld.update_time || '--' }}
+                                        </div>
+                                    </div>
+                                    <div class="flex-box-ce" style="justify-content: center; margin-top: 20px;">
+                                        <el-pagination @size-change="handleSizeChange"
+                                            @current-change="handleCurrentChange" :current-page.sync="params.page"
+                                            :page-size="params.pageSize" layout="total, prev, pager, next"
+                                            :total="total">
+                                        </el-pagination>
+                                    </div>
+                                </template>
+                                <noData v-else content="暂无内容" imgW="120px" imgH="80px"></noData>
+                            </template>
+                        </div>
+                    </el-tab-pane>
+                </el-tabs>
+            </div>
+
+            <!-- 考核模板列表 -->
+            <div class="dispose-left template-box " style="width: 30%;">
+                <div class="title-box">
+                    <div class="tem-title">
+                        <i class="plus-button el-icon-plus" @click="addTemplate"></i>
+                        考核模板
+                    </div>
+                    <div class="flex-box-ce">
+                        <el-button-group>
+                            <el-button type="primary" size="mini" @click="cateDetailsDialog = true">考核分类</el-button>
+                            <el-button type="primary" size="mini" @click="openTemplateMixed">快速考核</el-button>
+                            <el-button type="primary" size="mini" @click="openUploadPublish">自定义考核</el-button>
+                        </el-button-group>
+                    </div>
+                </div>
+                <div class="scroll-bar" style="max-height: 220px; overflow-y: auto; position: relative;">
+                    <template v-if="messageList.length > 0">
+                        <div class="flex-box-ce item" v-for="(item, index) in messageList" :key="index">
+                            <!-- <el-checkbox ></el-checkbox> -->
+                            <div class="flex-box flex-1" @click="$router.push(`/templateDetails/${item.templateId}`)">
+                                <div style="width: 20px;"><i class="el-icon-tickets blue"></i></div>
+                                <span class="message-content">{{ item.title || '默认标题' }}</span>
+                            </div>
+
+                            <div>
+                                <!-- <el-link style="margin-right: 10px;" type="primary">查看指标</el-link> -->
+                                <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="80px"></noData>
+                </div>
+
+            </div>
+        </div>
+
+        <!-- 指标审批列表 -->
+        <div class="gl-all scroll-bar" style="flex: 1; margin: 10px 0; overflow-y: auto;">
+            <IndicatorList />
+        </div>
+
+
+        <!-- 跟踪管理 -->
+        <TrackManagement :showDrawer.sync="isTrack" :isCz="has_finish ? true : false" :id="employeeID" :apList="apList"
+            :recordMemberIds="recordMemberIds" :assessId="employee_id" @confirm="getManagement"></TrackManagement>
+
+        <!-- 编辑流程节点 -->
+        <EditNodeDialog v-if="dialogData" v-model="dialogVisible" :activeName="activeName" :dialogTitle="dialogTitle"
+            :dialogData="dialogData" @handleSuccess="getNewList" @handleEditSuccess="changeDialogData" />
+
+        <!-- 面谈流程节点 -->
+        <InterviewNodeDialog v-if="interviewNodeVisible" v-model="interviewNodeVisible" :activeName="activeName"
+            :dialogTitle="dialogTitle" :dialogData="dialogData" @handleSuccess="getNewList"
+            @handleEditSuccess="changeDialogData" />
+
+        <!-- <PublishExamineList v-if="chooseExamineDialog" v-model="chooseExamineDialog" :examine-list="messageList">
+        </PublishExamineList> -->
+
+        <!-- 选择模板列表 -->
+        <BatchTemplatePublish v-if="chooseExamineDialog" v-model="chooseExamineDialog" :examine-list="messageList"
+            @onConfirm="confirmChooseExamine" />
+
+        <!-- 发布考核弹框 -->
+        <PublishComp v-if="showPublishDialog" v-model="showPublishDialog" :template-ids="templateIds"
+            @onConfirm="onPubishConfirm" />
+
+        <!-- 考核分类 -->
+        <el-drawer title="考核分类" :visible.sync="cateDetailsDialog" direction="rtl"
+            :before-close="handleCateDetailsClose">
+            <CateDetails v-if="cateDetailsDialog"></CateDetails>
+        </el-drawer>
+
+
+        <TemplateMixedPublish :show-visible.sync="showTemplateMixed" />
+
+        <UploadPublish :show-visible.sync="showUploadPublish" />
+
+        <el-drawer title="多模板发布考核" :visible.sync="batchTemplateDrawer" size="60%" direction="rtl"
+            :before-close="handleClose">
+            <div>
+                <div>{{ requestParams.title }}</div>
+                <div>{{ requestParams.cycleType }}</div>
+                <div>{{ requestParams.startDate }}</div>
+            </div>
+        </el-drawer>
+
+        <el-dialog :visible.sync="showReviewDetail" title="考核详情" center fullscreen>
+            <div style="height: 85vh;">
+                <PerReviewDetail v-if="currentReviewId" :review-id="currentReviewId" />
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import moment from 'moment';
+import { mapGetters } from 'vuex';
+import TrackManagement from '@/performance/components/public/TrackManagement';
+import EditNodeDialog from './Workbench/EditNode';
+import InterviewNodeDialog from './Workbench/InterviewNode';
+import IndicatorList from './Workbench/IndicatorList';
+import PublishComp from "./TemplateDetails/PublishComp"
+import BatchTemplatePublish from "./Workbench/BatchTemplatePublish"
+import TemplateMixedPublish from "./TemplateMixedPublish.vue";
+import UploadPublish from "./UploadPublish.vue";
+import PerReviewDetail from "./PerReviewDetail.vue";
+import CateDetails from "./TemplateDetails/CateDetails.vue" // 考核分类明细抽屉
+
+export default {
+    name: 'Workbench',
+    components: {
+        PerReviewDetail,
+        UploadPublish,
+        TemplateMixedPublish,
+        TrackManagement,
+        EditNodeDialog,
+        IndicatorList,
+        InterviewNodeDialog,
+        PublishComp,
+        BatchTemplatePublish,
+        CateDetails
+    },
+    data() {
+        return {
+            day: moment().format('YYYY-MM-DD'),
+            editableTabs: [
+                { title: '目标制定', name: '0', num: 0 },
+                { title: '确认目标', name: '1', num: 0 },
+                { title: '录入结果', name: '2', num: 0 },
+                { title: '自评', name: '3', num: 0 },
+                { title: '互评', name: '4', num: 0 },
+                { title: '评分', name: '5', num: 0 },
+                { title: '审批', name: '6', num: 0 },
+                { title: '面谈', name: '7', num: 0 },
+                { title: '绩效确认', name: '10', num: 0 }
+            ],
+            cateDetailsDialog: false,
+            userInfo: this.$userInfo(),
+            total: 0,
+            agencyList: [], //待办列表
+            agencyNum: 0,
+            messageList: [], //消息列表
+            messageNum: 0,
+            activeName: '0',
+            loading: false,
+            dialogVisible: false,
+            dialogTitle: '默认标题',
+            dialogData: {},
+            // 管理相关
+            options: [], //
+            headValue: [],
+            glIndex: '1',
+            glIist: [{ name: '1', title: '我管理的' }, { name: '2', title: '我评分的' }],
+            glLoading: false,
+            glDataList: [],
+            package_id: '', //考核包ID
+            name: '', //搜索Name
+            pendingList: [], //提供给考核详情上下切换人员列表
+            isTrack: false,
+            apList: [], //管理记录源数据
+            employee_id: 0, //被考核人ID
+            employeeID: 0,
+            recordMemberIds: [],
+            has_finish: 0,//是否归档
+            plcList: [],
+            site_info: null,
+            interviewNodeVisible: false,
+            params: {
+                type: "",
+                page: 1,
+                pageSize: 5
+            },
+            chooseExamineDialog: false,
+            showPublishDialog: false,
+            batchTemplateDrawer: false,
+            templateIds: [],
+            requestParams: {},
+            showTemplateMixed: false,
+            showUploadPublish: false,
+            showReviewDetail: false,
+            currentReviewId: null,
+        };
+    },
+
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    activated() {
+        this.getAgencyNum(); // 获取旧系统的待办列表 角标数量
+        this.getAgency(); // 获取旧系统的待办列表
+        this.glIndex == '1' ? this.getManagement() : this.getScorerRecord();
+        this.getPlc();
+        this.getTemplateList(); // 获取模板列表
+    },
+    created() {
+        this.getTemplateList(); // 获取模板列表
+        this.getAgencyNum(); // 获取旧系统的待办列表 角标数量
+        this.getNewAgencyNum(); // 获取新系统的待办列表 角标数量
+        this.getAgency(); // 获取旧系统的待办列表
+    },
+    mounted() {
+        // this.getMessage();
+        // this.assessTree();
+    },
+    watch: {
+        activeName(val) {
+            // // 目标制定,绩效确认节点去获取旧系统的数据
+            // if (val == 0 || val == 10) this.getAgency();
+            // // 其他节点获取新系统的数据
+            // else this.getNewAgency();
+            if (val == 10) {
+                this.$axiosUser('get', '/api/pro/per/package/plc/list', { status: 0, page: 1, page_size: 1000 }).then(res => {
+                    this.plcList = res.data.data.list
+                })
+            } else {
+                this.params.page = 1
+                this.params.pageSize = 5
+                this.getAgency();
+            }
+
+        },
+        glIndex(val) {
+            this.glDataList = [];
+            if (this.headValue.length != 0) {
+                val == '1' ? this.getManagement() : this.getScorerRecord();
+            }
+        },
+
+        headValue(val) {
+            this.package_id = val[val.length - 1];
+            this.glIndex == '1' ? this.getManagement() : this.getScorerRecord();
+        }
+    },
+    methods: {
+
+        handleCateDetailsClose() {
+            this.cateDetailsDialog = false
+        },
+        getNewList() {
+            this.getAgencyNum(); // 获取旧系统的待办列表 角标数量
+            this.getNewAgencyNum(); // 获取新系统的待办列表 角标数量
+            this.getAgency(); // 获取旧系统的待办列表
+        },
+        getTemplateList() {
+            this.$axiosUser("get", `/performance/template/list/self/${this.user_info.site_id}`).then(res => {
+                this.messageList = res.data.data.list;
+            })
+        },
+        closeDialog() {
+            this.dialogVisible = false
+        },
+
+        openExamineList() {
+            this.$router.push("/combinationTemplates")
+            // this.chooseExamineDialog = true
+        },
+        openTemplateMixed() {
+            this.showTemplateMixed = true;
+        },
+        openUploadPublish() {
+            this.showUploadPublish = true;
+        },
+        // 确认删除模板
+        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.messageList = res.data.data.list
+                })
+            })
+        },
+        cancelDelete() {
+            console.log("取消删除");
+        },
+
+        addTemplate() {
+            this.$axiosUser('post', `/performance/template/create/${this.user_info.site_id}`).then(res => {
+                let { data: { data, code } } = res
+                if (code == 1) this.messageList.push(data);
+            });
+        },
+        openAffirm(item) {
+            this.$router.push({ path: '/affirm', query: { pl_id: item.id, level_name: item.level_name, package_id: item.package_id } });
+        },
+        getPlc() {
+            this.loading = true
+            this.$axiosUser('get', '/api/pro/per/package/plc/list', { status: 0, page: 1, page_size: 1000 }).then(res => {
+                this.loading = false
+                this.plcList = res.data.data.list
+            })
+        },
+
+        returnPoint(item) {
+            let id = item.node_info.id;
+            let stats = item.status;
+            let arr = [{ name: '上级评分', pointList: [] }, { name: '特定指标评分', pointList: [] }];
+            item.score_info.forEach(e => {
+                if (stats == 0) {
+                    if (e.node_id < id) {
+                        if (e.node_code == 'score_supervisor') {
+                            //上级评分
+                            if (e.node_id == id && stats == 1) {
+                                arr[0].pointList.push(e.point + '(暂存)');
+                            } else {
+                                arr[0].pointList.push(e.point);
+                            }
+                        } else {
+                            //指定评分呢
+                            if (e.node_id == id && stats == 1) {
+                                arr[1].pointList.push(e.point + '(暂存)');
+                            } else {
+                                arr[1].pointList.push(e.point);
+                            }
+                        }
+                    }
+                } else {
+                    if (e.node_id <= id) {
+                        if (e.node_code == 'score_supervisor') {
+                            //上级评分
+                            if (e.node_id == id && stats == 1) {
+                                arr[0].pointList.push(e.point + '(暂存)');
+                            } else {
+                                arr[0].pointList.push(e.point);
+                            }
+                        } else {
+                            //指定评分呢
+                            if (e.node_id == id && stats == 1) {
+                                arr[1].pointList.push(e.point + '(暂存)');
+                            } else {
+                                arr[1].pointList.push(e.point);
+                            }
+                        }
+                    }
+                }
+            });
+            return arr;
+        },
+        openTx(item) {
+            this.employee_id = item.employee_id;
+            this.employeeID = item.record_id;
+            this.has_finish = item.record_id;
+            this.employeeDet(item.record_id, () => {
+                this.isTrack = true;
+            });
+        },
+        employeeDet(id, callBack) {
+            this.staffLoad = true;
+            this.$axiosUser('get', '/api/pro/per/package/employee/info', { id })
+                .then(res => {
+                    let data = res.data.data;
+                    this.apList = data.dimension;
+                    this.recordMemberIds = data.record_member_ids;
+                    callBack && callBack();
+                })
+                .finally(() => {
+                    this.staffLoad = false;
+                });
+        },
+        openDetail2(item) {
+            this.$router.push({
+                path: '/staffAssDet',
+                query: {
+                    assID: this.package_id,
+                    employeeID: item.record_id,
+                    employeeIDs: item.employee_id
+                }
+            });
+        },
+        //我管理记录
+        getManagement() {
+            if (this.headValue.length == 0) {
+                return false;
+            }
+            this.glLoading = true;
+            this.$axiosUser('get', '/api/pro/per/package/management_record', { package_id: this.package_id || 0, name: this.name, page: 1, page_size: 5 })
+                .then(res => {
+                    let list = res.data.data.list;
+                    list.forEach(item => {
+                        if (item.employee_id) {
+                            //当是导入导出时,显示登录者
+                            item.userInfo = this.$getEmployeeMapItem(item.employee_id);
+                            if (this.$getEmployeeMapItem(item.employee_id).employee_detail) {
+                                item.dept_list = this.$getEmployeeMapItem(item.employee_id).employee_detail.dept_list;
+                            }
+                        }
+                    });
+                    this.glDataList = list;
+                })
+                .finally(() => {
+                    this.glLoading = false;
+                });
+        },
+        //我评分的
+        getScorerRecord() {
+            if (this.headValue.length == 0) {
+                return false;
+            }
+            this.glLoading = true;
+            this.$axiosUser('get', '/api/pro/per/package/score_record', { package_id: this.package_id || 0, name: this.name, page: 1, page_size: 5 })
+                .then(res => {
+                    let list = res.data.data.list;
+                    list.forEach(item => {
+                        if (item.employee_id) {
+                            //当是导入导出时,显示登录者
+                            item.userInfo = this.$getEmployeeMapItem(item.employee_id);
+                            if (this.$getEmployeeMapItem(item.employee_id).employee_detail) {
+                                item.dept_list = this.$getEmployeeMapItem(item.employee_id).employee_detail.dept_list;
+                            }
+                        }
+                    });
+                    this.glDataList = list;
+                })
+                .finally(() => {
+                    this.glLoading = false;
+                });
+        },
+        //搜索
+        searchList(data) {
+            this.name = data;
+            this.glIndex == '1' ? this.getManagement() : this.getScorerRecord();
+        },
+        //请求绩效树
+        assessTree() {
+            this.$axiosUser('get', '/api/pro/per/package/tree').then(res => {
+                if (res.data.code == 1) {
+                    this.options = res.data.data;
+                    let headValue = [];
+                    this.options.some(item => {
+                        if (item.list.length > 0) {
+                            headValue.push(item.id);
+                            headValue.push(item.list[0].id);
+                            return true;
+                        }
+                    });
+                    if (headValue.length > 0) {
+                        this.headValue = headValue;
+                    }
+                }
+            });
+        },
+
+        openDetail(item) {
+            // 目标制定
+            if (this.activeName == 0) {
+                // type 1-个人考核记录 2-结果值数据录入
+                if (item.type == 1) {
+                    this.$router.push({
+                        path: '/staffAssDet',
+                        query: {
+                            assID: item.remark.package_id,
+                            employeeID: item.remark.packageEmployee_id,
+                            employeeIDs: item.remark.employee_id,
+                            pendingList: JSON.stringify(this.pendingList),
+                            activeName: this.activeName,
+                        }
+                    });
+                    // 结果录入
+                } else if (item.node_type == 4) {
+                    let name = item.content.split(',')[0]
+                    let query = {
+                        pe_ids: JSON.stringify(item.pe_ids.concat(item.done_pe_ids)),
+                        name: name,
+                        pendingList: JSON.stringify(this.pendingList),
+                        package_id: item.package_id,
+                        activeName: this.activeName,
+                    };
+                    this.$router.push({ path: '/staffAssDet', query: query });
+                } else {
+                    this.$router.push({ name: 'resultSet', query: { id: item.remark.packageEmployee_id, packageName: item.remark.package_name } });
+                }
+                // 绩效确认
+            } else if (this.activeName == 10) {
+                // this.disposeMessage(item);
+            } else {
+                // 新绩效系统数据 处理待办事项
+                if (item.type == 1) {
+                    if (this.activeName == 1) this.dialogTitle = "确认目标";
+                    if (this.activeName == 2) this.dialogTitle = "录入结果";
+                    if (this.activeName == 3) this.dialogTitle = "自评";
+                    if (this.activeName == 4) this.dialogTitle = "互评";
+                    if (this.activeName == 5) this.dialogTitle = "评分";
+                    if (this.activeName == 6) this.dialogTitle = "审批";
+                    if (this.activeName == 7) this.dialogTitle = "面谈";
+                    if (['1', '2', '3', '4', '5', '6'].includes(this.activeName)) {
+                        this.dialogVisible = true;
+                        let url = `/performance/review/job/${this.user_info.site_id}/${item.jobNew.reviewIndicatorId}/${item.jobNew.taskId}`;
+                        // 任务详情
+                        this.$axiosUser("get", url).then(res => {
+                            this.dialogData = res.data.data;
+                        })
+                        // 面谈 
+                    } else {
+                        this.currentReviewId = item.jobNew.reviewId;
+                        this.showReviewDetail = true;
+                        // this.interviewNodeVisible = true;
+                        // this.dialogData = item;
+                    }
+
+                    // 旧绩效系统数据 处理待办事项
+                } else if (item.type == 2) {
+                    // 个人考核记录
+                    if (item.jobOld.type == 1) {
+                        this.$router.push({
+                            path: '/staffAssDet',
+                            query: {
+                                assID: item.jobOld.remark.package_id,
+                                employeeID: item.jobOld.remark.packageEmployee_id,
+                                employeeIDs: item.jobOld.remark.employee_id,
+                                pendingList: JSON.stringify(this.pendingList),
+                                activeName: this.activeName,
+                            }
+                        });
+                        // 结果录入
+                    } else if (item.jobOld.node_type == 4) {
+                        let name = item.jobOld.content.split(',')[0]
+                        let query = {
+                            pe_ids: JSON.stringify(item.jobOld.pe_ids.concat(item.jobOld.done_pe_ids)),
+                            name: name,
+                            pendingList: JSON.stringify(this.pendingList),
+                            package_id: item.jobOld.package_id,
+                            activeName: this.activeName,
+                        };
+                        this.$router.push({ path: '/staffAssDet', query });
+                    } else {
+                        let query = { id: item.jobOld.remark.packageEmployee_id, packageName: item.jobOld.remark.package_name }
+                        this.$router.push({ name: 'resultSet', query });
+                    }
+
+                }
+            }
+
+        },
+
+        changeDialogData(data) {
+            this.dialogData = data;
+        },
+        // 处理未读信息
+        disposeMessage(item) {
+            if (item.type == 1 || item.type == 2) {
+                this.$router.push({
+                    path: '/staffAssDet',
+                    query: {
+                        assID: item.remark.package_id,
+                        employeeID: item.remark.packageEmployee_id,
+                        employeeIDs: item.remark.employee_id
+                    }
+                });
+            } else if (item.type == 3) {
+                //导入
+            } else {
+                //导出
+                window.open(this.$serverdomain + item.remark.file_path);
+            }
+            this.$axiosUser('post', '/api/pro/per/package/msg/cc', { id: item.id, type: item.type, employee_id: item.employee_id }).then(res => { });
+        },
+        //待办数量
+        getAgencyNum() {
+            this.$axiosUser('get', '/api/pro/per/package/msg/agency_num', { status: 0 }, 'v2').then(res => {
+                let data = res.data.data;
+                this.editableTabs[0].num = data.target; // 目标制定
+                this.editableTabs[8].num = data.level_confirm; // 绩效确认
+            });
+        },
+
+        getNewAgencyNum() {
+            this.$axiosUser('get', `/performance/statistics/job/${this.user_info.site_id}`).then(res => {
+                let data = res.data.data;
+                this.editableTabs[1].num = data.targetConfirm; // 确认目标
+                this.editableTabs[2].num = data.resultInput; // 录入结果
+                this.editableTabs[3].num = data.scoreSelf; // 自评
+                this.editableTabs[4].num = data.scoreEachOther; // 互评
+                this.editableTabs[5].num = data.scores; // 评分
+                this.editableTabs[6].num = data.reviews; // 审批
+                this.editableTabs[7].num = data.interviews; // 面谈
+
+            });
+        },
+        //待办
+        getNewAgency() {
+            this.loading = true;
+            this.params.type = this.activeName
+            this.$axiosUser("get", `/performance/review/job/${this.user_info.site_id}`, this.params).then(res => {
+                this.agencyList = res.data.data.list;
+                this.total = res.data.data.total || 0;
+                this.editableTabs.forEach(item => {
+                    if (item.name == this.activeName) item.num = res.data.data.total
+                })
+                this.loading = false;
+            })
+        },
+
+        handleSizeChange(val) {
+            console.log(`每页 ${val} 条`);
+        },
+        handleCurrentChange(val) {
+            console.log(`当前页: ${val}`);
+            this.getAgency();
+        },
+
+        // 待办列表
+        getAgency() {
+            // 旧系统待办列表(只取目标制定,绩效确认,这两个节点)
+            if (this.activeName == 0) {
+                this.loading = true;
+                this.$axiosUser('get', '/api/pro/per/package/msg/agency', {
+                    node_type: this.activeName,
+                    status: 0,
+                    page: this.params.page,
+                    page_size: this.params.pageSize
+                }, 'v2')
+                    .then(res => {
+                        this.loading = false;
+                        let list = res.data.data.list;
+                        this.total = res.data.data.total;
+                        let pendingList = [];
+                        list.forEach(item => {
+                            // 结果值录入
+                            if (item.node_type == 4) {
+                                let userInfo = this.$getEmployeeMapItem(item.first_employee_id);
+                                item.userInfo = userInfo;
+                                let name = item.content.split(',')[0]
+                                pendingList.push({ name: userInfo.name, img_url: userInfo.img_url, package_name: name, pe_ids: item.pe_ids.concat(item.done_pe_ids), employeeID: '结果', package_id: item.package_id });
+                                return false
+                            }
+
+                            if (item.remark.employee_id) {
+                                //被考核人
+                                let userInfo = this.$getEmployeeMapItem(item.remark.employee_id);
+                                item.userInfo = userInfo;
+                                pendingList.push({ name: userInfo.name, img_url: userInfo.img_url, employeeID: item.remark.packageEmployee_id, package_name: item.remark.package_name });
+                            }
+                            if (item.employee_id) {
+                                //当是导入导出时,显示登录者
+                                item.userInfo2 = this.$getEmployeeMapItem(item.employee_id);
+                            }
+                        });
+                        this.pendingList = pendingList;
+                        this.agencyList = list;
+                        this.agencyNum = res.data.data.total;
+                    })
+                    .finally(() => {
+                        this.loading = false;
+                    });
+            }
+
+            else if (this.activeName == 10) this.getPlc()
+            else {
+                //  获取新绩效待办列表
+                this.getNewAgency();
+            }
+        },
+
+        confirmChooseExamine(selectExamineIds) {
+            this.templateIds = selectExamineIds;
+            this.showPublishDialog = true
+        },
+
+        // 发布考核弹框的回调
+        onPubishConfirm(params) {
+            let { templateIds, title, startDate, endDate, cycleType, employeeIds, okrs, cateId, interviewFlow } = params
+            let templates = [], employees = [];
+            for (let i = 0; i < employeeIds.length > 0; i++) {
+                let employeeId
+                employees[i] = {
+                    employeeId: employeeIds[i],
+                    ids: [],
+                    interviewFlow
+                }
+            }
+            for (let i = 0; i < templateIds.length > 0; i++) {
+                let templateId
+                templates[i] = {
+                    templateId: templateIds[i],
+                    employees
+                }
+            }
+
+            this.requestParams = {
+                title,
+                cycleType,
+                startDate,
+                endDate,
+                okrs,
+                templates,
+                cateId
+            }
+            console.log(params)
+            console.log(templates)
+            console.log(this.requestParams)
+            this.batchTemplateDrawer = true
+        },
+
+        handleClose() {
+            this.batchTemplateDrawer = false
+
+        }
+    }
+}
+
+</script>
+
+
+<style scoped="scoped" lang="scss">
+.all {
+    width: 100%;
+    height: 100%;
+    font-size: 14px;
+    border-radius: 4px;
+    display: flex;
+    flex-direction: column;
+}
+
+
+.dispose-left ::v-deep.el-badge__content.is-fixed {
+
+    position: absolute;
+    top: 20px;
+    right: -2px;
+}
+
+.tabs ::v-deep.el-badge__content.is-fixed {
+    position: absolute;
+    top: 50%;
+    right: -2px;
+}
+
+
+
+.item {
+    font-size: 14px;
+    cursor: pointer;
+    padding: 5px;
+    display: flex;
+
+    .flag-old {
+        width: 20px;
+        height: 20px;
+        background: #e6a23c;
+        color: white;
+        border-radius: 50%;
+        text-align: center;
+        line-height: 20px;
+        margin-right: 5px;
+    }
+}
+
+.item:hover {
+    background-color: #f5f7fa;
+}
+
+.content {
+    padding: 0 10px;
+    display: flex;
+
+    .flag-new {
+        width: 20px;
+        height: 20px;
+        background: #409eff;
+        color: white;
+        border-radius: 50%;
+        text-align: center;
+        line-height: 20px;
+        margin-right: 5px;
+    }
+}
+
+.message-content {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    line-height: 22px;
+    font-size: 14px;
+}
+
+.message-title {
+    height: 40px;
+    line-height: 40px;
+    font-size: 16px;
+    font-weight: normal;
+    position: relative;
+    padding: 0 5px;
+}
+
+.open-detail {
+    cursor: pointer;
+}
+
+.dispose-left {
+    height: 300px;
+    background-color: #fff;
+    padding: 10px;
+    border-radius: 5px;
+    box-sizing: border-box;
+}
+
+.template-box {
+    height: 300px;
+    background-color: #fff;
+    padding: 20px;
+    border-radius: 5px;
+    box-sizing: border-box;
+    margin-left: 10px;
+    padding: 10px;
+    box-sizing: border-box;
+
+    .title-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        height: 50px;
+        border-bottom: 1px solid #f1f1f1;
+
+        .tem-title {
+            font-weight: 600;
+            display: flex;
+            align-items: center;
+        }
+    }
+}
+
+.left-main {
+    background-color: #fff;
+    padding: 20px;
+    border-radius: 5px;
+    margin-bottom: 10px;
+    box-sizing: border-box;
+}
+
+.title {
+    font-size: 14px;
+    font-weight: 600;
+}
+
+.tabss {
+    position: absolute;
+    right: 0px;
+    top: 146px;
+    cursor: pointer;
+    z-index: 999;
+
+    .tab-title {
+        position: relative;
+        vertical-align: middle;
+        display: inline-block;
+    }
+}
+
+.plus-button {
+    display: block;
+    color: #494949;
+    font-size: 16px;
+    margin-right: 10px;
+    transition: all 0.3s;
+
+    &:hover {
+        cursor: pointer;
+        color: #409eff;
+    }
+}
+
+
+::v-deep .el-tabs__nav-wrap::after {
+    content: '';
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    width: 100%;
+    height: 1px !important;
+    background-color: #e4e7ed;
+    z-index: 1;
+}
+</style>

+ 127 - 0
src/newPerformance/components/Workbench/BatchTemplatePublish.vue

@@ -0,0 +1,127 @@
+<template>
+    <div>
+        <el-dialog title="请选择考核模板" center :visible.sync="chooseExamineDialog" width="500px"
+            :before-close="dialogBeforeClose">
+            <div class="dialog-content" v-loading="loading">
+                <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange"
+                    style="margin-bottom: 10px;">全选</el-checkbox>
+                <div class="examine-list scroll-bar">
+                    <div class="examine-item" v-for="item in examineList" :key="item.templateId">
+                        <el-checkbox-group v-model="selectExamineIds" @change="changeSelectExamineIds">
+                            <el-checkbox :label="item.templateId"
+                                style="width: 100%; padding: 5px 0; box-sizing: border-box; margin-bottom: 10px; border-bottom: 1px solid #f1f1f1;">
+                                {{ item.title || '默认标题' }}
+                            </el-checkbox>
+                        </el-checkbox-group>
+                    </div>
+                </div>
+
+            </div>
+            <div slot="footer">
+                <el-button @click="dialogBeforeClose()">取 消</el-button>
+                <el-button type="primary" @click="next()" :disable="!(selectExamineIds && selectExamineIds.length > 0)">下一步</el-button>
+            </div>
+        </el-dialog>
+
+    </div>
+
+</template>
+
+
+<script>
+import moment from 'moment';
+import { mapGetters } from 'vuex';
+import InterviewFlow from "../TemplateDetails/InterviewFlow" // 面谈弹框
+
+export default {
+    components: {
+        InterviewFlow
+    },
+    model: {
+        prop: 'chooseExamineDialog',
+        event: 'close-dialog'
+    },
+    props: {
+        chooseExamineDialog: {
+            type: Boolean,
+            default: false
+        },
+        examineList: {
+            type: Array,
+            default: () => []
+        }
+    },
+
+    data() {
+        return {
+            loading: false,
+            selectExamineIds: [],
+            isIndeterminate: false,
+            checkAll: false,
+            interview: false,
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+    },
+    watch: {
+    },
+
+    mounted() {
+        
+    },
+
+    methods: {
+
+        handleCheckAllChange(v) {
+            if (v) {
+                this.examineList.forEach(examine => {
+                    this.selectExamineIds.push(examine.templateId)
+                })
+
+                this.isIndeterminate = true;
+                this.checkAll = true;
+            } else {
+                this.selectExamineIds = [];
+                this.isIndeterminate = false;
+                this.checkAll = false;
+            }
+        },
+        changeSelectExamineIds(v) { },
+
+        dialogBeforeClose() {
+            this.$emit('close-dialog', false)
+        },
+
+        next() {
+            this.$emit("onConfirm", this.selectExamineIds)
+            this.$emit('close-dialog', false)
+        }
+    }
+}
+
+</script>
+
+
+
+<style scoped="scoped" lang="scss">
+.status-btn-box {
+    width: 120px;
+    height: 40px;
+    z-index: 10;
+    position: absolute;
+    top: 20px;
+    left: 20px;
+}
+
+.dialog-content {
+    width: 100%;
+    height: 450px;
+
+    .examine-list {
+        width: 100%;
+        height: 400px;
+        overflow-y: auto;
+    }
+}
+</style>

+ 1007 - 0
src/newPerformance/components/Workbench/EditNode copy.vue

@@ -0,0 +1,1007 @@
+<template>
+    <div>
+        <el-dialog :title="dialogTitle" center :visible.sync="dialogVisible" width="1000px"
+            :before-close="dialogBeforeClose">
+            <div class="status-btn-box fadeInDown animated">
+                <div class="status-btn" :class="reviewStatus == 0 ? 'green-color' : 'gray-color'">
+                    {{ reviewStatus == 0 ? '进行中' : '已结束' }}
+                </div>
+            </div>
+            <div class="dialog-content" v-loading="loading">
+                <div class="dialog-content-left scroll-bar" style="min-height: 500px; overflow-y: auto;">
+                    <!-- 附加属性 -->
+                    <div v-if="dialogData.expand && dialogData.expand.length > 0" class="tips">自定义属性(只有通过excel上传发布的指标才有)
+                    </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>
+                    <!-- 附加属性 -->
+
+                    <!-- 基本信息 -->
+                    <el-descriptions :column="2" size="large" :border="true" style="width: 100%; margin-bottom: 5px;">
+                        <el-descriptions-item>
+                            <template slot="label">
+                                <i class="el-icon-user"></i>
+                                考核人
+                            </template>
+                            {{ dialogData.task && dialogData.task.assigneeName || '--' }}
+                        </el-descriptions-item>
+                        <el-descriptions-item>
+                            <template slot="label">
+                                <i class="el-icon-eleme"></i>
+                                考核名称
+                            </template>
+                            {{ dialogData.reviewTitle }}
+                        </el-descriptions-item>
+                        <el-descriptions-item>
+                            <template slot="label">
+                                <i class="el-icon-date"></i>
+                                周期类型
+                            </template>
+                            <el-tag size="small">{{ dialogData.cycleType | formatCycleType }}</el-tag>
+
+                        </el-descriptions-item>
+
+                        <el-descriptions-item>
+                            <template slot="label">
+                                <i class="el-icon-date"></i>
+                                考核周期
+                            </template>
+                            {{ dialogData.startTime | formatDate }} 至 {{ dialogData.endTime | formatDate }}
+                        </el-descriptions-item>
+
+
+                        <el-descriptions-item>
+                            <template slot="label">
+                                <i class="el-icon-date"></i>
+                                指标
+                            </template>
+                            {{ dialogData.title }}
+                        </el-descriptions-item>
+
+                        <el-descriptions-item>
+                            <template slot="label">
+                                <i class="el-icon-date"></i>
+                                目标
+                            </template>
+                            {{ dialogData.target }}
+                        </el-descriptions-item>
+
+                        <el-descriptions-item>
+                            <template slot="label">
+                                <i class="el-icon-date"></i>
+                                单位
+                            </template>
+                            {{ dialogData.unit }}
+                        </el-descriptions-item>
+
+                        <el-descriptions-item>
+                            <template slot="label">
+                                <i class="el-icon-date"></i>
+                                权重(%)
+                            </template>
+                            {{ dialogData.weight }}
+                        </el-descriptions-item>
+
+                        <el-descriptions-item>
+                            <template slot="label">
+                                <i class="el-icon-date"></i>
+                                结果值
+                            </template>
+                            {{ dialogData.result }}
+                        </el-descriptions-item>
+
+                        <el-descriptions-item>
+                            <template slot="label">
+                                <i class="el-icon-date"></i>
+                                最终评分
+                            </template>
+                            {{ dialogData.score }}
+                        </el-descriptions-item>
+
+                        <el-descriptions-item :span="2">
+                            <template slot="label">
+                                <i class="el-icon-date"></i>
+                                规则
+                            </template>
+                            {{ dialogData.content }}
+                        </el-descriptions-item>
+
+                        <el-descriptions-item :span="2">
+                            <template slot="label">
+                                <i class="el-icon-s-flag"></i>
+                                确认目标
+                            </template>
+                            <div class="content">
+                                <template v-if="confirmTargetTasks && confirmTargetTasks.length > 0">
+                                    <template v-for="task in confirmTargetTasks">
+                                        <div class="item" :key="task.taskId" style="margin-bottom: 5px;">
+
+                                            <div v-if="task.state === 'created'" style="color: #e6a23c">
+
+                                                <el-tooltip effect="dark" placement="right">
+                                                    <div slot="content">
+                                                        进行中
+                                                    </div>
+                                                    <i class="el-icon-reading"></i>
+                                                </el-tooltip>
+                                                {{ task.assigneeName }}
+                                            </div>
+                                            <div v-if="task.state === 'completed'" style="color: #67C23A">
+                                                <el-tooltip effect="dark" placement="right">
+                                                    <div slot="content">
+                                                        已完成
+                                                    </div>
+                                                    <i class="el-icon-check"></i>
+                                                </el-tooltip>
+                                                {{ 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>
+                                                    <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                </el-tooltip>
+                                            </div>
+                                        </div>
+
+                                    </template>
+
+                                </template>
+                            </div>
+                        </el-descriptions-item>
+
+                        <el-descriptions-item :span="2">
+                            <template slot="label">
+                                <i class="el-icon-s-flag"></i>
+                                录入结果
+                            </template>
+                            <div class="content">
+                                <template v-if="resultInputTasks && resultInputTasks.length > 0">
+                                    <template v-for="task in resultInputTasks">
+                                        <div class="item" :key="task.taskId" style="margin-bottom: 5px;">
+                                            <div v-if="task.state === 'created'" style="color: #e6a23c">
+                                                <el-tooltip effect="dark" placement="right">
+                                                    <div slot="content">
+                                                        进行中
+                                                    </div>
+                                                    <i class="el-icon-reading"></i>
+                                                </el-tooltip>
+                                                {{ task.assigneeName }}
+                                                录入结果: {{ task.result || '--' }}
+                                            </div>
+                                            <div v-if="task.state === 'completed'" style="color: #67C23A">
+                                                <el-tooltip effect="dark" placement="right">
+                                                    <div slot="content">
+                                                        已完成
+                                                    </div>
+                                                    <i class="el-icon-check"></i>
+                                                </el-tooltip>
+                                                {{ task.assigneeName }}
+                                                录入结果: {{ task.result || '--' }}{{ dialogData.unit }}
+                                            </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>
+                                                    <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                </el-tooltip>
+                                            </div>
+                                        </div>
+
+                                    </template>
+                                </template>
+                            </div>
+                        </el-descriptions-item>
+
+
+                        <el-descriptions-item :span="2">
+                            <template slot="label">
+                                <i class="el-icon-s-flag"></i>
+                                自评
+                            </template>
+                            <div class="content">
+                                <template v-if="scoreSelfTasks && scoreSelfTasks.length > 0">
+                                    <template v-for="task in scoreSelfTasks">
+                                        <div class="item" :key="task.taskId" style="margin-bottom: 5px;">
+                                            <div v-if="task.state === 'created'" style="color: #e6a23c">
+                                                <el-tooltip effect="dark" placement="right">
+                                                    <div slot="content">
+                                                        进行中
+                                                    </div>
+                                                    <i class="el-icon-reading"></i>
+                                                </el-tooltip>
+                                                {{ task.assigneeName }}
+                                                {{ task.score || '--' }}
+                                            </div>
+                                            <div v-if="task.state === 'completed'" style="color: #67C23A">
+                                                <el-tooltip effect="dark" placement="right">
+                                                    <div slot="content">
+                                                        已完成
+                                                    </div>
+                                                    <i class="el-icon-check"></i>
+                                                </el-tooltip>
+                                                {{ task.assigneeName }}
+                                                评分:{{ task.score || '--' }}分
+                                            </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>
+                                                    <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                </el-tooltip>
+                                            </div>
+                                        </div>
+                                    </template>
+                                </template>
+                            </div>
+                        </el-descriptions-item>
+
+                        <el-descriptions-item :span="2">
+                            <template slot="label">
+                                <i class="el-icon-s-flag"></i>
+                                互评
+                            </template>
+                            <div class="content">
+                                <template v-if="scoreEachOtherTasks && scoreEachOtherTasks.length > 0">
+                                    <template v-for="task in scoreEachOtherTasks">
+                                        <div class="item" :key="task.taskId" style="margin-bottom: 5px;">
+                                            <div v-if="task.state === 'created'" style="color: #e6a23c">
+                                                <el-tooltip effect="dark" placement="right">
+                                                    <div slot="content">
+                                                        进行中
+                                                    </div>
+                                                    <i class="el-icon-reading"></i>
+                                                </el-tooltip>
+                                                {{ task.assigneeName }}
+                                                {{ task.score || '--' }}
+                                            </div>
+                                            <div v-if="task.state === 'completed'" style="color: #67C23A">
+                                                <el-tooltip effect="dark" placement="right">
+                                                    <div slot="content">
+                                                        已完成
+                                                    </div>
+                                                    <i class="el-icon-check"></i>
+                                                </el-tooltip>
+                                                {{ task.assigneeName }}
+                                                评分:{{ task.score || '--' }}分
+                                            </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>
+                                                    <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                </el-tooltip>
+                                            </div>
+                                        </div>
+                                    </template>
+                                </template>
+                            </div>
+
+                        </el-descriptions-item>
+
+                        <el-descriptions-item :span="2">
+                            <template slot="label">
+                                <i class="el-icon-s-flag"></i>
+                                评分
+                            </template>
+                            <div class="content">
+                                <template v-if="scoreTasks && scoreTasks.length > 0">
+                                    <template v-for="task in scoreTasks">
+                                        <div class="item" :key="task.taskId" style="margin-bottom: 5px;">
+                                            <div v-if="task.state === 'created'" style="color: #e6a23c">
+                                                <el-tooltip effect="dark" placement="right">
+                                                    <div slot="content">
+                                                        进行中
+                                                    </div>
+                                                    <i class="el-icon-reading"></i>
+                                                </el-tooltip>
+
+                                                {{ task.assigneeName }}
+                                                {{ task.score || '--' }}
+                                            </div>
+                                            <div v-if="task.state === 'completed'" style="color: #67C23A">
+                                                <el-tooltip effect="dark" placement="right">
+                                                    <div slot="content">
+                                                        已完成
+                                                    </div>
+                                                    <i class="el-icon-check"></i>
+                                                </el-tooltip>
+                                                {{ task.assigneeName }}
+                                                {{ task.score || '--' }}分
+                                            </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>
+                                                    <div class="oneLine" slot="reference">意见:{{ task.comment || '暂无意见'
+                                                        }}</div>
+                                                </el-tooltip>
+                                            </div>
+                                        </div>
+                                    </template>
+                                </template>
+                            </div>
+                        </el-descriptions-item>
+
+                        <el-descriptions-item :span="2">
+                            <template slot="label">
+                                <i class="el-icon-s-flag"></i>
+                                审批
+                            </template>
+
+                            <div class="content">
+                                <template v-if="reviewTasks && reviewTasks.length > 0">
+                                    <template v-for="task in reviewTasks">
+                                        <div class="item" :key="task.taskId" style="margin-bottom: 5px;">
+                                            <div v-if="task.state === 'created'" style="color: #67C23A">
+                                                <el-tooltip effect="dark" placement="right">
+                                                    <div slot="content">
+                                                        进行中
+                                                    </div>
+                                                    <i class="el-icon-reading"></i>
+                                                </el-tooltip>
+                                                {{ task.assigneeName }}
+                                                {{ task.score || '--' }}
+                                            </div>
+                                            <div v-if="task.state === 'completed'" style="color: #e6a23c">
+                                                <el-tooltip effect="dark" placement="right">
+                                                    <div slot="content">
+                                                        已完成
+                                                    </div>
+                                                    <i class="el-icon-check"></i>
+                                                </el-tooltip>
+                                                {{ task.assigneeName }}
+                                                最终分:{{ task.score || '--' }}分
+                                            </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>
+                                                    <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                                </el-tooltip>
+                                            </div>
+                                        </div>
+                                    </template>
+                                </template>
+                            </div>
+
+                        </el-descriptions-item>
+                    </el-descriptions>
+                    <!-- 基本信息 -->
+                    <div style="height: 50px;"></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: 280px;"
+                            clearable @blur="handleEdit('title')" :disabled="!isEdit">
+                            <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: 280px;" rows="4" cols="4" clearable @blur="handleEdit('content')"
+                            :disabled="!isEdit">
+                            <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: 280px;" :disabled="!isEdit" clearable
+                            @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: 280px;" clearable
+                            @blur="handleEdit('unit')" :disabled="!isEdit">
+                            <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: 280px;"
+                            clearable @blur="handleEdit('result')">
+                            <template slot="prepend">结果值</template>
+                        </el-input>
+                    </div>
+
+
+                    <!-- 自评 -->
+                    <div class="data-box" v-if="activeName == 3">
+                        <el-input :controls="false" placeholder="评分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+                            v-model="formData && formData.task && formData.task.score" style="width: 280px;" clearable
+                            @blur="handleEdit('scoreSelf')">
+                            <template slot="prepend">评分</template>
+                        </el-input>
+                    </div>
+
+                    <!-- 互评 -->
+
+                    <div class="data-box" v-if="activeName == 4">
+                        <el-input placeholder="评分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+                            v-model="formData && formData.task && formData.task.score" style="width: 280px;" clearable
+                            @blur="handleEdit('scoreEachOther')">
+                            <template slot="prepend">评分</template>
+                        </el-input>
+                    </div>
+
+
+                    <!-- 评分 -->
+                    <div class="data-box" v-if="activeName == 5">
+
+                        <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>
+                        </div>
+                        <el-input placeholder="评分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+                            v-model="formData && formData.task && formData.task.score" style="width: 280px;" clearable
+                            @blur="handleEdit('score')">
+                            <template slot="prepend">评分</template>
+                        </el-input>
+                    </div>
+
+                    <!-- 审批 -->
+                    <div class="data-box" v-if="activeName == 6">
+                        <el-input disabled placeholder="最终分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+                            v-model="formData && formData.score" style="width: 280px;" clearable>
+                            <template slot="prepend">最终分</template>
+                        </el-input>
+                    </div>
+
+                    <div class="data-box">
+                        <el-input type="textarea" v-model="comment" placeholder="请输入意见" style="width: 280px;" clearable
+                            rows="5" cols="3" @blur="confirmCommentDialog(1)"></el-input>
+                    </div>
+
+
+                </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>
+            </div>
+        </el-dialog>
+
+        <!-- 输入意见 -->
+        <el-dialog title="意见" :visible.sync="commentDialog" width="500px" :before-close="beforCommentDialogClose">
+            <div>
+                <el-input type="textarea" v-model="comment" placeholder="请输入意见" rows="5" cols="5"></el-input>
+            </div>
+            <div slot="footer">
+                <el-button @click="commentDialog = false">取 消</el-button>
+                <el-button type="primary" @click="confirmCommentDialog">确 定</el-button>
+            </div>
+        </el-dialog>
+    </div>
+
+</template>
+
+
+<script>
+import moment from 'moment';
+import { mapGetters } from 'vuex';
+import cloneDeep from 'lodash.clonedeep';
+export default {
+    model: {
+        prop: 'dialogVisible',
+        event: 'close-dialog'
+    },
+    props: {
+        activeName: {
+            type: String,
+            default: "1"
+        },
+        dialogTitle: {
+            type: String,
+            default: ""
+        },
+        dialogVisible: {
+            type: Boolean,
+            default: false
+        },
+        dialogData: {
+            type: Object,
+            default: () => {}
+        }
+    },
+
+    watch: {
+        dialogData(v) {
+            if (this.dialogData) this.formData = cloneDeep(this.dialogData)
+        }
+    },
+    filters: {
+        formatCycleType(val) {
+            if (val == 0) return '未定义'
+            if (val == 1) return '年度'
+            if (val == 2) return '半年'
+            if (val == 3) return '季度'
+            if (val == 4) return '月度' 
+            else return '--' 
+        },
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    data() {
+        return {
+            loading: false,
+            commentDialog: false,
+            comment: "",
+            score: 0,
+            reject: false,
+            formData: null, // 用于提交的数据,指标,目标,规则,
+            baseInfo: null // 用于展示的数据,指标,目标,规则,权重,标题
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        // 是否可以编辑
+        isEdit() {
+            let children = [], selectChild = null;
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes && nodes.length > 0) {
+                nodes.forEach(node => {
+                    if (node.type === 'targetConfirms') {
+                        children = node.children
+                    }
+                })
+            }
+            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
+                            }
+                        })
+                    }
+                })
+            }
+            return selectChild && selectChild.allows && selectChild.allows.length > 0 && selectChild.allows.includes('edit')
+        },
+        // 整个审批的状态 0,进行中 1,已完成
+        reviewStatus() {
+            return this.dialogData && this.dialogData.reviewStatus
+        },
+        // 自评节点
+        scoreSelf() {
+            // 通过解构赋予默认值
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                return this.dialogData.flow.nodes.find(node => node.type == 'scoreSelf')
+            }
+        },
+        // 互评节点
+        scoreEachOther() {
+            // 通过解构赋予默认值
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                return this.dialogData.flow.nodes.find(node => node.type == 'scoreEachOther')
+            }
+        },
+        // 评分节点
+        scoreNode() {
+            // 通过解构赋予默认值
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                return this.dialogData.flow.nodes.find(node => node.type == 'score')
+            }
+        },
+        
+        // 确认目标节点,处理人
+        confirmTargetTasks() {
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                let node = nodes.find(node => node.type === 'targetConfirms');
+                let tasks = []
+                if (node.children && node.children.length > 0) {
+                    node.children.forEach(child => {
+                        if (child.tasks && child.tasks.length > 0) {
+                            child.tasks.forEach(task => {
+                                tasks.push(task)
+                            })
+                        }
+                    })
+                }
+                return tasks
+            }
+            
+        },
+        // 录入结果节点,处理人
+        resultInputTasks() {
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) { 
+                let node = nodes.find(node => node.type === 'resultInput');
+                let tasks = []
+                if (node.tasks && node.tasks.length > 0) {
+                    node.tasks.forEach(task => {
+                        tasks.push(task)
+                    })
+                }
+                return tasks
+            }
+            
+        },
+        // 自评节点,处理人
+        scoreSelfTasks() {
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                let node = nodes.find(node => node.type === 'scoreSelf');
+                let tasks = []
+                if (node.tasks && node.tasks.length > 0) {
+                    node.tasks.forEach(task => {
+                        tasks.push(task)
+                    })
+                }
+                return tasks
+            }
+            
+        },
+        // 互评节点,处理人
+        scoreEachOtherTasks() {
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                let node = nodes.find(node => node.type === 'scoreEachOther');
+                let tasks = []
+                if (node.tasks && node.tasks.length > 0) {
+                    node.tasks.forEach(task => {
+                        tasks.push(task)
+                    })
+                }
+                return tasks
+            }
+        },
+        // 评分节点,处理人
+        scoreTasks() {
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                let node = nodes.find(node => node.type === 'scores');
+                let tasks = []
+                if (node.children && node.children.length > 0) {
+                    node.children.forEach(child => {
+                        if (child.tasks && child.tasks.length > 0) {
+                            child.tasks.forEach(task => {
+                                tasks.push(task)
+                            })
+                        }
+                    })
+                }
+                return tasks
+            }
+            
+        },
+        // 审批节点,处理人
+        reviewTasks() {
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                let node = nodes.find(node => node.type === 'reviews');
+                let tasks = []
+                if (node.children && node.children.length > 0) {
+                    node.children.forEach(child => {
+                        if (child.tasks && child.tasks.length > 0) {
+                            child.tasks.forEach(task => {
+                                tasks.push(task)
+                            })
+                        }
+                    })
+                }
+                return tasks
+            }
+            
+        }
+    },
+    mounted() {
+        if (this.dialogData) this.formData = cloneDeep(this.dialogData);
+        // if (this.dialogData) this.baseInfo = cloneDeep(this.dialogData);
+    },
+    methods: {
+
+        handleScore() {
+            this.formData.task.score = this.dialogData.scoreExpression
+        },
+        dialogBeforeClose() {
+            this.$emit('close-dialog', false)
+        },
+
+        beforCommentDialogClose() {
+            this.commentDialog = false;
+        },
+
+        changeCommentShow(item) {
+            console.log(item)
+            item.isCommentShow = true
+            console.log(item)
+
+        },
+
+        // 编辑节点
+        handleEdit(type) { 
+            this.loading = true
+            let { reviewIndicatorId } = this.dialogData
+            let { taskId, nodeType } = this.dialogData.task
+            let data = { taskId }
+            if (type === 'scoreSelf' || type === 'scoreEachOther' || type === 'score') {
+                data['score'] = this.formData.task['score'];
+            }
+            else data[type] = this.formData[type];
+            let url;
+            switch (nodeType) {
+                // 确认目标
+                case 'targetConfirm':
+                    url = `/performance/review/target/confirm/${type}/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 录入结果值
+                case 'resultInput':
+                    url = `/performance/review/result/Input/${type}/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 自评
+                case 'scoreSelf':
+                    url = `/performance/review/score/self/score/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 互评
+                case 'scoreEachOther':
+                    url = `/performance/review/score/each/other/score/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break; 
+                // 评分
+                case 'score':
+                    url = `/performance/review/score/score/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;  
+                default:
+                    break;
+            }  
+            this.$http.post(url, data).then(res => {
+                // console.log(res)
+                // { code: 0, message: '非法操作', data: null }
+                if (res.code == 1) {
+                    let { data } = res
+                    this.$emit('handleEditSuccess', data)
+                }
+                else this.$message.error(res.message || '操作失败');
+                this.loading = false
+            })
+        },
+
+        confirmCommentDialog(type) {
+            if(this.loading) return
+            let { reviewIndicatorId } = this.dialogData;
+            let { taskId, nodeType } = this.dialogData.task
+            let url;
+            switch (nodeType) {
+                case 'targetConfirm':
+                    url = `/performance/review/target/confirm/complete/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                case 'resultInput':
+                    url = `/performance/review/result/Input/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 自评
+                case 'scoreSelf':
+                    url = `/performance/review/score/self/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break; 
+                // 互评
+                case 'scoreEachOther':
+                    url = `/performance/review/score/each/other/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 评分
+                case 'score':
+                    url = `/performance/review/score/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                case 'review':
+                    if (this.reject) url = `/performance/review/review/refuse/${this.user_info.site_id}/${reviewIndicatorId}`
+                    else url = `/performance/review/review/pass/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break; 
+                default:
+                    break;
+            }
+            let data = {
+                taskId,
+                comment: this.comment,
+                complete: type == 1 ? false : true // false 暂存,true 提交
+            }
+            this.$http.post(url, data).then(res => {
+                if (type == 1) {
+                    if (res.code == 1) { }
+                    else this.$message.error(res.message || '操作失败');
+                } else {
+                    if (res.code == 1) {
+                        this.$message.success("操作成功");
+                        this.$emit("handleSuccess")
+                        this.$emit('close-dialog', false);
+                        this.commentDialog = false;
+                        this.comment = "";
+                        this.reject = false; // 驳回标识
+                    } else {
+                        this.$message.error(res.message || '操作失败');
+                    }
+                }
+                
+            })
+        },
+        // 驳回
+        reset() {
+            this.reject = true;
+            this.commentDialog = true;
+        },
+        // 提交
+        confirm() {
+            this.commentDialog = true;
+        },
+    }
+}
+
+</script>
+
+<style lang="scss">
+
+    .dialog-content { 
+        .el-descriptions-item__label {
+            width: 120px;
+        }
+    }
+
+    .oneLine {
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+    }
+</style>
+
+
+<style scoped="scoped" lang="scss">
+.status-box {
+    padding: 2px;
+    display: inline-block;
+    border: 1px solid;
+    box-sizing: border-box;
+    border-radius: 4px;
+}
+.green-color {
+    border-color: #67C23A;
+    color: #67C23A;
+}
+
+
+.gray-color {
+    border-color: #f56c6c;
+    color: #f56c6c;
+}
+.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;
+    }
+}
+.dialog-content {
+    width: 100%;
+    height: 450px;
+    display: flex;
+    .tips {
+        height: 30px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        color: #999;
+        margin: 0 auto 10px auto;
+    }
+    .dialog-content-left, .dialog-content-right {
+        width: 65%;
+        height: 100%;
+        padding: 0 10px;
+        box-sizing: border-box;
+    }
+    .dialog-content-left {
+        border-right: 1px solid #ccc;
+        .content {
+            width: 100%;
+            // padding: 10px;
+            // background-color: #f7f7f7;
+            // color: #999;
+            .item {
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+                // font-size: 12px;
+            }
+        }
+    }
+    
+    .dialog-content-right {
+        width: 35%;
+    }
+    
+}
+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;
+        /* 奇数行背景色 */
+    }
+}
+
+.data-box {
+    width: 500px;
+    height: 60px;
+    margin-bottom: 10px;
+
+    .label {
+        width: 160px;
+        i {
+            font-size: 16px;
+            font-weight: 500;
+            margin-right: 5px;
+            color: #409EFF;
+        }
+    }
+
+    .value {
+        display: flex;
+        align-items: center;
+    }
+}
+</style>

+ 701 - 0
src/newPerformance/components/Workbench/EditNode.vue

@@ -0,0 +1,701 @@
+<template>
+    <div>
+        <el-dialog :title="dialogTitle" center :visible.sync="dialogVisible" width="800px"
+            :before-close="dialogBeforeClose">
+            <div class="status-btn-box fadeInDown animated">
+                <el-link type="primary" @click="detailsDialogVisible = true">查看处理详情</el-link>
+            </div>
+
+            <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>
+                    <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 || '--' }}
+                        </div>
+                    </div>
+
+                    <div class="base-info-box">
+                        <div class="label">
+                            <i class="el-icon-eleme"></i>
+                            考核名称
+                        </div>
+                        <div class="value">
+                            {{ dialogData.reviewTitle || '--' }}
+                        </div>
+                    </div>
+
+                    <div class="base-info-box">
+                        <div class="label">
+                            <i class="el-icon-eleme"></i>
+                            考核状态
+                        </div>
+                        <div class="value">
+                            <div class="status-btn" :class="reviewStatus == 0 ? 'green-color' : 'gray-color'">
+                                {{ reviewStatus == 0 ? '进行中' : '已结束' }}
+                            </div>
+                        </div>
+                    </div>
+
+                    <div class="base-info-box">
+                        <div class="label">
+                            <i class="el-icon-date"></i>
+                            周期类型
+                        </div>
+                        <div class="value">
+                            <el-tag size="small">{{ dialogData.cycleType | formatCycleType }}</el-tag>
+                        </div>
+                    </div>
+
+                    <div class="base-info-box">
+                        <div class="label">
+                            <i class="el-icon-time"></i>
+                            考核周期
+                        </div>
+                        <div class="value">
+                            {{ dialogData.startTime | formatDate }} 至 {{ dialogData.endTime | formatDate }}
+                        </div>
+                    </div>
+
+                    <div class="base-info-box">
+                        <div class="label">
+                            <i class="el-icon-thumb"></i>
+                            指标
+                        </div>
+                        <div class="value">
+                            {{ dialogData.title || '--'}}
+                        </div>
+                    </div>
+
+                    <div class="base-info-box">
+                        <div class="label">
+                            <i class="el-icon-aim"></i>
+                            目标
+                        </div>
+                        <div class="value">
+                            {{ dialogData.target || '--' }}
+                        </div>
+                    </div>
+
+                    <div class="base-info-box">
+                        <div class="label">
+                            <i class="el-icon-s-check"></i>
+                            单位
+                        </div>
+                        <div class="value">
+                            {{ dialogData.unit || '--' }}
+                        </div>
+                    </div>
+
+                    <div class="base-info-box">
+                        <div class="label">
+                            <i class="el-icon-pie-chart"></i>
+                            权重(%)
+                        </div>
+                        <div class="value">
+                            {{ dialogData.weight || '--' }}
+                        </div>
+                    </div>
+
+                    <div class="base-info-box">
+                        <div class="label">
+                            <i class="el-icon-s-flag"></i>
+                            结果值
+                        </div>
+                        <div class="value">
+                            {{ dialogData.result || '--' }}
+                        </div>
+                    </div>
+
+                    <div class="base-info-box">
+                        <div class="label">
+                            <i class="el-icon-document-checked"></i>
+                            最终评分
+                        </div>
+                        <div class="value">
+                            {{ dialogData.score || '--' }}
+                        </div>
+                    </div>
+
+                    <div class="base-info-box">
+                        <div class="label">
+                            <i class="el-icon-document"></i>
+                            规则
+                        </div>
+                        <div v-if="dialogData.content" class="value text-style" v-html="dialogData.content">
+                        </div>
+                        <div v-else>
+                            --
+                        </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">
+                            <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">
+                            <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
+                            @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">
+                            <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')">
+                            <template slot="prepend">结果值</template>
+                        </el-input>
+                    </div>
+
+
+                    <!-- 自评 -->
+                    <div class="data-box" v-if="activeName == 3">
+                        <el-input :controls="false" placeholder="评分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+                            v-model="formData && formData.task && formData.task.score" style="width: 300px;" clearable
+                            @blur="handleEdit('scoreSelf')">
+                            <template slot="prepend">评分</template>
+                        </el-input>
+                    </div>
+
+                    <!-- 互评 -->
+
+                    <div class="data-box" v-if="activeName == 4">
+                        <el-input placeholder="评分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+                            v-model="formData && formData.task && formData.task.score" style="width: 300px;" clearable
+                            @blur="handleEdit('scoreEachOther')">
+                            <template slot="prepend">评分</template>
+                        </el-input>
+                    </div>
+
+
+                    <!-- 评分 -->
+                    <div class="data-box" v-if="activeName == 5">
+
+                        <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>
+                        </div>
+                        <el-input placeholder="评分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+                            v-model="formData && formData.task && formData.task.score" style="width: 300px;" clearable
+                            @blur="handleEdit('score')">
+                            <template slot="prepend">评分</template>
+                        </el-input>
+                    </div>
+
+                    <!-- 审批 -->
+                    <div class="data-box" v-if="activeName == 6">
+                        <el-input disabled placeholder="最终分(数字)" oninput="value=value.replace(/[^\d.]/g,'')"
+                            v-model="formData && formData.score" style="width: 300px;" clearable>
+                            <template slot="prepend">最终分</template>
+                        </el-input>
+                    </div>
+
+                    <div class="data-box">
+                        <el-input type="textarea" v-model="comment" placeholder="请输入意见" style="width: 300px;" clearable
+                            rows="5" cols="3" @blur="confirmCommentDialog(1)"></el-input>
+                    </div>
+
+
+                </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>
+            </div>
+        </el-dialog>
+
+        <!-- 输入意见 -->
+        <el-dialog title="意见" :visible.sync="commentDialog" width="500px" :before-close="beforCommentDialogClose">
+            <div>
+                <el-input type="textarea" v-model="comment" placeholder="请输入意见" rows="5" cols="5"></el-input>
+            </div>
+            <div slot="footer">
+                <el-button @click="commentDialog = false">取 消</el-button>
+                <el-button type="primary" @click="confirmCommentDialog">确 定</el-button>
+            </div>
+        </el-dialog>
+
+        <DetailsDialog v-model="detailsDialogVisible" :dialog-data="dialogData" />
+    </div>
+
+</template>
+
+
+<script>
+import moment from 'moment';
+import { mapGetters } from 'vuex';
+import cloneDeep from 'lodash.clonedeep';
+import DetailsDialog from "./EditNodeDialog.vue"
+export default {
+    components: {
+        DetailsDialog
+    },
+    model: {
+        prop: 'dialogVisible',
+        event: 'close-dialog'
+    },
+    props: {
+        activeName: {
+            type: String,
+            default: "1"
+        },
+        dialogTitle: {
+            type: String,
+            default: ""
+        },
+        dialogVisible: {
+            type: Boolean,
+            default: false
+        },
+        dialogData: {
+            type: Object,
+            default: () => {}
+        }
+    },
+
+    watch: {
+        dialogData(v) {
+            if (this.dialogData) {
+                this.formData = cloneDeep(this.dialogData)
+                this.comment = this.formData.task.comment
+            }
+            
+        }
+    },
+    filters: {
+        formatCycleType(val) {
+            if (val == 0) return '未定义'
+            if (val == 1) return '年度'
+            if (val == 2) return '半年'
+            if (val == 3) return '季度'
+            if (val == 4) return '月度' 
+            else return '--' 
+        },
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    data() {
+        return {
+            loading: false,
+            commentDialog: false,
+            comment: "",
+            score: 0,
+            reject: false,
+            formData: null, // 用于提交的数据,指标,目标,规则,
+            baseInfo: null, // 用于展示的数据,指标,目标,规则,权重,标题
+            detailsDialogVisible: false
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        // 是否可以编辑
+        isEdit() {
+            let children = [], selectChild = null;
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes && nodes.length > 0) {
+                nodes.forEach(node => {
+                    if (node.type === 'targetConfirms') {
+                        children = node.children
+                    }
+                })
+            }
+            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
+                            }
+                        })
+                    }
+                })
+            }
+            return selectChild && selectChild.allows && selectChild.allows.length > 0 && selectChild.allows.includes('edit')
+        },
+        // 整个审批的状态 0,进行中 1,已完成
+        reviewStatus() {
+            return this.dialogData && this.dialogData.reviewStatus
+        },
+        // 自评节点
+        scoreSelf() {
+            // 通过解构赋予默认值
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                return this.dialogData.flow.nodes.find(node => node.type == 'scoreSelf')
+            }
+        },
+        // 互评节点
+        scoreEachOther() {
+            // 通过解构赋予默认值
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                return this.dialogData.flow.nodes.find(node => node.type == 'scoreEachOther')
+            }
+        },
+        // 评分节点
+        scoreNode() {
+            // 通过解构赋予默认值
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                return this.dialogData.flow.nodes.find(node => node.type == 'score')
+            }
+        },
+        
+    },
+    mounted() {
+        if (this.dialogData) {
+            this.formData = cloneDeep(this.dialogData);
+            this.comment = this.formData && this.formData.task && this.formData.task.comment ? this.formData.task.comment : ''
+        } 
+    },
+    methods: {
+
+        // 录入系统评分
+        handleScore() {
+            this.formData.task.score = this.dialogData.scoreExpression
+        },
+        dialogBeforeClose() {
+            this.comment = '';
+            this.$emit('close-dialog', false)
+        },
+
+        beforCommentDialogClose() {
+            this.commentDialog = false;
+        },
+
+
+        // 编辑节点
+        handleEdit(type) { 
+            this.loading = true
+            let { reviewIndicatorId } = this.dialogData
+            let { taskId, nodeType } = this.dialogData.task
+            let data = { taskId }
+            if (type === 'scoreSelf' || type === 'scoreEachOther' || type === 'score') {
+                data['score'] = this.formData.task['score'];
+            }
+            
+            else data[type] = this.formData[type];
+            let url;
+            switch (nodeType) {
+                // 确认目标
+                case 'targetConfirm':
+                    url = `/performance/review/target/confirm/${type}/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 录入结果值
+                case 'resultInput':
+                    url = `/performance/review/result/Input/${type}/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 自评
+                case 'scoreSelf':
+                    url = `/performance/review/score/self/score/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 互评
+                case 'scoreEachOther':
+                    url = `/performance/review/score/each/other/score/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break; 
+                // 评分
+                case 'score':
+                    url = `/performance/review/score/score/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;  
+                default:
+                    break;
+            }  
+            if (!url) return 
+            this.$http.post(url, data).then(res => {
+                if (res.code == 1) {
+                    let { data } = res
+                    this.$emit('handleEditSuccess', data)
+                }
+                else this.$message.error(res.message || '操作失败');
+                this.loading = false
+            })
+        },
+
+        confirmCommentDialog(type) {
+            if(this.loading) return
+            let { reviewIndicatorId } = this.dialogData;
+            let { taskId, nodeType } = this.dialogData.task
+            let url;
+            switch (nodeType) {
+                case 'targetConfirm':
+                    url = `/performance/review/target/confirm/complete/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                case 'resultInput':
+                    url = `/performance/review/result/Input/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 自评
+                case 'scoreSelf':
+                    url = `/performance/review/score/self/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break; 
+                // 互评
+                case 'scoreEachOther':
+                    url = `/performance/review/score/each/other/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                // 评分
+                case 'score':
+                    url = `/performance/review/score/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break;
+                case 'review':
+                    if (this.reject) url = `/performance/review/review/refuse/${this.user_info.site_id}/${reviewIndicatorId}`
+                    else url = `/performance/review/review/pass/${this.user_info.site_id}/${reviewIndicatorId}`
+                    break; 
+                default:
+                    break;
+            }
+            let data = {
+                taskId,
+                comment: this.comment,
+                complete: type == 1 ? false : true // false 暂存,true 提交
+            }
+            this.$http.post(url, data).then(res => {
+                if (type == 1) {
+                    if (res.code == 1) { }
+                    else this.$message.error(res.message || '操作失败');
+                } else {
+                    if (res.code == 1) {
+                        this.$message.success("操作成功");
+                        this.$emit("handleSuccess")
+                        this.$emit('close-dialog', false);
+                        this.formData = null;
+                        this.commentDialog = false;
+                        this.comment = "";
+                        this.reject = false; // 驳回标识
+                    } else {
+                        this.$message.error(res.message || '操作失败');
+                    }
+                }
+                
+            })
+        },
+        // 驳回
+        reset() {
+            this.reject = true;
+            this.commentDialog = true;
+        },
+        // 提交
+        confirm() {
+            this.commentDialog = true;
+        },
+    }
+}
+
+</script>
+
+<style lang="scss">
+
+    .dialog-content { 
+        .el-descriptions-item__label {
+            width: 120px;
+        }
+    }
+
+    .oneLine {
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+    }
+</style>
+
+
+<style scoped="scoped" lang="scss">
+.status-box {
+    padding: 2px;
+    display: inline-block;
+    border: 1px solid;
+    box-sizing: border-box;
+    border-radius: 4px;
+}
+.green-color {
+    border-color: #67C23A;
+    color: #67C23A;
+}
+
+
+.gray-color {
+    border-color: #f56c6c;
+    color: #f56c6c;
+}
+.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;
+    }
+}
+.dialog-content {
+    width: 100%;
+    height: 450px;
+    display: flex;
+    .tips {
+        height: 30px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        color: #999;
+        margin: 0 auto 10px auto;
+    }
+    .dialog-content-left, .dialog-content-right {
+        width: 50%;
+        height: 100%;
+        padding: 0 10px;
+        box-sizing: border-box;
+    }
+    .dialog-content-left {
+        border-right: 1px solid #f1f1f1;
+    }
+    
+    .dialog-content-right {
+        width: 50%;
+    }
+    
+}
+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%;
+    display: flex;
+    margin-bottom: 20px;
+    .label {
+        display: flex;
+        align-items: center;
+        color: #606266;
+        width: 120px;
+        i {
+            margin-right: 5px;
+        }
+    }
+    .value {
+        flex: 1;
+    }
+    .text-style {
+        padding: 5px;
+        border-radius: 4px;
+        background: #f7f7f7;
+        white-space: pre-line;
+        box-sizing: border-box;
+    }
+}
+
+.data-box {
+    width: 500px;
+    height: 60px;
+    margin-bottom: 10px;
+
+    .label {
+        width: 160px;
+        i {
+            font-size: 16px;
+            font-weight: 500;
+            margin-right: 5px;
+            color: #409EFF;
+        }
+    }
+
+    .value {
+        display: flex;
+        align-items: center;
+    }
+}
+</style>

+ 448 - 0
src/newPerformance/components/Workbench/EditNodeDialog.vue

@@ -0,0 +1,448 @@
+<template>
+    <div>
+        <el-dialog title="处理详情" center :visible.sync="detailsDialogVisible" width="600px"
+            :before-close="dialogBeforeClose">
+            <el-descriptions :column="2" size="large" :border="true" style="width: 100%; margin-bottom: 5px;">
+
+                <el-descriptions-item :span="2">
+                    <template slot="label">
+                        <i class="el-icon-s-flag"></i>
+                        确认目标
+                    </template>
+                    <div class="content">
+                        <template v-if="confirmTargetTasks && confirmTargetTasks.length > 0">
+                            <template v-for="task in confirmTargetTasks">
+                                <div class="item" :key="task.taskId" style="margin-bottom: 5px;">
+
+                                    <div v-if="task.state === 'created'" style="color: #e6a23c">
+
+                                        <!-- <el-tooltip effect="dark" placement="right">
+                                            <div slot="content">
+                                                进行中
+                                            </div>
+                                            <i class="el-icon-reading"></i>
+                                        </el-tooltip> -->
+                                        进行中 {{ task.assigneeName }}
+                                    </div>
+                                    <div v-if="task.state === 'completed'" style="color: #67C23A">
+                                        <!-- <el-tooltip effect="dark" placement="right">
+                                            <div slot="content">
+                                                已完成
+                                            </div>
+                                            <i class="el-icon-check"></i>
+                                        </el-tooltip> -->
+                                        已完成 {{ 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>
+                                            <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                        </el-tooltip>
+                                    </div>
+                                </div>
+
+                            </template>
+
+                        </template>
+                    </div>
+                </el-descriptions-item>
+
+                <el-descriptions-item :span="2">
+                    <template slot="label">
+                        <i class="el-icon-s-flag"></i>
+                        录入结果
+                    </template>
+                    <div class="content">
+                        <template v-if="resultInputTasks && resultInputTasks.length > 0">
+                            <template v-for="task in resultInputTasks">
+                                <div class="item" :key="task.taskId" style="margin-bottom: 5px;">
+                                    <div v-if="task.state === 'created'" style="color: #e6a23c">
+                                        <!-- <el-tooltip effect="dark" placement="right">
+                                            <div slot="content">
+                                                进行中
+                                            </div>
+                                            <i class="el-icon-reading"></i>
+                                        </el-tooltip> -->
+                                        进行中 {{ task.assigneeName }}
+                                        录入结果: {{ task.result || '--' }}
+                                    </div>
+                                    <div v-if="task.state === 'completed'" style="color: #67C23A">
+                                        <!-- <el-tooltip effect="dark" placement="right">
+                                            <div slot="content">
+                                                已完成
+                                            </div>
+                                            <i class="el-icon-check"></i>
+                                        </el-tooltip> -->
+                                        已完成 {{ task.assigneeName }}
+                                        录入结果: {{ task.result || '--' }}{{ dialogData.unit }}
+                                    </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>
+                                            <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                        </el-tooltip>
+                                    </div>
+                                </div>
+
+                            </template>
+                        </template>
+                    </div>
+                </el-descriptions-item>
+
+
+                <el-descriptions-item :span="2">
+                    <template slot="label">
+                        <i class="el-icon-s-flag"></i>
+                        自评
+                    </template>
+                    <div class="content">
+                        <template v-if="scoreSelfTasks && scoreSelfTasks.length > 0">
+                            <template v-for="task in scoreSelfTasks">
+                                <div class="item" :key="task.taskId" style="margin-bottom: 5px;">
+                                    <div v-if="task.state === 'created'" style="color: #e6a23c">
+                                        <!-- <el-tooltip effect="dark" placement="right">
+                                            <div slot="content">
+                                                进行中
+                                            </div>
+                                            <i class="el-icon-reading"></i>
+                                        </el-tooltip> -->
+                                        进行中
+                                        {{ task.assigneeName }}
+                                        {{ task.score || '--' }}
+                                    </div>
+                                    <div v-if="task.state === 'completed'" style="color: #67C23A">
+                                        <!-- <el-tooltip effect="dark" placement="right">
+                                            <div slot="content">
+                                                已完成
+                                            </div>
+                                            <i class="el-icon-check"></i>
+                                        </el-tooltip> -->
+                                        已完成
+                                        {{ task.assigneeName }}
+                                        评分:{{ task.score || '--' }}分
+                                    </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>
+                                            <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                        </el-tooltip>
+                                    </div>
+                                </div>
+                            </template>
+                        </template>
+                    </div>
+                </el-descriptions-item>
+
+                <el-descriptions-item :span="2">
+                    <template slot="label">
+                        <i class="el-icon-s-flag"></i>
+                        互评
+                    </template>
+                    <div class="content">
+                        <template v-if="scoreEachOtherTasks && scoreEachOtherTasks.length > 0">
+                            <template v-for="task in scoreEachOtherTasks">
+                                <div class="item" :key="task.taskId" style="margin-bottom: 5px;">
+                                    <div v-if="task.state === 'created'" style="color: #e6a23c">
+                                        <!-- <el-tooltip effect="dark" placement="right">
+                                            <div slot="content">
+                                                进行中
+                                            </div>
+                                            <i class="el-icon-reading"></i>
+                                        </el-tooltip> -->
+                                        进行中
+                                        {{ task.assigneeName }}
+                                        {{ task.score || '--' }}
+                                    </div>
+                                    <div v-if="task.state === 'completed'" style="color: #67C23A">
+                                        <!-- <el-tooltip effect="dark" placement="right">
+                                            <div slot="content">
+                                                已完成
+                                            </div>
+                                            <i class="el-icon-check"></i>
+                                        </el-tooltip> -->
+                                        已完成
+                                        {{ task.assigneeName }}
+                                        评分:{{ task.score || '--' }}分
+                                    </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>
+                                            <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                        </el-tooltip>
+                                    </div>
+                                </div>
+                            </template>
+                        </template>
+                    </div>
+
+                </el-descriptions-item>
+
+                <el-descriptions-item :span="2">
+                    <template slot="label">
+                        <i class="el-icon-s-flag"></i>
+                        评分
+                    </template>
+                    <div class="content">
+                        <template v-if="scoreTasks && scoreTasks.length > 0">
+                            <template v-for="task in scoreTasks">
+                                <div class="item" :key="task.taskId" style="margin-bottom: 5px;">
+                                    <div v-if="task.state === 'created'" style="color: #e6a23c">
+                                        <!-- <el-tooltip effect="dark" placement="right">
+                                            <div slot="content">
+                                                进行中
+                                            </div>
+                                            <i class="el-icon-reading"></i>
+                                        </el-tooltip> -->
+                                        进行中
+                                        {{ task.assigneeName }}
+                                        {{ task.score || '--' }}
+                                    </div>
+                                    <div v-if="task.state === 'completed'" style="color: #67C23A">
+                                        <!-- <el-tooltip effect="dark" placement="right">
+                                            <div slot="content">
+                                                已完成
+                                            </div>
+                                            <i class="el-icon-check"></i>
+                                        </el-tooltip> -->
+                                        已完成
+                                        {{ task.assigneeName }}
+                                        {{ task.score || '--' }}分
+                                    </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>
+                                            <div class="oneLine" slot="reference">意见:{{ task.comment ||
+                                                '暂无意见'
+                                                }}</div>
+                                        </el-tooltip>
+                                    </div>
+                                </div>
+                            </template>
+                        </template>
+                    </div>
+                </el-descriptions-item>
+
+                <el-descriptions-item :span="2">
+                    <template slot="label">
+                        <i class="el-icon-s-flag"></i>
+                        审批
+                    </template>
+
+                    <div class="content">
+                        <template v-if="reviewTasks && reviewTasks.length > 0">
+                            <template v-for="task in reviewTasks">
+                                <div class="item" :key="task.taskId" style="margin-bottom: 5px;">
+                                    <div v-if="task.state === 'created'" style="color: #67C23A">
+                                        <!-- <el-tooltip effect="dark" placement="right">
+                                            <div slot="content">
+                                                进行中
+                                            </div>
+                                            <i class="el-icon-reading"></i>
+                                        </el-tooltip> -->
+                                        进行中
+                                        {{ task.assigneeName }}
+                                        {{ task.score || '--' }}
+                                    </div>
+                                    <div v-if="task.state === 'completed'" style="color: #e6a23c">
+                                        <!-- <el-tooltip effect="dark" placement="right">
+                                            <div slot="content">
+                                                已完成
+                                            </div>
+                                            <i class="el-icon-check"></i>
+                                        </el-tooltip> -->
+                                        已完成
+                                        {{ task.assigneeName }}
+                                        最终分:{{ task.score || '--' }}分
+                                    </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>
+                                            <div class="oneLine">意见:{{ task.comment || '暂无意见' }}</div>
+                                        </el-tooltip>
+                                    </div>
+                                </div>
+                            </template>
+                        </template>
+                    </div>
+
+                </el-descriptions-item>
+            </el-descriptions>
+        </el-dialog>
+    </div>
+
+</template>
+
+
+<script>
+import { mapGetters } from 'vuex';
+export default {
+    model: {
+        prop: 'detailsDialogVisible',
+        event: 'close-dialog'
+    },
+    props: {
+        detailsDialogVisible: {
+            type: Boolean,
+            default: false
+        },
+        dialogData: {
+            type: Object,
+            default: () => {}
+        }
+    },
+
+    
+    data() {
+        return {
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        
+        
+        // 确认目标节点,处理人
+        confirmTargetTasks() {
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                let node = nodes.find(node => node.type === 'targetConfirms');
+                let tasks = []
+                if (node.children && node.children.length > 0) {
+                    node.children.forEach(child => {
+                        if (child.tasks && child.tasks.length > 0) {
+                            child.tasks.forEach(task => {
+                                tasks.push(task)
+                            })
+                        }
+                    })
+                }
+                return tasks
+            }
+            
+        },
+        // 录入结果节点,处理人
+        resultInputTasks() {
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) { 
+                let node = nodes.find(node => node.type === 'resultInput');
+                let tasks = []
+                if (node.tasks && node.tasks.length > 0) {
+                    node.tasks.forEach(task => {
+                        tasks.push(task)
+                    })
+                }
+                return tasks
+            }
+            
+        },
+        // 自评节点,处理人
+        scoreSelfTasks() {
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                let node = nodes.find(node => node.type === 'scoreSelf');
+                let tasks = []
+                if (node.tasks && node.tasks.length > 0) {
+                    node.tasks.forEach(task => {
+                        tasks.push(task)
+                    })
+                }
+                return tasks
+            }
+            
+        },
+        // 互评节点,处理人
+        scoreEachOtherTasks() {
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                let node = nodes.find(node => node.type === 'scoreEachOther');
+                let tasks = []
+                if (node.tasks && node.tasks.length > 0) {
+                    node.tasks.forEach(task => {
+                        tasks.push(task)
+                    })
+                }
+                return tasks
+            }
+        },
+        // 评分节点,处理人
+        scoreTasks() {
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                let node = nodes.find(node => node.type === 'scores');
+                let tasks = []
+                if (node.children && node.children.length > 0) {
+                    node.children.forEach(child => {
+                        if (child.tasks && child.tasks.length > 0) {
+                            child.tasks.forEach(task => {
+                                tasks.push(task)
+                            })
+                        }
+                    })
+                }
+                return tasks
+            }
+            
+        },
+        // 审批节点,处理人
+        reviewTasks() {
+            const { nodes = [] } = this.dialogData && this.dialogData.flow || {};
+            if (nodes.length > 0) {
+                let node = nodes.find(node => node.type === 'reviews');
+                let tasks = []
+                if (node.children && node.children.length > 0) {
+                    node.children.forEach(child => {
+                        if (child.tasks && child.tasks.length > 0) {
+                            child.tasks.forEach(task => {
+                                tasks.push(task)
+                            })
+                        }
+                    })
+                }
+                return tasks
+            }
+            
+        }
+    },
+    mounted() {
+    },
+    methods: {
+        dialogBeforeClose() {
+            this.$emit('close-dialog', false)
+        },
+
+    }
+}
+
+</script>
+
+<style lang="scss">
+
+    .el-descriptions-item__label {
+        width: 120px;
+    }
+
+</style>
+
+
+<style scoped="scoped" lang="scss">
+
+.content {
+    width: 100%;
+
+    // padding: 10px;
+    // background-color: #f7f7f7;
+    // color: #999;
+    .item {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        // font-size: 12px;
+    }
+}
+
+</style>

+ 486 - 0
src/newPerformance/components/Workbench/IndicatorList copy.vue

@@ -0,0 +1,486 @@
+<template>
+    <!-- 指标审批管理 -->
+    <div class="gl-all" v-loading="staffLoad">
+        <div class="search-box flex-box">
+            <div class="flex-box-ce">
+                <el-date-picker v-model="date" type="daterange" align="right" unlink-panels range-separator="至"
+                    start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd"
+                    :picker-options="pickerOptions" @change="changeDate">
+                </el-date-picker>
+
+                <div v-if="glIndex == 2" style=" margin-left: 10px;">
+                    <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple filterable
+                        style="width: 300px;" @change="changeEmployeeIds" clearable>
+                        <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
+                            style="width: 300px;"></el-option>
+                    </el-select>
+                </div>
+
+                <div v-if="glIndex == 3" class="w270" style="position: relative; margin-left: 10px;">
+                    <el-input auto-complete="off" v-model="deptVisibleName" placeholder="请选择部门"></el-input>
+                    <div @click="openDeptSelector"
+                        style=" position: absolute; top: 0; right: 0; left: 0; bottom: 0; z-index: 9;"></div>
+                </div>
+
+                <div style="margin-left: 10px; width:300px;">
+                    <JxSearch :screen="2" title="请输入考核表标题或指标标题" @confirm="searchList"></JxSearch>
+                </div>
+            </div>
+            <div class="flex-box-ce" style="margin-right: 60px;">
+                <div class="status-box" style="margin-right: 5px;">
+                    <span class="green-color-box"></span>已完成
+                </div>
+                <div class="status-box" style="margin-right: 5px;">
+                    <span class="orange-color-box" ></span>
+                    进行中
+                </div>
+                <div class="status-box">
+                    <span class="gray-color-box"></span>
+                    未开始
+                </div>
+                <div class="flex-box-ce more">
+                    <span @click="$router.push({ name: 'moreIndicatorList' })">
+                        查看更多
+                        <i class="el-icon-d-arrow-right"></i>
+                    </span>
+                </div>
+            </div>
+
+        </div>
+
+        <el-tabs v-model="glIndex" style="margin: 10px 0;">
+            <el-tab-pane :label="item.title" :name="item.name" v-for="(item, index) in glIist" :key="index">
+                <el-table ref="fmeaTableRef" :data="glDataList" v-loading="glLoading"
+                    :header-cell-style="{ background: '#f5f7fa' }" size=" mini" :border="true" style="width: 100%;"
+                    v-table-move="['fmeaTableRef']">
+                    <el-table-column prop="employeeName" label="被考核人" align="center" min-width="100">
+                        <template slot-scope="scope">
+                            <div>{{ scope.row.employeeName || '--' }}</div>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="title" label="指标" align="center" min-width="150">
+                        <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="reviewTitle" label="考核表" align="center" min-width="150">
+                        <template slot-scope="scope">
+                            <el-tooltip class="item" effect="dark" placement="top">
+                                <div v-html="scope.row.reviewTitle" slot="content" style="max-width:300px;"></div>
+                                <div class="oneLine">{{ scope.row.reviewTitle }}</div>
+                            </el-tooltip>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column label="周期" align="center" width="200">
+                        <template slot-scope="scope">
+                            <div>{{ scope.row.startTime | formatDate }} 至 {{ scope.row.endTime | formatDate }}</div>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column prop="target" label="目标" align="center" min-width="100">
+                        <template slot-scope="scope">
+                            <div>{{ scope.row.target || '--' }}</div>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="result" label="结果" align="center" min-width="100">
+                        <template slot-scope="scope">
+                            <div>{{ scope.row.result || '--' }}</div>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="score" label="最终评分" align="center" min-width="100">
+                        <template slot-scope="scope">
+                            <div>{{ scope.row.score || '--' }}</div>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column prop="confirm_target" label="确认目标" align="center" min-width="120">
+                        <template slot-scope="scope">
+                            <ShowHandlerComp :show-data="scope.row.flow.nodes[0]" />
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="input_result" label="录入结果" align="center" min-width="120">
+                        <template slot-scope="scope">
+                            <ShowHandlerComp :show-data="scope.row.flow.nodes[1]" />
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="self_assessment" label="自评" align="center" min-width="120">
+                        <template slot-scope="scope">
+                            <ShowHandlerComp :show-data="scope.row.flow.nodes[2]" />
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="peer_assessmen" label="互评" align="center" min-width="120">
+                        <template slot-scope="scope">
+                            <ShowHandlerComp :show-data="scope.row.flow.nodes[3]" />
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="grade" label="评分" align="center" min-width="120">
+                        <template slot-scope="scope">
+                            <ShowHandlerComp :show-data="scope.row.flow.nodes[4]" />
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="approval" label="审批" align="center" min-width="120">
+                        <template slot-scope="scope">
+                            <ShowHandlerComp :show-data="scope.row.flow.nodes[5]" />
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="carbon_copy" label="抄送" align="center" min-width="120">
+                        <template slot-scope="scope">
+                            <ShowHandlerComp :show-data="scope.row.flow.nodes[6]" />
+                        </template>
+                    </el-table-column>
+                </el-table>
+
+            </el-tab-pane>
+        </el-tabs>
+
+        <EmployeeSelector :isChecKedAll="false" :can_select_employee="false" :can_select_dept="true" :multi="true"
+            :selected="dept_selected" :visible.sync="show_dept_selector" @confirm="dept_confirm" />
+    </div>
+</template>
+<script>
+import JxSearch from '@/performance/components/public/JxSearch';
+import moment from 'moment';
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+import ShowHandlerComp from '@/newPerformance/components/PublicComp/ShowHandler'; // 显示节点数据组件
+
+export default {
+    components: {
+        JxSearch,
+        EmployeeSelector,
+        ShowHandlerComp
+    },
+    data() {
+        return {
+            params: {
+                keyword: '',
+                page: 1,
+                pageSize: 6,
+                startDate: '',
+                endDate: '',
+            },
+
+            // 部门可见
+            deptVisibleName: '',
+            dept_selected: { dept: [], employee: [] },
+            show_dept_selector: false,
+            deptList: [], // 部门列表 - 树形结构
+            dept_list: [], // 部门列表
+            selected_dept_ids: [], // 选择的部门列表
+            selected_employee_ids: [], // 选择的员工列表
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            staffLoad: false,
+            name: "",
+            headValue: [],
+            glIndex: '1',
+            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]);
+                    }
+                }]
+            },
+            date: [],
+            glDataList: [],
+            glLoading: false,
+            glIist: [{ name: '1', title: '我的' }, { name: '2', title: '管理员工' }, { name: '3', title: '部门管理' }],
+        }
+    },
+    watch: {
+        glIndex(val) {
+            this.getList();
+        },
+
+        headValue(val) {
+            this.package_id = val[val.length - 1];
+            this.glIndex == '1' ? this.getManagement() : this.getScorerRecord();
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        },
+    },
+    created() {
+        this.getList();
+    },
+    methods: {
+        // 指标审批列表
+        getList() {
+            this.glLoading = true;
+            let url, params = {};
+            // '/performance/statistics/review/indicators/self/{siteId}'
+            // 个人指标审批列表
+            if (this.glIndex == 1) {
+                url = `/performance/statistics/review/indicators/self/${this.user_info.site_id}`
+                params = { ...this.params }
+            // 我管理的审批列表
+            } else if (this.glIndex == 2) {
+                params = { ...this.params, employeeIds: this.selected_employee_ids.toString() }
+                url = `/performance/statistics/review/indicators/scope/${this.user_info.site_id}`
+            // 按部门管理的审批列表
+            } else if (this.glIndex == 3) {
+                params = { ...this.params, deptIds: this.selected_dept_ids.toString() }
+                url = `/performance/statistics/review/indicators/dept/manager/${this.user_info.site_id}`
+            }
+            this.$axiosUser('get', url, params).then(res => {
+                this.glLoading = false;
+                let { data: { data: { list }, code } } = res
+                this.glDataList = list
+            });
+        },
+        
+        // 选择部门
+        openDeptSelector() {
+            this.show_dept_selector = 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); // 处理成树状结构
+            });
+        },
+
+        // 选择部门
+        dept_confirm(data) {
+            this.dept_selected = { dept: [], employee: [] };
+            let dept_ids = [];
+            this.deptVisibleName = ''
+            if (data.dept !== null && data.dept.length != 0) {
+                this.dept_selected = data
+                data.dept.forEach(element => {
+                    this.selected_dept_ids.push(element.dept_id)
+                    dept_ids.push(element.dept_id)
+                    this.deptVisibleName += (element.dept_name + ',')
+                });
+            }
+            this.selected_dept_ids = Array.from(new Set(this.selected_dept_ids)); // 去重
+            this.getList();
+        },
+
+        // 选择员工
+        changeEmployeeIds(v) {
+            if (v && v.length > 0) {
+                v.forEach(item => {
+                    this.selected_employee_ids.push(item)
+                })
+            }
+            this.selected_employee_ids = Array.from(new Set(this.selected_employee_ids)); // 去重
+            this.getList();
+        },
+
+        //搜索
+        searchList(data) {
+            this.params.keyword = data;
+            this.getList()
+        },
+        // 日期选择时间
+        changeDate(v) {
+            this.params.startDate = v[0] || ''
+            this.params.endDate = v[1] || ''
+            this.getList();
+        },
+        returnStr(time) {
+            let date = `${time}000`;
+            let res = moment(Number(date)).format('YYYY/MM/DD HH:mm');
+            return this.fnTime(res);
+        },
+
+
+        fnTime(time) {
+            let staer = time.slice(0, 11);
+            let ptime = new Date(time).getTime();
+            const twentyFourHours = 24 * 60 * 60 * 1000;
+            const fortyEightHours = 24 * 60 * 60 * 1000 * 2;
+            const today = moment().format('YYYY/MM/DD');
+            const todayTime = new Date(today).getTime();
+            const yesterdayTime = new Date(todayTime - twentyFourHours).getTime();
+            const lastYesterdayTime = new Date(todayTime - fortyEightHours).getTime();
+            if (ptime >= todayTime) {
+                return '今天 ' + time.split(' ')[1] + ' 更新了执行计划';
+            } else if (ptime < todayTime && yesterdayTime <= ptime) {
+                return '昨天 ' + time.split(' ')[1] + ' 更新了执行计划';
+            } else if (ptime < yesterdayTime && lastYesterdayTime <= ptime) {
+                return '前天 ' + time.split(' ')[1] + ' 更新了执行计划';
+            } else if (this.dateSum(this.day, staer) > 30) {
+                return '近30天无计划更新';
+            } else {
+                return time + ' 更新了执行计划';
+            }
+        },
+        dateSum(sDate1, sDate2) {
+            //sDate1和sDate2是2008-12-13格式
+            var aDate, oDate1, oDate2, iDays;
+            aDate = sDate1.split('-');
+            oDate1 = new Date(aDate[1] + '-' + aDate[2] + '-' + aDate[0]); //转换为12-13-2008格式
+            aDate = sDate2.split('-');
+            oDate2 = new Date(aDate[1] + '-' + aDate[2] + '-' + aDate[0]);
+            iDays = parseInt(Math.abs(oDate1 - oDate2) / 1000 / 60 / 60 / 24); //把相差的毫秒数转换为天数
+            return iDays;
+        },
+    },
+
+    }
+</script>
+
+<style lang="scss">
+.gl-all {
+    .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;
+    }
+}
+
+.oneLine {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+</style>
+
+
+<style scoped lang="scss">
+
+
+.green-color-box {
+    display: inline-block;
+    border: 1px solid #67C23A;
+    color: #67c23a;
+    background: #f0f9eb;
+    padding: 5px;
+    margin-right: 5px;
+}
+
+.orange-color-box {
+    display: inline-block;
+    color: #e6a23c;
+    background: #fdf6ec;
+    border: 1px solid #f5dab1;
+    padding: 5px;
+    margin-right: 5px;
+}
+
+.gray-color-box {
+    display: inline-block;
+    color: #E9E9EB;
+    background: #F4F4F5;
+    border: 1px solid #E9E9EB;
+    padding: 5px;
+    margin-right: 5px;
+}
+
+.w270 {
+    width: 270px;
+}
+.gl-all {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    background-color: #fff;
+    padding: 10px 20px;
+    border-radius: 5px;
+    min-height: 400px;
+    box-sizing: border-box;
+    .search-box {
+        width: 100%;
+        height: 40px;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        .more {
+            color: #909399;
+            font-size: 14px;
+            height: 40px;
+            margin-left: 5px;
+            span {
+                cursor: pointer;
+    
+                &:hover {
+                    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>

+ 540 - 0
src/newPerformance/components/Workbench/IndicatorList.vue

@@ -0,0 +1,540 @@
+<template>
+    <!-- 指标审批管理 -->
+    <div class="gl-all" v-loading="staffLoad">
+        <div class="title">指标审批列表</div>
+        <div class="search-box flex-box">
+            <div class="flex-box-ce">
+                <el-date-picker v-model="date" type="daterange" align="right" unlink-panels range-separator="至"
+                    start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd"
+                    :picker-options="pickerOptions" @change="changeDate">
+                </el-date-picker>
+
+                <div v-if="glIndex == 2" style=" margin-left: 10px;">
+                    <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple filterable
+                        style="width: 300px;" @change="changeEmployeeIds" clearable>
+                        <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
+                            style="width: 300px;"></el-option>
+                    </el-select>
+                </div>
+
+                <el-cascader v-if="glIndex == 3" class="select" v-model="selected_dept_ids" size="small"
+                    style="width: 300px; margin-left: 10px; " :options="deptList" @change="deptChange" ref="dept"
+                    filterable change-on-select placeholder="请选择部门" clearable></el-cascader>
+
+                <div style="margin-left: 10px; width:300px;">
+                    <JxSearch :screen="2" title="请输入考核表标题或指标标题" @confirm="searchList"></JxSearch>
+                </div>
+            </div>
+            <div class="flex-box-ce">
+
+                <div class="flex-box-ce more">
+                    <span @click="$router.push({ name: 'moreIndicatorList' })">
+                        查看更多
+                        <i class="el-icon-d-arrow-right"></i>
+                    </span>
+                </div>
+            </div>
+
+        </div>
+
+        <el-tabs v-model="glIndex" style="margin: 10px 0;">
+            <el-tab-pane :label="item.title" :name="item.name" v-for="(item, index) in glIist" :key="index">
+                <el-table ref="fmeaTableRef" :data="glDataList" v-loading="glLoading"
+                    :header-cell-style="{ background: '#f5f7fa' }" size=" mini" :border="true" style="width: 100%;"
+                    v-table-move="['fmeaTableRef']">
+                    <template v-for="item in tableColumn">
+
+                        <el-table-column v-if="item.isShow && item.label === '指标审批状态'" :key="item.label"
+                            :prop="item.prop" :label="item.label" align="center" :min-width="item.width">
+                            <template slot-scope="scope">
+                                <el-tag v-if="scope.row[item.prop] !== 'end'" type="success">{{ scope.row[item.prop] |
+                                    filterNodeStatus }}</el-tag>
+                                <el-tag v-else type="warning">{{ scope.row[item.prop] |
+                                    filterNodeStatus }}</el-tag>
+                            </template>
+                        </el-table-column>
+
+                        <el-table-column v-else-if="item.isShow && item.label !== '指标审批状态'" :key="item.label"
+                            :prop="item.prop" :label="item.label" align="center" :min-width="item.width">
+                            <template slot-scope="scope">
+                                <el-tooltip class="item" effect="dark" placement="top">
+                                    <div v-html="scope.row[item.prop]" slot="content" style="max-width: 300px;"></div>
+                                    <div v-if="!flowColumn.includes(item.label)" class="oneLine">{{ scope.row[item.prop]
+                                        || '--' }}</div>
+
+                                </el-tooltip>
+                            </template>
+                        </el-table-column>
+                        <!-- <div v-if="!flowColumn.includes(item.label) && item.label === '指标审批状态'" class="oneLine">
+                                        {{ scope.row[item.prop] | filterNodeStatus }}
+                                    </div> -->
+                    </template>
+                    <el-table-column label="操作" align="center" min-width="120">
+                        <template slot-scope="scope">
+                            <el-link type="primary"
+                                @click="getDetails({ businessStatus: scope.row.businessStatus, nodes: scope.row.flow.nodes })">查看详情</el-link>
+                        </template>
+                    </el-table-column>
+                </el-table>
+
+            </el-tab-pane>
+        </el-tabs>
+
+        <ShowHandlerComp v-model="dialogVisible" :show-data="showData" />
+    </div>
+</template>
+<script>
+import moment from 'moment';
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+import ShowHandlerComp from '@/newPerformance/components/PublicComp/ShowHanderDialog2'; // 显示节点数据组件
+import JxSearch from '@/performance/components/public/JxSearch';
+
+export default {
+    components: {
+        JxSearch,
+        EmployeeSelector,
+        ShowHandlerComp
+    },
+    data() {
+        return {
+            dialogVisible: false,
+            params: {
+                keyword: '',
+                page: 1,
+                pageSize: 6,
+                startDate: '',
+                endDate: '',
+            },
+            flowColumn:["确认目标", "录入结果", "自评", "互评", "评分", "审批", "流程节点"],
+            tableColumn: [
+                { label: "被考核人", prop: "employeeName", isShow: true, width: 100},
+                { label: "指标", prop: "title", isShow: true, width: 150},
+                { label: "考核表", prop: "reviewTitle", isShow: true, width: 150},
+                { label: "周期", prop: "date", isShow: true, width: 200},
+                { label: "目标", prop: "target", isShow: true, width: 100},
+                { label: "结果", prop: "result", isShow: true, width: 100 },
+                { label: "指标审批状态", prop: "businessStatus", isShow: true, width: 150 },
+                { label: "最终评分", prop: "score", isShow: true, width: 150},
+                { label: "确认目标", prop: "", isShow: false, width: 120},
+                { label: "录入结果", prop: "", isShow: false, width: 120},
+                { label: "自评", prop: "", isShow: false, width: 120},
+                { label: "互评", prop: "", isShow: false, width: 120},
+                { label: "评分", prop: "", isShow: false, width: 120},
+                { label: "审批", prop: "", isShow: false, width: 120}
+            ],
+            checkColumns: [],
+            checkAll: false,
+            // 部门可见
+            deptVisibleName: '',
+            dept_selected: { dept: [], employee: [] },
+            show_dept_selector: false,
+            deptList: [], // 部门列表 - 树形结构
+            dept_list: [], // 部门列表
+            selected_dept_ids: [], // 选择的部门列表
+            selected_employee_ids: [], // 选择的员工列表
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            staffLoad: false,
+            name: "",
+            headValue: [],
+            glIndex: '1',
+            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]);
+                    }
+                }]
+            },
+            date: [],
+            glDataList: [],
+            glLoading: false,
+            glIist: [{ name: '1', title: '我的' }, { name: '2', title: '管理员工' }, { name: '3', title: '部门管理' }],
+            showData: []
+        }
+    },
+    watch: {
+        
+        glIndex(val) {
+            this.getList();
+        },
+
+        headValue(val) {
+            this.package_id = val[val.length - 1];
+            this.glIndex == '1' ? this.getManagement() : this.getScorerRecord();
+        }
+    },
+    filters: {
+        filterNodeStatus(v) {
+            // 指标审批状态 
+            // start - 开始 
+            // target_confirm - 目标确认 
+            // result_input - 录入结果值 
+            // score_self - 自评 
+            // score_each_other - 互评 
+            // score - 评分 
+            // review - 审批 
+            // cc - 抄送 
+            // end - 结束
+            if (v === 'start') return '开始'
+            if (v === 'target_confirm') return '确认目标'
+            if (v === 'result_input') return '录入结果值'
+            if (v === 'score_self') return '自评'
+            if (v === 'score_each_other') return '互评'
+            if (v === 'score') return '评分'
+            if (v === 'review') return '审批'
+            if (v === 'cc') return '抄送'
+            if (v === 'end') return '结束'
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    // filters: {
+    //     formatDate(val) {
+    //         if (val) return moment(val).format('YYYY-MM-DD')
+    //         else return "--"
+    //     },
+    // },
+    created() {
+        this.getList();
+        this.get_dept_list();
+    },
+    methods: {
+        getDetails(row) {
+            this.showData = row;
+            this.dialogVisible = true
+        },
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        },
+        // 指标审批列表
+        getList() {
+            this.glLoading = true;
+            let url, params = {};
+            // '/performance/statistics/review/indicators/self/{siteId}'
+            // 个人指标审批列表
+            if (this.glIndex == 1) {
+                url = `/performance/statistics/review/indicators/self/${this.user_info.site_id}`
+                params = { ...this.params }
+            // 我管理的审批列表
+            } else if (this.glIndex == 2) {
+                params = { ...this.params, employeeIds: this.selected_employee_ids.toString() }
+                url = `/performance/statistics/review/indicators/scope/${this.user_info.site_id}`
+            // 按部门管理的审批列表
+            } else if (this.glIndex == 3) {
+                params = { ...this.params, deptIds: this.selected_dept_ids.toString() }
+                url = `/performance/statistics/review/indicators/dept/manager/${this.user_info.site_id}`
+            }
+            this.$axiosUser('get', url, params).then(res => {
+                this.glLoading = false;
+                let { data: { data: { list }, code } } = res
+                this.glDataList = list;
+                if(this.glDataList && this.glDataList.length > 0) {
+                    this.glDataList.forEach(item => {
+                        item.date = this.formatDate(item.startTime) + "至" + this.formatDate(item.endTime)
+                    })
+                }
+            });
+        },
+
+        
+        // 处理部门树状结构数据
+        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); // 处理成树状结构
+            });
+        },
+
+        deptChange(val) {
+            this.selected_dept_ids = Array.from(new Set(this.selected_dept_ids)); // 去重
+            this.getList();
+            this.$nextTick(() => {
+                this.$refs.dept.dropDownVisible = false;
+            });
+        },
+
+
+        // 选择员工
+        changeEmployeeIds(v) {
+            if (v && v.length > 0) {
+                v.forEach(item => {
+                    this.selected_employee_ids.push(item)
+                })
+            }
+            this.selected_employee_ids = Array.from(new Set(this.selected_employee_ids)); // 去重
+            this.getList();
+        },
+
+        //搜索
+        searchList(data) {
+            this.params.keyword = data;
+            this.getList()
+        },
+        // 日期选择时间
+        changeDate(v) {
+            this.params.startDate = v[0] || ''
+            this.params.endDate = v[1] || ''
+            this.getList();
+        },
+        returnStr(time) {
+            let date = `${time}000`;
+            let res = moment(Number(date)).format('YYYY/MM/DD HH:mm');
+            return this.fnTime(res);
+        },
+
+
+        fnTime(time) {
+            let staer = time.slice(0, 11);
+            let ptime = new Date(time).getTime();
+            const twentyFourHours = 24 * 60 * 60 * 1000;
+            const fortyEightHours = 24 * 60 * 60 * 1000 * 2;
+            const today = moment().format('YYYY/MM/DD');
+            const todayTime = new Date(today).getTime();
+            const yesterdayTime = new Date(todayTime - twentyFourHours).getTime();
+            const lastYesterdayTime = new Date(todayTime - fortyEightHours).getTime();
+            if (ptime >= todayTime) {
+                return '今天 ' + time.split(' ')[1] + ' 更新了执行计划';
+            } else if (ptime < todayTime && yesterdayTime <= ptime) {
+                return '昨天 ' + time.split(' ')[1] + ' 更新了执行计划';
+            } else if (ptime < yesterdayTime && lastYesterdayTime <= ptime) {
+                return '前天 ' + time.split(' ')[1] + ' 更新了执行计划';
+            } else if (this.dateSum(this.day, staer) > 30) {
+                return '近30天无计划更新';
+            } else {
+                return time + ' 更新了执行计划';
+            }
+        },
+        dateSum(sDate1, sDate2) {
+            //sDate1和sDate2是2008-12-13格式
+            var aDate, oDate1, oDate2, iDays;
+            aDate = sDate1.split('-');
+            oDate1 = new Date(aDate[1] + '-' + aDate[2] + '-' + aDate[0]); //转换为12-13-2008格式
+            aDate = sDate2.split('-');
+            oDate2 = new Date(aDate[1] + '-' + aDate[2] + '-' + aDate[0]);
+            iDays = parseInt(Math.abs(oDate1 - oDate2) / 1000 / 60 / 60 / 24); //把相差的毫秒数转换为天数
+            return iDays;
+        },
+        // 全选复选框事件监听
+        checkAllChangeFn(val) {
+            if (val) {
+                // 全选
+                this.reset(true);
+            } else {
+                // 反全选
+                this.reset(false);
+            }
+        },
+        // 重置,flag: Boolean,全部重置为flag
+        reset(flag) {
+            this.tableColumn.forEach(item => {
+                item.isShow = flag;
+            })
+            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
+        }
+    },
+
+    }
+</script>
+
+<style lang="scss">
+.gl-all {
+    .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;
+    }
+}
+
+.oneLine {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+</style>
+
+
+<style scoped lang="scss">
+
+
+.green-color-box {
+    display: inline-block;
+    border: 1px solid #67C23A;
+    color: #67c23a;
+    background: #f0f9eb;
+    padding: 5px;
+    margin-right: 5px;
+}
+
+.orange-color-box {
+    display: inline-block;
+    color: #e6a23c;
+    background: #fdf6ec;
+    border: 1px solid #f5dab1;
+    padding: 5px;
+    margin-right: 5px;
+}
+
+.gray-color-box {
+    display: inline-block;
+    color: #E9E9EB;
+    background: #F4F4F5;
+    border: 1px solid #E9E9EB;
+    padding: 5px;
+    margin-right: 5px;
+}
+
+.w270 {
+    width: 270px;
+}
+.gl-all {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    padding: 10px 20px;
+    border-radius: 5px;
+    box-sizing: border-box;
+    background: #fff;
+    .title {
+        font-size: 14px;
+        font-weight: 600;
+        margin-bottom: 5px;
+    }
+    .search-box {
+        width: 100%;
+        height: 40px;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        .more {
+            color: #909399;
+            font-size: 14px;
+            height: 40px;
+            margin-left: 5px;
+            span {
+                cursor: pointer;
+    
+                &:hover {
+                    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>

+ 517 - 0
src/newPerformance/components/Workbench/InterviewNode.vue

@@ -0,0 +1,517 @@
+<template>
+    <div>
+        <el-dialog :title="dialogTitle" center :visible.sync="interviewNodeVisible" width="1200px"
+            :before-close="dialogBeforeClose">
+            <!-- <div class="status-btn-box fadeInDown animated">
+                <el-link type="primary" @click="openPage()">查看面谈详情</el-link>
+            </div> -->
+
+            <div class="dialog-content" v-loading="loading">
+                <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>
+
+                        <div class="flex-1 fontColorB item">
+                            <div>{{ score || '暂无评分' }}</div>
+                            <div class="fontColorC">考核评分</div>
+                        </div>
+
+                        <div class="flex-1 fontColorB item">
+                            <div>{{ status == 0 ? '考核中' : '已结束' }}</div>
+                            <div class="fontColorC">考核状态</div>
+                        </div>
+
+                        <div class="flex-1 fontColorB item">
+                            <div>{{ create_status }}</div>
+                            <div class="fontColorC">考核中的指标</div>
+                        </div>
+
+                        <div class="flex-1 fontColorB item">
+                            <div>{{ finish_status }}</div>
+                            <div class="fontColorC">考核完成的指标</div>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="btn-box flex-box-ce" style="justify-content: space-between;">
+                    <!-- <div v-if="levelName" class="status-btn-box fadeInDown animated">
+                        <div class="status-btn">
+                            {{ levelName }}
+                        </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>
+
+                </div>
+
+                <el-table ref="fmeaTableRef" :data="tableData" stripe style="width: 100%; " :height="300" border
+                    :header-cell-style="{ background: '#f5f7fa' }" v-table-move="['fmeaTableRef']">
+                    <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">
+                        <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="target" label="目标" align="center" min-width="100">
+                    </el-table-column>
+                    <el-table-column prop="unit" label="单位" align="center" min-width="100">
+                    </el-table-column>
+                    <el-table-column prop="weight" label="权重" align="center" min-width="100">
+                    </el-table-column>
+                    <el-table-column prop="result" label="结果值" align="center" min-width="100">
+                    </el-table-column>
+                    <el-table-column prop="formulae" label="计算公式" align="center" min-width="160">
+                        <template slot-scope="scope">
+                            <el-button v-if="scope.row.expression && 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>
+                            <div v-else style="color: #999;">暂无公式</div>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="cycleType" label="周期类型" align="center" min-width="100">
+                        <template slot-scope="scope">
+                            <div v-if="scope.row.cycleType == 0">未定义</div>
+                            <div v-else-if="scope.row.cycleType == 1">年度</div>
+                            <div v-else-if="scope.row.cycleType == 2">半年度</div>
+                            <div v-else-if="scope.row.cycleType == 3">季度</div>
+                            <div v-else-if="scope.row.cycleType == 4">月度</div>
+                            <div v-else="scope.row.cycleType == 1">--</div>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column label="考核周期" align="center" min-width="200">
+                        <template slot-scope="scope">
+                            <div>{{ scope.row.startTime | formatDate }} 至 {{ scope.row.endTime | formatDate }}</div>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column prop="score" label="最终评分" align="center" min-width="100">
+                        <template slot-scope="scope">
+                            <div>{{ scope.row.score || '--' }}</div>
+                        </template>
+                    </el-table-column>
+
+                    <el-table-column prop="levelName" label="等级" align="center" min-width="100">
+                    </el-table-column>
+
+                    <el-table-column prop="businessStatus" label="指标考核状态" align="center" min-width="120">
+                        <template slot-scope="scope">
+                            <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="confirm_target" label="确认目标" align="center" width="100"
+                        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="100"
+                        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="100"
+                        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="100"
+                        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="100" fixed="right">
+                        <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="100" 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]" />
+                        </template>
+                    </el-table-column>
+                    <el-table-column v-if="isShow" prop="carbon_copy" label="抄送" align="center" width="100"
+                        fixed="right">
+                        <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]" />
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </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> -->
+            </div>
+        </el-dialog>
+
+        <DetailsDialog v-model="detailsDialogVisible" :dialog-data="dialogData" />
+
+        <!-- 编辑计算公式 -->
+        <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" />
+
+    </div>
+
+</template>
+
+
+<script>
+import moment from 'moment';
+import { mapGetters } from 'vuex';
+import DetailsDialog from "./EditNodeDialog.vue"
+import ShowHandlerComp from '@/newPerformance/components/PublicComp/ShowHandler'; // 显示节点数据组件
+import FormulaComp from '@/newPerformance/components/TemplateDetails/FormulaComp'; // 计算公式弹框
+export default {
+    components: {
+        DetailsDialog,
+        ShowHandlerComp,
+        FormulaComp
+    },
+    model: {
+        prop: 'interviewNodeVisible',
+        event: 'close-dialog'
+    },
+    props: {
+        activeName: {
+            type: String,
+            default: "1"
+        },
+        dialogTitle: {
+            type: String,
+            default: ""
+        },
+        interviewNodeVisible: {
+            type: Boolean,
+            default: false
+        },
+        dialogData: {
+            type: Object,
+            default: () => { }
+        }
+    },
+
+    watch: {
+        interviewNodeVisible(v) {
+            if (v) {
+                let { reviewId } = this.dialogData.jobNew
+            }
+            this.getDetails(reviewId);
+        }
+    },
+    filters: {
+        formatCycleType(val) {
+            if (val == 0) return '未定义'
+            if (val == 1) return '年度'
+            if (val == 2) return '半年'
+            if (val == 3) return '季度'
+            if (val == 4) return '月度'
+            else return '--'
+        },
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    data() {
+        return {
+            loading: false,
+            title: "",
+            startTime: "",
+            endTime: "",
+            cycleType: "",
+            score: 0,
+            status: 0,
+            total: 0,
+            levelName: '', // 考核详情信息
+            detailsDialogVisible: false,
+            isShow: false,
+            tableData: [],
+            dialogVisible: false,
+            currentIndicator: null,
+            showFormula: false, // 公式弹框
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        create_status() {
+            if (this.tableData && this.tableData.length > 0) {
+                return this.tableData.filter(data => data.businessStatus !== 'end').length
+            }
+            return '--'
+        },
+        finish_status() {
+            if (this.tableData && this.tableData.length > 0) {
+                return this.tableData.filter(data => data.businessStatus === 'end').length
+            }
+            return '--'
+        }
+    },
+    mounted() {
+        let { reviewId } = this.dialogData.jobNew
+        this.getDetails(reviewId);
+    },
+    methods: {
+
+        // 获取考核详情
+        getDetails(reviewId) {
+            this.loading = true
+            let url = `/performance/statistics/review/info/${this.user_info.site_id}/${reviewId}`
+            this.$axiosUser('get', url).then(res => {
+                console.log(res)
+                let { data: { title, indicators, levelName, startTime, endTime, cycleType, score, status }, code } = res.data
+                if (code == 1) {
+                    this.title = title || ''
+                    this.cycleType = cycleType || ''
+                    this.score = score || ''
+                    this.status = status || '0'
+                    this.startTime = startTime || ''
+                    this.endTime = endTime || ''
+                    this.levelName = levelName || ''
+                    this.tableData = indicators || [];
+                    this.loading = false
+                }
+
+            });
+        },
+
+        dialogBeforeClose() {
+            this.$emit('close-dialog', false)
+        },
+
+        changeShow() {
+            this.$nextTick(() => {
+                if (this.$refs.fmeaTableRef) this.$refs.fmeaTableRef.doLayout();
+            })
+        },
+        openPage() {
+            let type = "interview"
+            window.open(`http://localhost:9527/#/interviewDetails?type=${type}`)
+        }
+    }
+}
+
+</script>
+
+<style lang="scss">
+.dialog-content {
+
+    .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;
+    }
+}
+
+.oneLine {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+</style>
+
+
+<style scoped="scoped" lang="scss">
+.status-box {
+    padding: 2px;
+    display: inline-block;
+    border: 1px solid;
+    box-sizing: border-box;
+    border-radius: 4px;
+}
+
+.green-color {
+    border-color: #67C23A;
+    color: #67C23A;
+}
+
+
+.gray-color {
+    border-color: #f56c6c;
+    color: #f56c6c;
+}
+
+.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;
+    }
+}
+
+.dialog-content {
+    width: 100%;
+    height: 450px;
+    .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;
+    }
+    .main {
+        width: 100%;
+        height: 100px;
+        margin: 0 0 10px 0;
+        background-color: #f7f7f7;
+        border-bottom: 1px solid #f1f1f1;
+        .main-header {
+            .bian {
+                position: absolute;
+                width: 1px;
+                height: 30px;
+                background-color: #e8e8e8;
+                right: 0;
+                top: 50%;
+                margin-top: -15px;
+            }
+        }
+
+        .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;
+            }
+        }
+    }
+
+    .btn-box {
+        width: 100%;
+        padding: 10px 0!important;
+        box-sizing: border-box;
+        justify-content: space-between;
+        color: #999;
+        background-color: #fff;
+        position: relative;
+        .status-btn-box {
+            width: 120px;
+            height: 30px;
+            z-index: 10;
+
+            .status-btn {
+                width: 100%;
+                height: 100%;
+                background: transparent;
+                border: 2px dashed #67C23A;
+                color: #67C23A;
+                text-align: center;
+                font-size: 16px;
+                line-height: 30px;
+            }
+        }
+    }
+
+
+    /* 设置滚动条的宽度和背景色 */
+    ::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>

+ 580 - 0
src/newPerformance/components/Workbench/MoreIndicatorList.vue

@@ -0,0 +1,580 @@
+<template>
+    <!-- 指标审批管理 -->
+    <div class="gl-all" v-loading="staffLoad">
+        <header class="header flex-box-ce">
+            <!-- 返回按钮 -->
+            <div class="header-content flex-box-ce">
+                <div class="flex-box-ce header-left" @click="$router.push('/newPerformance')">
+                    <i class="el-icon-arrow-left fontColorC"></i>
+                    <div class="fontColorB">返回</div>
+                    <div class="text fontColorB font-flex-word"></div>
+                </div>
+            </div>
+
+            <!-- 搜索条件 -->
+            <div class="flex-box-ce" style="justify-content: space-between;">
+                <div class="flex-box-ce">
+                    <el-date-picker v-model="date" type="daterange" align="right" unlink-panels range-separator="至"
+                        start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd"
+                        :picker-options="pickerOptions" @change="changeDate">
+                    </el-date-picker>
+
+                    <div v-if="glIndex == 2" style=" margin-left: 10px;">
+                        <el-select v-model="selected_employee_ids" placeholder="请选择指定人员" multiple filterable
+                            style="width: 300px;" @change="changeEmployeeIds" clearable>
+                            <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"
+                                style="width: 300px;"></el-option>
+                        </el-select>
+                    </div>
+
+
+                    <el-cascader v-if="glIndex == 3" class="select" v-model="selected_dept_ids" size="small"
+                        style="width: 300px; margin-left: 10px; " :options="deptList" @change="deptChange" ref="dept"
+                        filterable change-on-select placeholder="请选择部门" clearable></el-cascader>
+
+                    <div style="margin-left: 10px; width:300px;">
+                        <JxSearch :screen="2" title="请输入考核表标题或指标标题" @confirm="searchList"></JxSearch>
+                    </div>
+                </div>
+
+            </div>
+        </header>
+
+        <el-tabs v-model="glIndex" style="margin: 20px 0;">
+            <el-tab-pane :label="item.title" :name="item.name" v-for="(item, index) in glIist" :key="index">
+                <el-table :data="glDataList" v-loading="glLoading" :header-cell-style="{ background: '#f5f7fa' }"
+                    border>
+                    <template v-for="item in tableColumn">
+                        <el-table-column v-if="item.isShow && item.label === '指标审批状态'" :key="item.label"
+                            :prop="item.prop" :label="item.label" align="center" :min-width="item.width">
+                            <template slot-scope="scope">
+                                <el-tag v-if="scope.row[item.prop] !== 'end'" type="success">{{ scope.row[item.prop] | filterNodeStatus }}</el-tag>
+                                <el-tag v-else type="warning">{{ scope.row[item.prop] | filterNodeStatus }}</el-tag>
+                            </template>
+                        </el-table-column>
+                        <el-table-column v-if="item.isShow && item.label !== '指标审批状态'" :key="item.label" :prop="item.prop" :label="item.label"
+                            align="center" :min-width="item.width">
+                            <template slot-scope="scope">
+
+                                <el-tooltip class="item" effect="dark" placement="top">
+                                    <div v-html="scope.row[item.prop]" slot="content" style="max-width: 300px;"></div>
+                                    <div v-if="!flowColumn.includes(item.label)" class="oneLine">{{ scope.row[item.prop]
+                                        || '--' }}</div>
+                                </el-tooltip>
+                            </template>
+                        </el-table-column>
+                    </template>
+                    <el-table-column label="操作" align="center" min-width="120">
+                        <template slot-scope="scope">
+                            <el-link type="primary"
+                                @click="getDetails({ businessStatus: scope.row.businessStatus, nodes: scope.row.flow.nodes })">查看详情</el-link>
+                        </template>
+                    </el-table-column>
+                </el-table>
+
+                <div class="flex-box-ce" style="justify-content: center; margin-top: 30px;">
+                    <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
+                        :current-page="params.page" :page-sizes="[10, 20, 30, 40, 50]" :page-size="params.pageSize"
+                        layout="total, sizes, prev, pager, next" :total="total">
+                    </el-pagination>
+                </div>
+
+            </el-tab-pane>
+        </el-tabs>
+
+        <ShowHanderDialog v-model="dialogVisible" :show-data="showData" />
+
+    </div>
+</template>
+<script>
+import JxSearch from '@/performance/components/public/JxSearch';
+import moment from 'moment';
+import { mapGetters } from 'vuex';
+import EmployeeSelector from '@/components/EmployeeSelector'; // 选择部门组件
+import ShowHandlerComp from '@/newPerformance/components/PublicComp/ShowHandler'; // 显示节点数据组件
+import ShowHanderDialog from '@/newPerformance/components/PublicComp/ShowHanderDialog2'; // 显示节点数据组件
+
+export default {
+    components: {
+        JxSearch,
+        EmployeeSelector,
+        ShowHandlerComp,
+        ShowHanderDialog
+    },
+    data() {
+        return {
+            dialogVisible: false,
+            showData: [],
+            total: 0,
+            params: {
+                keyword: '',
+                page: 1,
+                pageSize: 10,
+                startDate: '',
+                endDate: '',
+            },
+
+            flowColumn:["确认目标", "录入结果", "自评", "互评", "评分", "审批"],
+            tableColumn: [
+                { label: "被考核人", prop: "employeeName", isShow: true, width: 100},
+                { label: "指标", prop: "title", isShow: true, width: 150},
+                { label: "考核表", prop: "reviewTitle", isShow: true, width: 150},
+                { label: "周期", prop: "date", isShow: true, width: 200},
+                { label: "目标", prop: "target", isShow: true, width: 100},
+                { label: "结果", prop: "result", isShow: true, width: 100 },
+                { label: "指标审批状态", prop: "businessStatus", isShow: true, width: 150 },
+                { label: "最终评分", prop: "score", isShow: true, width: 150},
+                { label: "确认目标", prop: "", isShow: false, width: 120},
+                { label: "录入结果", prop: "", isShow: false, width: 120},
+                { label: "自评", prop: "", isShow: false, width: 120},
+                { label: "互评", prop: "", isShow: false, width: 120},
+                { label: "评分", prop: "", isShow: false, width: 120},
+                { label: "审批", prop: "", isShow: false, width: 120}
+            ],
+            checkColumns: [],
+            checkAll: false,
+            // 部门可见
+            deptVisibleName: '',
+            dept_selected: { dept: [], employee: [] },
+            show_dept_selector: false,
+            deptList: [], // 部门列表 - 树形结构
+            dept_list: [], // 部门列表
+            selected_dept_ids: [], // 选择的部门列表
+            selected_employee_ids: [], // 选择的员工列表
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            staffLoad: false,
+            name: "",
+            headValue: [],
+            glIndex: '1',
+            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]);
+                    }
+                }]
+            },
+            date: [],
+            glDataList: [],
+            glLoading: false,
+            glIist: [{ name: '1', title: '我的' }, { name: '2', title: '管理员工' }, { name: '3', title: '部门管理' }],
+        }
+    },
+    
+    watch: {
+        glIndex(val) {
+            this.getList();
+        },
+
+        headValue(val) {
+            this.package_id = val[val.length - 1];
+            this.glIndex == '1' ? this.getManagement() : this.getScorerRecord();
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        },
+        filterNodeStatus(v) {
+            // 指标审批状态 
+            // start - 开始 
+            // target_confirm - 目标确认 
+            // result_input - 录入结果值 
+            // score_self - 自评 
+            // score_each_other - 互评 
+            // score - 评分 
+            // review - 审批 
+            // cc - 抄送 
+            // end - 结束
+            if (v === 'start') return '开始'
+            if (v === 'target_confirm') return '确认目标'
+            if (v === 'result_input') return '录入结果值'
+            if (v === 'score_self') return '自评'
+            if (v === 'score_each_other') return '互评'
+            if (v === 'score') return '评分'
+            if (v === 'review') return '审批'
+            if (v === 'cc') return '抄送'
+            if (v === 'end') return '结束'
+        },
+        filterTasks(data) {
+            if (data && data.length > 0) {
+                let users = [];
+                data.forEach(item => {
+                    // 确认目标,审批 处理人
+                    if (item.nodeType == 'targetConfirm' || item.nodeType == 'review' || item.nodeType == 'cc') {
+                        users.push({ id: item.assignee, name: item.assigneeName, nodeType: item.nodeType })
+                    }
+                    // 结果值录入 处理人
+                    if (item.nodeType == 'resultInput') {
+                        users.push({ id: item.assignee, name: item.assigneeName, result: item.result || '--', nodeType: item.nodeType })
+                    }
+                    // 自评,互评,评分 处理人
+                    if (['scoreSelf', 'scoreEachOther', 'score'].includes(item.nodeType)) {
+                        users.push({ id: item.assignee, name: item.assigneeName, score: item.score || '--', nodeType: item.nodeType })
+                    }
+                })
+                if (users && users.length > 0) {
+                    let str = ''
+                    users.forEach(user => {
+                        // 结果值录入处理人
+                        if (user.nodeType == 'targetConfirm' || user.nodeType == 'review' || user.nodeType == 'cc') str += user.name
+                        // 结果值录入处理人
+                        if (user.nodeType == 'resultInput') str += user.name + "," + user.result 
+                        // 自评,互评,评分 处理人
+                        if (['scoreSelf', 'scoreEachOther', 'score'].includes(user.nodeType)) str += user.name + "," + user.score
+                    })
+                    return str
+                }
+            }
+            else return '--'
+        }
+    },
+    created() {
+        this.getList();
+        this.get_dept_list();
+    },
+    methods: {
+        getDetails(row) {
+            this.showData = row;
+            this.dialogVisible = true
+        },
+        // 指标审批列表
+        getList() {
+            this.glLoading = true;
+            let url, params = {};
+            // 个人指标审批列表
+            if (this.glIndex == 1) {
+                url = `/performance/statistics/review/indicators/self/${this.user_info.site_id}`
+                params = { ...this.params }
+                // 我管理的审批列表
+            } else if (this.glIndex == 2) {
+                params = { ...this.params, employeeIds: this.selected_employee_ids.toString() }
+                url = `/performance/statistics/review/indicators/scope/${this.user_info.site_id}`
+                // 按部门管理的审批列表
+            } else if (this.glIndex == 3) {
+                params = { ...this.params, deptIds: this.selected_dept_ids.toString() }
+                url = `/performance/statistics/review/indicators/dept/manager/${this.user_info.site_id}`
+            }
+            this.$axiosUser('get', url, params).then(res => {
+                this.glLoading = false;
+                let { data: { data: { list, total }, code } } = res;
+                this.total = total;
+                this.glDataList = list;
+                if(this.glDataList && this.glDataList.length > 0) {
+                    this.glDataList.forEach(item => {
+                        item.date = this.formatDate(item.startTime) + "至" + this.formatDate(item.endTime)
+                    })
+                }
+            });
+        },
+
+        
+        // 处理部门树状结构数据
+        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); // 处理成树状结构
+            });
+        },
+
+        // 选择部门
+        deptChange(val) {
+            this.selected_dept_ids = Array.from(new Set(this.selected_dept_ids)); // 去重
+            this.getList();
+            this.$nextTick(() => {
+                this.$refs.dept.dropDownVisible = false;
+            });
+        },
+
+        // 选择员工
+        changeEmployeeIds(v) {
+            if (v && v.length > 0) {
+                v.forEach(item => {
+                    this.selected_employee_ids.push(item)
+                })
+            }
+            this.selected_employee_ids = Array.from(new Set(this.selected_employee_ids)); // 去重
+            this.getList();
+        },
+
+        //搜索
+        searchList(data) {
+            this.params.keyword = data;
+            this.getList()
+        },
+        // 日期选择时间
+        changeDate(v) {
+            this.params.startDate = v[0] || ''
+            this.params.endDate = v[1] || ''
+            this.getList();
+        },
+        returnStr(time) {
+            let date = `${time}000`;
+            let res = moment(Number(date)).format('YYYY/MM/DD HH:mm');
+            return this.fnTime(res);
+        },
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        },
+
+
+        fnTime(time) {
+            let staer = time.slice(0, 11);
+            let ptime = new Date(time).getTime();
+            const twentyFourHours = 24 * 60 * 60 * 1000;
+            const fortyEightHours = 24 * 60 * 60 * 1000 * 2;
+            const today = moment().format('YYYY/MM/DD');
+            const todayTime = new Date(today).getTime();
+            const yesterdayTime = new Date(todayTime - twentyFourHours).getTime();
+            const lastYesterdayTime = new Date(todayTime - fortyEightHours).getTime();
+            if (ptime >= todayTime) {
+                return '今天 ' + time.split(' ')[1] + ' 更新了执行计划';
+            } else if (ptime < todayTime && yesterdayTime <= ptime) {
+                return '昨天 ' + time.split(' ')[1] + ' 更新了执行计划';
+            } else if (ptime < yesterdayTime && lastYesterdayTime <= ptime) {
+                return '前天 ' + time.split(' ')[1] + ' 更新了执行计划';
+            } else if (this.dateSum(this.day, staer) > 30) {
+                return '近30天无计划更新';
+            } else {
+                return time + ' 更新了执行计划';
+            }
+        },
+        dateSum(sDate1, sDate2) {
+            //sDate1和sDate2是2008-12-13格式
+            var aDate, oDate1, oDate2, iDays;
+            aDate = sDate1.split('-');
+            oDate1 = new Date(aDate[1] + '-' + aDate[2] + '-' + aDate[0]); //转换为12-13-2008格式
+            aDate = sDate2.split('-');
+            oDate2 = new Date(aDate[1] + '-' + aDate[2] + '-' + aDate[0]);
+            iDays = parseInt(Math.abs(oDate1 - oDate2) / 1000 / 60 / 60 / 24); //把相差的毫秒数转换为天数
+            return iDays;
+        },
+
+        handleSizeChange(v) {
+            this.params.pageSize = v
+            this.getList();
+        },
+        handleCurrentChange(v) {
+            this.params.page = v
+            this.getList();
+        },
+        // 全选复选框事件监听
+        checkAllChangeFn(val) {
+            if (val) {
+                // 全选
+                this.reset(true);
+            } else {
+                // 反全选
+                this.reset(false);
+            }
+        },
+        // 重置,flag: Boolean,全部重置为flag
+        reset(flag) {
+            this.tableColumn.forEach(item => {
+                item.isShow = flag;
+            })
+            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
+        }
+    },
+
+}
+</script>
+
+<style lang="scss">
+
+</style>
+
+<style scoped lang="scss">
+.w270 {
+    width: 270px;
+}
+
+.green-color-box {
+    display: inline-block;
+    border: 1px solid #67C23A;
+    color: #67c23a;
+    background: #f0f9eb;
+    padding: 5px;
+    margin-right: 5px;
+}
+
+.orange-color-box {
+    display: inline-block;
+    color: #e6a23c;
+    background: #fdf6ec;
+    border: 1px solid #f5dab1;
+    padding: 5px;
+    margin-right: 5px;
+}
+
+.gray-color-box {
+    display: inline-block;
+    color: #E9E9EB;
+    background: #F4F4F5;
+    border: 1px solid #E9E9EB;
+    padding: 5px;
+    margin-right: 5px;
+}
+
+.gl-all {
+    width: 100%;
+    height: 100%;
+    background-color: #fff;
+    padding: 20px;
+    border-radius: 5px;
+    box-sizing: border-box;
+
+    /*头部样式*/
+    .header-content {
+        position: relative;
+        box-sizing: border-box;
+        height: 50px;
+        justify-content: space-between;
+        .header-left {
+            width: 80px;
+            box-sizing: border-box;
+            height: 50px;
+            cursor: pointer;
+
+            .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: 30px;
+                display: flex;
+
+                &::before {
+                    position: absolute;
+                    content: '';
+                    width: 1px;
+                    height: 36px;
+                    background-color: #ebebeb;
+                    left: 64px;
+                    top: 50%;
+                    margin-top: -18px;
+                }
+            }
+        }
+    }
+
+    /* 设置滚动条的宽度和背景色 */
+    ::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>
+
+

+ 91 - 0
src/newPerformance/components/tool/Seal.vue

@@ -0,0 +1,91 @@
+<template>
+  <div class="seal-container" :style="{ position: 'absolute', top: top + 'px', left: left + 'px', transform: `rotate(${rotate}deg)`,zIndex: zIndex }">
+    <svg :width="size" :height="size" viewBox="0 0 100 100" class="seal-svg">
+      <circle cx="50" cy="50" r="45" stroke="#8BC34A" :stroke-width="strokeWidth" fill="none" :stroke-dasharray="dashArray"/>
+      <text
+        :x="center"
+        :y="center"
+        text-anchor="middle"
+        dominant-baseline="middle"
+        :font-size="fontSize"
+        fill="#8BC34A"
+      >
+        {{ text }}
+      </text>
+    </svg>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "Seal",
+  props: {
+    text: {
+      type: String,
+      default: '优秀'
+    },
+    size: {
+      type: Number,
+      default: 50
+    },
+    strokeWidth: {
+      type: Number,
+      default: 3
+    },
+    top: {
+      type: Number,
+      default: 0
+    },
+    left: {
+      type: Number,
+      default: 0
+    },
+    rotate: {
+      type: Number,
+      default: 0
+    },
+    zIndex: {
+      type: Number,
+      default: 1
+    },
+    fontSize:{
+      type: String,
+      default: '10px'
+    },
+    dashArray: {
+      type: String,
+      default: '16, 5'
+    }
+  },
+  computed: {
+    center() {
+      return 50;
+    },
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.seal-container {
+  display: inline-block;
+}
+
+.seal-svg {
+  animation: stamp 0.5s ease-out;
+  transform-origin: center;
+}
+
+@keyframes stamp {
+  0% {
+    transform: scale(3);
+    opacity: 0;
+  }
+  50% {
+    transform: scale(0.9);
+    opacity: 1;
+  }
+  100% {
+    transform: scale(1);
+  }
+}
+</style>

+ 68 - 0
src/newPerformance/components/tool/WaStstistics.vue

@@ -0,0 +1,68 @@
+<template>
+  <div class="custom-statistic">
+    <div class="statistic-content">
+      <span class="statistic-value">
+        {{ formattedValue }}
+      </span>
+    </div>
+    <div v-if="title" class="statistic-title">
+      {{ title }}
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "WaStstistics",
+  props: {
+    title: {
+      type: [String, Number],
+      default: ''
+    },
+    value: {
+      type: [String, Number],
+      required: true
+    },
+    // 是否启用千分位格式化(仅对数字有效)
+    formatNumber: {
+      type: Boolean,
+      default: true
+    }
+  },
+  computed: {
+    formattedValue() {
+      if (typeof this.value === 'number' && this.formatNumber) {
+        return this.value.toLocaleString()
+      }
+      return this.value
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.custom-statistic {
+  text-align: center;
+}
+
+.statistic-title {
+  font-size: 12px;
+  color: #89919F !important;
+  line-height: 1.5;
+}
+
+.statistic-content {
+  font-size: 16px;
+  color: #409EFF;
+}
+
+.statistic-value {
+  font-weight: 600;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+}
+
+/* 可选:添加数字动画效果 */
+.statistic-value {
+  transition: all 0.3s cubic-bezier(0.38, 0, 0.24, 1);
+}
+</style>

+ 48 - 0
src/newPerformance/utils/exportToExcel.js

@@ -0,0 +1,48 @@
+import XLSX from 'xlsx';
+import FileSaver from 'file-saver';
+
+export function exportTableToExcel(tableName, elementName) {
+    // 如果未传入文件名,则使用当前时间戳
+    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);
+    }
+}

+ 306 - 0
src/newPerformance/views/CombinationTemplates.vue

@@ -0,0 +1,306 @@
+<template>
+    <div class="all">
+        <header class="header">
+            <div class="header-content flex-box-ce">
+                <!-- 返回按钮 -->
+                <div class="flex-box-ce header-left">
+                    <i class="el-icon-arrow-left fontColorC" @click="$router.go(-1)"></i>
+                    <div class="text fontColorB font-flex-word">组合模板发布</div>
+                </div>
+
+                <!-- 发布按钮 -->
+                <div class="header-right">
+                    <el-button type="primary">发布考核</el-button>
+                </div>
+            </div>
+
+        </header>
+
+        <div style="height: 1px; background-color: #DCDFE6; margin: 5px 0 10px 0;"></div>
+
+        <el-alert class="bounce animated" type="warning" :title="alertTilte" :closable="false" show-icon
+            style="width: 100%; margin-top: 10px;"></el-alert>
+
+        <div class="params-box">
+            <el-row :gutter="10">
+                <el-col :span="6">
+                    <el-input v-model="requestParams.title" placeholder="请输入考核名称"></el-input>
+                </el-col>
+                <el-col :span="6">
+                    <el-input v-model="requestParams.title" placeholder="请输入考核名称"></el-input>
+                </el-col>
+                <el-col :span="6">
+                    <el-input v-model="requestParams.title" placeholder="请输入考核名称"></el-input>
+                </el-col>
+                <el-col :span="6">
+                    <el-input v-model="requestParams.title" placeholder="请输入考核名称"></el-input>
+                </el-col>
+            </el-row>
+
+            <template v-if="selectedOkrs && selectedOkrs.length > 0">
+                <div class="title">考核模板关联的OKR</div>
+                <div class="selected-list">
+                    <div class="selected-item" v-for="item in selectedOkrs" @click="changeSelectedOkrs()">
+                        {{ item.name }}
+                    </div>
+                </div>
+            </template>
+
+            <el-table :data="tableData" style="width: 100%; margin-top: 10px;"
+                :header-cell-style="{ background: '#f5f7fa' }" border stripe size="small">
+                <el-table-column prop="title" label="指标组" width="300" align="center">
+                    <template slot-scope="scope">
+                        {{ scope.row.title || '默认标题' }}
+                    </template>
+                </el-table-column>
+                <el-table-column prop="employees" label="考核人员" align="center">
+                    <template slot-scope="scope">
+                        <div class="flex-box-ce" style="justify-content: center; flex-wrap: wrap;">
+                            <div class="flex-box-ce" v-for="item in scope.row.employees"
+                                style="justify-content: center; margin-left: 10px;"
+                                @click="openChooseOkrsDialog(scope.row.employees)">
+                                <userImage :id="item.employeeId" :img_url="item.img_url" :user_name="item.employeeName"
+                                    width="30px" height="30px" style="margin-right: 5px;"></userImage>
+                                {{ item.employeeName }}
+                            </div>
+                        </div>
+                    </template>
+                </el-table-column>
+            </el-table>
+
+        </div>
+
+        <!-- 选择模板列表 -->
+        <BatchTemplatePublish v-if="chooseExamineDialog" v-model="chooseExamineDialog" :examine-list="templateList"
+            @onConfirm="confirmChooseExamine" />
+
+        <!-- 发布考核弹框 -->
+        <PublishComp v-if="showPublishDialog" v-model="showPublishDialog" :template-ids="templateIds"
+            @onConfirm="onPubishConfirm" />
+
+        <!-- 关联OKR -->
+        <TargetSearch :visible.sync="isShowProject" @confirm="confirmProject" :selectedCheckList="requestParams.okrs"
+            :showSelectedData="selectedOkrs"></TargetSearch>
+
+        <!-- 考核人员关联OKR -->
+        <EmployeeChooseOkrs v-if="chooseOkrsDialog" v-model="chooseOkrsDialog" :selectEmployeeIds="selectEmployeeIds"
+            :employeeInfos="employeeInfos"/>
+
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import PublishComp from "../components/TemplateDetails/PublishComp"
+import BatchTemplatePublish from "../components/CombinationTemplates/BatchTemplatePublish"
+import EmployeeChooseOkrs from "../components/CombinationTemplates/EmployeeChooseOkrs"
+import TargetSearch from '@/performance/views/assessManagement/TargetSearch'; // 对齐目标
+
+export default {
+    components: {
+        PublishComp,
+        BatchTemplatePublish,
+        EmployeeChooseOkrs,
+        TargetSearch
+    },
+
+    data() {
+        return {
+            alertTilte: "每个考核模板可以关联OKR,每个考核人可以管理OKR",
+            chooseExamineDialog: false,
+            showPublishDialog: false,
+            templateList: [],
+            templateIds: [],
+            requestParams: {},
+            tableData: [],
+            employeeMap: this.$getEmployeeMap(), // 员工列表
+            selectedOkrs: [],
+            isShowProject: false,
+            chooseOkrsDialog: false,
+            selectEmployeeIds: [],
+            employeeInfos: []
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    created() {
+        this.getTemplateList()
+        this.chooseExamineDialog = true
+    },
+    methods: {
+        getTemplateList() {
+            this.$axiosUser("get", `/performance/template/list/self/${this.user_info.site_id}`).then(res => {
+                this.templateList = res.data.data.list;
+            })
+        },
+
+        confirmChooseExamine(selectExamineIds) {
+            this.templateIds = selectExamineIds;
+            this.showPublishDialog = true
+        },
+
+        // 发布考核弹框的回调
+        onPubishConfirm(params, selectOkrs) {
+            let { templateIds, title, startDate, endDate, cycleType, employeeIds, okrs, cateId, interviewFlow } = params
+            let templates = [], employees = [];
+            for (let i = 0; i < employeeIds.length > 0; i++) {
+                let employeeId
+                employees[i] = {
+                    employeeId: employeeIds[i],
+                    ids: [],
+                    interviewFlow
+                }
+            }
+            for (let i = 0; i < templateIds.length > 0; i++) {
+                let templateId
+                templates[i] = {
+                    templateId: templateIds[i],
+                    employees
+                }
+            }
+            this.selectedOkrs = selectOkrs
+            this.selectEmployeeIds = employeeIds;
+            this.requestParams = {
+                title,
+                cycleType,
+                startDate,
+                endDate,
+                okrs,
+                templates,
+                cateId
+            }
+
+            console.log(params)
+            console.log(templates)
+            console.log(this.requestParams)
+
+            this.initTableData()
+        },
+
+        // 初始化表格数据
+        initTableData() {
+
+            // 初始化模板标题
+            this.tableData = this.requestParams.templates.map(item => ({
+                templateId: item.templateId,
+                employees: item.employees
+            })
+            );
+
+
+            console.log(this.tableData)
+            this.tableData.forEach(data => {
+                data.title = this.templateList.find(tem => data.templateId === tem.templateId).title;
+                data.employees.forEach(employee => {
+                    employee.employeeName = this.employeeMap[employee.employeeId].name
+                    employee.img_url = this.employeeMap[employee.employeeId].img_url
+                })
+            })
+            console.log(this.tableData)
+
+        },
+
+        changeSelectedOkrs() {
+            this.isShowProject = true
+        },
+
+
+        confirmProject(okrs, selectedData) {
+            this.requestParams.okrs = okrs;
+            this.selectedOkrs = selectedData;
+        },
+
+        openChooseOkrsDialog(row) {
+            this.chooseOkrsDialog = true;
+            this.employeeInfos = row;
+            console.log(row)
+        }
+    }
+}
+</script>
+
+<style scoped lang="scss">
+    .all {
+        width: 100%;
+        height: 100%;
+        padding: 0 20px;
+        font-size: 14px;
+        box-sizing: border-box;
+        border-radius: 4px;
+        background: #fff;
+        /*头部样式*/
+        .header-content {
+            position: relative;
+            box-sizing: border-box;
+            height: 60px;
+            justify-content: space-between;
+    
+            .header-left {
+                width: 230px;
+                box-sizing: border-box;
+                height: 60px;
+                cursor: pointer;
+    
+                .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 {
+                width: 230px;
+                text-align: right;
+            }
+        }
+
+        .title {
+            margin: 10px 0;
+            font-weight: 600;
+        }
+
+        .selected-list {
+            display: flex;
+            align-items: center;
+            flex-wrap: wrap;
+            .selected-item {
+                width: 200px;
+                padding: 5px;
+                box-sizing: border-box;
+                border-radius: 4px;
+                background: #f7f7f7;
+                margin-bottom: 10px;
+                margin-right: 10px;
+                color: #999;
+                &:hover {
+                    cursor: pointer;
+                }
+            }
+        }
+        
+    }
+</style>

+ 239 - 0
src/newPerformance/views/ExamineDetails.vue

@@ -0,0 +1,239 @@
+<template>
+    <div class="box boxMinHeight">
+        <header class="header">
+            <div class="header-content flex-box-ce">
+                <!-- 返回按钮 -->
+                <div class="flex-box-ce header-left" @click="$router.go(-1)">
+                    <i class="el-icon-arrow-left fontColorC"></i>
+                    <el-tooltip class="item" effect="dark" :content="title" placement="bottom">
+                        <div class="text fontColorB font-flex-word">
+                            {{ title }}
+                            <div class="date-box fontColorC" style="margin-left: 20px; font-size: 14px;">
+                                考核周期: {{ startTime | formatDate }} 至 {{ endTime | formatDate }}
+                            </div>
+                        </div>
+                    </el-tooltip>
+                </div>
+
+                <!-- <div class="status-btn-box fadeInDown animated">
+                    <div class="status-btn">
+                        合格
+                    </div>
+                </div> -->
+                <div v-if="levelName" class="status-btn-box fadeInDown animated">
+                    <div class="status-btn">
+                        {{ levelName }}
+                    </div>
+                </div>
+            </div>
+        </header>
+        <div style="height: 1px; background-color: #DCDFE6;margin-top: 5px;"></div>
+
+        <el-table :data="tableData" stripe style="width: 100%; margin-top: 20px;" border
+            :header-cell-style="{ background: '#f5f7fa' }">
+            <el-table-column prop="title" label="指标" align="center">
+            </el-table-column>
+            <el-table-column prop="content" label="规则" align="center">
+            </el-table-column>
+            <el-table-column prop="target" label="目标" align="center">
+            </el-table-column>
+            <el-table-column prop="unit" label="单位" align="center">
+            </el-table-column>
+            <el-table-column prop="weight" label="权重" align="center">
+            </el-table-column>
+            <el-table-column prop="formulae" label="计算公式" align="center">
+                <template slot-scope="scope">
+                    <div>
+                        <div v-for="item in scope.row.formulae" :key="item.id">
+                            {{ item.name }}
+                        </div>
+                    </div>
+                </template>
+            </el-table-column>
+            <el-table-column prop="confirm_target" label="确认目标" align="center">
+                <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>
+                    <ShowDataComp v-else :show-data="scope.row.flow.nodes[0]" :select-nodes="scope.row" readonly />
+                </template>
+            </el-table-column>
+            <el-table-column prop="input_result" label="录入结果" align="center">
+                <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>
+                    <ShowDataComp v-else :show-data="scope.row.flow.nodes[1]" :select-nodes="scope.row" readonly />
+                </template>
+            </el-table-column>
+            <el-table-column prop="self_assessment" label="自评" align="center">
+                <template slot-scope="scope">
+                    <el-switch v-model="scope.row.flow.nodes[2].enable" disabled></el-switch>
+                </template>
+            </el-table-column>
+            <el-table-column prop="peer_assessmen" label="互评" align="center">
+                <template slot-scope="scope">
+                    <el-switch v-model="scope.row.flow.nodes[3].enable" disabled></el-switch>
+                </template>
+            </el-table-column>
+            <el-table-column prop="grade" label="评分" align="center">
+                <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>
+                    <ShowDataComp v-else :show-data="scope.row.flow.nodes[4]" :select-nodes="scope.row" readonly />
+                </template>
+            </el-table-column>
+            <el-table-column prop="approval" label="审批" align="center">
+                <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>
+                    <ShowDataComp v-else :show-data="scope.row.flow.nodes[5]" :select-nodes="scope.row" readonly />
+                </template>
+            </el-table-column>
+            <el-table-column prop="carbon_copy" label="抄送" align="center">
+                <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>
+                    <ShowDataComp v-else :show-data="scope.row.flow.nodes[6]" :select-nodes="scope.row" readonly />
+                </template>
+            </el-table-column>
+        </el-table>
+    </div>
+</template>
+
+<script>
+import moment from 'moment';
+import { mapGetters } from 'vuex';
+import ShowDataComp from '@/newPerformance/components/PublicComp/ShowData'; // 显示节点数据组件
+
+export default {
+    components: {
+        ShowDataComp
+    },
+    data() {
+        return {
+            title: "默认标题",
+            startTime: "",
+            endTime: "",
+            tableData: [],
+            levelName: ""
+        }
+    },
+    computed: {
+        ...mapGetters(['user_info'])
+    },
+    filters: {
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    created() {
+        this.getDetails()
+    },
+    methods: {
+        getDetails() {
+            let reviewId = this.$route.params.id;
+            let url = `/performance/statistics/review/info/${this.user_info.site_id}/${reviewId}`
+            this.$axiosUser('get', url).then(res => {
+                let { data: { title, levelName, indicators, startTime, endTime }, code } = res.data
+                this.title = title || "默认标题";
+                this.startTime = startTime || '';
+                this.endTime = endTime || '';
+                this.levelName = levelName || '';
+                this.tableData = indicators || [];
+            });
+        },
+        editTableData() {
+        }
+    }
+};
+</script>
+<style scoped="scoped" lang="scss">
+.box {
+    padding: 0 20px;
+    font-size: 14px;
+    background-color: #fff;
+    box-sizing: border-box;
+
+    &::-webkit-scrollbar {
+        width: 3px;
+        height: 10px;
+        background-color: #fff;
+    }
+
+    &::-webkit-scrollbar-thumb {
+        background-color: #d9d9d9;
+    }
+
+    /*头部样式*/
+    .header-content {
+        position: relative;
+        box-sizing: border-box;
+        height: 60px;
+        justify-content: space-between;
+
+        .header-left {
+            width: 600px;
+            box-sizing: border-box;
+            height: 60px;
+            cursor: pointer;
+
+            .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: 30px;
+                display: flex;
+                &::before {
+                    position: absolute;
+                    content: '';
+                    width: 1px;
+                    height: 36px;
+                    background-color: #ebebeb;
+                    left: 44px;
+                    top: 50%;
+                    margin-top: -18px;
+                }
+            }
+        }
+
+        .status-btn-box {
+            width: 120px;
+            height: 40px;
+            z-index: 10;
+            position: absolute;
+            top: 40px;
+            right: -20px;
+            .status-btn {
+                width: 100%;
+                height: 100%;
+                background: transparent;
+                border: 2px dashed #67C23A;
+                color: #67C23A;
+                text-align: center;
+                font-size: 20px;
+                line-height: 40px;
+                transform: rotate(45deg);
+            }
+        }
+    }
+
+    
+    .plus-button {
+        display: block;
+        margin: 20px auto;
+    }
+
+}
+
+
+</style>

+ 160 - 0
src/newPerformance/views/Index.vue

@@ -0,0 +1,160 @@
+<template>
+    <div class="all">
+
+        <el-container>
+            <el-aside>
+                <div class="title" style="">
+                    <span>新绩效系统</span>
+                    <el-popover placement="right" trigger="hover" effect="dark" content="切换旧绩效系统">
+                        <i class="el-icon-sort" slot="reference" @click="backJob()" />
+                    </el-popover>
+                </div>
+                <el-menu class="el-menu-vertical-demo" ref="elMenu" :default-active="currentId"
+                    @select="changeEntrance">
+                    <el-menu-item v-for="item in entranceList" :key="item.id" :index="item.id">
+                        <div class="flex-box-ce">
+                            <i :class="item.icon" :style="{ color: item.color }"></i>
+                            <span>{{ item.name }}</span>
+                        </div>
+                    </el-menu-item>
+                </el-menu>
+            </el-aside>
+            <el-main>
+                <!-- 工作台 -->
+                <Workbench v-if="currentId == '1'" />
+                <!-- 考核设计 -->
+                <!-- <ExamineSetting v-if="currentId == '2'" /> -->
+                <!-- 我的考核 -->
+                <MyPerformance v-if="currentId == '2'" />
+                <!-- 考核记录 -->
+                <ExamineRecord v-if="currentId == '3'" />
+                <!-- 考核对比 -->
+                <ExamineContrast v-if="currentId == '4'" />
+                <!-- 角色设置 -->
+                <RoleSetting v-if="currentId == '5'" />
+                <!-- 组织考核 -->
+                <OrganizationExamine v-if="currentId == '6'" />
+                <!-- 组织考核 -->
+                <PersonalExamine v-if="currentId == '7'" />
+            </el-main>
+        </el-container>
+
+    </div>
+</template>
+
+<script>
+
+import Workbench from '../components/Workbench'; // 工作台
+import ExamineSetting from '../components/ExamineSetting'; // 考核设计
+import MyPerformance from '../components/MyPerformance'; // 我的考核
+import ExamineRecord from '../components/ExamineRecord'; // 考核记录
+import ExamineContrast from '../components/ExamineContrast'; // 考核对比
+import RoleSetting from '../components/RoleSetting'; // 角色设置
+import OrganizationExamine from '../components/OrganizationExamine'; // 组织考核
+import PersonalExamine from '../components/PersonalExamine'; // 个人考核
+import PerformanceInterview from '../components/PerformanceInterview'; // 绩效面谈
+
+
+export default {
+    name: 'job',
+    components: { Workbench, ExamineSetting, MyPerformance, ExamineRecord, ExamineContrast, RoleSetting, OrganizationExamine, PersonalExamine, PerformanceInterview },
+    data() {
+        return {
+            entranceList: [
+                { id: '1', name: '工作台', icon: 'el-icon-menu', color: "#1BAEED" },
+                // { id: '2', name: '考核设计', icon: 'el-icon-menu', color: "#1BAEED" },
+                { id: '2', name: '我的考核', icon: 'el-icon-data-analysis', color: "#EEA016" },
+                { id: '3', name: '考核记录', icon: 'el-icon-s-data', color: "#67c23a" },
+                { id: '4', name: '考核对比', icon: 'el-icon-c-scale-to-original', color: "#C38FE9" },
+                // { id: '8', name: '绩效面谈', icon: 'el-icon-office-building', color: "#FF6347" },
+                { id: '6', name: '组织考核', icon: 'el-icon-office-building', color: "#FF6347" },
+                { id: '7', name: '个人考核', icon: 'el-icon-user', color: "#4C78E6" },
+                { id: '5', name: '角色设置', icon: 'el-icon-user-solid', color: "#4C78E6" }
+            ],
+            currentId: '1'
+        };
+    },
+    
+    
+    methods: {
+        changeEntrance(index) {
+            this.currentId = index;
+        },
+        backJob() {
+            this.$router.push("/performanceIndex")
+        }
+    }
+};
+</script>
+
+<style scoped="scoped" lang="scss">
+
+
+
+.all {
+    width: 100%;
+    height: 100%;
+    font-size: 14px;
+    border-radius: 4px;
+    display: flex;
+    flex-direction: column;
+    position: relative;
+    overflow: hidden;
+    
+
+    .title {
+        padding: 20px;
+        font-weight: 600;
+        border-bottom: 1px solid #f1f1f1;
+        font-size: 16px;
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        i {
+            transform: rotate(90deg);
+            color: #99a9bf;
+            font-size: 20px;
+            transition: all 0.3s;
+    
+            &:hover {
+                color: rgb(64, 158, 255);
+                cursor: pointer;
+            }
+        }
+    }
+
+    .el-menu-vertical-demo:not(.el-menu--collapse) {
+        width: 180px;
+    }
+
+    .el-menu {
+        overflow: hidden;
+        border: none;
+    }
+
+    .el-aside {
+        transition: width 0.28s;
+        width: 180px !important;
+        background-color: #fff;
+        height: calc(100vh - 80px);
+        border-radius: 5px;
+    }
+
+    .el-main {
+        display: flex;
+        flex-direction: column;
+        padding: 0 10px;
+        min-width: 1100px;
+    }
+}
+
+
+
+
+
+
+
+
+
+</style>

+ 786 - 0
src/newPerformance/views/PerformanceInterview.vue

@@ -0,0 +1,786 @@
+<template>
+    <div class="all">
+        <div class="flex-box-ce header" style="">
+            <div class="btn-box">
+                <el-button v-if="businessStatus === 'interview' && !interviewConnect" type="primary" size="small"
+                    @click="initMeeting()">加入面谈</el-button>
+                <el-button v-if="interviewConnect" type="warning" size="small" @click="leaveInterview()">离开面谈</el-button>
+                <el-button v-if="interviewConnect" type="danger" size="small" @click="finishInterview()">结束面谈</el-button>
+            </div>
+            <div>
+                <el-rate v-model="value" @change="sendSatisfaction()" :disable="canSatisfaction"></el-rate>
+            </div>
+        </div>
+
+        <div class="examine-info">
+            <div class="flex-box-ce" style="justify-content: space-between; padding: 5px ; box-sizing: border-box;">
+                <div>考核信息</div>
+                <div @click="detailsDialog = true">查看更多</div>
+            </div>
+            <el-descriptions :column="3" border>
+                <el-descriptions-item>
+                    <template slot="label">
+                        姓名
+                    </template>
+                    <div class="flex-box-ce" v-if="interviewInfo && interviewInfo.employee">
+                        <userImage :id="interviewInfo.employee.id" :img_url="interviewInfo.employee.imgUrl"
+                            :user_name="interviewInfo.employee.employeeName" width="30px" height="30px"
+                            style="margin-right: 5px;"></userImage>
+                        {{ interviewInfo && interviewInfo.employee && interviewInfo.employee.name || '--' }}
+                    </div>
+
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        考核名称
+                    </template>
+                    {{ interviewInfo && interviewInfo.reviewInfo && interviewInfo.reviewInfo.title || '--'}}
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        考核周期
+                    </template>
+                    <el-tag size="small">{{ interviewInfo && interviewInfo.reviewInfo &&
+                        interviewInfo.reviewInfo.cycleType | formatCycleType }}</el-tag>
+                </el-descriptions-item>
+
+                <el-descriptions-item>
+                    <template slot="label">
+                        考核时间
+                    </template>
+                    {{ interviewInfo && interviewInfo.reviewInfo && interviewInfo.reviewInfo.startTime | formatDate }}
+                    至
+                    {{ interviewInfo && interviewInfo.reviewInfo && interviewInfo.reviewInfo.endTime | formatDate }}
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        评分
+                    </template>
+                    {{ interviewInfo && interviewInfo.reviewInfo && interviewInfo.reviewInfo.score }}
+                </el-descriptions-item>
+                <el-descriptions-item>
+                    <template slot="label">
+                        评级
+                    </template>
+                    {{ interviewInfo && interviewInfo.reviewInfo && interviewInfo.reviewInfo.levelName }}
+                </el-descriptions-item>
+
+            </el-descriptions>
+        </div>
+
+        <div class="flex-box-ce flex-1" style="height: 100%; ">
+            <div class="left-box">
+                <div class="flex-box-ce" style="margin-bottom: 10px;">
+                    <!-- 主持人记录 -->
+                    <div class="emcee-record">
+                        <div class="title">主持人记录</div>
+                        <div class="content" v-if="interviewInfo && interviewInfo.assigneeComment">
+                            <el-input type="textarea" v-model="interviewInfo.assigneeComment" placeholder="输入内容"
+                                rows="5" @keydown.native="sendAssigneeComment($event)"
+                                :disable="canAssigneeComment"></el-input>
+                        </div>
+                    </div>
+                    <!-- 考核人记录 -->
+                    <div class="examinee-record">
+                        <div class="title">考核人记录</div>
+                        <div class="content" v-if="interviewInfo && interviewInfo.employeeComment">
+                            <el-input type="textarea" v-model="interviewInfo.employeeComment" placeholder="输入内容"
+                                rows="5" @keyup.enter.native="sendEmployeeComment($event)"></el-input>
+                        </div>
+                    </div>
+                </div>
+                <div class="input-box flex-1">
+                    <el-input type="textarea" v-model="chatLog" placeholder="输入内容" rows="5"
+                        @keyup.enter.native="sendChatLog($event)"></el-input>
+                    <div class="flex-box-ce" style="margin-top: 10px;">
+                        <el-button type="primary" size="small" style="margin-left: auto;" @click="sendChatLog2()">
+                            发 送
+                        </el-button>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 聊天记录 -->
+            <div class="chat-record">
+                <div class="chat-record-title">聊天记录</div>
+                <div class="scroll-bar"
+                    style="height: 500px; overflow-y: auto; padding-bottom: 50px; box-sizing: border-box;">
+                    <div class="chat-item" v-for="item in chatList" :key="item.logId">
+                        <div class="flex-box-ce" style="justify-content: space-between;">
+                            <div class="flex-box-ce">
+                                <userImage :id="item.employeeId" :img_url="item.imgUrl" :user_name="item.employeeName"
+                                    width="30px" height="30px" style="margin-right: 5px;"></userImage>
+                                {{ item.employeeName }}
+                            </div>
+                            <div class="fontColorC">
+                                {{ item.ct }}
+                            </div>
+                        </div>
+
+                        <div class="content fontColorC">
+                            {{ item.content }}
+                        </div>
+                    </div>
+                    <div ref="chatLogRef"></div>
+                </div>
+            </div>
+        </div>
+
+        <el-drawer title="考核信息" :visible.sync="detailsDialog" direction="rtl" :before-close="handleClose" size="70%">
+            <!-- <el-descriptions :column="1" border style="padding: 0 5px; box-sizing: border-box;">
+                <el-descriptions-item>
+                    <template slot="label">
+                        姓名
+                    </template>
+                </el-descriptions-item>
+            </el-descriptions> -->
+            <!-- <el-table :data="tableData" style="width: 100%">
+                <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"
+                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">
+                </el-table-column>
+                <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="target" label="目标" align="center" min-width="100">
+                </el-table-column>
+                <el-table-column prop="unit" label="单位" align="center" min-width="100">
+                </el-table-column>
+                <el-table-column prop="weight" label="权重" align="center" min-width="100">
+                </el-table-column>
+                <el-table-column prop="result" label="结果值" align="center" min-width="100">
+                </el-table-column>
+                <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"
+                            @click="openFormula(scope.row, scope.$index)">
+                            {{ scope.row.expression && scope.row.expression.formulas.length > 0 ? `公式
+                            ${scope.row.expression.formulas.length} 条` : '公式' }}
+                        </el-button>
+                        <div v-else style="color: #999;">暂无公式</div>
+                    </template>
+                </el-table-column>
+
+
+                <el-table-column prop="businessStatus" label="指标考核状态" align="center" min-width="120">
+                    <template slot-scope="scope">
+                        <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 prop="confirm_target" label="确认目标" align="center" width="80" 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 prop="input_result" label="录入结果" align="center" width="80" 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 prop="self_assessment" label="自评" align="center" width="80" 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 prop="peer_assessmen" label="互评" align="center" width="80" 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 prop="grade" label="评分" align="center" width="80" fixed="right">
+                    <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 prop="approval" label="审批" align="center" width="80" 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]" />
+                    </template>
+                </el-table-column>
+                <el-table-column prop="carbon_copy" label="抄送" align="center" width="80" fixed="right">
+                    <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]" />
+                    </template>
+                </el-table-column>
+            </el-table>
+        </el-drawer>
+
+        <!-- 编辑计算公式 -->
+        <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" />
+
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex';
+import moment from 'moment';
+import Stomp from 'stompjs'
+import ShowHandlerComp from '@/newPerformance/components/PublicComp/ShowHandler'; // 显示节点数据组件
+import FormulaComp from '@/newPerformance/components/TemplateDetails/FormulaComp'; // 计算公式弹框
+
+export default {
+    components: {
+        ShowHandlerComp,
+        // FormulaComp
+    },
+    data() {
+        return {
+            chatLog: '',
+            value: 0,
+            isShow: false,
+            chatList: [],
+            interviewInfo: {},
+            stompClient: null, // 实例
+            interviewConnect: false, // 面试是否连接
+            interviewId: 7,
+            hasJoinInterview: false, // 是否已加入面谈会议
+            users: [], // 当前面谈会议人员
+            detailsDialog: false,
+            showFormula: false,
+            currentIndicator: null
+        }
+    },
+
+    created() {
+        this.getChatList();
+        this.getInterviewDetails();
+    },
+    computed: {
+        ...mapGetters(['user_info']),
+        businessStatus() {
+            return this.interviewInfo && this.interviewInfo.businessStatus
+        },
+        tableData() {
+            return this.interviewInfo && this.interviewInfo.reviewInfo && this.interviewInfo.reviewInfo.indicators
+        },
+        canInterviewLog() {
+            return this.interviewConnect && this.hasJoinInterview;
+        },
+        //是否可以给予满意度,被考核人不管会议是否开始或者面谈状态是否已结束都可以
+        canSatisfaction() {
+            return this.interviewInfo && this.interviewInfo.assessor;
+        },
+
+        //是否可以填写主持人记录,主持人并且只有在进入面谈会议状态下可以填写
+        canAssigneeComment() {
+            return this.interviewInfo && this.interviewInfo.host && this.hasJoinInterview;
+        },
+
+        //是否可以填写被考核人记录,与填写满意度一样
+        canEmployeeComment() {
+            return this.canSatisfaction;
+        }
+    },
+    filters: {
+        formatCycleType(val) {
+            if (val == 0) return '未定义'
+            if (val == 1) return '年度'
+            if (val == 2) return '半年'
+            if (val == 3) return '季度'
+            if (val == 4) return '月度'
+            else return '--'
+        },
+        formatDate(val) {
+            if (val) return moment(val).format('YYYY-MM-DD')
+            else return "--"
+        }
+    },
+    methods: {
+        handleClose() {
+            this.detailsDialog = false
+        },
+        // 打开计算公式弹框
+        openFormula(row, index) {
+            this.currentIndicator = row;
+            this.showFormula = true;
+        },
+
+        onFormulaConfirm() { },
+        initMeetingState() {
+            this.stompClient = null;
+            this.interviewConnect = false;
+            this.hasJoinInterview = false;
+            this.users = [];
+        },
+        // 聊天列表
+        getChatList() {
+            let url = `/performance/interview/chat/logs/${this.user_info.site_id}/${this.interviewId}`
+            this.$axiosUser('get', url).then(res => {
+                this.chatList = res.data.data.list;
+                this.$nextTick(() => {
+                    if (this.$refs.chatLogRef) this.$refs.chatLogRef.scrollIntoView(true);
+                })
+            });
+        },
+        // 面谈详情
+        getInterviewDetails() {
+            let url = `/performance/interview/info/${this.user_info.site_id}/${this.interviewId}`
+            this.$axiosUser('get', url).then(res => {
+                this.interviewInfo = res.data.data;
+                this.value = this.interviewInfo.satisfactionLevel
+            });
+        },
+
+        // 加入面谈
+        initMeeting() {
+            console.log("加入面谈");
+            if (this.stompClient && this.interviewConnect) return;
+            this.initMeetingState();
+            // 连接到 STOMP 服务器
+            this.stompClient = Stomp.client('ws://new.gdy.g107.com/performance/ws') // 创建stomp对象
+            this.stompClient.connect(
+                {
+                    at: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOm51bGwsImlhdCI6MTc0MTgyNjk5OCwiZXhwIjotMSwibmJmIjoxNzQxODI2OTk4LCJqdGkiOiJDWk1PQnVoa0lFV2RndzNvIiwic3ViIjoxOTc3LCJwcnYiOiJjYTY0ODlkNTBmMjQwN2E2NzgzMGU4MDkwZDAxNDg4MzU2ODU5NjJiIiwicm9sZSI6ImVtcGxveWVlIn0.GfSKlOCdSUDSPSebuT7CswEhJtmOFWLTfzeOU1-QcVk'
+                },
+                (res) => { // 连接成功,此时可以在network中看到建立了ws连接
+                    console.log("连接成功,此时可以在network中看到建立了ws连接")
+                    this.interviewConnect = true;
+                    // 在这里进行订阅 主题由后端提供
+                    // 发起订阅
+                    this.subscribe()
+
+                    // 加入面谈
+                    this.joinInterview()
+                },
+                (err) => { // 连接失败
+                    console.log(err)
+                }
+            )
+            this.stompClient.heartbeat.outgoing = 10000 // 10秒发一次
+            this.stompClient.heartbeat.incoming = 10000 // 10秒发一次
+            this.stompClient.debug = function (str) {
+                console.log(str)
+            };
+        },
+
+        subscribe() {
+            if (!this.stompClient || !this.interviewConnect) return;
+            //订阅参加面谈
+            this.stompClient.subscribe(`/topic/interview/join/${this.interviewId}`, (message) => {
+                console.log('received interview join message', message);
+                let user = JSON.parse(message.body);
+                if (!user) return;
+                if (this.user_info.id !== user.id) {
+                    // 别人进入面谈
+                    this.$notify({
+                        title: `${user.name}`,
+                        message: '加入了会议',
+                        type: 'success'
+                    });
+
+                } else {
+                    // 自己进入面谈
+                    this.getChatList();
+                    this.getInterviewDetails();
+                    this.getInterviewUsers();
+                    this.hasJoinInterview = true;
+                }
+            });
+            // 订阅离开面谈
+            this.stompClient.subscribe(`/topic/interview/leave/${this.interviewId}`, (message) => {
+                console.log('received interview leave message', message);
+                let user = JSON.parse(message.body);
+
+                if (this.user_info.id !== user.id) {
+
+                    //别人离开面谈
+
+                    this.$notify({
+                        title: `${user.name}`,
+                        message: '离开了会议',
+                        type: 'error'
+                    });
+                } else {
+                    //自己离开面谈
+                    this.disconnect();
+                }
+            });
+            // 订阅主持人记录更新
+            this.stompClient.subscribe(`/topic/interview/comment/assignee/${this.interviewId}`, (message) => {
+                console.log('received comment assignee message', message);
+                if (!this.interviewInfo) return;
+                this.interviewInfo.assigneeComment = message.body;
+                this.$message.success("操作成功")
+            });
+
+            //订阅被考核人记录更新
+            this.stompClient.subscribe(`/topic/interview/comment/employee/${this.interviewId}`, (message) => {
+                console.log('received comment employee message', message);
+                if (!this.interviewInfo) return;
+                this.interviewInfo.employeeComment = message.body;
+                this.$message.success("操作成功")
+            });
+
+            //订阅聊天记录更新
+            this.stompClient.subscribe(`/topic/interview/chat/log/${this.interviewId}`, (message) => {
+                console.log('received chat log message', message);
+                let log = JSON.parse(message.body);
+                if (log) this.chatList.push(log);
+                this.$nextTick(() => {
+                    if (this.$refs.chatLogRef) this.$refs.chatLogRef.scrollIntoView(false);
+                })
+            });
+
+            // 订阅面谈满意度更新
+            this.stompClient.subscribe(`/topic/interview/satisfaction/${this.interviewId}`, (message) => {
+                console.log('订阅面谈满意度更新', message);
+                let tmp = JSON.parse(message.body);
+                if (tmp) this.interviewInfo.satisfactionLevel = tmp.satisfactionLevel || this.interviewInfo.satisfactionLevel;
+                this.$message.success("操作成功")
+            })
+        },
+
+        // 加入会议
+        joinInterview() {
+            if (!this.stompClient || !this.interviewConnect) return;
+
+            this.stompClient.send(`/app/interview/join/${this.interviewId}`);
+            // 获取当前会议的人员列表
+            this.getInterviewUsers();
+        },
+
+        // 获取当前会议的人员列表
+        getInterviewUsers() {
+            let url = `/performance/interview/users/${this.user_info.site_id}/${this.interviewId}`
+            this.$axiosUser('get', url).then(res => {
+                if(res.data.code) this.users = res.data.data
+            });
+        },
+
+        // 离开会议
+        leaveInterview() {
+            if (!this.stompClient || !this.interviewConnect) return;
+
+            this.stompClient.send(`/app/interview/leave/${this.interviewId}`)
+        },
+
+        // 发送聊天记录
+        sendChatLog(event) {
+            // 去掉换行符
+            if (event.keyCode == 13) {
+                if (!event.ctrlKey) {
+                    event.preventDefault();
+                    if (!this.stompClient || !this.hasJoinInterview || !this.canInterviewLog || !this.chatLog) return;
+                    this.stompClient.send(`/app/interview/chat/log/${this.interviewId}`,
+                        {},
+                        this.chatLog
+                    );
+                    this.chatLog = '';
+                    // alt + enter换行
+                } else {
+                    message += "\n";
+                }
+            }
+
+        },
+
+        sendChatLog2() {
+            if (!this.stompClient || !this.hasJoinInterview || !this.canInterviewLog || !this.chatLog) return;
+            this.stompClient.send(`/app/interview/chat/log/${this.interviewId}`,
+                {},
+                this.chatLog
+            );
+            this.chatLog = '';
+        },
+
+        // 主持人发布记录
+        sendAssigneeComment(event) {
+            // 去掉换行符
+            if (event.keyCode == 13) {
+                if (!event.ctrlKey) {
+                    event.preventDefault();
+                    if (!this.stompClient || !this.hasJoinInterview || !this.interviewInfo.host) return;
+                    this.stompClient.send(`/app/interview/comment/assignee/${this.interviewId}`,
+                        {},
+                        this.interviewInfo.assigneeComment
+                    )
+                // alt + enter换行
+                } else {
+                    message += "\n";
+                }
+            }
+        },
+
+        // 被考核人发布记录
+        sendEmployeeComment(event) {
+            // 去掉换行符
+            if (event.keyCode == 13) {
+                if (!event.ctrlKey) {
+                    event.preventDefault();
+                    if (this.canEmployeeComment) return;
+                    if (!this.stompClient || !this.hasJoinInterview || !this.interviewInfo.assessor) {
+                        //普通面谈详情模式
+                        this.apiEmployeeComment();
+                    } else {
+                        //面谈会议模式
+                        stompClient.value.send(`/app/interview/comment/employee/${interviewId.value}`,
+                            {},
+                            interview.value.employeeComment)
+                    }
+                    // alt + enter换行
+                } else {
+                    message += "\n";
+                }
+            }
+
+        },
+
+        // 普通面谈详情模式 - 提交被考核人记录
+        apiEmployeeComment() {
+            if (!this.interviewInfo || !this.canEmployeeComment) return;
+            let url = `/performance/interview/comment/employee/${this.user_info.siteId}`
+            let data = {
+                interviewId: this.interviewId,
+                comment: this.interviewInfo.employeeComment
+            }
+            this.$http.post(url, data).then(res => {
+                if (res.data.code == 1) this.interviewInfo = res.data.data;
+                if (!this.interviewInfo) this.leaveInterview();
+            })
+        },
+
+        // 提交满意度
+        sendSatisfaction() {
+            if (!this.canSatisfaction) return;
+            if (!this.stompClient || !this.hasJoinInterview || !this.interviewInfo.assessor) {
+                //普通面谈详情模式
+                this.apiSatisfaction();
+            } else {
+                //面谈会议模式
+                this.stompClient.send(`/app/interview/satisfaction/${this.interviewId}`,
+                    {},
+                    this.interviewInfo.satisfactionLevel
+                )
+            }
+        },
+
+        apiSatisfaction() {
+            if (!this.interviewInfo || !this.canSatisfaction) return;
+            let url = `/performance/interview/satisfaction/${this.user_info.site_id}`
+            let data = {
+                interviewId: this.interviewId,
+                level: this.value
+            }
+            this.$http.post(url, data).then(res => {
+                if (res.data.code == 1) this.interviewInfo = res.data.data;
+                if (!this.interviewInfo) this.leaveInterview();
+            })
+        },
+
+        disconnect(){
+            if (!this.stompClient || !this.interviewConnect) return;
+            this.leaveInterview();
+            this.stompClient.disconnect();
+            this.initMeetingState();
+
+        },
+
+        // 结束面谈
+        finishInterview() {
+            if (this.stompClient || this.interviewConnect) return;
+            let url = `/performance/interview/close/${this.user_info.site_id}/${this.interviewId}`
+            this.$http.post(url, {}).then(res => {
+                if (res.data.code == 1) this.interviewInfo = res.data.data;
+                if (!this.interviewInfo) this.leaveInterview();
+            })
+        },
+
+
+        // onAssigneeFocused(focused) {
+        //     if (!focused) this.sendAssigneeComment();
+        // },
+
+        // onEmployeeFocused(focused) {
+        //     if (!focused) this.sendEmployeeComment();
+        // }
+    },
+    beforeDestroy() {
+        if(this.stompClient) this.stompClient.disconnect()
+    }
+}
+</script>
+
+<style lang="scss">
+.all {
+    .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;
+    }
+}
+
+.oneLine {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+</style>
+
+<style scoped lang="scss">
+.all {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    box-sizing: border-box;
+    background: #f0f4fa;
+    display: flex;
+    flex-direction: column;
+    .header {
+        background: #fff;
+        padding: 10px;
+        margin-bottom: 10px;
+        box-sizing: border-box;
+        justify-content: space-between;
+    }
+    .examine-info {
+        background: #fff;
+        padding: 10px;
+        margin-bottom: 10px;
+        box-sizing: border-box;
+    }
+
+
+    .left-box {
+        width: 76%;
+        height: 100%;
+        margin-right: 10px;
+        display: flex;
+        flex-direction: column;
+
+        .emcee-record, .examinee-record {
+            width: 100%;
+            height: 450px;
+            margin-right: 10px;
+            background-color: #fff;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            .title {
+                width: 100%;
+                line-height: 40px;
+                font-size: 16px;
+                font-weight: 600;
+                padding: 0 10px;
+                box-sizing: border-box;
+                border-bottom: 1px solid #f7f7f7;
+            }
+            .content {
+                width: 95%;
+                height: 85%;
+                padding: 10px;
+                box-sizing: border-box;
+                background: #F7F8FA;
+                margin-top: 10px;
+            }
+        }
+
+        .examinee-record {
+            margin-right: 0;
+        }
+
+        .input-box {
+            width: 100%;
+            background-color: #fff;
+            padding: 10px;
+            box-sizing: border-box;
+            margin-right: 10px;
+        }
+    }
+
+
+
+    .chat-record {
+        width: 24%;
+        height: 100%;
+        background-color: #fff;
+        // overflow-y: auto;
+        .chat-record-title {
+            width: 100%;
+            line-height: 50px;
+            font-size: 16px;
+            font-weight: 600;
+            padding: 0 10px;
+            box-sizing: border-box;
+            border-bottom: 1px solid #f7f7f7;
+        }
+        .chat-item {
+            margin-bottom: 10px;
+            border-radius: 4px;
+            padding: 5px;
+            box-sizing: border-box;
+            margin-top: 10px;
+            .content {
+                padding: 5px;
+                box-sizing: border-box;
+                background: #F7F8FA;
+                margin: 5px 0 0 20px;
+            }
+        }
+    }
+
+    /* 设置滚动条的宽度和背景色 */
+    ::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>

+ 920 - 0
src/newPerformance/views/TemplateDetails copy.vue

@@ -0,0 +1,920 @@
+<template>
+    <div class="box boxMinHeight">
+        <header class="header">
+            <div class="header-content flex-box-ce">
+                <!-- 返回按钮 -->
+                <div class="flex-box-ce header-left">
+                    <i class="el-icon-arrow-left fontColorC" @click="$router.go(-1)"></i>
+                    <el-tooltip class="item" effect="dark" :content="templateTitle" placement="bottom">
+                        <div class="text fontColorB font-flex-word">{{ 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 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" @click="publish" :disabled="isTitleNull || isScoreNull">发布考核</el-button>
+                </div>
+            </div>
+        </header>
+
+        <div style="height: 1px; background-color: #DCDFE6; margin-top: 5px;"></div>
+
+        <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="justify-content: space-between; margin-top: 10px;">
+            <div class="flex-box-ce">
+                <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>
+
+
+            <div class="flex-box-ce" style="padding: 0 20px; 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">
+                        <i class="el-icon-s-tools" style="margin-right: 5px;"></i>流程节点展示
+                    </div>
+                </el-popover>
+            </div>
+        </div>
+
+        <el-table :data="tableData" ref="fmeaTableRef" v-table-move="['fmeaTableRef']" v-loading="loading" stripe
+            style="width: 100%; margin-top: 10px;" border :header-cell-style="{ background: '#f5f7fa' }"
+            @selection-change="handleSelectChange" :height="600">
+            <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-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
+    },
+    data() {
+        return {
+            isDataShow: false,
+            isShow: false,
+            templateTitle: "默认标题",
+            loading: false,
+            title: "默认标题",
+            alertTilte: "可在表格中直接编辑指标,规则 (规则支持富文本) ,目标,单位,权重,计算公式以及流程节点, 注意: 每个指标的标题不能为空!每个指标的评分节点一定要开启!",
+            templateId: "", // 模板ID
+            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: 150},
+                { label: "单位", prop: "unit", isShow: true, width: 100 },
+                { label: "权重(%)", prop: "weight", isShow: true, width: 100 },
+                { label: "计算公式", prop: "formulae", isShow: true, width: 100 },
+                { 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.templateId = this.$route.params.templateDetailId;
+        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 = "录入结果11"
+                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 => {
+                // if (res.data.code == 1) this.get_table_data();
+                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) {
+            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 20px;
+    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: 60px;
+        justify-content: space-between;
+
+        .header-left {
+            width: 230px;
+            box-sizing: border-box;
+            height: 60px;
+            cursor: pointer;
+
+            .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 {
+            width: 230px;
+            text-align: right;
+        }
+    }
+
+    .plus-button {
+        display: block;
+        margin: 20px auto;
+    }
+
+}
+</style>

+ 921 - 0
src/newPerformance/views/TemplateDetails.vue

@@ -0,0 +1,921 @@
+<template>
+    <div class="box boxMinHeight">
+        <header class="header">
+            <div class="header-content flex-box-ce">
+                <!-- 返回按钮 -->
+                <div class="flex-box-ce header-left">
+                    <i class="el-icon-arrow-left fontColorC" @click="$router.go(-1)"></i>
+                    <el-tooltip class="item" effect="dark" :content="templateTitle" placement="bottom">
+                        <div class="text fontColorB font-flex-word">{{ 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 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" @click="publish" :disabled="isTitleNull || isScoreNull">发布考核</el-button>
+                </div>
+            </div>
+        </header>
+
+        <div style="height: 1px; background-color: #DCDFE6; margin-top: 5px;"></div>
+
+        <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="justify-content: space-between; margin-top: 10px;">
+            <div class="flex-box-ce">
+                <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>
+
+
+            <div class="flex-box-ce" style="padding: 0 20px; 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">
+                        <i class="el-icon-s-tools" style="margin-right: 5px;"></i>流程节点展示
+                    </div>
+                </el-popover>
+            </div>
+        </div>
+
+        <el-table :data="tableData" ref="fmeaTableRef" v-table-move="['fmeaTableRef']" v-loading="loading" stripe
+            style="width: 100%; margin-top: 10px;" border :header-cell-style="{ background: '#f5f7fa' }"
+            @selection-change="handleSelectChange" :height="600">
+            <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-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
+    },
+    data() {
+        return {
+            isDataShow: false,
+            isShow: false,
+            templateTitle: "默认标题",
+            loading: false,
+            title: "默认标题",
+            alertTilte: "可在表格中直接编辑指标,规则 (规则支持富文本) ,目标,单位,权重,计算公式以及流程节点, 注意: 每个指标的标题不能为空!每个指标的评分节点一定要开启!",
+            templateId: "", // 模板ID
+            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: 150},
+                { label: "单位", prop: "unit", isShow: true, width: 100 },
+                { label: "权重(%)", prop: "weight", isShow: true, width: 100 },
+                { label: "计算公式", prop: "formulae", isShow: true, width: 100 },
+                { 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.templateId = this.$route.params.templateDetailId;
+        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 = "录入结果11"
+                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 => {
+                // if (res.data.code == 1) this.get_table_data();
+                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 20px;
+    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: 60px;
+        justify-content: space-between;
+
+        .header-left {
+            width: 230px;
+            box-sizing: border-box;
+            height: 60px;
+            cursor: pointer;
+
+            .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 {
+            width: 230px;
+            text-align: right;
+        }
+    }
+
+    .plus-button {
+        display: block;
+        margin: 20px auto;
+    }
+
+}
+</style>

+ 16 - 6
src/okr/components/TargetDetail/Evolve.vue

@@ -2,14 +2,14 @@
   <div style="padding-top: 20px;">
     <div>
       <div class="flex-box-end" style="margin-bottom: 14px;" >
-        <div class="cursor add-task" @click="operation(1)" v-if="isOperation"><i class="el-icon-plus" style="padding-right: 5px;"></i>添加进展</div>
+        <div class="cursor add-task" @click="operation(1)" v-if="isOperation && !readonly"><i class="el-icon-plus" style="padding-right: 5px;"></i>添加进展</div>
       </div>
       <div v-for="(item, index) in krProcessList" :key="index" class="record-list" style="background-color: #F8FCFF;margin-bottom: 14px;">
         <div class="flex-box-ce record-date fontColorB">
           <userImage :user_name="item.userInfo.name||'系统'" :img_url="item.userInfo.img_url" fontSize="12" width="32px" height="32px"></userImage>
           <div class="record-name">{{ item.userInfo.name||'系统' }}</div>
           <span class="fontColorC flex-1">{{ item.create_time }} 添加了进展</span>
-          <span class="blue cursor" style="padding-right: 10px;display: none;" v-if="userId==item.userInfo.id" @click="operation(2,item)">编辑</span>
+          <span class="blue cursor" style="padding-right: 10px;display: none;" v-if="userId == item.userInfo.id && !readonly" @click="operation(2,item)">编辑</span>
         </div>
         <div class="record-content">
           <pre class="pre fontColorA" style="font-size: 15px;" v-if="item.content">{{ item.content }}</pre>
@@ -43,6 +43,10 @@ export default {
   name: 'Evolve',
   components:{AddEvole,Tooltip},
   props: {
+    readonly: {
+      type: Boolean,
+      default: false
+    },
     target_id: {//对象id 根据对象种类不同而不同
       type: Number,
       default: 0,
@@ -85,15 +89,21 @@ export default {
         this.title='编辑'
         this.target_data=item;
         this.isShowAddJz=true;
-      }else{
-        this.$axiosUser('get', '/api/pro/okr/process/list', {target_type:this.target_type,target_id:this.target_id,page:1,page_size:100}).then(res=>{
-            this.getProcess();
+      } else {
+        let url = ""
+        if (!this.readonly) url = '/api/pro/okr/process/list'
+        else url = '/api/pro/okr/public/process/list'
+        this.$axiosUser('get', url, {target_type:this.target_type,target_id:this.target_id,page:1,page_size:100}).then(res=>{
+          this.getProcess();
         })
       }
     },
     //kr进展
     getProcess(){
-      this.$axiosUser('get', '/api/pro/okr/process/list', {target_type:this.target_type,target_id:this.target_id,page:1,page_size:100}).then(res=>{
+      let url = ""
+      if (!this.readonly) url = '/api/pro/okr/process/list'
+      else url = '/api/pro/okr/public/process/list'
+      this.$axiosUser('get', url, {target_type:this.target_type,target_id:this.target_id,page:1,page_size:100}).then(res=>{
         let list=res.data.data.list
         list.forEach(item=>{
           item.userInfo=this.$getEmployeeMapItem(item.publisher_id);

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません