guojy 1 yıl önce
ebeveyn
işleme
93ecef9163
61 değiştirilmiş dosya ile 10467 ekleme ve 329 silme
  1. 1 1
      config/index.js
  2. 1 0
      package.json
  3. 2 2
      src/App.vue
  4. 2226 0
      src/assets/css/dhtmlxgantt.css
  5. 10 0
      src/assets/js/dhtmlx.js
  6. 20 7
      src/components/EmployeeSelector.vue
  7. 4 4
      src/home.vue
  8. 0 0
      src/icons/iconfont.js
  9. 1 1
      src/okr/components/TargetDetail/AddEvole.vue
  10. 4 3
      src/okr/components/TargetDetail/AddInteraction.vue
  11. 69 22
      src/okr/components/TargetDetail/Interaction.vue
  12. 453 0
      src/okr/components/project/AddProject.vue
  13. 687 0
      src/okr/components/project/Dhtmlx.vue
  14. 598 0
      src/okr/components/project/KanMilestone.vue
  15. 908 0
      src/okr/components/project/Milestone.vue
  16. 150 0
      src/okr/components/project/ProjectInputBox.vue
  17. 704 0
      src/okr/components/project/ProjectPreview.vue
  18. 329 0
      src/okr/components/project/ProjectSchedule.vue
  19. 439 0
      src/okr/components/project/ProjectTj.vue
  20. 157 0
      src/okr/components/project/SelectProjectGj.vue
  21. 22 11
      src/okr/components/public/AddTask.vue
  22. 5 1
      src/okr/components/public/CopyTarget.vue
  23. 1 1
      src/okr/components/public/Schedule.vue
  24. 63 19
      src/okr/components/public/TargetDetail.vue
  25. 251 66
      src/okr/components/public/TargetSearch.vue
  26. 62 12
      src/okr/components/public/TaskDetail.vue
  27. 147 92
      src/okr/components/public/TaskItem.vue
  28. 28 3
      src/okr/utils/auth.js
  29. 56 24
      src/okr/views/okrIndex.vue
  30. 2 1
      src/okr/views/planTask/planTable.vue
  31. 40 0
      src/okr/views/project/allProject.vue
  32. 39 0
      src/okr/views/project/deptProject.vue
  33. 617 0
      src/okr/views/project/myProject.vue
  34. 418 0
      src/okr/views/project/projectDetail.vue
  35. 39 0
      src/okr/views/project/publicProject.vue
  36. 8 0
      src/okr/views/project/recentlyProject.vue
  37. 5 1
      src/okr/views/targetMap/targetMap.vue
  38. 43 5
      src/okr/views/targetMt/myTargert.vue
  39. 4 2
      src/okr/views/targetMt/okrInform.vue
  40. 2 0
      src/performance/views/assessManagement/assessDetails.vue
  41. 4 5
      src/performance/views/assessManagement/staffAssDet.vue
  42. 7 7
      src/performance/views/myPerformance/myPerformance.vue
  43. 39 1
      src/performance/views/myPerformance/resultSetAll2.vue
  44. 1 1
      src/point/views/common/applicationIntegrationPopup.vue
  45. 1 1
      src/point/views/common/bonusPointsPopup.vue
  46. 1 1
      src/point/views/common/examinePopup.vue
  47. 1 1
      src/point/views/statistics/custom_rank.vue
  48. 13 9
      src/router/index.js
  49. 82 16
      src/router/okrRouter.js
  50. 9 9
      src/styles/index.scss
  51. 595 0
      src/views/demo.vue
  52. 202 0
      src/views/demo1.vue
  53. 886 0
      src/views/demo2.vue
  54. 11 0
      src/views/ganttData.json
  55. BIN
      static/images/icon_fw2zsw6wfdi.zip
  56. BIN
      static/images/no_data.png
  57. BIN
      static/images/下箭头.png
  58. BIN
      static/images/任务.png
  59. BIN
      static/images/右箭头.png
  60. BIN
      static/images/里程碑.png
  61. BIN
      static/images/里程碑2.png

+ 1 - 1
config/index.js

@@ -9,7 +9,7 @@ module.exports = {
     proxyTable: {},
 	
     host: 'localhost',
-    port: 9528, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
+    port: 9527, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
     autoOpenBrowser: false,
     errorOverlay: true,
     notifyOnErrors: false,

+ 1 - 0
package.json

@@ -32,6 +32,7 @@
     "babel-runtime": "^6.26.0",
     "clipboard": "^2.0.11",
     "connect": "^3.7.0",
+    "dhtmlx-gantt": "^8.0.6",
     "echarts": "^4.9.0",
     "element-ui": "^2.14.1",
     "i": "^0.3.7",

+ 2 - 2
src/App.vue

@@ -21,7 +21,7 @@
                 if(!versions){
                   this.$setCache('versions',version);
                   return false
-                } 
+                }
                 if(versions!=version){
                   this.$store.dispatch('LogOut')
                 }
@@ -49,7 +49,7 @@
     max-width: 500px;
   }
   .el-cascader-node__label{
-    white-space: initial;
+    /* white-space: initial; */
     overflow: initial;
     text-overflow: initial;
   }

+ 2226 - 0
src/assets/css/dhtmlxgantt.css

@@ -0,0 +1,2226 @@
+.gridHoverStyle,
+.gridSelection,
+.timelineSelection {
+  background-color: #fff3a1
+}
+
+.gantt_grid_scale .gantt_grid_head_cell {
+  color: #909399;
+  font-size: 14px;
+  /* font-weight: 600; */
+  border-top: none !important;
+  border-right: none !important
+}
+
+.gantt_grid_data .gantt_cell {
+  border-right: none;
+  color: #374755
+}
+
+.gantt_task_link .gantt_link_arrow_right {
+  border-width: 6px;
+  margin-top: -3px
+}
+
+.gantt_task_link .gantt_link_arrow_left {
+  border-width: 6px;
+  margin-left: -6px;
+  margin-top: -3px
+}
+
+.gantt_task_link .gantt_link_arrow_down,
+.gantt_task_link .gantt_link_arrow_up {
+  border-width: 6px
+}
+
+.gantt_task_line .gantt_task_progress_drag {
+  bottom: -4px;
+  height: 10px;
+  margin-left: -8px;
+  width: 16px
+}
+
+.chartHeaderBg {
+  background-color: #fff
+}
+
+.gantt_task .gantt_task_scale .gantt_scale_cell {
+  color: #909399;
+  border-right: 1px solid #ebebeb
+}
+
+.gantt_row.gantt_project,
+.gantt_row.odd.gantt_project {
+  background-color: #edffef
+}
+
+.gantt_task_row.gantt_project,
+.gantt_task_row.odd.gantt_project {
+  background-color: #f5fff6
+}
+
+.gantt_task_line.gantt_project {
+  background-color: #65c16f;
+  border: 1px solid #3c9445
+}
+
+.gantt_task_line.gantt_project .gantt_task_progress {
+  background-color: #46ad51
+}
+
+.buttonBg {
+  background: #fff
+}
+
+.gantt_cal_light .gantt_btn_set {
+  margin: 5px 10px
+}
+
+.gantt_btn_set.gantt_cancel_btn_set {
+  background: #fff;
+  color: #454545;
+  border: 1px solid #DFE3EA
+}
+
+.gantt_btn_set.gantt_save_btn_set {
+  background: #3db9d3;
+  text-shadow: 0 -1px 0 #248a9f;
+  color: #fff
+}
+
+.gantt_btn_set.gantt_delete_btn_set {
+  text-shadow: 0 -1px 0 #6f6f6f;
+  background: #ec8e00;
+  text-shadow: 0 -1px 0 #a60;
+  color: #fff
+}
+
+.gantt_cal_light_wide {
+  padding-left: 0 !important;
+  padding-right: 0 !important
+}
+
+.gantt_cal_light_wide .gantt_cal_larea {
+  border-left: none !important;
+  border-right: none !important
+}
+
+.gantt_popup_button.gantt_ok_button {
+  background: #3db9d3;
+  text-shadow: 0 -1px 0 #248a9f;
+  color: #fff;
+  font-weight: 700;
+  border-width: 0
+}
+
+.gantt_popup_button.gantt_cancel_button {
+  font-weight: 700;
+  color: #454544
+}
+
+.gantt_popup_title {
+  background-color: #fff
+}
+
+.gantt_popup_shadow {
+  box-shadow: 3px 3px 3px rgba(0, 0, 0, .07)
+}
+
+.gantt_qi_big_icon.icon_edit {
+  color: #454545;
+  background: #fff
+}
+
+.gantt_qi_big_icon.icon_delete {
+  text-shadow: 0 -1px 0 #a60;
+  background: #ec8e00;
+  color: #fff;
+  border-width: 0
+}
+
+.gantt_tooltip {
+  box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+  border-left: 1px solid rgba(0, 0, 0, .07);
+  border-top: 1px solid rgba(0, 0, 0, .07);
+  font-size: 8pt;
+  color: #454545
+}
+
+.gantt_container,
+.gantt_tooltip {
+  background-color: #fff;
+  font-family: Arial
+}
+
+.gantt_container {
+  font-size: 13px;
+  border: 1px solid #DFE3EA;
+  position: relative;
+  white-space: nowrap;
+  overflow-x: hidden;
+  overflow-y: hidden;
+  border-radius: 10px;
+}
+
+.gantt_touch_active {
+  overscroll-behavior: none
+}
+
+.gantt_task_scroll {
+  overflow-x: scroll
+}
+
+.gantt_grid,
+.gantt_task {
+  position: relative;
+  overflow-x: hidden;
+  overflow-y: hidden;
+  display: inline-block;
+  vertical-align: top
+}
+
+.gantt_grid_scale,
+.gantt_task_scale {
+  color: #6b6b6b;
+  font-size: 12px;
+  border-bottom: 1px solid #DFE3EA;
+  box-sizing: border-box
+}
+
+.gantt_grid_scale,
+.gantt_task_scale,
+.gantt_task_vscroll {
+  background-color: #fff
+}
+
+.gantt_scale_line {
+  box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  border-top: 1px solid #DFE3EA
+}
+
+.gantt_scale_line:first-child {
+  border-top: none
+}
+
+.gantt_grid_head_cell {
+  display: inline-block;
+  vertical-align: top;
+  border-right: 1px solid #DFE3EA;
+  text-align: center;
+  position: relative;
+  cursor: default;
+  height: 100%;
+  box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  line-height: 33px;
+  -moz-user-select: -moz-none;
+  -webkit-user-select: none;
+  user-select: none;
+  overflow: hidden
+}
+
+.gantt_scale_line {
+  clear: both
+}
+
+.gantt_grid_data {
+  width: 100%;
+  overflow: hidden;
+  position: relative
+}
+
+.gantt_row {
+  position: relative;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -moz-user-select: -moz-none
+}
+
+.gantt_add,
+.gantt_grid_head_add {
+  width: 100%;
+  height: 100%;
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTQ3MjMyMENDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTQ3MjMyMERDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1NDcyMzIwQUM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1NDcyMzIwQkM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PshZT8UAAABbSURBVHjaYrTdeZmBEsCER+4wEP+H4sPkGGCDg020ARR7gb4GIAcYDKMDdPnDyAbYkGG5DVW9cIQMvUdBBAuUY4vDz8iAcZinA2zgCHqAYQMseAywJcYFAAEGAM+UFGuohFczAAAAAElFTkSuQmCC);
+  background-position: 50%;
+  background-repeat: no-repeat;
+  cursor: pointer;
+  position: relative;
+  -moz-opacity: .3;
+  opacity: .3
+}
+
+.gantt_grid_head_cell.gantt_grid_head_add {
+  -moz-opacity: .6;
+  opacity: .6;
+  top: 0
+}
+
+.gantt_grid_head_cell.gantt_grid_head_add:hover {
+  -moz-opacity: 1;
+  opacity: 1
+}
+
+.gantt_grid_data .gantt_row.odd:hover,
+.gantt_grid_data .gantt_row:hover {
+  background-color: #ebebeb
+}
+
+.gantt_grid_data .gantt_row.odd:hover .gantt_add,
+.gantt_grid_data .gantt_row:hover .gantt_add {
+  -moz-opacity: 1;
+  opacity: 1
+}
+
+.gantt_row,
+.gantt_task_row {
+  border-bottom: 1px solid #ebebeb;
+  background-color: #fff
+}
+
+.gantt_row.odd,
+.gantt_task_row.odd {
+  background-color: #fff
+}
+
+.gantt_cell,
+.gantt_grid_head_cell,
+.gantt_row,
+.gantt_scale_cell,
+.gantt_task_cell,
+.gantt_task_row {
+  box-sizing: border-box;
+  -moz-box-sizing: border-box
+}
+
+.gantt_grid_head_cell,
+.gantt_scale_cell {
+  line-height: inherit
+}
+
+.gantt_grid_scale .gantt_grid_column_resize_wrap {
+  cursor: col-resize;
+  position: absolute;
+  width: 13px;
+  margin-left: -7px
+}
+
+.gantt_grid_column_resize_wrap .gantt_grid_column_resize {
+  background-color: #DFE3EA;
+  height: 100%;
+  width: 1px;
+  margin: 0 auto
+}
+
+.gantt_task_grid_row_resize_wrap {
+  cursor: row-resize;
+  position: absolute;
+  height: 13px;
+  margin-top: -7px;
+  left: 0;
+  width: 100%
+}
+
+.gantt_task_grid_row_resize_wrap .gantt_task_grid_row_resize {
+  background-color: #ebebeb;
+  top: 6px;
+  height: 1px;
+  width: 100%;
+  margin: 0 auto;
+  position: relative
+}
+
+.gantt_drag_marker.gantt_grid_resize_area,
+.gantt_drag_marker.gantt_row_grid_resize_area {
+  background-color: hsla(0, 0%, 91%, .5);
+  height: 100%;
+  width: 100%;
+  box-sizing: border-box
+}
+
+.gantt_drag_marker.gantt_grid_resize_area {
+  border-left: 1px solid #DFE3EA;
+  border-right: 1px solid #DFE3EA;
+}
+
+.gantt_drag_marker.gantt_row_grid_resize_area {
+  border-top: 1px solid #DFE3EA;
+  border-bottom: 1px solid #DFE3EA;
+  pointer-events: none
+}
+
+.gantt_row {
+  display: flex
+}
+
+.gantt_row>div {
+  flex-shrink: 0;
+  flex-grow: 0
+}
+
+.gantt_cell {
+  vertical-align: top;
+  border-right: 1px solid #ebebeb;
+  padding-left: 6px;
+  padding-right: 6px;
+  height: 100%;
+  overflow: hidden;
+  white-space: nowrap;
+  font-size: 13px
+}
+
+.gantt_cell_tree {
+  display: flex;
+  flex-wrap: nowrap
+}
+
+.gantt_grid_data .gantt_last_cell,
+.gantt_grid_scale .gantt_last_cell,
+.gantt_task .gantt_task_scale .gantt_scale_cell.gantt_last_cell,
+.gantt_task_bg .gantt_last_cell {
+  border-right-width: 0
+}
+
+.gantt_task .gantt_task_scale .gantt_scale_cell.gantt_last_cell {
+  border-right-width: 1px
+}
+
+.gantt_task_bg {
+  overflow: hidden
+}
+
+.gantt_scale_cell {
+  display: inline-block;
+  white-space: nowrap;
+  overflow: hidden;
+  border-right: 1px solid #DFE3EA;
+  text-align: center;
+  height: 100%
+}
+
+.gantt_task_cell {
+  display: inline-block;
+  height: 100%;
+  border-right: 1px solid #ebebeb
+}
+
+.gantt_layout_cell.gantt_ver_scroll {
+  width: 0;
+  background-color: transparent;
+  height: 1px;
+  overflow-x: hidden;
+  overflow-y: scroll;
+  position: absolute;
+  right: 0;
+  z-index: 1
+}
+
+.gantt_ver_scroll>div {
+  width: 1px;
+  height: 1px
+}
+
+.gantt_hor_scroll {
+  height: 0;
+  background-color: transparent;
+  width: 100%;
+  clear: both;
+  overflow-x: scroll;
+  overflow-y: hidden
+}
+
+.gantt_layout_cell .gantt_hor_scroll {
+  position: absolute
+}
+
+.gantt_hor_scroll>div {
+  width: 5000px;
+  height: 1px
+}
+
+.gantt_tree_icon,
+.gantt_tree_indent {
+  flex-grow: 0;
+  flex-shrink: 0
+}
+
+.gantt_tree_indent {
+  width: 15px;
+  height: 100%
+}
+
+.gantt_tree_content,
+.gantt_tree_icon {
+  vertical-align: top
+}
+
+.gantt_tree_icon {
+  /* width: 28px; */
+  width: 8px;
+  height: 100%;
+  background-repeat: no-repeat;
+  background-position: 50%
+}
+
+.gantt_tree_content {
+  height: 100%;
+  white-space: nowrap;
+  min-width: 0
+}
+
+.gantt_tree_icon.gantt_open {
+  /* background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAArklEQVQ4T2NkoBJgpJI5DEPAoFOnTv0/c+YMQR+bmJgwmJmZwX2E4bVp06b9j4yMZODg4MBp2I8fPxiWL1/OkJWVNUAGcXJyMnz//h3uQrJdRLFBIAPQAchlJLsIFuCMjIwM////B5sJMoRkg2CuIdtrQcHBDOxsbHBfCQgIMHz48AHO//nrF8O6tWsJR7+7uzsDIxMTznT0/98/hp07d+I3iGopm2DewKFg8OV+AJWkfRMrTobLAAAAAElFTkSuQmCC); */
+  background-image: url(../../../static/images/右箭头.png);
+  background-size: 80%;
+  width: 18px;
+  cursor: pointer
+}
+
+.gantt_tree_icon.gantt_close {
+  /* background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAkUlEQVQ4T2NkoBJgpJI5DEPAoFOnTv0/c+YMQR+bmJgwmJmZwX2E4bVp06b9j4yMZODg4MBp2I8fPxiWL1/OkJWVNeIN4uTkxAin79+/M5AcRtgCHGQIyQbhijaiDQoKDmZgZ2PDGf0/f/1iWLd2LeHod3d3Z2BkYsJp0P9//xh27tyJ3yCqpWyCeQOHgsGX+wEZpW4T5LCxKwAAAABJRU5ErkJggg==); */
+  background-image: url(../../../static/images/下箭头.png);
+  background-size: 80%;
+  width: 18px;
+  cursor: pointer
+}
+
+.gantt_tree_icon.gantt_blank {
+  width: 18px
+}
+
+.gantt_tree_icon.gantt_folder_open {
+  /* background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAs0lEQVQ4T62T0Q2EIBBEpQlzuaaMsQoqooqLsSljbMLLmMxmUXBR4U+Qt7Mzi2sqLVeJ00SgEMKWAnvvzYLyAyHfT5sU2fXDJSwCAXK8MI0/UTkva7IIFJsg3NSwnKdFoKtAWOQ1CN7CEqeTotE5L7QyJhmBcklZM4ZgTiAr3iOU3kD93ppO5SkMjB1EeXdBWoSkRql3YeIRe+cGvktS056JR9wsmeBUkujCfNXWCPC8GugPqn5ii/hV+FoAAAAASUVORK5CYII=) */
+   /* background-image: url(../../../static/images/里程碑2.png); */
+   background-size: 60%;
+}
+
+.gantt_tree_icon.gantt_folder_closed {
+  /* background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAfElEQVQ4T2NkoBJgpJI5DCgGTZ8+/T82gzMzMwlaCFcAM0RKQgyrI/0Dg/EahmIQyBB0DRvXr4W78tmLV1gtAbmYoEEgnciG4QpTogzCFyEwSyg2CBS2oCAZNQh3cA+hMAJ5AlcKxuVBlOgnNgVjMwyUrQjmamKLGaoZBAAOTFyLnFFW4wAAAABJRU5ErkJggg==) */
+  /* background-image: url(../../../static/images/里程碑.png); */
+  background-size: 60%;
+}
+
+.gantt_tree_icon.gantt_file {
+  /* background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAeElEQVQ4T2NkoBJgRDZn+vTp/wmZm5mZiaIHph7DICkJMUJmMfgHBmMYhtUgbAo3rl+L4lp0NUQbBPI2umuRDaPIIFAYwAyjv0HoMQALM5JdhG4QLMxGDcKdyIdoGIE89OzFK4KZF5Rl8EY/QROQFGA1iBQD0NUCAJVjcxO0naAQAAAAAElFTkSuQmCC) */
+  /* background-image: url(../../../static/images/任务.png); */
+  background-size: 50%;
+}
+
+.gantt_grid_head_cell .gantt_sort {
+  position: absolute;
+  right: 5px;
+  top: 8px;
+  width: 7px;
+  height: 13px;
+  background-repeat: no-repeat;
+  background-position: 50%
+}
+
+.gantt_grid_head_cell .gantt_sort.gantt_asc {
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR4nGNgQAKGxib/GbABkIS7b8B/DAUwCRiGK0CXwFBAb1DfP/U/LszwHwi2X7qFgUEArBtdAVwCBmAKMCSQFSDzAWXXaOHsXeqkAAAAAElFTkSuQmCC)
+}
+
+.gantt_grid_head_cell .gantt_sort.gantt_desc {
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR42mNgQAL1/VP/M2ADIIntF2/9x1AAlrh0C47hCmA60DFYwX88gIFGwNDY5D8uDFbg7hvwHx2jmIBTAlkB0e4BAEjlaNtBWJPnAAAAAElFTkSuQmCC)
+}
+
+.gantt_inserted,
+.gantt_updated {
+  font-weight: 700
+}
+
+.gantt_deleted {
+  text-decoration: line-through
+}
+
+.gantt_invalid {
+  background-color: #ffe0e0
+}
+
+.gantt_error {
+  color: red
+}
+
+.gantt_status {
+  right: 1px;
+  padding: 5px 10px;
+  background: hsla(0, 0%, 61%, .1);
+  position: absolute;
+  top: 1px;
+  transition: opacity .2s;
+  opacity: 0
+}
+
+.gantt_status.gantt_status_visible {
+  opacity: 1
+}
+
+#gantt_ajax_dots span {
+  transition: opacity .2s;
+  background-repeat: no-repeat;
+  opacity: 0
+}
+
+#gantt_ajax_dots span.gantt_dot_visible {
+  opacity: 1
+}
+
+.gantt_column_drag_marker {
+  border: 1px solid #DFE3EA;
+  opacity: .8
+}
+
+.gantt_grid_head_cell_dragged {
+  border: 1px solid #DFE3EA;
+  opacity: .3
+}
+
+.gantt_grid_target_marker {
+  position: absolute;
+  top: 0;
+  width: 2px;
+  height: 100%;
+  background-color: #ffa011;
+  transform: translateX(-1px)
+}
+
+.gantt_grid_target_marker:after,
+.gantt_grid_target_marker:before {
+  display: block;
+  content: "";
+  position: absolute;
+  left: -5px;
+  width: 0;
+  height: 0;
+  border: 6px solid transparent
+}
+
+.gantt_grid_target_marker:before {
+  border-top-color: #ffa011
+}
+
+.gantt_grid_target_marker:after {
+  bottom: 0;
+  border-bottom-color: #ffa011
+}
+
+.gantt_message_area {
+  position: fixed;
+  right: 5px;
+  width: 250px;
+  z-index: 1000
+}
+
+.gantt-info {
+  min-width: 120px;
+  padding: 4px 4px 4px 20px;
+  font-family: Arial;
+  z-index: 10000;
+  margin: 5px;
+  margin-bottom: 10px;
+  transition: all .5s ease
+}
+
+.gantt-info.hidden {
+  height: 0;
+  padding: 0;
+  border-width: 0;
+  margin: 0;
+  overflow: hidden
+}
+
+.gantt_modal_box {
+  overflow: hidden;
+  display: inline-block;
+  min-width: 250px;
+  width: 250px;
+  text-align: center;
+  position: fixed;
+  z-index: 20000;
+  box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+  font-family: Arial;
+  border-radius: 6px;
+  border: 1px solid #DFE3EA;
+  background: #fff
+}
+
+.gantt_popup_title {
+  border-top-left-radius: 6px;
+  border-top-right-radius: 6px;
+  border-width: 0
+}
+
+.gantt_button,
+.gantt_popup_button {
+  border: 1px solid #DFE3EA;
+  height: 30px;
+  line-height: 30px;
+  display: inline-block;
+  margin: 0 5px;
+  border-radius: 4px;
+  background: #fff
+}
+
+.gantt-info,
+.gantt_button,
+.gantt_popup_button {
+  user-select: none;
+  -webkit-user-select: none;
+  -moz-user-select: -moz-none;
+  cursor: pointer
+}
+
+.gantt_popup_text {
+  overflow: hidden
+}
+
+.gantt_popup_controls {
+  border-radius: 6px;
+  padding: 10px
+}
+
+.gantt_popup_button {
+  min-width: 100px
+}
+
+div.dhx_modal_cover {
+  background-color: #000;
+  cursor: default;
+  filter: progid:DXImageTransform.Microsoft.Alpha(opacity=20);
+  opacity: .2;
+  position: fixed;
+  z-index: 19999;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  border: none;
+  zoom: 1
+}
+
+.gantt-info img,
+.gantt_modal_box img {
+  float: left;
+  margin-right: 20px
+}
+
+.gantt-alert-error,
+.gantt-confirm-error {
+  border: 1px solid red
+}
+
+.gantt_button input,
+.gantt_popup_button div {
+  border-radius: 4px;
+  font-size: 14px;
+  box-sizing: content-box;
+  padding: 0;
+  margin: 0;
+  vertical-align: top
+}
+
+.gantt_popup_title {
+  border-bottom: 1px solid #DFE3EA;
+  height: 40px;
+  line-height: 40px;
+  font-size: 20px
+}
+
+.gantt_popup_text {
+  margin: 15px 15px 5px;
+  font-size: 14px;
+  color: #000;
+  min-height: 30px;
+  border-radius: 6px
+}
+
+.gantt-error,
+.gantt-info {
+  font-size: 14px;
+  color: #000;
+  box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+  padding: 0;
+  background-color: #fff;
+  border-radius: 3px;
+  border: 1px solid #fff
+}
+
+.gantt-info div {
+  padding: 5px 10px;
+  background-color: #fff;
+  border-radius: 3px;
+  border: 1px solid #DFE3EA
+}
+
+.gantt-error {
+  background-color: #d81b1b;
+  border: 1px solid #ff3c3c
+}
+
+.gantt-error div {
+  background-color: #d81b1b;
+  border: 1px solid #940000;
+  color: #fff
+}
+
+.gantt-warning {
+  background-color: #ff9000;
+  border: 1px solid #ffa633
+}
+
+.gantt-warning div {
+  background-color: #ff9000;
+  border: 1px solid #b36500;
+  color: #fff
+}
+
+.gantt_data_area div,
+.gantt_grid div {
+  -ms-touch-action: none;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0)
+}
+
+.gantt_data_area {
+  position: relative;
+  overflow-x: hidden;
+  overflow-y: hidden;
+  -moz-user-select: -moz-none;
+  -webkit-user-select: none;
+  user-select: none
+}
+
+.gantt_links_area {
+  position: absolute;
+  left: 0;
+  top: 0
+}
+
+.gantt_side_content,
+.gantt_task_content,
+.gantt_task_progress {
+  line-height: inherit;
+  overflow: hidden;
+  height: 100%
+}
+
+.gantt_task_content {
+  font-size: 12px;
+  color: #fff;
+  width: 100%;
+  top: 0;
+  cursor: pointer;
+  position: absolute;
+  white-space: nowrap;
+  /* text-align: center */
+}
+
+.gantt_task_progress {
+  text-align: center;
+  z-index: 0;
+  background: #299cb4
+}
+
+.gantt_task_progress_wrapper {
+  border-radius: inherit;
+  position: relative;
+  width: 100%;
+  height: 100%;
+  overflow: hidden
+}
+
+.gantt_task_line {
+  border-radius: 25px;
+  position: absolute;
+  box-sizing: border-box;
+  background-color: #3db9d3;
+  /* border: 1px solid #2898b0; */
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -moz-user-select: -moz-none
+}
+
+.gantt_task_line.gantt_drag_move div {
+  cursor: move
+}
+
+.gantt_touch_move,
+.gantt_touch_progress .gantt_touch_resize {
+  transform: scale(1.02, 1.1);
+  transform-origin: 50%
+}
+
+.gantt_touch_progress .gantt_task_progress_drag,
+.gantt_touch_resize .gantt_task_drag {
+  transform: scaleY(1.3);
+  transform-origin: 50%
+}
+
+.gantt_side_content {
+  position: absolute;
+  white-space: nowrap;
+  color: #6e6e6e;
+  top: 0;
+  font-size: 11px
+}
+
+.gantt_side_content.gantt_left {
+  right: 100%;
+  padding-right: 20px
+}
+
+.gantt_side_content.gantt_right {
+  left: 100%;
+  padding-left: 20px
+}
+
+.gantt_side_content.gantt_link_crossing {
+  bottom: 8.75px;
+  top: auto
+}
+
+.gantt_link_arrow,
+.gantt_task_link .gantt_line_wrapper {
+  position: absolute;
+  cursor: pointer
+}
+
+.gantt_line_wrapper div {
+  background-color: #ffa011
+}
+
+.gantt_task_link:hover .gantt_line_wrapper div {
+  box-shadow: 0 0 5px 0 #ffa011
+}
+
+.gantt_task_link div.gantt_link_arrow {
+  background-color: transparent;
+  border-style: solid;
+  width: 0;
+  height: 0
+}
+
+.gantt_link_control {
+  position: absolute;
+  width: 20px;
+  top: 0
+}
+
+.gantt_link_control div {
+  display: none;
+  cursor: pointer;
+  box-sizing: border-box;
+  position: relative;
+  top: 50%;
+  margin-top: -7.5px;
+  vertical-align: middle;
+  border: 1px solid #929292;
+  border-radius: 6.5px;
+  height: 13px;
+  width: 13px;
+  background-color: #f0f0f0
+}
+
+.gantt_link_control.task_right div.gantt_link_point {
+  margin-left: 7px
+}
+
+.gantt_link_control div:hover {
+  background-color: #fff
+}
+
+.gantt_link_control.task_left {
+  left: -20px
+}
+
+.gantt_link_control.task_right {
+  right: -20px
+}
+
+.gantt_link_target .gantt_link_control div,
+.gantt_task_line.gantt_drag_move .gantt_link_control div,
+.gantt_task_line.gantt_drag_move .gantt_task_drag,
+.gantt_task_line.gantt_drag_move .gantt_task_progress_drag,
+.gantt_task_line.gantt_drag_progress .gantt_link_control div,
+.gantt_task_line.gantt_drag_progress .gantt_task_drag,
+.gantt_task_line.gantt_drag_progress .gantt_task_progress_drag,
+.gantt_task_line.gantt_drag_resize .gantt_link_control div,
+.gantt_task_line.gantt_drag_resize .gantt_task_drag,
+.gantt_task_line.gantt_drag_resize .gantt_task_progress_drag,
+.gantt_task_line.gantt_selected .gantt_link_control div,
+.gantt_task_line.gantt_selected .gantt_task_drag,
+.gantt_task_line.gantt_selected .gantt_task_progress_drag,
+.gantt_task_line:hover .gantt_link_control div,
+.gantt_task_line:hover .gantt_task_drag,
+.gantt_task_line:hover .gantt_task_progress_drag {
+  display: block
+}
+
+.gantt_link_source,
+.gantt_link_target {
+  box-shadow: 0 0 3px #3db9d3
+}
+
+.gantt_link_target.link_finish_allow,
+.gantt_link_target.link_start_allow {
+  box-shadow: 0 0 3px #ffbf5e
+}
+
+.gantt_link_target.link_finish_deny,
+.gantt_link_target.link_start_deny {
+  box-shadow: 0 0 3px #e87e7b
+}
+
+.link_finish_allow .gantt_link_control.task_end_date div,
+.link_start_allow .gantt_link_control.task_start_date div {
+  background-color: #ffbf5e;
+  border-color: #ffa011
+}
+
+.link_finish_deny .gantt_link_control.task_end_date div,
+.link_start_deny .gantt_link_control.task_start_date div {
+  background-color: #e87e7b;
+  border-color: #dd3e3a
+}
+
+.gantt_link_arrow_right {
+  border-width: 4px 0 4px 6px;
+  border-top-color: transparent !important;
+  border-right-color: transparent !important;
+  border-bottom-color: transparent !important;
+  border-left-color: #ffa011
+}
+
+.gantt_link_arrow_left {
+  border-width: 4px 6px 4px 0;
+  margin-top: -1px;
+  border-top-color: transparent !important;
+  border-right-color: #ffa011;
+  border-bottom-color: transparent !important;
+  border-left-color: transparent !important
+}
+
+.gantt_link_arrow_up {
+  border-width: 0 4px 6px;
+  border-color: transparent transparent #ffa011;
+  border-top-color: transparent !important;
+  border-right-color: transparent !important;
+  border-bottom-color: #ffa011;
+  border-left-color: transparent !important
+}
+
+.gantt_link_arrow_down {
+  border-width: 4px 6px 0 4px;
+  border-top-color: #ffa011;
+  border-right-color: transparent !important;
+  border-bottom-color: transparent !important;
+  border-left-color: transparent !important
+}
+
+.gantt_task_drag,
+.gantt_task_progress_drag {
+  cursor: ew-resize;
+  display: none;
+  position: absolute
+}
+
+.gantt_task_drag.task_right {
+  cursor: e-resize
+}
+
+.gantt_task_drag.task_left {
+  cursor: w-resize
+}
+
+.gantt_task_drag {
+  height: 100%;
+  width: 8px;
+  z-index: 1;
+  top: -1px
+}
+
+.gantt_task_drag.task_left {
+  left: -7px
+}
+
+.gantt_task_drag.task_right {
+  right: -7px
+}
+
+.gantt_task_progress_drag {
+  height: 8px;
+  width: 8px;
+  bottom: -4px;
+  margin-left: -4px;
+  background-position: bottom;
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAYAAAB24g05AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkY3Rjk0RUVDMkYzMTFFMkI1OThEQTA3ODU0OTkzMEEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkY3Rjk0RUZDMkYzMTFFMkI1OThEQTA3ODU0OTkzMEEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyRjdGOTRFQ0MyRjMxMUUyQjU5OERBMDc4NTQ5OTMwQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyRjdGOTRFREMyRjMxMUUyQjU5OERBMDc4NTQ5OTMwQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PobPBzIAAADkSURBVHjaYpk2bRoDDsAExL1QdjEQ/8OmiAWHZk4gXqymqhQM4ty6fU8OSMUA8XdiDBAB4k0a6iqWRga6EKcwMQXduHlnL5DpB8Rv0J2JDFSA+JiOtgZcMwiA2CAxkBxUDVYDLEAKgIpV9XQ0MZwFEgPJAZnHoWpRDAgC4n2W5saiQKfjClQGkBxQDciL+6B6wAbkA/EqJwdrTkUFOQZCAKQGpBbIXA3SCzJggo+XK7OEuBgDsQCkFqgHrBfsBT5eHgZSAUwP2IBfv36TbABMDygdtK1Zv6UESLORaAbIhG6AAAMAKN8wE24DXWcAAAAASUVORK5CYII=);
+  background-repeat: no-repeat;
+  z-index: 1
+}
+
+.gantt_task_progress_drag:hover {
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAYAAAB24g05AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAs0lEQVQoz6WMPW7CQBgFJxZaiZ60qcgdwjVMmzu8gpwhDULabXyBdHAGuzRHivQiQZovigS2+Jtu95t5T03TMITtCtjEc5VSOgx5k5F4CnxJWgKUUl5sv6eUvk/daiCeAe1fDCCpBtq4jQ/YngO9pMWpGH99OOcDtt8ifmWEuO3D/R+wXQOdpGcuIGkGdNFQ2RawlTTlSsLd2RY55+O95JyPFQ/y8MAE+CylfADpxvYHWP8CXj+JR4wdKHYAAAAASUVORK5CYII=)
+}
+
+.gantt_link_tooltip {
+  box-shadow: 3px 3px 3px #888;
+  background-color: #fff;
+  border-left: 1px dotted #DFE3EA;
+  border-top: 1px dotted #DFE3EA;
+  font-family: Tahoma;
+  font-size: 8pt;
+  color: #444;
+  padding: 6px;
+  line-height: 20px
+}
+
+.gantt_link_direction {
+  height: 0;
+  border: 0 none #ffa011;
+  border-bottom-style: dashed;
+  border-bottom-width: 2px;
+  transform-origin: 0 0;
+  -ms-transform-origin: 0 0;
+  -webkit-transform-origin: 0 0;
+  z-index: 2;
+  margin-left: 1px;
+  position: absolute
+}
+
+.gantt_grid_data .gantt_row.gantt_selected,
+.gantt_grid_data .gantt_row.odd.gantt_selected,
+.gantt_task_row.gantt_selected {
+  background-color: #f1f1f1
+}
+
+.gantt_task_row.gantt_selected .gantt_task_cell {
+  /* border-right-color: #ffec6e */
+}
+
+.gantt_task_line.gantt_selected {
+  box-shadow: 0 0 5px #888
+}
+
+.gantt_task_line.gantt_project.gantt_selected {
+  box-shadow: 0 0 5px #46ad51
+}
+
+.gantt_task_line.gantt_milestone {
+  visibility: hidden;
+  top: 12px !important;
+  /* background-color: #409EFF; */
+  /* border: 0 solid #61164f; */
+/*  height: 10px !important;
+  width: 20px !important;
+  border-radius: 5px !important; */
+  box-sizing: content-box;
+  -moz-box-sizing: content-box
+}
+
+.gantt_task_line.gantt_milestone div {
+  visibility: visible
+}
+
+.gantt_task_line.gantt_milestone .gantt_task_content {
+/*  background: inherit;
+  border: inherit;
+  border-width: 1px;
+  border-radius: inherit;
+  box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  transform: rotate(45deg) */
+  background-color: #409EFF;
+  /* border: 0 solid #61164f; */
+  height: 8px !important;
+  width: 20px !important;
+  border-radius: 5px !important;
+}
+
+.gantt_task_line.gantt_task_inline_color {
+  border-color: #999
+}
+
+.gantt_task_line.gantt_task_inline_color .gantt_task_progress {
+  background-color: #363636;
+  opacity: .2
+}
+
+.gantt_task_line.gantt_task_inline_color.gantt_project.gantt_selected,
+.gantt_task_line.gantt_task_inline_color.gantt_selected {
+  box-shadow: 0 0 5px #999
+}
+
+.gantt_task_link.gantt_link_inline_color:hover .gantt_line_wrapper div {
+  box-shadow: 0 0 5px 0 #999
+}
+
+.gantt_critical_task {
+  background-color: #e63030;
+  border-color: #9d3a3a
+}
+
+.gantt_critical_task .gantt_task_progress {
+  background-color: rgba(0, 0, 0, .4)
+}
+
+.gantt_critical_link .gantt_line_wrapper>div {
+  background-color: #e63030
+}
+
+.gantt_critical_link .gantt_link_arrow {
+  border-color: #e63030
+}
+
+.gantt_btn_set:focus,
+.gantt_cell:focus,
+.gantt_grid_head_cell:focus,
+.gantt_popup_button:focus,
+.gantt_qi_big_icon:focus,
+.gantt_row:focus {
+  box-shadow: inset 0 0 1px 1px #4d90fe
+}
+
+.gantt_split_parent,
+.gantt_split_subproject {
+  opacity: .1;
+  pointer-events: none
+}
+
+.gantt_rollup_child .gantt_link_control,
+.gantt_rollup_child:hover .gantt_link_control {
+  display: none
+}
+
+.gantt_unselectable,
+.gantt_unselectable div {
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -moz-user-select: -moz-none
+}
+
+.gantt_cal_light {
+  -webkit-tap-highlight-color: transparent;
+  background: #fff;
+  border-radius: 6px;
+  font-family: Arial;
+  font-size: 13px;
+  border: 1px solid #DFE3EA;
+  color: #6b6b6b;
+  font-size: 12px;
+  position: absolute;
+  z-index: 10001;
+  width: 550px;
+  height: 250px;
+  box-shadow: 3px 3px 3px rgba(0, 0, 0, .07)
+}
+
+.gantt_cal_light_wide {
+  width: 650px
+}
+
+.gantt_cal_light select {
+  font-family: Arial;
+  border: 1px solid #DFE3EA;
+  font-size: 13px;
+  padding: 2px;
+  margin: 0
+}
+
+.gantt_cal_ltitle {
+  padding: 7px 10px;
+  overflow: hidden;
+  -webkit-border-top-left-radius: 6px;
+  -webkit-border-bottom-left-radius: 0;
+  -webkit-border-top-right-radius: 6px;
+  -webkit-border-bottom-right-radius: 0;
+  -moz-border-radius-topleft: 6px;
+  -moz-border-radius-bottomleft: 0;
+  -moz-border-radius-topright: 6px;
+  -moz-border-radius-bottomright: 0;
+  border-top-left-radius: 6px;
+  border-bottom-left-radius: 0;
+  border-top-right-radius: 6px;
+  border-bottom-right-radius: 0
+}
+
+.gantt_cal_ltitle,
+.gantt_cal_ltitle span {
+  white-space: nowrap
+}
+
+.gantt_cal_lsection {
+  color: #727272;
+  font-weight: 700;
+  padding: 12px 0 5px 10px
+}
+
+.gantt_cal_lsection .gantt_fullday {
+  float: right;
+  margin-right: 5px;
+  font-size: 12px;
+  font-weight: 400;
+  line-height: 20px;
+  vertical-align: top;
+  cursor: pointer
+}
+
+.gantt_cal_lsection {
+  font-size: 13px
+}
+
+.gantt_cal_ltext {
+  padding: 2px 10px;
+  overflow: hidden
+}
+
+.gantt_cal_ltext textarea {
+  overflow-y: auto;
+  overflow-x: hidden;
+  font-family: Arial;
+  font-size: 13px;
+  box-sizing: border-box;
+  border: 1px solid #DFE3EA;
+  height: 100%;
+  width: 100%;
+  outline: none !important;
+  resize: none
+}
+
+.gantt_section_constraint [data-constraint-time-select] {
+  margin-left: 20px
+}
+
+.gantt_time {
+  font-weight: 700
+}
+
+.gantt_cal_light .gantt_title {
+  padding-left: 10px
+}
+
+.gantt_cal_larea {
+  border: 1px solid #DFE3EA;
+  border-left: none;
+  border-right: none;
+  background-color: #fff;
+  overflow: hidden;
+  height: 1px
+}
+
+.gantt_btn_set {
+  margin: 10px 7px 5px 10px;
+  padding: 5px 15px 5px 10px;
+  float: left;
+  border-radius: 4px;
+  border: 0 solid #DFE3EA;
+  height: 32px;
+  font-weight: 700;
+  background: #fff;
+  box-sizing: border-box;
+  cursor: pointer
+}
+
+.gantt_hidden {
+  display: none
+}
+
+.gantt_btn_set div {
+  float: left;
+  font-size: 13px;
+  height: 22px;
+  line-height: 22px;
+  background-repeat: no-repeat;
+  vertical-align: middle
+}
+
+.gantt_save_btn {
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTk1OUU5RDFDMzA0MTFFMkExMUZBQTdDNDAzOUE5RjMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTk1OUU5RDJDMzA0MTFFMkExMUZBQTdDNDAzOUE5RjMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxOTU5RTlDRkMzMDQxMUUyQTExRkFBN0M0MDM5QTlGMyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxOTU5RTlEMEMzMDQxMUUyQTExRkFBN0M0MDM5QTlGMyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjDroXYAAAEXSURBVHjaYvz//z8DJYCRUgPIAUxAbAnEHiAHMIBcQCwGaRYXF3e6evXqoffv39/dv38/CymaGSUkJBzv3LlzCsj///fv3wdAihkkIQnEvkAshU8zLy+v7a1bt06ANP/79+87kDIAy505cybq06dPr3p7ezuwGQLTfOPGjWP/ESAZLg8kPKBO+g01RBJNszWyZqC6uSgWgIg/f/4shxnS2dnZBjMEqNkSFGBImi8CKTYMA4BYCGjIczRDHC5dunQQSfN7IKWI4UUkjjdMMdCwnw8ePLjwHxV4Yw1gZA5Q47z/2EELzhhCE+ABGvIQWSeQvwcU38QaAML2wHj+C/X3MyAlijeB4ZBoBOIPQGxJKIVSnBsBAgwABddBclWfcZUAAAAASUVORK5CYII=);
+  margin-top: 2px;
+  width: 21px
+}
+
+.gantt_cancel_btn {
+  margin-top: 2px;
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDkzMDA3MzlDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDkzMDA3M0FDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowOTMwMDczN0MzMDQxMUUyODZBNUUzMURDODBGQkRENiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowOTMwMDczOEMzMDQxMUUyODZBNUUzMURDODBGQkRENiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmYuYOUAAAEdSURBVHjaYvz//z8DJYAFXWDlypU8QKoIiD2A2AwqfAqIdwBxX3h4+Bdk9YzILgBqtgdS84FYEYeF94E4EWjIQZgAE5LmQCB1AKoZZKMPEAtAMYh9GSp3AKjWD8UFQAEhIPshEIOc3wHENUBb/qJ57SyQMoJyPwKxElDNO1gYFEE17wMKVmIJlzNQzeegrjaA6qmBecEbSvfh0GwMxGeBhoPoemQ9MAO0kEIbl2YTqPAFKK2IbMB3AjabYIkRZmQD7kNpMyI0G0PpO8gGbIUFJj7NQDk2INWIrIcJKfBAKcwJqvkcDs0TgFgXGo19KCkRmpDWQdWDEk0NUoCBoq0FqhkE/IEWbKJKUmZEz43QzFSKIzN1481M5ACAAAMAlfl/lCwRpagAAAAASUVORK5CYII=);
+  width: 20px
+}
+
+.gantt_delete_btn {
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjFENzI3NUNDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjFENzI3NURDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMUQ3Mjc1QUMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMUQ3Mjc1QkMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmUD0gAAAABvSURBVHjaYvz//z8DIyMjAxYQicReji4J0ofKQNP8HwmgGQbXB8IsWGwDSSwDuioKjY9uBthVjFAXYHUGAQA2kYmBUoAUBpGk0LAwgBvwH+YX4mkwptgLowYMRgOITUyYKRFIN/wnDjQgJySAAAMApryKzL8wjfUAAAAASUVORK5CYII=);
+  margin-top: 2px;
+  width: 20px
+}
+
+.gantt_cal_cover {
+  width: 100%;
+  height: 100%;
+  position: fixed;
+  z-index: 10000;
+  top: 0;
+  left: 0;
+  background-color: #000;
+  opacity: .1;
+  filter: progid:DXImageTransform.Microsoft.Alpha(opacity=10)
+}
+
+.gantt_custom_button {
+  padding: 0 3px;
+  font-family: Arial;
+  font-size: 13px;
+  font-weight: 400;
+  margin-right: 10px;
+  margin-top: -5px;
+  cursor: pointer;
+  float: right;
+  height: 21px;
+  width: 90px;
+  border: 1px solid #DFE3EA;
+  text-align: center;
+  border-radius: 4px
+}
+
+.gantt_custom_button div {
+  cursor: pointer;
+  float: none;
+  height: 21px;
+  line-height: 21px;
+  vertical-align: middle
+}
+
+.gantt_custom_button div:first-child {
+  display: none
+}
+
+.gantt_cal_light_wide {
+  width: 580px;
+  padding: 2px 4px
+}
+
+.gantt_cal_light_wide .gantt_cal_larea {
+  box-sizing: border-box;
+  border: 1px solid #DFE3EA
+}
+
+.gantt_cal_light_wide .gantt_cal_lsection {
+  border: 0;
+  float: left;
+  text-align: right;
+  width: 80px;
+  height: 20px;
+  padding: 5px 10px 0 0
+}
+
+.gantt_cal_light_wide .gantt_wrap_section {
+  position: relative;
+  padding: 10px 0;
+  overflow: hidden;
+  border-bottom: 1px solid #ebebeb
+}
+
+.gantt_cal_light_wide .gantt_section_time {
+  overflow: hidden;
+  padding-top: 2px !important;
+  padding-right: 0;
+  height: 20px !important
+}
+
+.gantt_cal_light_wide .gantt_cal_ltext {
+  padding-right: 0
+}
+
+.gantt_cal_light_wide .gantt_cal_larea {
+  padding: 0 10px;
+  width: 100%
+}
+
+.gantt_cal_light_wide .gantt_section_time {
+  background: transparent
+}
+
+.gantt_cal_light_wide .gantt_cal_checkbox label {
+  padding-left: 0
+}
+
+.gantt_cal_light_wide .gantt_cal_lsection .gantt_fullday {
+  float: none;
+  margin-right: 0;
+  font-weight: 700;
+  cursor: pointer
+}
+
+.gantt_cal_light_wide .gantt_custom_button {
+  position: absolute;
+  top: 0;
+  right: 0;
+  margin-top: 2px
+}
+
+.gantt_cal_light_wide .gantt_repeat_right {
+  margin-right: 55px
+}
+
+.gantt_cal_light_wide.gantt_cal_light_full {
+  width: 738px
+}
+
+.gantt_cal_wide_checkbox input {
+  margin-top: 8px;
+  margin-left: 14px
+}
+
+.gantt_cal_light input {
+  font-size: 13px
+}
+
+.gantt_section_time {
+  background-color: #fff;
+  white-space: nowrap;
+  padding: 2px 10px 5px;
+  padding-top: 2px !important
+}
+
+.gantt_section_time .gantt_time_selects {
+  float: left;
+  height: 25px
+}
+
+.gantt_section_time .gantt_time_selects select {
+  height: 23px;
+  padding: 2px;
+  border: 1px solid #DFE3EA
+}
+
+.gantt_duration {
+  width: 100px;
+  height: 23px;
+  float: left;
+  white-space: nowrap;
+  margin-left: 20px;
+  line-height: 23px
+}
+
+.gantt_duration .gantt_duration_dec,
+.gantt_duration .gantt_duration_inc,
+.gantt_duration .gantt_duration_value {
+  box-sizing: border-box;
+  text-align: center;
+  vertical-align: top;
+  height: 100%;
+  border: 1px solid #DFE3EA
+}
+
+.gantt_duration .gantt_duration_value {
+  width: 40px;
+  padding: 3px 4px;
+  border-left-width: 0;
+  border-right-width: 0
+}
+
+.gantt_duration .gantt_duration_value.gantt_duration_value_formatted {
+  width: 70px
+}
+
+.gantt_duration .gantt_duration_dec,
+.gantt_duration .gantt_duration_inc {
+  width: 20px;
+  padding: 1px;
+  padding-bottom: 1px;
+  background: #fff
+}
+
+.gantt_duration .gantt_duration_dec {
+  -moz-border-top-left-radius: 4px;
+  -moz-border-bottom-left-radius: 4px;
+  -webkit-border-top-left-radius: 4px;
+  -webkit-border-bottom-left-radius: 4px;
+  border-top-left-radius: 4px;
+  border-bottom-left-radius: 4px
+}
+
+.gantt_duration .gantt_duration_inc {
+  margin-right: 4px;
+  -moz-border-top-right-radius: 4px;
+  -moz-border-bottom-right-radius: 4px;
+  -webkit-border-top-right-radius: 4px;
+  -webkit-border-bottom-right-radius: 4px;
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 4px
+}
+
+.gantt_resources {
+  max-height: 150px;
+  height: auto;
+  overflow-y: auto
+}
+
+.gantt_resource_row {
+  display: block;
+  padding: 10px 0;
+  border-bottom: 1px solid #ebebeb;
+  cursor: pointer
+}
+
+.gantt_resource_row input[type=checkbox]:not(:checked),
+.gantt_resource_row input[type=checkbox]:not(:checked)~div {
+  opacity: .5
+}
+
+.gantt_resource_toggle {
+  vertical-align: middle
+}
+
+.gantt_resources_filter .gantt_resources_filter_input {
+  padding: 1px 2px;
+  box-sizing: border-box
+}
+
+.gantt_resources_filter .switch_unsetted {
+  vertical-align: middle
+}
+
+.gantt_resource_cell {
+  display: inline-block
+}
+
+.gantt_resource_cell.gantt_resource_cell_checkbox {
+  width: 24px;
+  max-width: 24px;
+  min-width: 24px;
+  vertical-align: middle
+}
+
+.gantt_resource_cell.gantt_resource_cell_label {
+  width: 40%;
+  max-width: 40%;
+  vertical-align: middle
+}
+
+.gantt_resource_cell.gantt_resource_cell_value {
+  width: 30%;
+  max-width: 30%;
+  vertical-align: middle
+}
+
+.gantt_resource_cell.gantt_resource_cell_value input,
+.gantt_resource_cell.gantt_resource_cell_value select {
+  width: 80%;
+  vertical-align: middle;
+  padding: 1px 2px;
+  box-sizing: border-box
+}
+
+.gantt_resource_cell.gantt_resource_cell_unit {
+  width: 10%;
+  max-width: 10%;
+  vertical-align: middle
+}
+
+.gantt_resource_early_value {
+  opacity: .8;
+  font-size: .9em
+}
+
+.gantt_cal_quick_info {
+  border: 1px solid #DFE3EA;
+  border-radius: 6px;
+  position: absolute;
+  z-index: 300;
+  box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+  background-color: #fff;
+  width: 300px;
+  transition: left .5s ease, right .5s;
+  -moz-transition: left .5s ease, right .5s;
+  -webkit-transition: left .5s ease, right .5s;
+  -o-transition: left .5s ease, right .5s
+}
+
+.gantt_no_animate {
+  transition: none;
+  -moz-transition: none;
+  -webkit-transition: none;
+  -o-transition: none
+}
+
+.gantt_cal_quick_info.gantt_qi_left .gantt_qi_big_icon {
+  float: right
+}
+
+.gantt_cal_qi_title {
+  -webkit-border-top-left-radius: 6px;
+  -webkit-border-bottom-left-radius: 0;
+  -webkit-border-top-right-radius: 6px;
+  -webkit-border-bottom-right-radius: 0;
+  -moz-border-radius-topleft: 6px;
+  -moz-border-radius-bottomleft: 0;
+  -moz-border-radius-topright: 6px;
+  -moz-border-radius-bottomright: 0;
+  border-top-left-radius: 6px;
+  border-bottom-left-radius: 0;
+  border-top-right-radius: 6px;
+  border-bottom-right-radius: 0;
+  padding: 5px 0 8px 12px;
+  color: #454545;
+  background-color: #fff;
+  border-bottom: 1px solid #DFE3EA
+}
+
+.gantt_cal_qi_tdate {
+  font-size: 14px;
+  font-weight: 700
+}
+
+.gantt_cal_qi_tcontent {
+  font-size: 13px
+}
+
+.gantt_cal_qi_content {
+  padding: 16px 8px;
+  font-size: 13px;
+  color: #454545;
+  overflow: hidden
+}
+
+.gantt_cal_qi_controls {
+  -webkit-border-top-left-radius: 0;
+  -webkit-border-bottom-left-radius: 6px;
+  -webkit-border-top-right-radius: 0;
+  -webkit-border-bottom-right-radius: 6px;
+  -moz-border-radius-topleft: 0;
+  -moz-border-radius-bottomleft: 6px;
+  -moz-border-radius-topright: 0;
+  -moz-border-radius-bottomright: 6px;
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 6px;
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 6px;
+  padding-left: 7px
+}
+
+.gantt_cal_qi_controls .gantt_menu_icon {
+  margin-top: 6px;
+  background-repeat: no-repeat
+}
+
+.gantt_cal_qi_controls .gantt_menu_icon.icon_edit {
+  width: 20px;
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3QYFCjI5ZQj5bAAAAFNJREFUOMvt0zEOACAIA0DkwTymH8bJTRTKZGJXyaWEKPKTCQAH4Ls37cItcDUzsxHNDLZNhCq7Gt1wh9ErV7EjyGAhyGLphlnsClWuS32rn0czAV+vNGrM/LBtAAAAAElFTkSuQmCC)
+}
+
+.gantt_cal_qi_controls .gantt_menu_icon.icon_delete {
+  width: 20px;
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjFENzI3NUNDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjFENzI3NURDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMUQ3Mjc1QUMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMUQ3Mjc1QkMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmUD0gAAAABvSURBVHjaYvz//z8DIyMjAxYQicReji4J0ofKQNP8HwmgGQbXB8IsWGwDSSwDuioKjY9uBthVjFAXYHUGAQA2kYmBUoAUBpGk0LAwgBvwH+YX4mkwptgLowYMRgOITUyYKRFIN/wnDjQgJySAAAMApryKzL8wjfUAAAAASUVORK5CYII=)
+}
+
+.gantt_qi_big_icon {
+  font-size: 13px;
+  border-radius: 4px;
+  font-weight: 700;
+  background: #fff;
+  margin: 5px 9px 8px 0;
+  min-width: 60px;
+  line-height: 32px;
+  vertical-align: middle;
+  padding: 0 10px 0 5px;
+  cursor: pointer;
+  border: 1px solid #DFE3EA
+}
+
+.gantt_cal_qi_controls div {
+  float: left;
+  height: 32px;
+  text-align: center;
+  line-height: 32px
+}
+
+.gantt_tooltip {
+  padding: 10px;
+  position: absolute;
+  z-index: 50;
+  white-space: nowrap;
+  background: #303133 !important;
+  color: #fff;
+  border-radius: 4px;
+}
+
+.gantt_resource_marker {
+  position: absolute;
+  text-align: center;
+  font-size: 14px;
+  color: #fff
+}
+
+.gantt_resource_marker_ok {
+  background: rgba(78, 208, 134, .75)
+}
+
+.gantt_resource_marker_overtime {
+  background: hsla(0, 100%, 76%, .69)
+}
+
+.gantt_histogram_label {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  z-index: 1;
+  font-weight: 700;
+  font-size: 13px
+}
+
+.gantt_histogram_fill {
+  background-color: rgba(41, 157, 180, .2);
+  width: 100%;
+  position: absolute;
+  bottom: 0
+}
+
+.gantt_histogram_hor_bar {
+  height: 1px;
+  margin-top: -1px
+}
+
+.gantt_histogram_hor_bar,
+.gantt_histogram_vert_bar {
+  position: absolute;
+  background: #299db4;
+  margin-left: -1px
+}
+
+.gantt_histogram_vert_bar {
+  width: 1px
+}
+
+.gantt_histogram_cell {
+  position: absolute;
+  text-align: center;
+  font-size: 13px;
+  color: #000
+}
+
+.gantt_marker {
+  height: 100%;
+  width: 2px;
+  top: 0;
+  position: absolute;
+  text-align: center;
+  background-color: #409EFF;
+  box-sizing: border-box
+}
+
+.gantt_marker .gantt_marker_content {
+  padding: 5px;
+  background: inherit;
+  color: #fff;
+  position: absolute;
+  font-size: 12px;
+  line-height: 12px;
+  opacity: .8;
+  border-top-right-radius:5px;
+  border-bottom-right-radius:5px;
+}
+
+.gantt_marker_area {
+  position: absolute;
+  top: 0;
+  left: 0
+}
+
+.gantt_grid_editor_placeholder {
+  position: absolute
+}
+
+.gantt_grid_editor_placeholder>div,
+.gantt_grid_editor_placeholder input,
+.gantt_grid_editor_placeholder select {
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box
+}
+
+.gantt_row_placeholder div {
+  opacity: .5
+}
+
+.gantt_row_placeholder .gantt_add,
+.gantt_row_placeholder .gantt_file {
+  display: none
+}
+
+.gantt_drag_marker.gantt_grid_dnd_marker {
+  background-color: transparent;
+  transition: all .1s ease
+}
+
+.gantt_grid_dnd_marker_line {
+  height: 4px;
+  width: 100%;
+  background-color: #3498db
+}
+
+.gantt_grid_dnd_marker_line:before {
+  background: #fff;
+  width: 12px;
+  height: 12px;
+  box-sizing: border-box;
+  border: 3px solid #3498db;
+  border-radius: 6px;
+  content: "";
+  line-height: 1px;
+  display: block;
+  position: absolute;
+  margin-left: -11px;
+  margin-top: -4px;
+  pointer-events: none
+}
+
+.gantt_grid_dnd_marker_folder {
+  height: 100%;
+  width: 100%;
+  position: absolute;
+  pointer-events: none;
+  box-sizing: border-box;
+  box-shadow: inset 0 0 0 2px #3f98db;
+  background: transparent
+}
+
+.gantt_overlay_area {
+  display: none
+}
+
+.gantt_overlay,
+.gantt_overlay_area {
+  position: absolute;
+  height: inherit;
+  width: inherit;
+  top: 0;
+  left: 0
+}
+
+.gantt_click_drag_rect {
+  position: absolute;
+  left: 0;
+  top: 0;
+  outline: 1px solid #3f98db;
+  background-color: rgba(52, 152, 219, .3)
+}
+
+.gantt_timeline_move_available,
+.gantt_timeline_move_available * {
+  cursor: move
+}
+
+.gantt_rtl .gantt_grid {
+  text-align: right
+}
+
+.gantt_rtl .gantt_cell,
+.gantt_rtl .gantt_row {
+  flex-direction: row-reverse
+}
+
+.gantt_layout_content {
+  width: 100%;
+  overflow: auto;
+  box-sizing: border-box
+}
+
+.gantt_layout_cell {
+  position: relative;
+  box-sizing: border-box
+}
+
+.gantt_layout_cell>.gantt_layout_header {
+  background: #33aae8;
+  color: #fff;
+  font-size: 17px;
+  padding: 5px 10px;
+  box-sizing: border-box
+}
+
+.gantt_layout_header.collapsed_x {
+  background: #a9a9a9
+}
+
+.gantt_layout_header.collapsed_x .gantt_header_arrow:before {
+  content: "\21E7"
+}
+
+.gantt_layout_header.collapsed_y {
+  background: #a9a9a9
+}
+
+.gantt_layout_header.collapsed_y .gantt_header_arrow:before {
+  content: "\21E9"
+}
+
+.gantt_layout_header {
+  cursor: pointer
+}
+
+.gantt_layout_header .gantt_header_arrow {
+  float: right;
+  text-align: right
+}
+
+.gantt_layout_header .gantt_header_arrow:before {
+  content: "\21E6"
+}
+
+.gantt_layout_header.vertical .gantt_header_arrow:before {
+  content: "\21E7"
+}
+
+.gantt_layout_outer_scroll_vertical .gantt_layout_content {
+  overflow-y: hidden
+}
+
+.gantt_layout_outer_scroll_horizontal .gantt_layout_content {
+  overflow-x: hidden
+}
+
+.gantt_layout_x>.gantt_layout_cell {
+  display: inline-block;
+  vertical-align: top
+}
+
+.gantt_layout_x {
+  white-space: nowrap
+}
+
+.gantt_resizing {
+  opacity: .7;
+  background: #f2f2f2
+}
+
+.gantt_layout_cell_border_right.gantt_resizer {
+  overflow: visible;
+  border-right: 0
+}
+
+.gantt_resizer {
+  cursor: e-resize;
+  position: relative
+}
+
+.gantt_resizer_y {
+  cursor: n-resize
+}
+
+.gantt_resizer_stick {
+  background: #33aae8;
+  z-index: 9999;
+  position: absolute;
+  top: 0;
+  width: 100%
+}
+
+.gantt_resizer_x .gantt_resizer_x {
+  position: absolute;
+  width: 20px;
+  height: 100%;
+  margin-left: -10px;
+  top: 0;
+  left: 0;
+  z-index: 1
+}
+
+.gantt_resizer_y .gantt_resizer_y {
+  position: absolute;
+  height: 20px;
+  width: 100%;
+  top: -10px;
+  left: 0;
+  z-index: 1
+}
+
+.gantt_resizer_error {
+  background: #cd5c5c !important
+}
+
+.gantt_layout_cell_border_left {
+  border-left: 1px solid #DFE3EA
+}
+
+.gantt_layout_cell_border_right {
+  border-right: 1px solid #DFE3EA
+}
+
+.gantt_layout_cell_border_top {
+  border-top: 1px solid #DFE3EA
+}
+
+.gantt_layout_cell_border_bottom {
+  border-bottom: 1px solid #DFE3EA
+}
+
+.gantt_layout_cell_border_transparent {
+  border-color: transparent
+}
+
+.gantt_window {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  z-index: 999999999;
+  background: #fff
+}
+
+.gantt_window_content {
+  position: relative
+}
+
+.gantt_window_content_header {
+  background: #39c;
+  color: #fff;
+  height: 33px;
+  padding: 10px 10px 0;
+  border-bottom: 2px solid #fff;
+  position: relative
+}
+
+.gantt_window_content_header_text {
+  padding-left: 10%
+}
+
+.gantt_window_content_header_buttons {
+  position: absolute;
+  top: 10px;
+  right: 10px
+}
+
+.gantt_window_content_header_buttons:hover {
+  color: #000;
+  cursor: pointer
+}
+
+.gantt_window_content_resizer {
+  position: absolute;
+  width: 15px;
+  height: 15px;
+  bottom: 0;
+  line-height: 15px;
+  right: -1px;
+  text-align: center;
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAMAAAAMCGV4AAAABlBMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAABZJREFUeAFjIAUwUshlpJDLSIhLGAAACQ4AFk79JaMAAAAASUVORK5CYII=);
+  cursor: nw-resize;
+  z-index: 999
+}
+
+.gantt_window_content_frame {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, .1);
+  z-index: 9999
+}
+
+.gantt_window_drag {
+  cursor: pointer !important
+}
+
+.gantt_window_resizing {
+  overflow: visible
+}
+
+.gantt_window_resizing_body {
+  overflow: hidden !important
+}
+
+.gantt_window_modal {
+  background: rgba(0, 0, 0, .1);
+  z-index: 9999;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  position: fixed
+}
+
+.gantt_cal_light,
+.gantt_cal_quick_info,
+.gantt_container,
+.gantt_message_area,
+.gantt_modal_box,
+.gantt_tooltip {
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale
+}
+
+.gantt_noselect {
+  -moz-user-select: -moz-none;
+  -webkit-user-select: none;
+  -ms-user-select: none;
+  user-select: none
+}
+
+.gantt_noselect .gantt_grid_data .gantt_row.odd:hover,
+.gantt_noselect .gantt_grid_data .gantt_row:hover {
+  background-color: unset
+}
+
+.gantt_drag_marker {
+  position: absolute;
+  top: -1000px;
+  left: -1000px;
+  font-family: Arial;
+  font-size: 13px;
+  z-index: 1;
+  white-space: nowrap
+}
+
+.gantt_drag_marker .gantt_tree_icon.gantt_blank,
+.gantt_drag_marker .gantt_tree_icon.gantt_close,
+.gantt_drag_marker .gantt_tree_icon.gantt_open,
+.gantt_drag_marker .gantt_tree_indent {
+  display: none
+}
+
+.gantt_drag_marker,
+.gantt_drag_marker .gantt_row.odd {
+  background-color: #fff
+}
+
+.gantt_drag_marker .gantt_row {
+  border-left: 1px solid #d2d2d2;
+  border-top: 1px solid #d2d2d2
+}
+
+.gantt_drag_marker .gantt_cell {
+  border-color: #d2d2d2
+}
+
+.gantt_row.gantt_over,
+.gantt_task_row.gantt_over {
+  background-color: #0070fe
+}
+
+.gantt_row.gantt_transparent .gantt_cell {
+  opacity: .7
+}
+
+.gantt_task_row.gantt_transparent {
+  background-color: #f8fdfd
+}
+
+.gantt_popup_button.gantt_delete_button {
+  background: #3db9d3;
+  text-shadow: 0 -1px 0 #248a9f;
+  color: #fff;
+  font-weight: 700;
+  border-width: 0
+}
+
+.gantt_container_resize_watcher {
+  background: transparent;
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: -1;
+  pointer-events: none;
+  border: 0;
+  box-sizing: border-box;
+  opacity: 0
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 10 - 0
src/assets/js/dhtmlx.js


+ 20 - 7
src/components/EmployeeSelector.vue

@@ -230,6 +230,11 @@ export default {
       //是否必须选择人员或者部门
       type: Boolean,
       default: false
+    },
+    include_deleted:{
+    	//是否查询已被删除的人员
+    	type: Boolean,
+    	default: false
     }
   },
   name: 'EmployeeSelector',
@@ -545,6 +550,10 @@ export default {
            }
          }
       }
+      if(this.max > 0 && this.dept_selected_list.length > this.max){
+        this.$message.error('最多只能选'+ this.max +'个部门');
+        return
+      }
       if(this.max > 0 && this.employee_selected_list.length > this.max){
         this.$message.error('最多只能选'+ this.max +'人');
         return
@@ -632,14 +641,18 @@ export default {
           if (this.user_employee_list) {
             var employee_list = this.employee_list;
             var userData = [];
-            list.map(item => {
-              // 列表数据是否是自己的管理范围
-              employee_list.map(item2 => {
-                if (item.id == item2.id) {
-                  userData.push(item);
-                }
+            if(this.include_deleted){
+              userData=employee_list;
+            }else{
+              list.map(item => {
+                // 列表数据是否是自己的管理范围
+                employee_list.map(item2 => {
+                  if (item.id == item2.id) {
+                    userData.push(item);
+                  }
+                });
               });
-            });
+            }
             this.parse_list(userData);
           } else {
             // 没有指定人员列表

+ 4 - 4
src/home.vue

@@ -43,7 +43,7 @@
                 </ul>
 
                 <noData isSolt v-else imgW="200px" imgH="150px" imgUrl="static/images/nodata_default.png">
-                    <div v-else  class="fontColorC" style="text-align: center;" v-if="isAdministrator">暂无公告,去<span class="blue cursor" @click="openUrl(1)">添加</span></div>
+                    <div  class="fontColorC" style="text-align: center;" v-if="isAdministrator">暂无公告,去<span class="blue cursor" @click="openUrl(1)">添加</span></div>
                 </noData>
             </div>
 
@@ -84,7 +84,7 @@
             </div>
             <div style="background-color: #fff;padding: 16px;">
                 <div v-for="(item,index) in updataAll" :key="index" class="versions" @click="openContent(1,item)">
-                    <div style="margin-bottom: 5px;">{{item.title}}</div>
+                    <div >{{item.title}}</div>
                     <div class="fontColorC" style="font-size: 13px;" :v-html="item.focus"></div>
                 </div>
             </div>
@@ -232,7 +232,7 @@ export default {
 }
 .versions{
   position: relative;
-  padding:6px 0;
+  padding:10px 0;
   padding-left:24px;
   cursor: pointer;
 }
@@ -244,7 +244,7 @@ export default {
   border: 3px solid #64CBBA;
   border-radius: 100%;
   left: 0px;
-  top: 10px;
+  top: 14px;
   z-index: 2;
 }
 .versions2{

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
src/icons/iconfont.js


+ 1 - 1
src/okr/components/TargetDetail/AddEvole.vue

@@ -8,7 +8,7 @@
               </el-input>
            </el-form-item>
            <el-form-item label="进展内容" prop="content"  :rules="[{ required: true, message: '请输入进展与障碍'}]">
-              <el-input type="textarea" v-model="form.content" rows="4" placeholder="请输入进展与障碍" maxlength="100" show-word-limit></el-input>
+              <el-input type="textarea" v-model="form.content" rows="4" placeholder="请输入进展与障碍" maxlength="500" show-word-limit></el-input>
            </el-form-item>
            <el-form-item label="时间">
                <el-date-picker :clearable="false" size="small" v-model="form.time" type="daterange" value-format="yyyy-MM-dd" range-separator="~" start-placeholder="开始日期"end-placeholder="结束日期"></el-date-picker>

+ 4 - 3
src/okr/components/TargetDetail/AddInteraction.vue

@@ -9,12 +9,13 @@
       </div>
       <el-input type="textarea" v-model="form.content" rows="4" placeholder="请输入反馈内容(必填)" maxlength="500" show-word-limit clearable></el-input>
       <div style="margin-top: 10px;">
-
         <div class="flex-box-ce">
           <uploadOss :file-list="fileList" :headers="$xtoken" :action="$action" :limit="3" :accept="$acceptImgFile" :multiple="true" :on-success="handleSuccess" :before-upload="beforeFilesUpload">
-              <div class="fontColorC" style="margin-right: 10px;"><i class="el-icon-paperclip" style="padding-right: 5px;"></i>附件</div>
+              <el-button class="primaryBtn"  style="margin-right: 10px;">
+                <i class="el-icon-paperclip" style="padding-right: 5px;"></i>附件
+              </el-button>
           </uploadOss>
-          <div @click="selectUser" class="fontColorC cursor">@同事</div>
+          <el-button class="primaryBtn"  @click="selectUser">@同事</el-button>
         </div>
         <div v-if="files.length>0" class="flex-box-ce" style="margin-top: 20px;">
             <div class="files-box flex-box-ce" v-for="(item,index) in files" :key="index">

+ 69 - 22
src/okr/components/TargetDetail/Interaction.vue

@@ -1,13 +1,20 @@
 <template>
   <div>
-    <div>
-      <div class="fontColorC hoverBlue" style="font-size: 12px;">
-        <span @click="isShowAll=!isShowAll">全部操作 {{total}} <i :class="isShowAll? 'el-icon-arrow-up':'el-icon-arrow-down'"></i></span>
-      </div>
-      <Record :record="record" :isShowAll="isShowAll"></Record>
+    <div class="scroll-bar" :class="{'setHeight':target_type==4}">
+      <template v-if="target_type!=5">
+        <div class="fontColorC hoverBlue" style="font-size: 12px;">
+          <span @click="isShowAll=!isShowAll">全部操作 {{total}} <i :class="isShowAll? 'el-icon-arrow-up':'el-icon-arrow-down'"></i></span>
+        </div>
+        <Record :record="record" :isShowAll="isShowAll"></Record>
+      </template>
+
       <div class="fontColorC flex-box-ce flex-d-center">
         <span class="hoverBlue" style="font-size: 12px;" @click="isShowAll2=!isShowAll2">全部沟通 {{total2}} <i :class="isShowAll2? 'el-icon-arrow-up':'el-icon-arrow-down'"></i></span>
-        <div @click="huiFu({},true)" style="font-size: 14px;font-weight: 600;cursor: pointer;" class="hoverBlue"><i class="el-icon-edit"></i>需要沟通,请 @ta</div>
+        <div class="flex-box-ce">
+            <el-checkbox v-model="checked">仅看有附件的</el-checkbox>
+            <el-input  class="input" maxlength="20" prefix-icon="el-icon-search" style="width: 206px;margin: 0 10px;" size="small" v-model="keyword" clearable placeholder="按沟通内容搜索" />
+            <el-button @click="huiFu({},true)"  class="primaryBtn"  size="small" v-if="isOperation"><i class="el-icon-edit"></i>立即沟通</el-button>
+        </div>
       </div>
       <div class="record" v-if="feedbackList.length > 0" style="margin: 20px 0;">
         <div v-for="(item, index) in feedbackList" :key="index" class="record-list" v-if="isShowAll2">
@@ -52,6 +59,7 @@
 import Record from '@/okr/components/public/Record'; //流程
 import AddInteraction from '@/okr/components/TargetDetail/AddInteraction'; //流程
 import Tooltip from '@/components/Tooltip'; //鼠标悬浮显示文字
+import { _debounce} from '@/utils/auth';
 export default {
   name: 'Interaction',
   components: { Record, AddInteraction,Tooltip },
@@ -63,7 +71,12 @@ export default {
     target_type: {//沟通记录所属的对象种类 1-目标 2-KR 3-计划 4-项目 5-里程碑 6-进展
       type: Number,
       default: 0,
-    }
+    },
+    isOperation: { //是否可以操作
+      type: Boolean,
+      default: true
+    },
+
   },
   data() {
     return {
@@ -79,9 +92,20 @@ export default {
       isShowAll:false,//是否显示全部操作记录
       isShowAll2:true,
       isShowCommunication: false, //沟通反馈
+
+      keyword:'',
+      file:'',
+      checked:false,
     };
   },
-  watch: {},
+  watch: {
+    keyword:_debounce(function(val) {
+        this.getLog();
+    }),
+    checked(val) {
+      this.getLog();
+    },
+  },
   mounted() {
     this.getLog()
   },
@@ -106,22 +130,38 @@ export default {
     },
     getLog() {
       let axios= this.$axiosUser('get', '/api/pro/okr/log',{target_id:this.target_id,target_type:this.target_type})
-      let axios2= this.$axiosUser('get', '/api/pro/okr/feedback/list', {target_id:this.target_id,target_type:this.target_type,page:1,page_size:100})
-      Promise.all([axios,axios2]).then(res => {
-          let data1=res[0].data.data
-          let data2=res[1].data.data
-          this.record=data1.list;
-          this.total=data1.total;
-          let list=data2.list;
+      let axios2= this.$axiosUser('get', '/api/pro/okr/feedback/list', {target_id:this.target_id,target_type:this.target_type,page:0,page_size:100,keyword:this.keyword,file:this.checked? 1:0})
+      let urls=this.target_type==5? [axios2]:[axios,axios2];
 
-          list.forEach(item=>{
-            item.userInfo=this.$getEmployeeMapItem(item.publisher_id);
-            item.notice_employee_ids=item.notice_employee_ids.map(e=>{
-               return this.$getEmployeeMapItem(e)
+      Promise.all(urls).then(res => {
+          if(this.target_type==5){
+            let data2=res[0].data.data
+            let list=data2.list;
+            list.forEach(item=>{
+              item.userInfo=this.$getEmployeeMapItem(item.publisher_id);
+              item.notice_employee_ids=item.notice_employee_ids.map(e=>{
+                 return this.$getEmployeeMapItem(e)
+              })
             })
-          })
-          this.feedbackList=list;
-          this.total2=data2.total;
+            this.feedbackList=list;
+            this.total2=data2.total;
+          }else{
+            let data1=res[0].data.data
+            this.record=data1.list;
+            this.total=data1.total;
+
+
+            let data2=res[1].data.data
+            let list=data2.list;
+            list.forEach(item=>{
+              item.userInfo=this.$getEmployeeMapItem(item.publisher_id);
+              item.notice_employee_ids=item.notice_employee_ids.map(e=>{
+                 return this.$getEmployeeMapItem(e)
+              })
+            })
+            this.feedbackList=list;
+            this.total2=data2.total;
+          }
       })
     }
   }
@@ -129,6 +169,13 @@ export default {
 </script>
 
 <style scoped="scoped" lang="scss">
+  ::v-deep input {
+    border-radius: 20px;
+  }
+ .setHeight{
+   height: calc(100vh - 280px);
+   overflow-y: auto;
+ }
 .add-task {
   width: 120px;
   text-align: center;

+ 453 - 0
src/okr/components/project/AddProject.vue

@@ -0,0 +1,453 @@
+<template>
+  <el-dialog :title="title" :visible.sync="visible_" :close-on-click-modal="false" :before-close="close_before" top="5%" append-to-body  width="600px">
+    <div style="max-height: 600px;overflow-y: scroll;" class="scroll-bar">
+      <el-form label-width="80px" :model="form">
+
+        <div class="add-task-title">基本信息</div>
+        <el-form-item label="项目名称" class="is-required">
+          <el-input v-model="form.name" maxlength="30" show-word-limit class="w270" type="textarea" autosize placeholder="请输入项目名称"></el-input>
+        </el-form-item>
+        <el-form-item label="项目描述">
+          <el-input v-model="form.desc" placeholder="请输入描述" class="w270"  maxlength="200" show-word-limit type="textarea" clearable></el-input>
+        </el-form-item>
+        <el-form-item label="负责人">
+          <div class="flex-box-ce cursor" @click="showSelectorUser(1)">
+             <userImage :id="owner_userInfo.id" fontSize="14" width="36px" height="36px" :user_name="owner_userInfo.name"></userImage>
+             <span style="margin-left: 10px;font-size: 14px;color: #3F4755;">{{ owner_userInfo.name }}</span>
+         </div>
+        </el-form-item>
+        <el-form-item label="起止时间">
+            <el-date-picker class="w270" :picker-options="instantPickerOptions" v-model="form.date" :clearable="false" type="daterange" range-separator="至" value-format="yyyy-MM-dd" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['8:00:00', '18:00:00']"></el-date-picker>
+        </el-form-item>
+
+        <el-form-item label="所属部门" prop="dept_ids">
+          <div class="w270" style="position: relative;">
+              <el-input auto-complete="off" v-model="deptVisibleName" placeholder="请选择部门"></el-input>
+              <div @click="show_dept_selector = true" style=" position: absolute; top: 0; right: 0; left: 0; bottom: 0; z-index: 9;"></div>
+          </div>
+        </el-form-item>
+
+        <el-form-item label="参与人">
+            <div class="cursor" style="border-radius: 4px;border: 1px solid #dcdfe6;line-height: 34px;width: 400px;padding: 0 15px;" @click="show_employee_selector_all=true">
+              <div class="flex-box-ce fontColorB font-flex-word" v-if="employee_selected_all.employee.length>0">
+                  <span v-for="item in employee_selected_all.employee" :key="item.id"> {{item.name}},</span>
+              </div>
+              <div v-else style="color: #C0C4CF;">请选择参与人员</div>
+            </div>
+        </el-form-item>
+
+        <el-form-item label="可见范围">
+          <el-select class="w270" v-model="form.scope_type" placeholder="请选择">
+            <el-option label="仅项目成员可见" :value="1"></el-option>
+            <el-option label="全员公开可见" :value="2"></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="关联KR" v-if="!kr_id">
+          <div class="cursor" style="border-radius: 4px;border: 1px solid #dcdfe6;line-height: 34px;width: 400px;padding: 0 15px;">
+              <div class="fontColorC" v-if="!target_id" @click="isShowDateSearch=true">请选择关联KR</div>
+              <div class="showUpdate" v-else ><span class="cursor" @click="isShowDateSearch=true"><span class="blue">KR</span> {{krName}}</span> <i class="el-icon-error cursor"  @click="target_id=0"></i></div>
+          </div>
+        </el-form-item>
+
+      </el-form>
+    </div>
+    <div class="flex-box-ce flex-box-end" style="margin-top: 20px;">
+      <el-button @click="close">取 消</el-button>
+      <el-button type="primary" @click="confirm" :disabled="wait">确 定</el-button>
+    </div>
+
+    <!-- 对齐目标 -->
+    <TargetSearch :visible.sync="isShowDateSearch" title="所属KR" :showType="2" @confirm="confirmTarget"></TargetSearch>
+
+    <!-- 选择部门 -->
+    <EmployeeSelector title="选择部门" :isChecKedAll="false" :can_select_employee="false" :can_select_dept="true" :multi="true" :selected="dept_selected" :visible.sync="show_dept_selector"@confirm="dept_confirm"/>
+
+    <!-- 选择负责人 -->
+    <EmployeeSelector :is_filtration_creator="false" title="选择人员" :isChecKedAll="false" :isRequired="selectUserIndex==1" :multi="false" :selected="employee_selected" :visible.sync="show_employee_selector" @confirm="employee_confirm"/>
+
+    <!-- 选择参与人 -->
+    <EmployeeSelector :is_filtration_creator="false" title="选择人员" :isChecKedAll="false" :max="50" :selected="employee_selected_all" :visible.sync="show_employee_selector_all" @confirm="employee_confirm_all"/>
+  </el-dialog>
+</template>
+
+<script>
+import EmployeeSelector from '@/components/EmployeeSelector';
+import TargetSearch from '@/okr/components/public/TargetSearch'; //反馈时间组件
+import Tooltip from '@/components/Tooltip'; //鼠标悬浮显示文字
+import moment from 'moment' // 时间库
+export default {
+  name: 'AddProject',
+  components:{EmployeeSelector,Tooltip,TargetSearch},
+  props: {
+    title: {
+      // 标题
+      type: String,
+      default: '创建项目'
+    },
+    visible: {
+      // 是否显示组件
+      type: Boolean,
+      default: false
+    },
+    target_type:{ //	计划绑定的对象种类 1-目标 2-KR 3-计划(分解计划下的子计划的时候) 0不绑定
+      type: Number,
+      default: 2
+    },
+    isShowGl:{//是否显示关联
+      type: Boolean,
+      default: false
+    },
+    kr_id:{ //	绑定的对象id 跟对象种类配对使用
+      type: Number,
+      default: 0,
+    },
+    initDate:{
+      type: Array,
+      default:()=>{
+        return [];
+      }
+    }
+  },
+  data() {
+    return {
+      wait:false,
+      owner_userInfo: this.$userInfo(),//负责人
+      reviewer_userInfo:{},//审批人
+      visible_:false,
+      unitList:this.$getCache('unitList'),
+      files: [], //文件附件
+      fileList:[],
+
+      isShowImg:false,
+      imgUrl:'',//显示图片Url
+      form: {
+          name:	'',//	是 计划名
+          owner_id:	 this.$userInfo().id,// 是 integer	负责人id
+          desc:	'',
+          date:[`${this.$moment().format('YYYY-MM-DD')}`,`${this.$moment().add(30, "days").format("YYYY-MM-DD")}`],
+          start_date:	`${this.$moment().format('YYYY-MM-DD')} `,//是	string	计划开始日期 格式:2022-01-01
+          end_date:	`${this.$moment().format('YYYY-MM-DD')} `,//是	string	计划结束日期 格式:2022-01-01
+          dept_ids:	'',//	否	string	部门id列表,以逗号分割,只有可见范围种类为指定部门可见才需要上传这个字段,没有的时候不要上传这个字段,空字符也不行
+          joiner_ids:	'',//	否	string	参与者id列表,以逗号分割,没有的时候不要上传这个字段,空字符也不行
+          scope_type:	1,//	可见范围 1-相关人员可见 2-全员可见
+          kr_id:0,
+      },
+
+      employee_selected: { dept: [], employee: [] },
+      show_employee_selector: false,
+      employee_selected_all: { dept: [], employee: [] },
+      show_employee_selector_all: false,
+      selectUserIndex:1,
+
+      // 部门可见
+      deptVisibleName: null,
+      dept_selected: {dept: [],employee:[]},
+      show_dept_selector: false,
+      instantPickerOptions: {
+        shortcuts: [
+          {
+            text: "今天",
+            onClick(picker) {
+              picker.$emit("pick", [`${moment().format('YYYY-MM-DD')} `,`${moment().format('YYYY-MM-DD')} `]);
+            }
+          },
+          {
+            text: "明天",
+            onClick(picker) {
+              picker.$emit("pick", [`${moment().add(1, "days").format('YYYY-MM-DD')} `,`${moment().add(1, "days").format('YYYY-MM-DD')} `]);
+            }
+          },
+          {
+            text: "本周",
+            onClick(picker) {
+              let start_date=moment().week(moment().week()).startOf('isoweek').format('YYYY-MM-DD ')
+              let end_date=moment().week(moment().week()).endOf('isoweek').format('YYYY-MM-DD ')
+              picker.$emit("pick", [start_date, end_date]);
+            }
+          },
+          {
+            text: "下周",
+            onClick(picker) {
+              let start_date=moment().week(moment().week() + 1).startOf('isoweek').format('YYYY-MM-DD ')
+              let end_date=moment().week(moment().week() + 1).endOf('isoweek').format('YYYY-MM-DD ')
+              picker.$emit("pick", [start_date, end_date]);
+            }
+          },
+          {
+            text: "本月",
+            onClick(picker) {
+              let start_date=moment().startOf('month').format('YYYY-MM-DD ')
+              let end_date=moment().endOf('month').format('YYYY-MM-DD ')
+              picker.$emit("pick", [start_date, end_date]);
+            }
+          },
+          {
+            text: "下个月",
+            onClick(picker) {
+              let start_date=moment().add(1, "month").startOf('month').format('YYYY-MM-DD ')
+              let end_date=moment().add(1, "month").endOf('month').format('YYYY-MM-DD ')
+              picker.$emit("pick", [start_date, end_date]);
+            }
+          }
+        ]
+      },
+
+      //关联母任务
+      isShowRelevanceTask:false,
+      motherTaskName:'',
+      target_plan_id:0,
+
+      //关联KR
+      target_id:0,
+      krName:'',
+      isShowDateSearch:false,
+
+    };
+  },
+  watch: {
+    visible(val) {
+      this.visible_ = JSON.parse(JSON.stringify(val));
+      if(val){
+        this.initData();
+      }
+    },
+    'form.date'(newValue, oldValue) {
+        if(newValue){
+          this.form.start_date=newValue[0];
+          this.form.end_date=newValue[1];
+        }else{
+          this.form.start_date='';
+          this.form.end_date='';
+        }
+    },
+  },
+  methods: {
+    confirmTarget(item){
+      this.krName=item.item.name;
+      this.target_id=item.item.id;
+    },
+    initData(){
+      this.form= {
+          name:	'',//	是 计划名
+          owner_id:	 this.$userInfo().id,// 是 integer	负责人id
+          desc:	'',
+          date:[`${this.$moment().format('YYYY-MM-DD')}`,`${this.$moment().add(30, "days").format("YYYY-MM-DD")}`],
+          start_date:	`${this.$moment().format('YYYY-MM-DD')} `,//是	string	计划开始日期 格式:2022-01-01
+          end_date:	`${this.$moment().format('YYYY-MM-DD')} `,//是	string	计划结束日期 格式:2022-01-01
+          dept_ids:	'',//	否	string	部门id列表,以逗号分割,只有可见范围种类为指定部门可见才需要上传这个字段,没有的时候不要上传这个字段,空字符也不行
+          joiner_ids:	'',//	否	string	参与者id列表,以逗号分割,没有的时候不要上传这个字段,空字符也不行
+          scope_type:	1,//	可见范围 1-相关人员可见 2-全员可见
+          kr_id:0,
+      };
+      if(this.initDate.length>0){
+        let initDate=this.initDate;
+        this.form.date=[`${initDate[0]} `,`${initDate[1]} `];
+      }
+      this.owner_userInfo=this.$userInfo();//负责人
+      this.employee_selected= { dept: [], employee: [] };
+      this.show_employee_selector= false;
+      this.employee_selected_all= { dept: [], employee: [] };
+      this.show_employee_selector_all= false;
+      // 部门可见
+      this.deptVisibleName= null;
+      this.dept_selected= {dept: [],employee:[]};
+      this.show_dept_selector= false;
+
+      //关联KR
+      this.target_id=0;
+      this.krName='';
+    },
+
+    showSelectorUser(index){
+      this.employee_selected.employee=[this.owner_userInfo];
+      this.show_employee_selector=true;
+    },
+    // 部门可见
+    dept_confirm(data){
+      this.dept_selected = {dept: [],employee:[]};
+      let dept_ids = [];
+      this.form.dept_ids='';
+      this.deptVisibleName = ''
+      if (data.dept !== null && data.dept.length != 0) {
+        this.dept_selected = data
+        data.dept.forEach(element => {
+    			dept_ids.push(element.dept_id)
+    			this.deptVisibleName += (element.dept_name+',')
+        });
+      }
+      this.form.dept_ids=dept_ids.toString();
+    },
+    employee_confirm_all(val){
+      this.employee_selected_all.employee=val.employee;
+      let joiner_ids=val.employee.map(item=>{
+        return item.id
+      })
+      this.form.joiner_ids=joiner_ids.toString()
+    },
+
+    employee_confirm(val){
+      let user=val.employee[0]
+      this.employee_selected.employee=val.employee;
+      this.owner_userInfo=user;
+      this.form.owner_id=user.id
+    },
+    close_before(done) {
+      this.close();
+      done();
+    },
+    //关闭||清空数据
+    close() {
+      this.$emit('update:visible', false);
+    },
+    // 确定
+    confirm() {
+      if(!this.form.name){
+        this.$message.error('请输入任务名称');
+        return false;
+      }
+      let params={
+          name:	this.form.name,//	是 计划名
+          desc:this.form.desc,
+          owner_id:	 this.form.owner_id,// 是 integer	负责人id
+          start_date:	this.form.start_date,//是	string	计划开始日期 格式:2022-01-01
+          end_date:	this.form.end_date,//是	string	计划结束日期 格式:2022-01-01
+          owner_id:	 this.form.owner_id,// 是 integer	负责人id
+          scope_type:this.form.scope_type,//	否	string	部门id列表,以逗号分割,只有可见范围种类为指定部门可见才需要上传这个字段,没有的时候不要上传这个字段,空字符也不行
+          dept_ids:this.form.dept_ids,//	否	string	部门id列表,以逗号分割,只有可见范围种类为指定部门可见才需要上传这个字段,没有的时候不要上传这个字段,空字符也不行
+          joiner_ids:	this.form.joiner_ids,//	否	string	参与者id列表,以逗号分割,没有的时候不要上传这个字段,空字符也不行
+          kr_id:this.kr_id||this.target_id,
+      }
+      this.wait=true;
+      this.$axiosUser('post', 'api/pro/okr/project/create', params).then(res => {
+          this.$message.success('已添加');
+          this.$emit('confirm', {});
+          this.close();
+      }).finally(_=>{
+        this.wait=false;
+      })
+    },
+
+  },
+};
+</script>
+
+<style scoped lang="scss">
+  .files-box{
+    margin-right: 10px;
+    height: 46px;
+    width: 200px;
+    padding-left: 10px;
+    position: relative;
+  }
+  .files-box div{
+    width: 100px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .files-box:hover{
+    background-color: #dcdfe6;
+  }
+  .files-box i{
+    color: #606266;
+  }
+  .files-box i:hover{
+    color: #f56c6c;
+  }
+  .showUpdate .el-icon-error:hover{
+    color: #f56c6c;
+  }
+  .el-icon-plus{
+    width: 40px;
+    height: 40px;
+    border-radius: 100%;
+    border: 1px dashed #909399;
+    font-size: 18px;
+    text-align: center;
+    line-height: 40px;
+    color: #909399;
+  }
+  .bj{
+    background-color: #fff;
+  }
+  .inputDc {
+    color: #606266;
+    position: absolute;
+    border-radius: 4px;
+    padding: 0 15px;
+    top: 0;
+    right: 30px;
+    left: 0;
+    bottom: 0;
+    z-index: 9;
+    cursor: pointer;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .border {
+    -webkit-appearance: none;
+    background-color: #fff;
+    background-image: none;
+    border-radius: 4px;
+    border: 1px solid #dcdfe6;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+    color: #C0C4CF;
+    font-size: inherit;
+    height: auto;
+    outline: 0;
+    padding: 0 15px;
+    padding-right: 10px;
+    line-height: 34px;
+    height: 34px;
+    width: 400px;
+    position: relative;
+    cursor: pointer;
+  }
+  .border .font-flex-word{
+    color: #606266;
+  }
+  .border:hover{
+    border: 1px solid #c0c4cc;
+  }
+ .w270{
+   width: 400px;
+ }
+// ::v-deep .el-form-item__label{
+//     line-height: 14px;
+//     padding-bottom: 8px;
+//     font-weight: 500;
+//     color: #909399;
+//     padding-left: 3px;
+// }
+.add-task-title{
+  padding: 10px;
+  position: relative;
+  color: #141c28;
+}
+.add-task-title::after{
+  content: "";
+  position: absolute;
+  width: 4px;
+  height: 14px;
+  border-radius: 5px;
+  background-color: #409EFF;
+  left: 0;
+  top: 13px;
+}
+.form {
+  // margin: 30px 0;
+}
+::v-deep .el-form-item {
+  margin-bottom: 16px;
+}
+::v-deep .el-dialog__body {
+  padding: 20px;
+  padding-right: 14px;
+}
+::v-deep .el-textarea__inner{
+  min-height: 36px !important;
+}
+</style>

+ 687 - 0
src/okr/components/project/Dhtmlx.vue

@@ -0,0 +1,687 @@
+<template>
+  <div class="br-5 scroll-bar" style="height: calc(100vh - 260px);box-sizing: border-box;">
+    <div class="flex-box-ce flex-d-center" style="margin: 20px 0;">
+      <div class="flex-box-ce">
+        <el-select class="select" size="small" v-model="taskForm.composite_states" placeholder="状态">
+          <el-option :key="0" label="全部" :value="0"></el-option>
+          <el-option v-for="item in taskStatus" :key="item.value" :label="item.name" :value="item.value"></el-option>
+        </el-select>
+<!--        <el-select class="select" size="small" v-model="taskForm.sort" placeholder="排序">
+          <el-option :key="0" label="默认排序" :value="0"></el-option>
+          <el-option :key="1" label="创建时间" :value="1"></el-option>
+          <el-option :key="2" label="开始时间" :value="2"></el-option>
+          <el-option :key="3" label="结束时间" :value="3"></el-option>
+        </el-select> -->
+        <el-select class="select" collapse-tags filterable size="small" v-model="taskForm.owner_id" clearable placeholder="人员">
+          <el-option  v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"></el-option>
+        </el-select>
+        <el-select class="select" size="small" v-model="taskForm.type" placeholder="排序">
+          <el-option :key="0" label="按日查看" :value="0"></el-option>
+          <el-option :key="1" label="按周查看" :value="1"></el-option>
+          <el-option :key="2" label="按月查看" :value="2"></el-option>
+          <el-option :key="3" label="按年查看" :value="3"></el-option>
+        </el-select>
+        <el-input prefix-icon="el-icon-search" class="input"  style="width: 200px;" size="small" v-model="taskForm.keyword" clearable placeholder="请输入任务关键字" />
+      </div>
+      <div class="flex-box-ce">
+        <div class="tab-item" @click="changeToday">回到今天</div>
+        <div class="fontColorB"><i class="el-icon-warning"></i> 图例说明</div>
+      </div>
+    </div>
+
+    <div class="rightGatt" style="min-height:calc(78vh - 120px);width: 100%;overflow: hidden;" ref="gantt"></div>
+
+    <TaskDetail :visible.sync="isShowTaskDetail" :taskId="taskId" @confirm="closeDetail"></TaskDetail>
+
+    <el-drawer :visible.sync="showDrawerTow" :append-to-body="true" :before-close="handleClose" :with-header="false">
+      <header class="drawer-header flex-box-ce">
+        <div class="flex-1">里程碑</div>
+<!--        <el-popconfirm title="此操作不可恢复,您确定删除吗?" @confirm="deleteM" v-if="isReturnPJ">
+            <div slot="reference" class="red cursor" style="margin-right: 20px;font-size: 16px;">删除 <i class="el-icon-delete"></i></div>
+        </el-popconfirm>
+        <div class="blue cursor" style="margin-right: 20px;font-size: 16px;" @click="showAdd(2)" v-if="isReturnPJ||detailData.owner_id==userInfo.id">编辑 <i class="el-icon-edit"></i></div> -->
+        <i @click="handleClose()" class="el-icon-close"></i>
+      </header>
+      <div class="drawer-main">
+        <div class="flex-box-ce">
+          <el-progress type="circle" :color="detailData.statusColor" :percentage="Number(detailData.process)" :width="66" :stroke-width="8"></el-progress>
+          <div style="padding-left: 12px;">
+            <div class="project-name clamp">{{detailData.name}}</div>
+            <div class="flex-box-ce" style="margin-top: 10px;">
+                <span class="flex-box-ce fontColorC">
+                  <svg-icon icon-class="#icon-biaoqian_wode" class="svgIcon" style="width: 1rem;height: 1rem;"></svg-icon>
+                  <span>&nbsp;{{$getEmployeeMapItem(detailData.owner_id).name}}</span>
+                </span>
+                <span class="flex-box-ce fontColorC" style="margin-left: 30px;">
+                  <i class="el-icon-date"></i>
+                  <span>{{detailData.end_date}}
+                    <span v-if="detailData.day>0">(剩余<span class="green">{{detailData.day}}</span>天)</span>
+                    <span v-if="detailData.day==0">(剩余<span class="green">1</span>天)</span>
+                    <span v-if="detailData.day<0">(逾期<span class="red">{{Math.abs(detailData.day)}}</span>天)</span>
+                  </span>
+                </span>
+            </div>
+          </div>
+        </div>
+        <div class="fontColorC" style="margin: 24px 0;padding: 10px;border-radius: 5px;background-color: #f1f1f1;">
+          <span v-if="detailData.desc">{{detailData.desc}}</span>
+          <span v-else>暂无描述</span>
+        </div>
+        <div class="flex-box flex-d-center">
+          <div style="color: #141c28;font-weight: 600;">进展说明</div>
+          <!-- <div class="blue" @click="openMp(1)" v-if="isReturnPJ||detailData.owner_id==userInfo.id"><i class="el-icon-refresh"></i>更新进展</div> -->
+        </div>
+        <div class="record-box" style="margin-top: 20px;">
+          <div v-for="(item, index) in projectProcessList" :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="userInfo.id==item.userInfo.id" @click="openMp(2,item)">编辑</span> -->
+            </div>
+            <div class="record-content">
+              <pre class="pre fontColorA" style="font-size: 15px;" v-if="item.content">{{ item.content }}</pre>
+              <div v-else class="fontColorD">无内容</div>
+            </div>
+            <div class="flex-box-ce flex-d-center fontColorC" style="padding-left: 40px;font-size: 13px;margin-top: 14px;">
+              <div>进度为{{item.process}}%</div>
+              <div>进展日期:{{$moment(item.start_time).format('YYYY/MM/DD')}}~{{$moment(item.end_time).format('YYYY/MM/DD')}}</div>
+            </div>
+          </div>
+          <div class="dotted-line" v-if="projectProcessList.length==0"><div>事务有新的进展?马上记录吧</div></div>
+        </div>
+      </div>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import {taskStatus } from '@/okr/utils/auth';
+import TaskDetail from '@/okr/components/public/TaskDetail'; //任务详情
+import moment from 'moment' // 时间库
+// 引入 组件库
+import '@/assets/js/dhtmlx.js';
+import "@/assets/css/dhtmlxgantt.css";
+export default {
+  name: 'Dhtmlx',
+  components:{TaskDetail},
+  props: {
+    isReturnPJ: { //是否是项目管理者
+      type: Boolean,
+      default:true
+    },
+    isPstake: { //是否是项目相关成员
+      type: Boolean,
+      default:true
+    },
+  },
+  data() {
+    return {
+      taskForm:{composite_states:0,sort:3,owner_id:'',type:0,keyword:''},
+      taskStatus:taskStatus(),
+      employeeMap:this.$getEmployeeMap(),
+      userInfo:this.$userInfo(),
+
+      tasks: {
+        data: [
+        ],
+        // 格式 id:数据id
+        //     source:开始链接的项目id  ----为tasks.data中数据的id
+        //     target:要链接项目的id  ----为tasks.data中数据的id
+        //     type: 0--进行-开始  `尾部链接头部`  1--开始-开始  `头部链接头部` 2--进行-进行  `尾部链接尾部` 3--开始-进行  `头部链接尾部`
+        links: [
+          // {
+          //    id:1,//数据id
+          //    source:1,
+          //    target:2,
+          //    type:0
+          //  },
+        ],
+      },
+      projectId:0,
+      milestones:[],//
+
+      showDrawerTow:false,
+      projectProcessList:[],//进展
+      projectId:0,
+      isShowTaskDetail:false,
+      detailData:{},
+      taskId:0,
+      events:[],//
+      taskList:[],
+    };
+  },
+  watch:{
+    'taskForm.composite_states'(val){
+      this.returnList();
+    },
+    'taskForm.owner_id'(val){
+      this.returnList();
+    },
+    'taskForm.keyword'(val){
+      this.returnList();
+    },
+    'taskForm.type'(val){
+      this.ganttChangeDateView(val);
+    },
+  },
+  methods: {
+    returnList(){
+      let list=[];
+      let {composite_states,sort,owner_id,keyword}=this.taskForm
+      this.taskList.forEach((item)=>{
+        if(item.type=='task'){
+            let is=true;
+            if(composite_states!==0&&composite_states!=item.composite_state){
+              is=false
+            }
+            if(owner_id&&owner_id!=item.owner_id){
+              is=false
+            }
+            if(keyword&&item.name.indexOf(keyword)==-1){
+              is=false
+            }
+            if(is){
+               list.push(item)
+            }
+        }else{
+          list.push(item)
+        }
+      })
+      gantt.clearAll();
+      this.$nextTick(()=>{
+        this.tasks.data=list;
+        gantt.parse(this.tasks);
+      })
+    },
+    deleteM(){},
+    closeDetail(){},
+    handleClose(done) {
+      this.showDrawerTow = false;
+      if (done) {
+        done();
+      }
+    },
+    returnStatus(item){
+      let isGq=this.$moment().format('YYYY-MM-DD')>item.end_date;//是否过期
+      let status='#409EFF';
+      if(item.id=='-1'){
+        return status
+      }
+      if(isGq){
+        if(item.statistics.plan_primary_delay>0){
+          status='#f56c6c';
+        }
+      }else{
+        if(item.statistics.plan_primary_delay>0){
+          status='#FF9600';
+        }
+      }
+      return status
+
+    },
+    getMilestoneList(isUpdata){
+      this.loading=true;
+      this.$axiosUser('get', '/api/pro/okr/project/milestones',{project_id:this.projectId,sub:1}).then(res => {
+          let milestones=res.data.data.milestones;
+          let plans=res.data.data.plans;
+          let taskList=[];
+          if(plans.length>0){
+            milestones.push({id:'-1',name:'暂无设置里程碑',plans:plans})
+          }
+          milestones.forEach(item=>{
+            item.statusColor=this.returnStatus(item);
+            item.day=this.$moment(item.end_date).diff(this.$moment().format('YYYY-MM-DD'), 'day')
+            if(item.id=='-1'){
+              taskList.push({
+                 id: item.id,//必填
+                 name:item.name,//必填
+                 text:item.name,//必填
+                 type: "milestone",// 项目类型 task任务 project项目  milestone里程碑  
+                 open: true,//是否展开显示
+              })
+              if(item.plans.length>0){
+                item.plans.forEach((a)=>{
+                  a.parent=item.id
+                  this.returnArr(a,taskList);
+                })
+              }
+              return false
+            }
+            let userInfo=this.$getEmployeeMapItem(item.owner_id)
+            taskList.push({
+               id: item.id+'_1',//必填
+               isId: item.id,//必填
+               name:item.name,//必填
+               text:item.name,//必填
+               type: "milestone",// 项目类型 task任务 project项目  milestone里程碑  
+               userName:userInfo.name,//人名
+               deptName:userInfo.employee_detail.dept_list.length>0? userInfo.employee_detail.dept_list[0].dept_name:'--',
+               start_date:this.$moment(this.$moment(item.end_date).add(1, 'days')).format('DD-MM-YYYY'),
+               dateStr:this.$moment(item.end_date).format('MM-DD'),
+               open: true,//是否展开显示
+               taskProgress:Number(item.process)+'%',
+            })
+            if(item.plans.length>0){
+              item.plans.forEach((a)=>{
+                a.parent=item.id+'_1'
+                this.returnArr(a,taskList);
+              })
+            }
+          })
+          this.tasks.data=taskList;
+          this.taskList=JSON.parse(JSON.stringify(taskList));
+          this.milestones=milestones;
+          // console.log(taskList)
+          this.initGantt();
+      }).finally(()=>{
+        this.loading=false;
+      })
+    },
+    returnArr(item,arr){
+      let userInfo=this.$getEmployeeMapItem(item.owner_id);
+      let obj={
+           id: item.id,//必填
+           name:item.name,//必填
+           text:item.name,//必填
+           type: "task",// 项目类型 task任务 project项目  milestone里程碑  
+           userName:userInfo.name,//人名
+           deptName:userInfo.employee_detail.dept_list.length>0? userInfo.employee_detail.dept_list[0].dept_name:'--',
+           start_date:this.$moment(item.start_date).format('DD-MM-YYYY'),
+           end_date:this.$moment(this.$moment(item.end_date).add(1, 'days')).format('DD-MM-YYYY'),
+           dateStr:this.$moment(item.start_date).format('MM-DD')+'~'+this.$moment(item.end_date).format('MM-DD'),
+           taskProgress:Number(item.process)+'%',
+           stateInfo:this.returnColor(item.composite_state),
+           progress:Number(item.process)/100,
+           parent:item.parent,
+           composite_state:item.composite_state,
+           create_time:item.create_time,
+           start_time:item.start_time,
+           end_time:item.end_time,
+           owner_id:item.owner_id,
+      }
+      if(this.$moment(item.start_date).format('DD-MM-YYYY')==this.$moment(item.end_date).format('DD-MM-YYYY')){
+        obj.duration=1;
+        delete obj.end_date
+      }
+      arr.push(obj)
+      if(item.plans.length>0){
+        item.plans.forEach((a)=>{
+          a.parent=item.id
+          this.returnArr(a,arr);
+        })
+      }
+    },
+    returnColor(num){
+      let statusArr = [
+        {
+          name: '未开始',
+          value: 1,
+          icon: 'el-icon-video-play',
+          color:'#409EFF',
+        },
+        {
+          name: '进行中',
+          value: 2,
+          icon: 'el-icon-time',
+          color:'#409EFF',
+        },
+        {
+          name: '已逾期',
+          value: 3,
+          icon: 'el-icon-warning-outline',
+          color:'#f56c6c',
+        },
+        {
+          name: '暂停中',
+          value: 4,
+          icon: 'el-icon-video-pause',
+          color:'#89919F',
+        },
+        {
+          name: '审批中',
+          value: 5,
+          icon: 'el-icon-edit-outline',
+          color:'#409EFF',
+        },
+        {
+          name: '已完成',
+          value: 6,
+          icon: 'el-icon-circle-check',
+          color:'#67c23a',
+        },
+        {
+          name: '已取消',
+          value: 7,
+          icon: 'el-icon-circle-close',
+          color:'#89919F',
+        },
+        {
+          name: '已达标',
+          value: 8,
+          icon: 'el-icon-document-checked',
+          color:'#409EFF',
+        },
+      ]
+      let obj={}
+      statusArr.forEach(item=>{
+        if(item.value==num){
+          obj=item
+        }
+      })
+      return obj
+    },
+
+    // 切换 年 季 月 周 日视图
+    ganttChangeDateView(type) {
+      switch (type) {
+        case 0:
+            gantt.config.scales = [
+              {unit: 'month', step: 1, format: '%Y / %n'},
+              {unit: 'day', step: 1, format: '%j 周%D'},
+            ];
+          break ;
+        case 1:
+            gantt.config.scales = [
+              // {unit: 'year', step: 1, format: '%Y年'},
+              {unit: 'month', step: 1, format: '%Y / %n'},
+              {unit: 'week', step: 1, format: '第%W周'},
+              {unit: 'day', step: 1, format: '周%D'},
+              // {unit: 'day', step: 1, format: '%j'},
+            ];
+          break;
+        case 2:
+            gantt.config.scales = [
+              {unit: 'year', step: 1, format: '%Y年'},
+              {unit: 'month', step: 1, format: '%n月'},
+              // {unit: 'week', step: 1, format: '第%W周'},
+              // {unit: 'day', step: 1, format: '周%D'},
+              // {unit: 'day', step: 1, format: '%j'},
+            ];
+          break;
+        case 3:
+            gantt.config.scales = [
+              {unit: 'year', step: 1, format: '%Y年'},
+              // {unit: 'month', step: 1, format: '%Y年-%n月'},
+              // {unit: 'week', step: 1, format: '第%W周'},
+              // {unit: 'day', step: 1, format: '周%D'},
+              // {unit: 'day', step: 1, format: '%j'},
+            ];
+          break;
+      }
+      gantt.render();
+    },
+    // 定位到今日线
+    changeToday() {
+      this.$nextTick(() => {
+        let ganTT = document.getElementsByClassName('gantt_marker today')
+        gantt.scrollTo(ganTT[0].offsetLeft - 300, null);
+      })
+    },
+    getProjectProcessList(fun=function(){}){
+      this.$axiosUser('get','/api/pro/okr/process/list',{target_type:5,target_id:this.detailData.id,page:1,page_size:100}).then(res => {
+          let list=res.data.data.list
+          list.forEach(item=>{
+            item.userInfo=this.$getEmployeeMapItem(item.publisher_id);
+          })
+          this.projectProcessList=list
+          fun();
+      });
+    },
+    initGantt() {
+      gantt.config.branch_loading = true; // 启用动态加载
+      gantt.config.show_links = false; //禁用连线 (本需求是不需要连线功能的)
+      gantt.config.drag_progress = false;//禁用工作进度拖拽 (必须通过界面弹窗的方式进行修改信息)
+      gantt.config.readonly = true //是否只读
+      gantt.config.scale_height = 90;//行高
+      gantt.config.resize_rows = true;//用户可以通过拖拽调整行高
+      gantt.config.autofit = true;//甘特图图表宽度自适应
+
+      gantt.i18n.setLocale('cn') //设置语言
+      // 设置周末高亮
+      gantt.templates.timeline_cell_class = (item, date) => {if (date.getDay() === 0 || date.getDay() === 6) {return 'weekend';}return''};
+      // 日期格式https://docs.dhtmlx.com/gantt/desktop__date_format.html
+      gantt.config.scales = [
+        // {unit: 'year', step: 1, format: '%Y年'},
+        {unit: 'month', step: 1, format: '%Y/%n'},
+        // {unit: 'week', step: 1, format: '第%W周'},
+        // {unit: 'day', step: 1, format: '周%D'},
+        {unit: 'day', step: 1, format: '%j 周%D'},
+      ];
+
+      // // 左侧显示列名
+      gantt.config.columns = [{
+          name: "name",
+          min_width: 200,
+          label: "任务",
+          tree: true,
+          align: "left",
+          resize: true,
+          template: function(obj) {
+            if(obj.type=='task'){
+              return `<div class='taskProgress'><i class="el-icon-s-flag fontColorC"></i> ${obj.name}</div>`;
+            }else{
+              return `<div style="font-weight: 600;"><i class="el-icon-bangzhu fontColorC"></i> ${obj.name}</div>`;
+            }
+
+          }
+        },
+        {
+          name: "userName",
+          label: "负责人",
+          resize: true,
+          align: "center",
+        },
+        {
+          name: "deptName",
+          label: "部门",
+          resize: true,
+          align: "center"
+        },
+        {
+          name: "taskProgress",
+          label: "完成度",
+          resize: true,
+          align: "center"
+        },
+        {
+          name: "dateStr",
+          label: "起止日期",
+
+          resize: true,
+          align: "center",
+          template: function (obj) {
+            return obj.dateStr;
+          }
+        },
+      ]
+      gantt.plugins({
+        drag_timeline: true, // 拖动图
+        marker: true, // 时间标记
+        tooltip: true, // 鼠标经过时信息
+      })
+
+
+      gantt.addMarker({
+          start_date: new Date(),
+          text: '今日',
+          css: "today",
+      });
+      // 点击任务列表
+      const onTaskDblClick=gantt.attachEvent("onTaskDblClick",this.openDetail)
+      //根据任务状态改变颜色
+      gantt.templates.task_text = function (start, end,item) {
+          let html="";
+          if(item.stateInfo){
+            html=`<div class="clamp task-item" style="background:${item.stateInfo.color}";><i class="${item.stateInfo.icon}"></i> ${item.text}</div>`;
+          }
+          return html
+      };
+
+      // 鼠标经过时信息
+      gantt.templates.tooltip_text = function(start,end,task){
+          let str=`
+              <div class="tooltip_text">
+                  <b>${task.type=='task'? '任务':'里程碑'}:${task.name}</b><br/>
+                  <b>时间:${task.dateStr}</b><br/>
+              </div>`
+           return str
+      };
+      gantt.init(this.$refs.gantt);
+      gantt.parse(this.tasks);
+
+      this.events.push(onTaskDblClick)
+    },
+    openDetail(id,e){
+      console.log(id);
+      if(id==='-1'){return false}
+      if(id.split('_').length>1){
+          if(!this.showDrawerTow){
+            let yuanId=id.split('_')[0]
+            let id_m=this.milestones.forEach(item=>{
+              if(yuanId==item.id){
+                this.detailData=item;
+                this.getProjectProcessList(()=>{
+                  this.showDrawerTow=true;
+                })
+              }
+            })
+          }
+      }else{
+        if(!this.isShowTaskDetail){
+          this.taskId=Number(id);
+          this.isShowTaskDetail=true;
+        }
+      }
+      return false;
+    },
+  },
+  created() {
+      if(this.$route.query.id){
+        this.projectId=Number(this.$route.query.id);
+        this.getMilestoneList();
+      }
+  },
+  mounted() {
+    this.initGantt();
+  },
+   beforeDestroy() {
+      this.events.forEach(ele => {
+        gantt.detachEvent(ele)
+      })
+    },
+};
+</script>
+
+<style scoped lang="scss">
+  ::v-deep .task-item{
+    padding: 0 10px;
+    border-radius: 25px;
+  }
+  ::v-deep .weekend{
+      background: #F3F6FB;
+  }
+  ::v-deep .over {
+      background-color: #e43c59;
+  }
+  ::v-deep .firstLevelTask {
+      border: none;
+   }
+  .tab-item{
+    padding: 6px 20px;
+    text-align: center;
+    background-color: #F7F8FA;
+    margin-right: 10px;
+    border-radius: 20px;
+    cursor: pointer;
+  }
+  .select{
+    width: 100px;
+    margin-right: 10px;
+  }
+  .select ::v-deep .el-input__inner {
+    border-radius: 25px;
+  }
+  .input ::v-deep .el-input__inner {
+    border: none;
+    border-bottom: 1px solid #f1f1f1;
+    border-radius: 0px;
+  }
+  ::v-deep .gantt_grid{
+    position: inherit !important;
+  }
+
+
+  ::v-deep .el-drawer__body{
+    overflow: hidden;
+  }
+  ::v-deep .el-drawer {
+    width: 700px !important;
+  }
+  .drawer-header {
+    height: 60px;
+    border-bottom: 1px solid #ebebeb;
+    line-height: 60px;
+    font-size: 18px;
+    font-weight: 500;
+    padding: 0 20px;
+  }
+  .drawer-main {
+    height: calc(100vh - 120px);
+    padding: 20px;
+    overflow-y: scroll;
+    overflow-x: hidden;
+    min-width: 450px;
+    font-size: 14px;
+  }
+  .drawer-main::-webkit-scrollbar-track {
+    -webkit-box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.3);
+    border-radius: 5px;
+    background-color: rgba(216, 216, 216, 0.8);
+  }
+  .drawer-main::-webkit-scrollbar {
+    width: 5px;
+    background-color: rgba(201, 201, 201, 0);
+  }
+  .drawer-main::-webkit-scrollbar-thumb {
+    border-radius: 5px;
+    -webkit-box-shadow: inset 0 0 5px rgb(153, 145, 145) (160, 154, 154);
+    background-color: rgb(168, 167, 167);
+  }
+  .drawer-footer {
+    background-color: #fff;
+    border-top: 1px solid #ebebeb;
+    padding: 10px 20px;
+    font-size: 14px;
+  }
+  .record-message {
+    font-size: 13px;
+    margin: 5px 60px;
+  }
+  .record-list {
+    position: relative;
+    padding: 8px;
+    border-radius: 5px;
+  }
+  .record-list:hover .blue {
+    display: block !important;
+  }
+  .record-title {
+    padding: 16px 0;
+    font-size: 16px;
+  }
+  .record-date {
+    position: relative;
+    font-size: 13px;
+  }
+  .record-content {
+    margin-left: 40px;
+    border-radius: 5px;
+  }
+  .record-name {
+    margin-right: 10px;
+    margin-left: 10px;
+    color: #141c28;
+    font-weight: 600;
+  }
+</style>

+ 598 - 0
src/okr/components/project/KanMilestone.vue

@@ -0,0 +1,598 @@
+<template>
+  <div>
+    <div class="flex-box-ce flex-d-center" style="margin: 20px 0;">
+      <div class="flex-box-ce">
+        <el-select class="select2" size="small" v-model="taskForm.composite_state" placeholder="状态">
+          <el-option :key="0" label="全部" :value="0"></el-option>
+          <el-option v-for="item in taskStatus" :key="item.value" :label="item.name" :value="item.value"></el-option>
+        </el-select>
+        <el-select class="select2" size="small" v-model="taskForm.sort" placeholder="排序">
+          <el-option :key="0" label="默认排序" :value="0"></el-option>
+          <el-option :key="1" label="创建时间" :value="1"></el-option>
+          <el-option :key="2" label="开始时间" :value="2"></el-option>
+          <el-option :key="3" label="结束时间" :value="3"></el-option>
+        </el-select>
+        <el-select class="select2" collapse-tags filterable size="small" v-model="taskForm.owner_id" clearable placeholder="人员">
+          <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"></el-option>
+        </el-select>
+        <el-input prefix-icon="el-icon-search" class="input" style="width: 200px;" size="small" v-model="taskForm.keyword" clearable placeholder="请输入任务关键字" />
+      </div>
+    </div>
+    <div style="overflow-x: auto;" class="scroll-bar">
+        <div class="flex-box">
+            <div v-for="(item,index) in milestoneList" :key="index" class="task-box" :class="{'background-blue':selectM_id===item.id}" :data-id="item.id" :draggable="false" @dragend.stop="dragend($event,item,index)"  @dragover.stop="dragover($event,item,index)"  @dragenter.stop="dragenter($event,item,index)" @drop.stop="drop($event,item,index)">
+                  <div class="flex-box-ce flex-d-center" style="margin-bottom:10px">
+                    <div style="font-size:16px;font-weight:600;width:220px" class="clamp">{{item.name}}</div>
+                    <div style="margin:0 10px">{{returnLet(item.plans)}}/{{item.plans.length}}</div>
+                    <el-dropdown @command="handleCommand($event,item)" trigger="click" class="hoverBlue">
+                      <i class="el-icon-more" v-if="isReturnPJ"></i>
+                      <el-dropdown-menu slot="dropdown">
+                        <el-dropdown-item :command="1">编辑</el-dropdown-item>
+                        <el-dropdown-item :command="2">删除</el-dropdown-item>
+                      </el-dropdown-menu>
+                    </el-dropdown>
+                  </div>
+                  <div class="add-task blue" @click="openShowTaskAdd(item)" v-if="isPstake"><i class="el-icon-plus"></i><span>添加任务</span></div>
+                  <div class="task-items scroll-bar" :ref="item.id">
+                    <transition-group name="drag" tag="div">
+                      <div v-for="(item2,index2) in  item.plans" :key="item2.id" class="task-item" v-if="isShowTask(item2)" draggable @dragstart.stop="dragstart($event,item2,index2,item,index)">
+                       <div class="flex-box-ce flex-d-center" style="font-size: 16px;padding-bottom: 10px;">
+                          <div class="blue"><i :class="taskStatusFun(item2.composite_state).icon" style="margin-right: 6px;"></i>{{taskStatusFun(item2.composite_state).name}}</div>
+                          <userImage :id="item2.owner_id" width="30px" height="30px" fontSize="12"></userImage>
+                        </div>
+                        <div class="clamp2 task-name" @click="openShowTask(item2.id)">{{item2.name}}</div>
+                        <div class="flex-box-ce flex-d-center fontColorC" style="padding-top: 10px;">
+                          <div><i class="el-icon-date"></i> <span>{{item2.end_date}}截止</span></div>
+                          <div v-if="item2.statistics"><i class="el-icon-folder-checked"></i> <span>{{item2.statistics.plan_finish}}/{{item2.statistics.plan_total}}</span></div>
+                        </div>
+                      </div>
+                    </transition-group>
+                  </div>
+            </div>
+            <div style="padding:10px;border-radius: 5px;background-color: #F0F4FA;text-align: center;width: 300px;height: 40px;color:#89919F;" class="cursor blue-text" @click="showAdd(1)"><i class="el-icon-plus"></i> 创建新看板</div>
+        </div>
+    </div>
+
+    <!-- 添加里程碑 -->
+    <el-dialog title="里程碑" :visible.sync="isShowAdd" :append-to-body="true" width="500px">
+      <el-form :model="formData" :rules="rules" ref="formData" label-width="120px">
+        <el-form-item label="看板名称" prop="name">
+          <el-input maxlength="30" v-model="formData.name" placeholder="请输入看板名称" clearable></el-input>
+        </el-form-item>
+      </el-form>
+      <div class="flex-box-end" style="margin-top: 30px;">
+        <el-button @click="isShowAdd=false">取消</el-button>
+        <el-button type="primary" @click="submitPoint('formData')">确定</el-button>
+      </div>
+    </el-dialog>
+
+
+    <TaskDetail  :visible.sync="isShowTaskDetail" :taskId="taskId" @confirm="closeDetail"></TaskDetail>
+
+    <!--添加任务 -->
+    <AddTask :visible.sync="isShowAddTask" @confirm="ActiveAddTask" :id="target_id" :target_type="target_type" :initDate="initDate"></AddTask>
+
+  </div>
+</template>
+
+<script>
+  import EmployeeSelector from '@/components/EmployeeSelector';
+  import AddTask from '@/okr/components/public/AddTask'; //添加任务
+  import TaskDetail from '@/okr/components/public/TaskDetail'; //任务详情
+  import { getYearArr,taskStatus} from '@/okr/utils/auth';
+  import {_debounce} from '@/utils/auth';
+  export default {
+    name: 'Milestone',
+    components:{EmployeeSelector,AddTask,TaskDetail},
+    props: {
+      isReturnPJ: { //是否是项目管理者
+        type: Boolean,
+        default:true
+      },
+      isPstake: { //是否是项目相关成员
+        type: Boolean,
+        default:true
+      },
+    },
+    data() {
+      return {
+        userInfo:this.$userInfo(),
+        owner_userInfo:this.$userInfo(),
+        taskForm:{composite_state:0,sort:3,owner_id:'',keyword:''},
+        taskStatus:taskStatus(),
+        taskStatusFun:taskStatus,
+        employeeMap:this.$getEmployeeMap(),
+        taskList:[],
+        milestoneList:[],
+        plans:[],
+        formData:{
+          name:'',
+        },
+        rules: {
+          name: [
+            { required: true, message: '请输入看板名称', trigger: 'blur' },
+            { min: 2, max: 30, message: '长度在 2 到 30 个字符', trigger: 'blur' }
+          ],
+        },
+        isShowAdd:false,
+        employee_selected: { dept: [], employee: [] },
+        show_employee_selector: false,
+        loading:false,
+        dragIndex:0,
+        isTd:false,//是否移动位置
+        border_bottom:0,
+        directionIndex:0,
+        selectItem:{},
+        showDrawerTow:false,
+
+        projectProcessList:[],//进展
+        projectId:0,
+
+        showAddIndex:1,
+        target_type:4,
+        target_id:0,
+        isShowAddTask:false,
+        initDate:[],
+        isShowTaskDetail:false,
+        taskId:0,
+        p_index:0,//拖动的父元素下标
+        selectM_data:{},//目标位置的里程碑
+        selectM_id:'-1',
+
+        isShowUpdataTime:false,
+        time:[],
+        detailData:{},
+
+        isShowMp:false,
+        processFormData:{
+          content:'',
+          processTime:[this.$moment().format('YYYY-MM-DD'),this.$moment().format('YYYY-MM-DD')],
+          target_type:5,
+        },
+        selectItem_p:{},
+
+      };
+    },
+    computed: {
+      returnLet(){
+        return function(arr) {
+         let sum=0;
+         arr.forEach(item=>{
+           if(item.composite_state==6){
+             sum++;
+           }
+         })
+         return sum
+        }
+      }
+    },
+    watch:{
+      'taskForm.sort'(val){
+            this.getMilestoneList()
+      },
+      'taskForm.composite_state'(val){
+            this.getMilestoneList()
+      },
+      'taskForm.keyword': {
+        deep: true,
+        handler: _debounce(function(val) {
+          this.getMilestoneList();
+        })
+      },
+    },
+    methods: {
+      isShowTask(item){
+        let is=true;
+        if(this.taskForm.owner_id&&this.taskForm.owner_id!=item.owner_id){
+          is=false
+        }
+        return is
+      },
+      openShowTask(id){
+        this.taskId=id;
+        this.isShowTaskDetail=true;
+      },
+      closeDetail(){
+        this.getMilestoneList();
+      },
+      openShowTaskAdd(item){
+        if(item.id){
+          this.tab_id=item.id;
+        }else{
+          this.tab_id=0;
+        }
+        this.target_type=4;
+        this.target_id=this.projectId;
+        this.initDate=[];
+        this.isShowAddTask=true;
+      },
+      ActiveAddTask(id){
+        if(this.tab_id){
+         this.$axiosUser('post','/api/pro/okr/tag/bind',{plan_id:id,tag_id:this.tab_id}).then(res => {
+           this.getMilestoneList()
+         })
+        }else{
+          this.getMilestoneList();
+        }
+      },
+      handleCommand(index,item){
+        if(index==1){
+          this.showAdd(2,item)
+        }else{
+          this.$confirm('确认删除该看板吗?', '提示', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'warning'
+          }).then(() => {
+            this.$axiosUser('post','/api/pro/okr/tag/d',{tag_id:item.id}).then(res => {
+              this.getMilestoneList()
+            })
+          }).catch(() => {});
+        }
+      },
+      showAdd(index,item){
+        this.showAddIndex=index;
+        if(index==1){
+          this.formData={
+            name:'',
+          };
+        }else{
+          this.formData={
+            name:item.name,
+            tag_id:item.id
+          };
+        }
+        this.isShowAdd=true
+      },
+
+      getMilestoneList(isUpdata){
+        this.loading=true;
+        let data={
+          target_type:4,
+          target_id:this.projectId,
+          composite_state:this.taskForm.composite_state,
+          keyword:this.taskForm.keyword,
+        }
+        if(this.taskForm.sort){
+          this.taskForm.sort==1? data.sort_c=1:this.taskForm.sort==2? data.sort_s=1:data.sort_e=1
+        }
+        this.$axiosUser('get', '/api/pro/okr/tag/list',data).then(res => {
+            let milestones=res.data.data.tags;
+            let plans=res.data.data.plans;
+            milestones.forEach(item=>{
+              item.day=this.$moment(item.end_date).diff(this.$moment().format('YYYY-MM-DD'), 'day')
+              if(item.plans.length>0&&item.id){
+                item.plans.forEach((a)=>{
+                    a.pName=item.name
+                    a.pId=item.id
+                })
+              }
+            })
+            if(plans.length>0){
+              milestones.unshift({id:0,plans:plans,name:'默认分组'})
+            }
+            milestones.forEach((item)=>{
+                if(isUpdata&&item.id==this.detailData.id){
+                  this.detailData=item;
+                }
+            })
+            this.$nextTick(()=>{
+              this.milestoneList=JSON.parse(JSON.stringify(milestones));
+              this.plans=plans;
+            })
+        }).finally(()=>{
+          this.loading=false;
+        })
+      },
+      handleClose(done) {
+        this.showDrawerTow = false;
+        if (done) {
+          done();
+        }
+      },
+
+      submitPoint(formName){
+        this.$refs[formName].validate((valid) => {
+          if (valid) {
+            let data={
+              target_type:4,
+              name:this.formData.name,
+              target_id:this.projectId,
+              tag_id:this.formData.tag_id,
+            }
+            if(this.showAddIndex==1){
+              this.$axiosUser('post', '/api/pro/okr/tag/create',data).then(res => {
+                  this.$message.success('已创建');
+                  this.isShowAdd=false
+                  this.getMilestoneList();
+              })
+            }else{
+              var http1 = this.$axiosUser('POST','api/pro/okr/tag/name', data); // 风险
+              Promise.all([http1]).then(res => {
+                  this.$message.success('已修改');
+                  this.isShowAdd=false
+                  this.getMilestoneList(true);
+              })
+            }
+          } else {
+            return false;
+          }
+        });
+      },
+
+      // 拖拽前记录下标
+      dragstart(e,item,index,p_item,p_index) {
+        e.stopPropagation()
+        this.dragIndex = index;
+        this.p_index=p_index;
+        this.selectItem= item;
+        this.selectItem_p= p_item;
+        this.isTd=true;
+        setTimeout(() => {
+        	e.target.classList.add('moveing')
+        }, 0)
+      },
+      //进入目标元素触发,相当于mouseover
+      dragenter(e,item,index) {
+          let moving =this.selectItem; //获取之前记录的元素
+          let target_id=e.target.dataset.id;//里程碑ID
+          if(item&&(target_id||target_id===0)&&target_id!=moving.pId&&this.isTd){
+            setTimeout(() => {
+              this.selectM_id=item.id
+            }, 0)
+          }
+      },
+      // 鼠标松开 防抖触发排序
+      drop(e,item,index) {
+        e.preventDefault(); //阻止默认行为
+        let moving = this.selectItem; //获取之前记录的元素
+        if(item.plans&&item.id!=moving.pId&&this.isTd){
+          moving.pId=item.id;
+          this.milestoneList[this.p_index].plans.splice(this.dragIndex,1); //删除列表的记录元素
+          this.milestoneList[index].plans.unshift(moving);//插入元素
+          this.dragIndex = index;
+          this.$nextTick(()=>{
+            this.$refs[item.id][0].scrollTop=0; //距离左  横滚动条
+          })
+          let data;
+          if(item.id){
+             data={
+              plan_id:moving.id,//	是	integer	当前的计划id
+              tag_id:item.id,//	是	要移除的标签ID
+            }
+          }else{
+            data={
+              plan_id:moving.id,//	是	integer	当前的计划id
+              tag_id:this.selectItem_p.id,//	是	要移除的标签ID
+              d:1,
+            }
+          }
+          this.upMain(data);
+        }
+      },
+      //用户完成拖动后触发
+      dragend(e,item,index){
+        e.target.classList.remove('moveing')
+        this.selectM_id='-1';
+        if(!this.selectM_data.id){
+          this.selectItem={};
+        }
+      	this.isTd=false;
+      },
+      //进入目标、离开目标之间,连续触发
+      dragover(e,item,index) {
+        e.preventDefault();
+        e.dataTransfer.dropEffect = 'move'
+      },
+      upMain(data,is){
+        this.$axiosUser('post','/api/pro/okr/tag/bind',data).then(res => {
+          if(is){
+            this.getMilestoneList();
+          }
+        });
+      },
+      format(){
+        return function(num){
+          if(num){
+             return `${num}%`;
+          }else{
+             return `0%`;
+          }
+        }
+      },
+    },
+    created() {
+        if(this.$route.query.id){
+          this.projectId=Number(this.$route.query.id);
+          this.getMilestoneList();
+        }
+    },
+  };
+</script>
+
+<style scoped="scoped" lang="scss">
+  .blue-text:hover{
+    color: #409EFF !important;
+  }
+  .background-blue{
+     border: 1px solid #409EFF !important;
+     box-shadow: 0 0 3px #409EFF;
+  }
+  .project-name{
+      font-size: 18px;
+      font-weight: 550;
+      color: rgb(20, 28, 40);
+      max-width: 550px;
+  }
+  .task-name{
+    cursor: pointer;
+    height: 38px;
+  }
+  .moveing {
+  	opacity: 0;
+  }
+  .drag-move {
+  	transition: transform .3s;
+  }
+  .border_top::after{
+    content: "";
+    position: absolute;
+    height: 8px;
+    left: 0;
+    right: 0;
+    background-color: #409EFF;
+    border-radius: 5px;
+    top: -8px;
+    z-index: 2;
+  }
+  .border_bottom::after{
+  	content: "";
+  	position: absolute;
+  	height: 8px;
+  	left: 0;
+  	right: 0;
+  	background-color: #409EFF;
+  	border-radius: 5px;
+  	bottom: -8px;
+  	z-index: 2;
+  }
+
+  .select2 {
+    width: 100px;
+    margin-right: 10px;
+  }
+
+  .select2 ::v-deep .el-input__inner {
+    border-radius: 25px;
+  }
+
+  .input ::v-deep .el-input__inner {
+    border: none;
+    border-bottom: 1px solid #f1f1f1;
+    border-radius: 0px;
+  }
+
+  .add-task {
+    text-align: center;
+    color: #409EFF;
+    background-color: #fff;
+    padding: 8px;
+    border-radius: 5px;
+    cursor: pointer;
+  }
+  .milestone-item{
+    background-color: #F0F4FA;
+    padding:10px;
+    border-radius: 5px;
+    width: 300px;
+    margin-right: 14px;
+    box-sizing: border-box;
+    height: 62px;
+    position: relative;
+    flex-shrink:0;
+    cursor: pointer;
+  }
+  ::v-deep .el-progress__text{
+    font-size: 12px !important;
+  }
+  .add-milestone:hover,.task-name:hover{
+    color: #409EFF;
+  }
+  .task-box{
+    background-color: #F0F4FA;
+    padding:10px;
+    border: 1px solid #F0F4FA;
+    border-radius: 5px;
+    width: 300px;
+    margin-right: 14px;
+    position: relative;
+    min-height: calc(100vh - 325px);
+    box-sizing: border-box;
+    flex-shrink:0;
+  }
+  .task-item{
+    background-color: #fff;
+    padding:10px 16px;
+    border-radius: 5px;
+    position: relative;
+    margin-bottom: 10px;
+    cursor: move;
+  }
+  .task-items{
+     height: calc(100vh - 410px);
+     overflow-y: auto;
+     padding-top: 10px;
+  }
+  ::v-deep .el-drawer__body{
+    overflow: hidden;
+  }
+  ::v-deep .el-drawer {
+    width: 700px !important;
+  }
+  .drawer-header {
+    height: 60px;
+    border-bottom: 1px solid #ebebeb;
+    line-height: 60px;
+    font-size: 18px;
+    font-weight: 500;
+    padding: 0 20px;
+  }
+  .drawer-main {
+    height: calc(100vh - 120px);
+    padding: 20px;
+    overflow-y: scroll;
+    overflow-x: hidden;
+    min-width: 450px;
+    font-size: 14px;
+  }
+  .drawer-main::-webkit-scrollbar-track {
+    -webkit-box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.3);
+    border-radius: 5px;
+    background-color: rgba(216, 216, 216, 0.8);
+  }
+  .drawer-main::-webkit-scrollbar {
+    width: 5px;
+    background-color: rgba(201, 201, 201, 0);
+  }
+  .drawer-main::-webkit-scrollbar-thumb {
+    border-radius: 5px;
+    -webkit-box-shadow: inset 0 0 5px rgb(153, 145, 145) (160, 154, 154);
+    background-color: rgb(168, 167, 167);
+  }
+  .drawer-footer {
+    background-color: #fff;
+    border-top: 1px solid #ebebeb;
+    padding: 10px 20px;
+    font-size: 14px;
+  }
+  .record-message {
+    font-size: 13px;
+    margin: 5px 60px;
+  }
+  .record-list {
+    position: relative;
+    padding: 8px;
+    border-radius: 5px;
+  }
+  .record-list:hover .blue {
+    display: block !important;
+  }
+  .record-title {
+    padding: 16px 0;
+    font-size: 16px;
+  }
+  .record-date {
+    position: relative;
+    font-size: 13px;
+  }
+  .record-content {
+    margin-left: 40px;
+    border-radius: 5px;
+  }
+  .record-name {
+    margin-right: 10px;
+    margin-left: 10px;
+    color: #141c28;
+    font-weight: 600;
+  }
+</style>

+ 908 - 0
src/okr/components/project/Milestone.vue

@@ -0,0 +1,908 @@
+<template>
+  <div>
+    <div class="flex-box-ce flex-d-center" style="margin: 20px 0;">
+      <div class="flex-box-ce">
+        <el-select class="select2" size="small" v-model="taskForm.composite_states" placeholder="状态">
+          <el-option :key="0" label="全部" :value="0"></el-option>
+          <el-option v-for="item in taskStatus" :key="item.value" :label="item.name" :value="item.value"></el-option>
+        </el-select>
+        <el-select class="select2" size="small" v-model="taskForm.sort" placeholder="排序">
+          <el-option :key="0" label="默认排序" :value="0"></el-option>
+          <el-option :key="1" label="创建时间" :value="1"></el-option>
+          <el-option :key="2" label="开始时间" :value="2"></el-option>
+          <el-option :key="3" label="结束时间" :value="3"></el-option>
+        </el-select>
+        <el-select class="select2" collapse-tags filterable size="small" v-model="taskForm.owner_id" clearable placeholder="人员">
+          <el-option v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"></el-option>
+        </el-select>
+        <el-input prefix-icon="el-icon-search" class="input" style="width: 200px;" size="small" v-model="taskForm.keyword" clearable placeholder="请输入任务关键字" />
+      </div>
+    </div>
+    <div style="overflow-x: auto;" class="scroll-bar">
+        <div class="flex-box-ce" style="margin-bottom: 14px;">
+          <template v-for="(item,index) in milestoneList">
+            <div :key="index" class="flex-box-ce milestone-item" v-if="item.id" @click="openDetail(item)">
+                <template v-if="item.id">
+                  <el-progress type="circle" :color="item.statusColor" :percentage="Number(item.process)" :width="42"  :stroke-width="5"></el-progress>
+                  <div style="padding-left: 6px;width: 244px;">
+                    <div class="clamp">{{item.name}}</div>
+                    <div style="font-size: 12px;margin-top: 4px;" class="fontColorC"><i class="el-icon-date"></i><span> {{item.end_date}}截止</span></div>
+                  </div>
+                  <i class="el-icon-arrow-right" style="position: absolute;right: -13px;"></i>
+                </template>
+            </div>
+            <div class="milestone-item" style="background-color: #F2F7FF;font-size: 16px;line-height: 42px;cursor: default;" v-else>
+              <span>默认分组</span>
+               <i class="el-icon-arrow-right" style="position: absolute;right: -12px;top: 23px;font-size: 14px;"></i>
+            </div>
+          </template>
+
+          <div class="milestone-item" v-if="isReturnPJ">
+            <div class="cursor add-milestone" style="margin: 0 auto;padding-top: 10px;text-align: center;" @click="showAdd(1)">
+              <i class="el-icon-plus"></i>
+              <span> 创建里程碑</span>
+            </div>
+          </div>
+        </div>
+        <div class="flex-box-ce">
+            <div v-for="(item,index) in milestoneList" :key="index" class="task-box" :class="{'background-blue':selectM_id===item.id}" :data-id="item.id" :draggable="false" @dragend.stop="dragend($event,item,index)"  @dragover.stop="dragover($event,item,index)"  @dragenter.stop="dragenter($event,item,index)" @drop.stop="drop($event,item,index)">
+                <div class="add-task blue" @click="openShowTaskAdd(item)" v-if="isPstake"><i class="el-icon-plus"></i><span>添加任务</span></div>
+                <div class="task-items scroll-bar" :ref="item.id">
+                  <transition-group name="drag" tag="div">
+                    <div v-for="(item2,index2) in item.plans" :key="item2.id" class="task-item" v-if="isShowTask(item2)" draggable @dragstart.stop="dragstart($event,item2,index2,index)">
+                     <div class="flex-box-ce flex-d-center" style="font-size: 16px;padding-bottom: 10px;">
+                        <div class="blue"><i :class="taskStatusFun(item2.composite_state).icon" style="margin-right: 6px;"></i>{{taskStatusFun(item2.composite_state).name}}</div>
+                        <userImage :id="item2.owner_id" width="30px" height="30px" fontSize="12"></userImage>
+                      </div>
+                      <div class="clamp2 task-name" @click="openShowTask(item2.id)">{{item2.name}}</div>
+                      <div class="flex-box-ce flex-d-center fontColorC" style="padding-top: 10px;">
+                        <div><i class="el-icon-date"></i> <span>{{item2.end_date}}截止</span></div>
+                        <div v-if="item2.statistics"><i class="el-icon-folder-checked"></i> <span>{{item2.statistics.plan_finish}}/{{item2.statistics.plan_total}}</span></div>
+                      </div>
+                    </div>
+                  </transition-group>
+                </div>
+            </div>
+        </div>
+    </div>
+    <el-drawer :visible.sync="showDrawerTow" :append-to-body="true" :before-close="handleClose" :with-header="false">
+      <header class="drawer-header flex-box-ce">
+        <div class="flex-1">里程碑</div>
+        <el-popconfirm title="此操作不可恢复,您确定删除吗?" @confirm="deleteM" v-if="isReturnPJ">
+            <div slot="reference" class="red cursor" style="margin-right: 20px;font-size: 16px;">删除 <i class="el-icon-delete"></i></div>
+        </el-popconfirm>
+        <div class="blue cursor" style="margin-right: 20px;font-size: 16px;" @click="showAdd(2)" v-if="isReturnPJ||detailData.owner_id==userInfo.id">编辑 <i class="el-icon-edit"></i></div>
+        <i @click="handleClose()" class="el-icon-close"></i>
+      </header>
+      <div class="drawer-main">
+        <div class="flex-box-ce">
+          <el-progress type="circle" :color="detailData.statusColor" :percentage="Number(detailData.process)" :width="66" :stroke-width="8"></el-progress>
+          <div style="padding-left: 12px;">
+            <div class="project-name clamp">{{detailData.name}}</div>
+            <div class="flex-box-ce" style="margin-top: 10px;">
+                <span class="flex-box-ce fontColorC">
+                  <svg-icon icon-class="#icon-biaoqian_wode" class="svgIcon" style="width: 1rem;height: 1rem;"></svg-icon>
+                  <span>&nbsp;{{$getEmployeeMapItem(detailData.owner_id).name}}</span>
+                </span>
+                <span class="flex-box-ce fontColorC" style="margin-left: 30px;">
+                  <i class="el-icon-date"></i>
+                  <span>{{detailData.end_date}}
+                    <span v-if="detailData.day>0">(剩余<span class="green">{{detailData.day}}</span>天)</span>
+                    <span v-if="detailData.day==0">(剩余<span class="green">1</span>天)</span>
+                    <span v-if="detailData.day<0">(逾期<span class="red">{{Math.abs(detailData.day)}}</span>天)</span>
+                  </span>
+                </span>
+            </div>
+          </div>
+        </div>
+        <div class="fontColorC" style="margin: 24px 0;padding: 10px;border-radius: 5px;background-color: #f1f1f1;">
+          <span v-if="detailData.desc">{{detailData.desc}}</span>
+          <span v-else>暂无描述</span>
+        </div>
+        <div class="flex-box flex-d-center">
+          <div style="color: #141c28;font-weight: 600;">进展说明</div>
+          <div class="blue" @click="openMp(1)" v-if="isReturnPJ||detailData.owner_id==userInfo.id"><i class="el-icon-refresh"></i>更新进展</div>
+        </div>
+        <div class="record-box" style="margin-top: 20px;">
+          <div v-for="(item, index) in projectProcessList" :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="userInfo.id==item.userInfo.id" @click="openMp(2,item)">编辑</span>
+            </div>
+            <div class="record-content">
+              <pre class="pre fontColorA" style="font-size: 15px;" v-if="item.content">{{ item.content }}</pre>
+              <div v-else class="fontColorD">无内容</div>
+            </div>
+            <div class="flex-box-ce flex-d-center fontColorC" style="padding-left: 40px;font-size: 13px;margin-top: 14px;">
+              <div>进度为{{item.process}}%</div>
+              <div>进展日期:{{$moment(item.start_time).format('YYYY/MM/DD')}}~{{$moment(item.end_time).format('YYYY/MM/DD')}}</div>
+            </div>
+          </div>
+          <div class="dotted-line" v-if="projectProcessList.length==0"><div>事务有新的进展?马上记录吧</div></div>
+        </div>
+
+
+      </div>
+    </el-drawer>
+
+    <!-- 添加里程碑 -->
+    <el-dialog :title="showAddIndex==2?'编辑里程碑':'创建里程碑'" :visible.sync="isShowAdd" :append-to-body="true" width="600px">
+       <el-form :model="formData" :rules="rules" ref="formData" label-width="120px">
+          <el-form-item label="里程碑名称" prop="name">
+            <el-input maxlength="30" v-model="formData.name" placeholder="请输入里程碑名称" clearable></el-input>
+          </el-form-item>
+          <el-form-item label="里程碑描述">
+            <el-input maxlength="200" placeholder="请输入里程碑描述"  show-word-limit v-model="formData.desc" type="textarea" clearable></el-input>
+          </el-form-item>
+          <el-form-item label="负责人" class="required">
+            <div class="flex-box-ce cursor" @click="showSelectorUser()">
+               <userImage :id="owner_userInfo.id" fontSize="14" width="36px" height="36px" :user_name="owner_userInfo.name"></userImage>
+               <span style="margin-left: 10px;font-size: 14px;color: #3F4755;">{{ owner_userInfo.name }}</span>
+           </div>
+          </el-form-item>
+          <el-form-item label="截至" style="margin-right: 20px;">
+              <el-date-picker v-model="formData.end_date" :clearable="false" type="date"  value-format="yyyy-MM-dd"></el-date-picker>
+          </el-form-item>
+      </el-form>
+      <div class="flex-box-end" style="margin-top: 30px;">
+        <el-button @click="isShowAdd=false">取消</el-button>
+        <el-button type="primary" @click="submitPoint('formData')">确定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 选择负责人 -->
+    <EmployeeSelector :is_filtration_creator="false" title="选择人员" :isChecKedAll="false" isRequired :multi="false" :selected="employee_selected" :visible.sync="show_employee_selector" @confirm="employee_confirm"/>
+
+    <!--添加任务 -->
+    <AddTask :visible.sync="isShowAddTask" @confirm="ActiveAddTask" :id="target_id" :target_type="target_type" :initDate="initDate"></AddTask>
+
+    <TaskDetail  :visible.sync="isShowTaskDetail" :taskId="taskId" @confirm="closeDetail"></TaskDetail>
+
+    <!-- 添加里程碑 -->
+    <el-dialog title="提示" :visible.sync="isShowUpdataTime" :append-to-body="true" width="500px">
+      <div>
+        <div style="margin-bottom: 20px;"><i class="el-icon-warning yellow"></i> 当前任务时间超出了里程碑起止时间,请调整</div>
+        <el-form ref="form" label-width="80px" class="form">
+            <el-form-item label="起止时间">
+                <el-date-picker :picker-options="instantPickerOptions"  style="width: 370px;" v-model="time" :clearable="false" type="datetimerange" range-separator="至" value-format="yyyy-MM-dd HH:mm:ss" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['8:00:00', '18:00:00']"></el-date-picker>
+            </el-form-item>
+        </el-form>
+      </div>
+      <div class="flex-box-end" style="margin-top: 30px;">
+        <el-button @click="isShowUpdataTime=false">取消</el-button>
+        <el-button type="primary" @click="confirmTime">确定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 里程碑进度 -->
+    <el-dialog title="进度" :visible.sync="isShowMp" :append-to-body="true" width="500px">
+      <div>
+        <el-form label-width="80px" class="form" :model="processFormData" ref="form">
+           <el-form-item label="进展内容" prop="content" :rules="[{ required: true, message: '请输入进展'}]">
+              <el-input type="textarea" v-model="processFormData.content" rows="4" placeholder="请输入进展" maxlength="500" show-word-limit></el-input>
+           </el-form-item>
+           <el-form-item label="起止时间">
+              <el-date-picker :clearable="false" size="small" v-model="processFormData.processTime" type="daterange" value-format="yyyy-MM-dd" range-separator="~" start-placeholder="开始日期"end-placeholder="结束日期"></el-date-picker>
+           </el-form-item>
+        </el-form>
+      </div>
+      <div class="flex-box-end" style="margin-top: 30px;">
+        <el-button @click="isShowMp=false">取消</el-button>
+        <el-button type="primary" @click="confirmProcessm('form')">确定</el-button>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+  import EmployeeSelector from '@/components/EmployeeSelector';
+  import AddTask from '@/okr/components/public/AddTask'; //添加任务
+  import TaskDetail from '@/okr/components/public/TaskDetail'; //任务详情
+  import { getYearArr,taskStatus} from '@/okr/utils/auth';
+  import {_debounce} from '@/utils/auth';
+  export default {
+    name: 'Milestone',
+    components:{EmployeeSelector,AddTask,TaskDetail},
+    props: {
+      isReturnPJ: { //是否是项目管理者
+        type: Boolean,
+        default:true
+      },
+      isPstake: { //是否是项目相关成员
+        type: Boolean,
+        default:true
+      },
+    },
+    data() {
+      return {
+        instantPickerOptions: {
+          shortcuts: [
+            {
+              text: "今天",
+              onClick(picker) {
+                picker.$emit("pick", [`${moment().format('YYYY-MM-DD')} 08:00:00`,`${moment().format('YYYY-MM-DD')} 18:00:00`]);
+              }
+            },
+            {
+              text: "明天",
+              onClick(picker) {
+                picker.$emit("pick", [`${moment().add(1, "days").format('YYYY-MM-DD')} 08:00:00`,`${moment().add(1, "days").format('YYYY-MM-DD')} 18:00:00`]);
+              }
+            },
+            {
+              text: "本周",
+              onClick(picker) {
+                let start_date=moment().week(moment().week()).startOf('isoweek').format('YYYY-MM-DD 08:00:00')
+                let end_date=moment().week(moment().week()).endOf('isoweek').format('YYYY-MM-DD 18:00:00')
+                picker.$emit("pick", [start_date, end_date]);
+              }
+            },
+            {
+              text: "下周",
+              onClick(picker) {
+                let start_date=moment().week(moment().week() + 1).startOf('isoweek').format('YYYY-MM-DD 08:00:00')
+                let end_date=moment().week(moment().week() + 1).endOf('isoweek').format('YYYY-MM-DD 18:00:00')
+                picker.$emit("pick", [start_date, end_date]);
+              }
+            },
+            {
+              text: "本月",
+              onClick(picker) {
+                let start_date=moment().startOf('month').format('YYYY-MM-DD 08:00:00')
+                let end_date=moment().endOf('month').format('YYYY-MM-DD 18:00:00')
+                picker.$emit("pick", [start_date, end_date]);
+              }
+            },
+            {
+              text: "下个月",
+              onClick(picker) {
+                let start_date=moment().add(1, "month").startOf('month').format('YYYY-MM-DD 08:00:00')
+                let end_date=moment().add(1, "month").endOf('month').format('YYYY-MM-DD 18:00:00')
+                picker.$emit("pick", [start_date, end_date]);
+              }
+            }
+          ]
+        },
+        userInfo:this.$userInfo(),
+        owner_userInfo:this.$userInfo(),
+        taskForm:{composite_states:0,sort:3,owner_id:'',keyword:''},
+        taskStatus:taskStatus(),
+        taskStatusFun:taskStatus,
+        employeeMap:this.$getEmployeeMap(),
+        taskList:[],
+        milestoneList:[],
+        plans:[],
+        formData:{
+          name:'',
+          desc:'',
+          owner_id:this.$userInfo().id,
+          end_date:this.$moment().format('YYYY-MM-DD'),
+        },
+        rules: {
+          name: [
+            { required: true, message: '请输入里程碑名称', trigger: 'blur' },
+            { min: 2, max: 30, message: '长度在 2 到 30 个字符', trigger: 'blur' }
+          ],
+        },
+        isShowAdd:false,
+        employee_selected: { dept: [], employee: [] },
+        show_employee_selector: false,
+        loading:false,
+        dragIndex:0,
+        isTd:false,//是否移动位置
+        border_bottom:0,
+        directionIndex:0,
+        selectItem:{},
+        showDrawerTow:false,
+
+        projectProcessList:[],//进展
+        projectId:0,
+
+        showAddIndex:1,
+        target_type:4,
+        target_id:0,
+        isShowAddTask:false,
+        initDate:[],
+        isShowTaskDetail:false,
+        taskId:0,
+        p_index:0,//拖动的父元素下标
+        selectM_data:{},//目标位置的里程碑
+        selectM_id:'-1',
+
+        isShowUpdataTime:false,
+        time:[],
+        detailData:{},
+
+        isShowMp:false,
+        processFormData:{
+          content:'',
+          processTime:[this.$moment().format('YYYY-MM-DD'),this.$moment().format('YYYY-MM-DD')],
+          target_type:5,
+        }
+
+      };
+    },
+    watch:{
+      'taskForm.sort'(val){
+        this.milestoneList.forEach((item)=>{
+            let plans=[];
+            if(item.plans.length>0){
+              item.plans.sort((a,b)=>{
+                  if(this.taskForm.sort==0){
+                    return a.id-b.id
+                  }
+                  if(this.taskForm.sort==1){
+                    return a.create_time-b.create_time
+                  }
+                  if(this.taskForm.sort==2){
+                    return a.start_time-b.start_time
+                  }
+                  if(this.taskForm.sort==3){
+                    return a.end_time-b.end_time
+                  }
+              })
+            }
+        })
+      },
+    },
+    methods: {
+      deleteM(){
+        this.$axiosUser('post','/api/pro/okr/feedback/delete',{fb_id:item.id}).then(res => {
+          this.getLog()
+        })
+      },
+      confirmProcessm(formName) {
+        this.$refs[formName].validate((valid) => {
+          if (valid) {
+              let data={
+                target_type:5,
+                target_id:this.detailData.id,
+                process:this.detailData.process,
+                content:this.processFormData.content,
+                start_date:this.processFormData.processTime[0],
+                end_date:this.processFormData.processTime[1],
+              }
+              if(this.processFormData.p_id){//编辑
+                data.p_id=this.processFormData.p_id;
+                delete data.target_type
+                delete data.target_id
+                delete data.process
+                this.$axiosUser('post', '/api/pro/okr/process/content',data).then(res=>{
+                  this.$message.success('已编辑');
+                  this.getProjectProcessList();
+                  this.isShowMp=false;
+                })
+              }else{//添加
+                this.$axiosUser('post', '/api/pro/okr/process/create',data).then(res=>{
+                  this.$message.success('已添加');
+                  this.getProjectProcessList();
+                  this.isShowMp=false;
+                })
+              }
+          }
+        });
+      },
+      getProjectProcessList(fun=function(){}){
+        this.$axiosUser('get','/api/pro/okr/process/list',{target_type:5,target_id:this.detailData.id,page:1,page_size:100}).then(res => {
+            let list=res.data.data.list
+            list.forEach(item=>{
+              item.userInfo=this.$getEmployeeMapItem(item.publisher_id);
+            })
+            this.projectProcessList=list
+            fun();
+        });
+      },
+      openMp(index,item){
+          if(index==1){
+            this.processFormData={
+              content:'',
+              processTime:[this.$moment().format('YYYY-MM-DD'),this.$moment().format('YYYY-MM-DD')],
+              target_type:5,
+            }
+          }else{
+            this.processFormData={
+              content:item.content,
+              processTime:[this.$moment(item.start_time).format('YYYY-MM-DD'),this.$moment(item.end_time).format('YYYY-MM-DD')],
+              target_type:5,
+              p_id:item.id,
+            }
+          }
+          this.isShowMp=true;
+      },
+      openDetail(item){
+        this.detailData=item;
+        this.getProjectProcessList(()=>{
+          this.showDrawerTow=true;
+        })
+      },
+      confirmTime(){
+        if(!this.time||this.time.length==0){
+          this.$message.error("请选择时间");
+          return false
+        }
+        let params={
+          plan_id:this.selectItem.id,
+          start_date:this.time[0],
+          end_date:this.time[1],
+        }
+        this.$axiosUser('post', 'api/pro/okr/plan/time', params).then(res => {
+            this.isShowUpdataTime=false;
+            let item=this.selectM_data;
+            this.selectItem.pId=item.id;
+            this.selectItem.pId=item.id;
+            this.milestoneList[this.p_index].plans.splice(this.dragIndex,1); //删除列表的记录元素
+            this.milestoneList[item.index].plans.unshift(this.selectItem);//插入元素
+            this.dragIndex = item.index;
+            this.$nextTick(()=>{
+              this.$refs[item.id][0].scrollTop=0; //距离左  横滚动条
+            })
+            let data={
+              plan_id:this.selectItem.id,//	是	integer	当前的计划id
+              target_type:item.id===0? 4:5,//	是	integer	要关联的对象种类 1-目标 2-KR 4-项目 5-里程碑
+              target_id:item.id===0? this.projectId:item.id,//	是	integer	要关联的对象id
+            }
+            this.upMain(data,true);
+        })
+      },
+      isShowTask(item){
+        let is=true;
+        if(this.taskForm.composite_states!==0&&this.taskForm.composite_states!=item.composite_state){
+          is=false
+        }
+        if(this.taskForm.owner_id&&this.taskForm.owner_id!=item.owner_id){
+          is=false
+        }
+        if(this.taskForm.keyword&&item.name.indexOf(this.taskForm.keyword)==-1){
+          is=false
+        }
+        return is
+      },
+      customColorMethod(percentage) {
+        if (percentage < 30) {
+          return '#909399';
+        } else if (percentage < 70) {
+          return '#e6a23c';
+        } else {
+          return '#67c23a';
+        }
+      },
+      openShowTask(id){
+        this.taskId=id;
+        this.isShowTaskDetail=true;
+      },
+      closeDetail(){
+        this.getMilestoneList();
+      },
+      openShowTaskAdd(item){
+        if(item.id){
+          this.target_type=5;
+          this.target_id=item.id;
+          this.initDate=[item.end_date,item.end_date];
+        }else{
+          this.target_type=4;
+          this.target_id=this.projectId;
+          this.initDate=[];
+        }
+        this.isShowAddTask=true;
+      },
+      ActiveAddTask(){
+        this.getMilestoneList();
+      },
+      showAdd(index){
+        this.showAddIndex=index;
+        if(index==1){
+          this.owner_userInfo=this.$userInfo();
+          this.formData={
+            name:'',
+            desc:'',
+            owner_id:this.$userInfo().id,
+            end_date:this.$moment().format('YYYY-MM-DD'),
+          };
+        }else{
+          this.owner_userInfo=this.$getEmployeeMapItem(this.detailData.owner_id);
+          this.formData={
+            name:this.detailData.name,
+            desc:this.detailData.desc,
+            owner_id:this.detailData.owner_id,
+            end_date:this.detailData.end_date,
+          };
+        }
+        this.isShowAdd=true
+      },
+      showSelectorUser(){
+        this.employee_selected.employee=[this.owner_userInfo];
+        this.show_employee_selector=true;
+      },
+      getMilestoneList(isUpdata){
+        this.loading=true;
+        this.$axiosUser('get', '/api/pro/okr/project/milestones',{project_id:this.projectId}).then(res => {
+            let milestones=res.data.data.milestones;
+            let plans=res.data.data.plans;
+            milestones.forEach(item=>{
+              item.statusColor=this.returnStatus(item);
+              item.day=this.$moment(item.end_date).diff(this.$moment().format('YYYY-MM-DD'), 'day')
+              if(item.plans.length>0&&item.id){
+                item.plans.forEach((a)=>{
+                    a.pName=item.name
+                    a.pId=item.id
+                })
+              }
+            })
+            if(plans.length>0){
+              milestones.unshift({id:0,plans:plans})
+            }
+            milestones.forEach((item)=>{
+                if(isUpdata&&item.id==this.detailData.id){
+                  this.detailData=item;
+                }
+                let plans=[];
+                if(item.plans.length>0){
+                  item.plans.sort((a,b)=>{
+                      if(this.taskForm.sort==0){
+                        return a.id-b.id
+                      }
+                      if(this.taskForm.sort==1){
+                        return a.create_time-b.create_time
+                      }
+                      if(this.taskForm.sort==2){
+                        return a.start_time-b.start_time
+                      }
+                      if(this.taskForm.sort==3){
+                        return a.end_time-b.end_time
+                      }
+                  })
+                }
+            })
+            this.$nextTick(()=>{
+              this.milestoneList=JSON.parse(JSON.stringify(milestones));
+              this.plans=plans;
+            })
+        }).finally(()=>{
+          this.loading=false;
+        })
+      },
+      returnStatus(item){
+        //statistics.plan_primary_delay		主任务中逾期的任务
+        let isGq=this.$moment().format('YYYY-MM-DD')>item.end_date;//是否过期
+        let status='#409EFF';
+        if(isGq){
+          if(item.statistics.plan_primary_delay>0){
+            status='#f56c6c';
+          }
+        }else{
+          if(item.statistics.plan_primary_delay>0){
+            status='#FF9600';
+          }
+        }
+        return status
+
+      },
+      handleClose(done) {
+        this.showDrawerTow = false;
+        if (done) {
+          done();
+        }
+      },
+      employee_confirm(val){
+          let user=val.employee[0]
+          this.owner_userInfo=user;
+          this.formData.owner_id=user.id
+      },
+      submitPoint(formName){
+        this.$refs[formName].validate((valid) => {
+          if (valid) {
+            let data={
+              project_id:this.projectId,
+              name:this.formData.name,
+              desc:this.formData.desc,
+              owner_id:this.formData.owner_id,
+              end_date:this.formData.end_date,
+              milestone_id:this.detailData.id,
+            }
+            if(this.showAddIndex==1){
+              this.$axiosUser('post', '/api/pro/okr/project/milestone/create',data).then(res => {
+                  this.$message.success('已创建');
+                  this.isShowAdd=false
+                  this.getMilestoneList();
+              })
+            }else{
+              var http1 = this.$axiosUser('POST','api/pro/okr/project/milestone/name', data); // 风险
+              var http2 = this.$axiosUser('POST','api/pro/okr/project/milestone/desc', data); // 风险
+              var http3 = this.$axiosUser('POST','api/pro/okr/project/milestone/owner', data); // 风险
+              var http4 = this.$axiosUser('POST','api/pro/okr/project/milestone/time', data); // 风险
+              Promise.all([http1,http2,http3,http4]).then(res => {
+                  this.$message.success('已修改');
+                  this.isShowAdd=false
+                  this.getMilestoneList(true);
+              })
+            }
+          } else {
+            return false;
+          }
+        });
+      },
+
+
+      // 拖拽前记录下标
+      dragstart(e,item,index,p_index) {
+        e.stopPropagation()
+        this.dragIndex = index;
+        this.p_index=p_index;
+        this.selectItem= item;
+        this.isTd=true;
+        setTimeout(() => {
+        	e.target.classList.add('moveing')
+        }, 0)
+      },
+      //进入目标元素触发,相当于mouseover
+      dragenter(e,item,index) {
+          let moving =this.selectItem; //获取之前记录的元素
+          let target_id=e.target.dataset.id;//里程碑ID
+          if(item&&(target_id||target_id===0)&&target_id!=moving.pId&&this.isTd){
+            setTimeout(() => {
+              this.selectM_id=item.id
+            }, 0)
+          }
+      },
+      // 鼠标松开 防抖触发排序
+      drop(e,item,index) {
+        e.preventDefault(); //阻止默认行为
+        let moving = this.selectItem; //获取之前记录的元素
+        if(item.plans&&item.id!=moving.pId&&this.isTd){
+          if(moving.composite_state==5||moving.composite_state==6){//完成或者审批中
+            this.$message.error(`任务${moving.composite_state==5? '审批中':'已完成'}且与里程碑时间冲突,移动失败`);
+            return false
+          }else{
+            if(this.$moment(moving.end_date).format('YYYY-MM-DD')>item.end_date){
+              this.selectM_data=item;
+              this.selectM_data.index=index;
+
+              this.time=[moving.start_date,moving.end_date];
+              this.isShowUpdataTime=true;
+              return false
+            }else{
+              this.selectM_data={};
+            }
+          }
+          moving.pId=item.id;
+          this.milestoneList[this.p_index].plans.splice(this.dragIndex,1); //删除列表的记录元素
+          this.milestoneList[index].plans.unshift(moving);//插入元素
+          this.dragIndex = index;
+          this.$nextTick(()=>{
+            this.$refs[item.id][0].scrollTop=0; //距离左  横滚动条
+          })
+          let data={
+            plan_id:moving.id,//	是	integer	当前的计划id
+            target_type:item.id===0? 4:5,//	是	integer	要关联的对象种类 1-目标 2-KR 4-项目 5-里程碑
+            target_id:item.id===0? this.projectId:item.id,//	是	integer	要关联的对象id
+          }
+          this.upMain(data);
+        }
+      },
+      //用户完成拖动后触发
+      dragend(e,item,index){
+        e.target.classList.remove('moveing')
+        this.selectM_id='-1';
+        if(!this.selectM_data.id){
+          this.selectItem={};
+        }
+      	this.isTd=false;
+      },
+      //进入目标、离开目标之间,连续触发
+      dragover(e,item,index) {
+        e.preventDefault();
+        e.dataTransfer.dropEffect = 'move'
+      },
+      upMain(data,is){
+        this.$axiosUser('post','/api/okr/plan/relate/main',data).then(res => {
+          // if(is){
+          //   this.getMilestoneList();
+          // }
+        });
+      },
+
+      format(){
+        return function(num){
+          if(num){
+             return `${num}%`;
+          }else{
+             return `0%`;
+          }
+        }
+      },
+    },
+    created() {
+        if(this.$route.query.id){
+          this.projectId=Number(this.$route.query.id);
+          this.getMilestoneList();
+        }
+    },
+  };
+</script>
+
+<style scoped="scoped" lang="scss">
+  .background-blue{
+     border: 1px solid #409EFF !important;
+     box-shadow: 0 0 3px #409EFF;
+  }
+  .project-name{
+      font-size: 18px;
+      font-weight: 550;
+      color: rgb(20, 28, 40);
+      max-width: 550px;
+  }
+  .task-name{
+    cursor: pointer;
+    height: 38px;
+  }
+  .moveing {
+  	opacity: 0;
+  }
+  .drag-move {
+  	transition: transform .3s;
+  }
+  .border_top::after{
+    content: "";
+    position: absolute;
+    height: 8px;
+    left: 0;
+    right: 0;
+    background-color: #409EFF;
+    border-radius: 5px;
+    top: -8px;
+    z-index: 2;
+  }
+  .border_bottom::after{
+  	content: "";
+  	position: absolute;
+  	height: 8px;
+  	left: 0;
+  	right: 0;
+  	background-color: #409EFF;
+  	border-radius: 5px;
+  	bottom: -8px;
+  	z-index: 2;
+  }
+
+  .select2 {
+    width: 100px;
+    margin-right: 10px;
+  }
+
+  .select2 ::v-deep .el-input__inner {
+    border-radius: 25px;
+  }
+
+  .input ::v-deep .el-input__inner {
+    border: none;
+    border-bottom: 1px solid #f1f1f1;
+    border-radius: 0px;
+  }
+
+  .add-task {
+    text-align: center;
+    color: #409EFF;
+    background-color: #fff;
+    padding: 8px;
+    border-radius: 5px;
+    cursor: pointer;
+  }
+  .milestone-item{
+    background-color: #F0F4FA;
+    padding:10px;
+    border-radius: 5px;
+    width: 300px;
+    margin-right: 14px;
+    box-sizing: border-box;
+    height: 62px;
+    position: relative;
+    flex-shrink:0;
+    cursor: pointer;
+  }
+  ::v-deep .el-progress__text{
+    font-size: 12px !important;
+  }
+  .add-milestone:hover,.task-name:hover{
+    color: #409EFF;
+  }
+  .task-box{
+    background-color: #F0F4FA;
+    padding:10px;
+    border: 1px solid #F0F4FA;
+    border-radius: 5px;
+    width: 300px;
+    margin-right: 14px;
+    position: relative;
+    min-height: calc(100vh - 415px);
+    box-sizing: border-box;
+    flex-shrink:0;
+  }
+  .task-item{
+    background-color: #fff;
+    padding:10px 16px;
+    border-radius: 5px;
+    position: relative;
+    margin-bottom: 10px;
+    cursor: move;
+  }
+  .task-items{
+     height: calc(100vh - 470px);
+     overflow-y: auto;
+     padding-top: 10px;
+  }
+  ::v-deep .el-drawer__body{
+    overflow: hidden;
+  }
+  ::v-deep .el-drawer {
+    width: 700px !important;
+  }
+  .drawer-header {
+    height: 60px;
+    border-bottom: 1px solid #ebebeb;
+    line-height: 60px;
+    font-size: 18px;
+    font-weight: 500;
+    padding: 0 20px;
+  }
+  .drawer-main {
+    height: calc(100vh - 120px);
+    padding: 20px;
+    overflow-y: scroll;
+    overflow-x: hidden;
+    min-width: 450px;
+    font-size: 14px;
+  }
+  .drawer-main::-webkit-scrollbar-track {
+    -webkit-box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.3);
+    border-radius: 5px;
+    background-color: rgba(216, 216, 216, 0.8);
+  }
+  .drawer-main::-webkit-scrollbar {
+    width: 5px;
+    background-color: rgba(201, 201, 201, 0);
+  }
+  .drawer-main::-webkit-scrollbar-thumb {
+    border-radius: 5px;
+    -webkit-box-shadow: inset 0 0 5px rgb(153, 145, 145) (160, 154, 154);
+    background-color: rgb(168, 167, 167);
+  }
+  .drawer-footer {
+    background-color: #fff;
+    border-top: 1px solid #ebebeb;
+    padding: 10px 20px;
+    font-size: 14px;
+  }
+  .record-message {
+    font-size: 13px;
+    margin: 5px 60px;
+  }
+  .record-list {
+    position: relative;
+    padding: 8px;
+    border-radius: 5px;
+  }
+  .record-list:hover .blue {
+    display: block !important;
+  }
+  .record-title {
+    padding: 16px 0;
+    font-size: 16px;
+  }
+  .record-date {
+    position: relative;
+    font-size: 13px;
+  }
+  .record-content {
+    margin-left: 40px;
+    border-radius: 5px;
+  }
+  .record-name {
+    margin-right: 10px;
+    margin-left: 10px;
+    color: #141c28;
+    font-weight: 600;
+  }
+</style>

+ 150 - 0
src/okr/components/project/ProjectInputBox.vue

@@ -0,0 +1,150 @@
+<template>
+    <div style="position:relative;" :style="{width:inputSize? '100%':text(name)}">
+        <el-input
+          class="input-border"
+          :class="{'inputSize':inputSize}"
+          @focus="inputFocus(1)"
+          v-model="name"
+          :type="upType==1? 'input':'textarea'"
+          :maxlength="maxlength"
+          :placeholder="placeholder">
+        </el-input>
+        <CollapseTransition>
+          <div v-if="isShowInput" class="isShowInput">
+              <div class="red" style="padding: 0 10px;"><span v-if="isShowError">请输入名称</span>&nbsp</div>
+              <div class="flex-box-end">
+                <el-button size="small" round @click="inputFocus(2)">取消</el-button>
+                <el-button size="small" round  type="primary" @click="inputFocus(3)">确定</el-button>
+              </div>
+          </div>
+        </CollapseTransition>
+    </div>
+</template>
+
+<script>
+import CollapseTransition from '@/okr/utils/collapse-transition';
+export default {
+  name:'ProjectInputBox',
+  components:{CollapseTransition},
+  props:{
+        value:{
+            type: String,
+            default: '',
+        },
+        placeholder:{
+            type: String,
+            default: '请输入名称',
+        },
+        upType:{ //1项目名称  2项目描述
+          type:Number,
+          default:1
+        },
+        maxlength:{
+          type:String,
+          default:'30'
+        },
+        project_id:{
+          type:Number,
+          default:0
+        },
+        inputSize:{
+          type:Boolean,
+          default:false
+        }
+  },
+  data() {
+    return {
+      is:true,
+      name:'',
+      isShowInput:false,
+      isShowError:false,
+    };
+  },
+  watch:{
+    value(){
+      this.name=this.value
+    }
+  },
+  computed:{
+    text () {
+      return function(value) {
+        if (value == '' || value == 0) {
+          return '100%'
+        } else {
+          return String(value).length * 20 + 'px'
+        }
+      }
+    }
+  },
+  created() {
+    this.name=this.value
+  },
+  methods: {
+    inputFocus(index){
+      if(index==1){
+
+      }else if(index==2){
+        this.name=this.value
+      }else if(index==3){
+        if(!this.name&&this.upType==1){
+          this.isShowError=true;
+          return false
+        }
+        let url=this.upType==1? '/api/pro/okr/project/name':'/api/pro/okr/project/desc'
+
+        this.$axiosUser('POST',url, {name:this.name,project_id:this.project_id,desc:this.name}).then(res => {
+            this.$emit('confirm');
+        })
+      }
+      this.isShowError=false
+      this.isShowInput=index==1? true:false;
+    },
+  }
+};
+</script>
+
+<style scoped="scoped" lang="scss">
+  .input-border ::v-deep .el-textarea__inner{
+    border: none;
+    border-radius: 5px;
+    font-size: 18px;
+    font-weight: 550;
+    padding: 0 10px;
+    color: #141c28 !important;
+  }
+  .input-border ::v-deep .el-textarea__inner:hover{
+     background-color: #F0F4FA;
+  }
+  .inputSize ::v-deep .el-textarea__inner{
+    font-size: 14px;
+  }
+  .input-border ::v-deep .el-input__inner{
+    border: none;
+    border-radius: 5px;
+    font-size: 18px;
+    font-weight: 550;
+    padding: 0 10px;
+    color: #141c28 !important;
+  }
+  .input-border ::v-deep .el-input__inner:hover{
+     background-color: #F0F4FA;
+  }
+  .inputSize ::v-deep .el-input__inner{
+    border: none;
+    border-radius: 5px;
+    font-size: 14px;
+    font-weight: 550;
+    color: #141c28 !important;
+  }
+  .isShowInput{
+    position: absolute;
+    z-index: 2;
+    box-shadow: 3px 5px 5px #F3F5F8;
+    background-color: #fff;
+    padding: 6px;
+    box-sizing: border-box;
+    border-radius: 5px;
+    left: 0;
+    right: 0;
+  }
+</style>

+ 704 - 0
src/okr/components/project/ProjectPreview.vue

@@ -0,0 +1,704 @@
+<template>
+  <div class="br-5 scroll-bar" style="height: calc(100vh - 260px);padding-top: 30px;box-sizing: border-box;overflow-y: scroll;">
+    <el-row  v-loading="loading">
+      <el-col :span="12">
+        <div class="circular_item" style="margin-bottom: 10px;">
+            <div class="flex-box">
+              <div class="title">项目完成度</div>
+              <div class="flex-1"></div>
+              <div class="blue cursor" v-if="isReturnPJ" @click="isShowSchedule=true"><i class="el-icon-refresh" ></i>更新进度</div>
+              <div style="margin-left: 20px;" v-if="detailData.process>0" class="fontColorC cursor" @click="showDrawerTow=true"><i class="el-icon-s-order" ></i>进度记录</div>
+            </div>
+            <div style="text-align: center;margin-top: 50px;position: relative;" v-if="detailData.process>0">
+                <el-progress :color="customColor" :width="150" :stroke-width="18" type="circle" :show-text="false" :percentage="Number(detailData.process)"></el-progress>
+                <div class="percent">{{detailData.process}}%</div>
+            </div>
+            <div v-else class="flex-box-v flex-center-center">
+                <img src="static/images/nodata.png" style="width: 120px;"/>
+                <div class="fontColorC" style="margin-bottom: 10px;">暂无进度</div>
+                <el-button type="primary" size="small" round style="width: 100px;" @click="isShowSchedule=true" v-if="isReturnPJ"><i class="el-icon-refresh"></i> 更新进度</el-button>
+            </div>
+            <template v-if="projectProcessList[0]">
+              <div v-for="(item, index) in [projectProcessList[0]]" :key="index" class="record-list" style="background-color: #F8FCFF;margin-bottom: 10px;margin-top: 20px;">
+                <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="$userInfo().id==item.userInfo.id" @click="openMp(item)">编辑</span>
+                </div>
+                <div class="record-content">
+                  <pre class="pre fontColorA" style="font-size: 15px;" v-if="item.content">{{ item.content }}</pre>
+                  <div v-else class="fontColorD">无内容</div>
+                </div>
+                <div class="flex-box-ce flex-d-center fontColorC" style="padding-left: 40px;font-size: 13px;margin-top: 14px;">
+                  <div>进度为{{item.process}}%</div>
+                  <div>进展日期:{{$moment(item.start_time).format('YYYY/MM/DD')}}~{{$moment(item.end_time).format('YYYY/MM/DD')}}</div>
+                </div>
+              </div>
+            </template>
+
+        </div>
+        <div class="circular_item">
+            <div class="title">项目完成趋势</div>
+            <div v-if="trend.length>0" class="pie" id="dept_score" style="width: 100%;height: 300px;margin-top: 20px;"></div>
+            <noData v-else></noData>
+        </div>
+      </el-col>
+      <el-col :span="12" style="padding:20px;border-radius: 10px;border: 1px solid #f1f1f1;padding-top: 10px;">
+        <div class="title">项目基本信息</div>
+        <div class="kr-message" style="position: relative;">
+          <div class="flex-box kr-message-item">
+            <div class="label" style="padding-top: 8px;">项目名称</div>
+            <ProjectInputBox class="flex-1" :project_id="projectId" :value="detailData.name" inputSize @confirm="getProjectDateil()"></ProjectInputBox>
+          </div>
+          <div class="flex-box kr-message-item">
+            <div class="label" style="padding-top: 8px;">项目描述</div>
+            <ProjectInputBox class="flex-1" :project_id="projectId" :value="detailData.desc" :upType='2' inputSize maxlength="200" placeholder="请输入项目描述" @confirm="getProjectDateil()"></ProjectInputBox>
+          </div>
+          <div class="flex-box-ce kr-message-item">
+            <div class="label">负责人</div>
+            <div class="flex-box-ce input-box cursor" @click="showSelectorUser()">
+                <userImage :id="owner_userInfo.id" width="36px" height="36px" fontSize="14" :user_name="owner_userInfo.name"></userImage>
+                <span style="font-weight: 600;padding-left: 10px;" class="fontColorB">{{owner_userInfo.name}}</span>
+            </div>
+          </div>
+          <div class="flex-box-ce kr-message-item">
+            <div class="label">起止时间</div>
+            <div class="hover-border" style="margin-right: 10px;" @click="showTime">{{detailData.start_date}}~{{detailData.end_date}}</div>
+            <template>
+              <span v-if="detailData.day>0">剩余<span class="green">{{detailData.day}}</span>天</span>
+              <span v-if="detailData.day==0">剩余<span class="green">1</span>天</span>
+              <span v-if="detailData.day<0">逾期<span class="red">{{Math.abs(detailData.day)}}</span>天</span>
+            </template>
+          </div>
+          <div class="flex-box-ce kr-message-item">
+            <div class="label">所属部门</div>
+            <div class="cursor">
+              <div class="flex-box-ce flex-d-wrap input-box" @click="show_dept_selector=true" v-if="dept_selected.dept.length>0">
+                <span class="dept-item" v-for="item in dept_selected.dept" :key="item.id"> <svg-icon icon-class="#icon-bumenguanli" class="svgIcon"></svg-icon>{{item.dept_name}}</span>
+              </div>
+              <div v-else class="fontColorC input-box" style="padding: 5px;" @click="show_dept_selector=true">请选择部门</div>
+            </div>
+          </div>
+          <div class="flex-box-ce kr-message-item">
+            <div class="label">参与人</div>
+            <div class="cursor">
+              <div class="flex-box-ce flex-d-wrap input-box" @click="show_employee_selector_all=true" v-if="employee_selected_all.employee.length>0">
+                <span class="dept-item" v-for="item in employee_selected_all.employee" :key="item.id">{{item.name}}</span>
+              </div>
+              <div v-else class="fontColorC input-box" style="padding: 5px;" @click="show_employee_selector_all=true">请选择参与人员</div>
+            </div>
+          </div>
+          <div class="flex-box-ce kr-message-item">
+            <div class="label">可见范围</div>
+            <el-popover placement="right" trigger="manual" v-model="isShowFw">
+              <div class="searchBox">
+                <el-select class="w270" v-model="scope_type" placeholder="请选择">
+                  <el-option label="仅项目成员可见" :value="1"></el-option>
+                  <el-option label="全员公开可见" :value="2"></el-option>
+                </el-select>
+                <div class="flex-box-end" style="margin-top: 20px;">
+                  <el-button size="small" plain round @click="isShowFw = false">取 消</el-button>
+                  <el-button size="small" type="primary" plain round @click="confirmFw">确 定</el-button>
+                </div>
+              </div>
+              <!-- 内容 -->
+              <template slot="reference">
+                <div @click="showFw" class="hover-border">{{detailData.scope_type==1? '仅项目成员可见':'全员公开可见'}} <i class="el-icon-caret-bottom fontColorC"></i></div>
+              </template>
+            </el-popover>
+          </div>
+          <div class="flex-box kr-message-item">
+            <div class="label">关联KR</div>
+            <div>
+              <template v-if="detailData.relate.length>0">
+                <span class="showUpdate flex-box-ce" v-for="(item,index) in detailData.relate" style="margin-bottom: 10px;">
+                  <div style="max-width: 400px;" class="font-flex-word flex-box-ce">
+                     <span class="krIcon">KR</span>
+                     <span @click="openDetail(item.kr_id,2)" style="max-width: 200px;"  class="font-flex-word cursor">{{item.kr_name}}</span>
+                  </div>
+                  <i class="el-icon-error" style="display: none;" @click="relieveKr(item)"></i>
+                  <Tooltip preHtml="新增">
+                    <i class="el-icon-plus" style="display: none;" @click="isShowDateSearch=true">新增</i>
+                  </Tooltip>
+                </span>
+              </template>
+              <div v-else class="input-box fontColorC cursor" @click="isShowDateSearch=true">暂无关联</div>
+            </div>
+          </div>
+          <div v-if="!isReturnPJ" style="position: absolute;z-index: 2;left: 0;right: 0;top: 0;bottom: 0;"></div>
+        </div>
+      </el-col>
+    </el-row>
+
+    <el-drawer :visible.sync="showDrawerTow" :append-to-body="true" :before-close="handleClose" :with-header="false">
+       <header class="drawer-header flex-box-ce"><div class="flex-1">项目进展列表</div></header>
+       <div class="drawer-main">
+         <div class="record-box">
+           <div v-for="(item, index) in projectProcessList" :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="$userInfo().id==item.userInfo.id" @click="openMp(item)">编辑</span>
+             </div>
+             <div class="record-content">
+               <pre class="pre fontColorA" style="font-size: 15px;" v-if="item.content">{{ item.content }}</pre>
+               <div v-else class="fontColorD">无内容</div>
+             </div>
+             <div class="flex-box-ce flex-d-center fontColorC" style="padding-left: 40px;font-size: 13px;margin-top: 14px;">
+               <div>进度为{{item.process}}%</div>
+               <div>进展日期:{{$moment(item.start_time).format('YYYY/MM/DD')}}~{{$moment(item.end_time).format('YYYY/MM/DD')}}</div>
+             </div>
+           </div>
+           <div class="dotted-line" v-if="projectProcessList.length==0"><div>事务有新的进展?马上记录吧</div></div>
+         </div>
+       </div>
+    </el-drawer>
+
+    <!-- 里程碑进度 -->
+    <el-dialog title="进度" :visible.sync="isShowMp" :append-to-body="true" width="500px">
+      <div>
+        <el-form label-width="80px" class="form" :model="processFormData" ref="form">
+           <el-form-item label="进展内容" prop="content" :rules="[{ required: true, message: '请输入进展'}]">
+              <el-input type="textarea" v-model="processFormData.content" rows="4" placeholder="请输入进展" maxlength="500" show-word-limit></el-input>
+           </el-form-item>
+           <el-form-item label="起止时间">
+              <el-date-picker :clearable="false" size="small" v-model="processFormData.processTime" type="daterange" value-format="yyyy-MM-dd" range-separator="~" start-placeholder="开始日期"end-placeholder="结束日期"></el-date-picker>
+           </el-form-item>
+        </el-form>
+      </div>
+      <div class="flex-box-end" style="margin-top: 30px;">
+        <el-button @click="isShowMp=false">取消</el-button>
+        <el-button type="primary" @click="confirmProcessm('form')">确定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 修改起止时间 -->
+    <el-dialog title="修改起止时间" :visible.sync="isShowDqMyTarget" :append-to-body="true" width="500px">
+      <div>
+          <el-form ref="form" label-width="80px" class="form">
+              <el-form-item label="起止时间">
+                  <el-date-picker :clearable="false" size="small" v-model="time" type="daterange" value-format="yyyy-MM-dd" range-separator="~" start-placeholder="开始日期"end-placeholder="结束日期"></el-date-picker>
+              </el-form-item>
+          </el-form>
+      </div>
+      <div class="flex-box-end" style="margin-top: 50px;">
+      <el-button size="small"  @click="isShowDqMyTarget = false">取 消</el-button>
+      <el-button size="small" type="primary"  @click="confirmTime">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 选择部门 -->
+    <EmployeeSelector title="选择部门" :dept_children="false" :isChecKedAll="false" :max="3" :can_select_employee="false" :can_select_dept="true" :multi="true" :selected="dept_selected" :visible.sync="show_dept_selector"@confirm="dept_confirm"/>
+
+    <!-- 对齐目标 -->
+    <TargetSearch :visible.sync="isShowDateSearch" title="所属KR" :showType="2" @confirm="confirmTarget"></TargetSearch>
+
+    <!-- 选择负责人 -->
+    <EmployeeSelector :is_filtration_creator="false" title="选择人员" :isChecKedAll="false" isRequired :multi="false" :selected="employee_selected" :visible.sync="show_employee_selector" @confirm="employee_confirm"/>
+
+    <!-- 选择参与人 -->
+    <EmployeeSelector :is_filtration_creator="false" title="选择人员" :isChecKedAll="false" :max="50" :selected="employee_selected_all" :visible.sync="show_employee_selector_all" @confirm="employee_confirm_all"/>
+
+    <!-- 设置完成度 -->
+    <ProjectSchedule :visible.sync="isShowSchedule" :progressData="progressData" @confirm="getProjectDateil()"></ProjectSchedule>
+
+    <!-- 目标详情 -->
+    <TargetDetail :type="showDetailType" :id="showDetailId" v-if="isShowTargetDetail" :showDrawer.sync="isShowTargetDetail"></TargetDetail>
+  </div>
+</template>
+
+<script>
+import {taskStatus,isReturnPJ,isPstake} from '@/okr/utils/auth';
+import ProjectInputBox from '@/okr/components/project/ProjectInputBox'; //input组件
+import EmployeeSelector from '@/components/EmployeeSelector';
+import TargetSearch from '@/okr/components/public/TargetSearch'; //反馈时间组件
+import ProjectSchedule from '@/okr/components/project/ProjectSchedule'; //完成度
+import TargetDetail from '@/okr/components/public/TargetDetail'; //目标详情
+import Tooltip from '@/components/Tooltip'; //鼠标悬浮显示文字文字
+import {_debounce} from '@/utils/auth';
+export default {
+  name: 'ProjectTj',
+  components: {ProjectInputBox,EmployeeSelector,TargetSearch,ProjectSchedule,TargetDetail,Tooltip},
+  data() {
+    return {
+      loading:false,
+      owner_userInfo: this.$userInfo(),//负责人
+      projectId:0,
+      detailData:{
+        process:0,
+        relate:[],
+      },
+      isShowDqMyTarget:false,
+
+      // 部门可见
+      deptVisibleName: null,
+      dept_selected: {dept: [],employee:[]},
+      show_dept_selector: false,
+
+      employee_selected: { dept: [], employee: [] },
+      show_employee_selector: false,
+      employee_selected_all: { dept: [], employee: [] },
+      show_employee_selector_all: false,
+      selectUserIndex:1,
+      time:[],
+      isShowDateSearch:false,
+      kr_id:0,
+      isShowFw:false,
+      scope_type:1,
+      progressData:{},
+      isShowSchedule:false,
+      customColor:'#409EFF',
+
+      showDrawerTow:false,
+      projectProcessList:[],//进展
+      isShowMp:false,
+      processFormData:{
+        content:'',
+        processTime:[this.$moment().format('YYYY-MM-DD'),this.$moment().format('YYYY-MM-DD')],
+        target_type:5,
+      },
+      showDetailType:2,
+      showDetailId:0,
+      isShowTargetDetail:false,
+      trend:[],
+
+      isReturnPJ:false,
+      isPstake:false,//是否是相关成员
+    };
+  },
+  methods: {
+    openDetail(id,type){
+      this.showDetailType=type;//打开详情的类型
+      this.showDetailId=id;//打开详情的ID
+      this.isShowTargetDetail=true;
+    },
+    relieveKr(item){
+        this.$axiosUser('POST', '/api/pro/okr/project/unbind',{project_id:this.projectId,kr_id:item.kr_id}).then(res => {
+             this.getProjectDateil();
+        })
+    },
+    getTuData(){
+      let data={
+        target_type:4,
+        target_id:this.projectId,
+        start_date:this.detailData.start_date,
+        end_date:this.detailData.end_date,
+      }
+      this.$axiosUser('get', '/api/pro/okr/process/trend',data).then(res=>{
+        this.trend=res.data.data.trend;
+        this.$nextTick(()=>{
+          this.initDeptScore(res.data.data.trend)
+        })
+      })
+    },
+    confirmProcessm(formName) {
+      this.$refs[formName].validate((valid) => {
+        if (valid) {
+            let data={
+              process:this.detailData.process,
+              content:this.processFormData.content,
+              start_date:this.processFormData.processTime[0],
+              end_date:this.processFormData.processTime[1],
+              p_id:this.processFormData.p_id
+            }
+            this.$axiosUser('post', '/api/pro/okr/process/content',data).then(res=>{
+              this.$message.success('已编辑');
+              this.getProjectProcessList();
+              this.isShowMp=false;
+            })
+        }
+      });
+    },
+    openMp(item){
+        this.processFormData={
+          content:item.content,
+          processTime:[this.$moment(item.start_time).format('YYYY-MM-DD'),this.$moment(item.end_time).format('YYYY-MM-DD')],
+          p_id:item.id,
+        }
+        this.isShowMp=true;
+    },
+    handleClose(done) {
+      this.showDrawerTow = false;
+      if (done) {
+        done();
+      }
+    },
+    getProjectProcessList(){
+      this.$axiosUser('get','/api/pro/okr/process/list',{target_type:4,target_id:this.projectId,page:1,page_size:100}).then(res => {
+          let list=res.data.data.list
+          list.forEach(item=>{
+            item.userInfo=this.$getEmployeeMapItem(item.publisher_id);
+          })
+          this.projectProcessList=list
+      });
+    },
+    showFw(){
+      this.scope_type=this.detailData.scope_type
+      this.isShowFw=true;
+    },
+    confirmFw(){
+      this.$axiosUser('post', 'api/pro/okr/project/scope',{scope_type:this.scope_type,project_id:this.projectId}).then(res => {
+        this.getProjectDateil();
+        this.isShowFw=false;
+      })
+    },
+    confirmTarget(item){
+      this.$axiosUser('post', 'api/pro/okr/project/bind',{kr_id:item?item.item.id:0,project_id:this.projectId}).then(res => {
+        this.getProjectDateil();
+      })
+    },
+    showTime(){
+      this.time=[this.detailData.start_date,this.detailData.start_date];
+      this.isShowDqMyTarget=true;
+    },
+    confirmTime(){
+      if(this.time.length==0){
+        this.$message.error("请选择时间");
+        return false
+      }
+      let params={
+        project_id:this.projectId,
+        start_date:this.time[0],
+        end_date:this.time[1],
+      }
+      this.$axiosUser('post', 'api/pro/okr/project/time', params).then(res => {
+        this.isShowDqMyTarget=false;
+        this.getProjectDateil();
+      })
+    },
+    employee_confirm_all(val){
+      this.employee_selected_all.employee=val.employee;
+      let joiner_ids=val.employee.map(item=>{return item.id}).toString()
+      this.$axiosUser('post', 'api/pro/okr/project/joiner', {joiner_ids:joiner_ids,project_id:this.projectId}).then(res => {
+          this.getProjectDateil();
+      })
+    },
+    employee_confirm(val){
+      let user=val.employee[0]
+      this.owner_userInfo=val.employee[0];
+      this.$axiosUser('post', 'api/pro/okr/project/owner', {owner_id:user.id,project_id:this.projectId}).then(res => {
+          this.getProjectDateil();
+      })
+    },
+    showSelectorUser(index){
+      this.employee_selected.employee=[this.owner_userInfo];
+      this.show_employee_selector=true;
+    },
+    // 部门可见
+    dept_confirm(data){
+      this.dept_selected = {dept: [],employee:[]};
+      let dept_ids = [];
+      if (data.dept !== null && data.dept.length != 0) {
+        this.dept_selected = data
+        data.dept.forEach(element => {
+    			dept_ids.push(element.dept_id)
+        });
+      }
+      let obj={
+        project_id:this.projectId,
+        dept_ids:JSON.stringify(dept_ids),
+      }
+      this.$axiosUser('post', '/api/pro/okr/project/dept',obj).then(res => {
+          this.getProjectDateil();
+      })
+    },
+    // 项目详情
+    getProjectDateil(){
+      this.loading=true;
+      this.$axiosUser('get', '/api/pro/okr/project/info',{project_id:this.projectId}).then(res => {
+          let data=res.data.data;
+          let depts=[];
+          data.day=this.$moment(data.end_date).diff(this.$moment().format('YYYY-MM-DD'), 'day')
+          data.dept_ids.forEach(id=>{
+            let item = this.$getCache('dept_tree_pin')[id]||{};
+            if(item.id){
+              depts.push({
+                dept_id: item.id,
+                dept_name: item.name
+              })
+            }
+          })
+          this.dept_selected.dept = depts;
+          this.owner_userInfo=this.$getEmployeeMapItem(data.owner_id);
+          this.customColor=data.composite_state==3? '#f56c6c':'#409EFF';
+          this.employee_selected_all.employee=data.joiner_ids.map(id=>{
+              return this.$getEmployeeMapItem(id);
+          });
+          // 进度
+          data.process_conf.process=data.process;
+          data.process_conf.id=this.projectId;
+          this.progressData=data.process_conf;
+          this.detailData=data;
+          this.getTuData();
+          this.getProjectProcessList();
+          this.isReturnPJ=isReturnPJ(data);
+          this.isPstake=isPstake(data)
+      }).finally(()=>{
+        this.loading=false;
+      })
+    },
+
+    initDeptScore(list) {
+      let dates=[],series=[];
+      list.forEach(item=>{
+        series.push(item.process)
+        dates.push(this.$moment(item.date).format('MM-DD'));
+      })
+      let elm=document.getElementById('dept_score')
+      if(!elm){return}
+      var dept_char =this.$echarts.init(document.getElementById('dept_score'));
+      let option = {
+        color: ["#FF9600", "#67c23a", "#4cabce", "#e5323e"],
+        tooltip: {
+          trigger: 'axis',
+        },
+        xAxis: {
+          type: 'category',
+          boundaryGap: false,
+          data: dates
+        },
+        yAxis: {
+          type: 'value',
+          axisLine: {
+            show: false
+          },
+          axisTick: {
+            show: false
+          }
+        },
+        series: [
+          {
+            name: '项目进度',
+            type: 'line',
+            data: series,
+            connectNulls:true,
+          },
+        ]
+      };
+      dept_char.setOption(option)
+    },
+    //echarts自适应
+    selfAdaption() {
+      var myChart2 = this.$echarts.init(document.getElementById('dept_score'));
+      myChart2.resize();
+    },
+  },
+  mounted() {
+    if(this.$route.query.id){
+      this.projectId=Number(this.$route.query.id)
+      this.getProjectDateil();
+      // this.getProjectProcessList();
+      window.addEventListener('resize', this.selfAdaption);
+    }
+
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.selfAdaption); //取消echarts自适应
+  },
+};
+</script>
+
+<style scoped lang="scss">
+  .krIcon{
+      position: relative;
+      width: 22px;
+      height: 16px;
+      border-radius: 25px;
+      background-color: #e9f1fe;
+      -webkit-box-sizing: border-box;
+      box-sizing: border-box;
+      text-align: center;
+      line-height: 17px;
+      margin-right: 5px;
+      font-weight: 600;
+      color: #409EFF;
+      font-size: 12px;
+  }
+  .showUpdate:hover i{
+      display: block !important;
+      padding-left: 5px;
+      cursor: pointer;
+      position: relative;
+      top:1px
+  }
+  .showUpdate .el-icon-error:hover{
+    color: #f56c6c;
+  }
+  .deleteKr{
+    position: relative;
+    top: 2px;
+    margin-left: 10px;
+    color: #89919F;
+  }
+  .deleteKr:hover{
+    color: #f56c6c;
+  }
+  .krIcon{
+      position: relative;
+      width: 22px;
+      height: 16px;
+      top: 2px;
+      border-radius: 25px;
+      background-color: #e9f1fe;
+      -webkit-box-sizing: border-box;
+      box-sizing: border-box;
+      text-align: center;
+      line-height: 17px;
+      margin-right: 5px;
+      font-weight: 600;
+      color: #409EFF;
+      font-size: 12px;
+  }
+  .input-box:hover{
+    background-color: #F0F4FA;
+    border-radius: 5px;
+  }
+  .dept-item{
+    background-color: #F0F4FA;
+    border-radius: 5px;
+    margin: 2px 0;
+    margin-right: 5px;
+    padding: 5px;
+  }
+  .border{
+    -webkit-appearance: none;
+    background-color: #fff;
+    background-image: none;
+    border-radius: 4px;
+    border: 1px solid #dcdfe6;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+    font-size: inherit;
+    height: auto;
+    outline: 0;
+    color: #606266;
+    padding: 0 15px;
+    padding-right: 10px;
+    line-height: 34px;
+    width: 100%;
+    position: relative;
+    cursor: pointer;
+  }
+  .title{
+    font-weight: 600;
+    font-size: 14px;
+  }
+  .circular_item {
+    background-color: #fff;
+    border-radius: 10px;
+    box-sizing: border-box;
+    padding: 10px;
+    margin-right: 10px;
+    border: 1px solid #f1f1f1;
+  }
+  .kr-message-item{
+    margin-top: 20px;
+  }
+  .label{
+    width: 80px;
+    color:#606266;
+    flex-shrink: 0;
+  }
+  .pie {
+    width: 100%;
+    height: 400px;
+    margin: 0 auto;
+  }
+  .tab-item{
+    padding: 7px 20px;
+    text-align: center;
+    background-color: #F7F8FA;
+    margin-right: 10px;
+    border-radius: 20px;
+    cursor: pointer;
+  }
+  .percent{
+    position: absolute;
+    left:50%;
+    z-index: 2;
+    width: 126px;
+    text-align: center;
+    margin-left: -63px;
+    top: 50%;
+    font-size: 24px;
+    font-weight: 700;
+    margin-top: -18px;
+  }
+  ::v-deep .el-drawer__body{
+    overflow: hidden;
+  }
+  ::v-deep .el-drawer {
+    width: 700px !important;
+  }
+  .drawer-header {
+    height: 60px;
+    border-bottom: 1px solid #ebebeb;
+    line-height: 60px;
+    font-size: 18px;
+    font-weight: 500;
+    padding: 0 20px;
+  }
+  .drawer-main {
+    height: calc(100vh - 120px);
+    padding: 20px;
+    overflow-y: scroll;
+    overflow-x: hidden;
+    min-width: 450px;
+    font-size: 14px;
+  }
+  .drawer-main::-webkit-scrollbar-track {
+    -webkit-box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.3);
+    border-radius: 5px;
+    background-color: rgba(216, 216, 216, 0.8);
+  }
+  .drawer-main::-webkit-scrollbar {
+    width: 5px;
+    background-color: rgba(201, 201, 201, 0);
+  }
+  .drawer-main::-webkit-scrollbar-thumb {
+    border-radius: 5px;
+    -webkit-box-shadow: inset 0 0 5px rgb(153, 145, 145) (160, 154, 154);
+    background-color: rgb(168, 167, 167);
+  }
+  .drawer-footer {
+    background-color: #fff;
+    border-top: 1px solid #ebebeb;
+    padding: 10px 20px;
+    font-size: 14px;
+  }
+  .record-message {
+    font-size: 13px;
+    margin: 5px 60px;
+  }
+  .record-list {
+    position: relative;
+    padding: 8px;
+    border-radius: 5px;
+  }
+  .record-list:hover .blue {
+    display: block !important;
+  }
+  .record-title {
+    padding: 16px 0;
+    font-size: 16px;
+  }
+  .record-date {
+    position: relative;
+    font-size: 13px;
+  }
+  .record-content {
+    margin-left: 40px;
+    border-radius: 5px;
+  }
+  .record-name {
+    margin-right: 10px;
+    margin-left: 10px;
+    color: #141c28;
+    font-weight: 600;
+  }
+
+
+
+</style>

+ 329 - 0
src/okr/components/project/ProjectSchedule.vue

@@ -0,0 +1,329 @@
+<template>
+  <el-dialog :title="title" :visible.sync="visible_" :close-on-click-modal="false" :before-close="close_before" append-to-body @open="openDialog" width="560px">
+    <div>
+      <el-form ref="form" :model="form" label-width="80px">
+        <el-form-item label="完成度">
+          <el-input placeholder="请输入大于0" v-model.trim="form.process" :disabled="isOpen" @input="form.process = form.process.replace(/[^\d]/g, '');" size="small" style="width: 200px;">
+            <template slot="append">%</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="进展内容" v-if="!isOpen">
+          <el-input size="small" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" v-model="form.content" maxlength="500" show-word-limit placeholder="进展内容"></el-input>
+        </el-form-item>
+      </el-form>
+      <div class="flex-box-ce flex-d-center" style="margin-bottom: 20px;">
+        <div class="add-task-title black flex-box-ce">设置项目完成度自动更新<el-switch v-model="isOpen" style="padding-left: 5px;position: relative;top:1px;"></el-switch></div>
+      </div>
+      <el-radio-group v-model="value" class="flex-box-v" v-if="isOpen">
+        <el-radio :label="1" style="margin-bottom: 15px;width: 220px;">
+          根据任务完成度自动更新
+          <Tooltip preHtml="选择后,项目完成度不可编辑,由系统根据任务完成度自动计算"><i class="el-icon-question fontColorC" style="padding-left: 5px;"></i></Tooltip>
+        </el-radio>
+        <el-radio :label="2" style="width: 220px;">
+          根据里程碑完成度自动更新
+          <Tooltip preHtml="选择后,项目完成度不可编辑,由系统根据里程碑完成度自动计算"><i class="el-icon-question fontColorC" style="padding-left: 5px;"></i></Tooltip>
+          <span class="blue cursor" v-if="value==2" @click.stop="getMilestoneList">权重设置</span>
+        </el-radio>
+      </el-radio-group>
+    </div>
+    <div class="flex-box-ce flex-box-end" style="margin-top: 20px;">
+      <el-button @click="close">取 消</el-button>
+      <el-button type="primary" @click="confirm">确 定</el-button>
+    </div>
+    <!-- 设置权重 -->
+    <el-dialog title="里程碑权重设置" :visible.sync="isShowWeight" :append-to-body="true" width="660px">
+      <div style="padding:0 10px;max-height: 400px;overflow: auto;height: 200px;" class="scroll-bar">
+        <div class="flex-box-ce " v-for="(item, index) in milestoneList" :key="index" style="margin-bottom: 10px;">
+          <span class="blue" style="width: 40px;">{{ index + 1 }}</span>
+          <div class="flex-1">
+            <Tooltip :preHtml="item.name">
+              <div class="font-flex-word" style="width:400px;">{{item.name}}</div>
+            </Tooltip>
+          </div>
+          <span>权重</span>
+          <el-input @input="[(item.weight = isFloor(item.weight))]" class="input" style="width: 80px;" size="small" v-model="item.weight" clearable/>
+          <span>%</span>
+        </div>
+        <div class="flex-box-ce flex-box-end" style="margin-top: 20px;">
+            <div class="fontColorC" style="font-size: 12px;margin-right: 20px;">总权重:{{getKrWeight}}%</div>
+            <el-button round type="primary" size="mini" @click="pingJun">平均权重</el-button>
+        </div>
+      </div>
+      <div class="flex-box-end" style="margin-top: 30px;">
+        <el-button @click="isShowWeight=false">取消</el-button>
+        <el-button type="primary" @click="updateWeight()">确定</el-button>
+      </div>
+    </el-dialog>
+  </el-dialog>
+</template>
+
+<script>
+import Tooltip from '@/components/Tooltip'; //鼠标悬浮显示文字
+export default {
+  components:{Tooltip},
+  props: {
+    visible: {
+      // 是否显示组件
+      type: Boolean,
+      default: false
+    },
+    progressData:{
+      type: Object,
+      default: _=>{
+        return {}
+      }
+    }
+  },
+  name: 'ProjectSchedule',
+  data() {
+    return {
+      title:'更项目完成度',
+      value:1,
+      isOpen:false,
+      form:{
+        process:0,
+        risk_level:0,
+        content:'',
+      },
+      visible_: false,
+      isShowWeight:false,
+      milestoneList:[],
+      sumWeight:0,
+    };
+  },
+  computed:{
+    getKrWeight(){
+      let sumWeight=0;
+      this.milestoneList.forEach(item=>{
+        sumWeight+=Number(item.weight)
+      })
+      this.sumWeight= Math.round(sumWeight)
+      return this.sumWeight
+    }
+  },
+  watch: {
+    visible(val) {
+      this.visible_ = JSON.parse(JSON.stringify(val));
+    },
+    isOpen(val) {
+      if(val){
+        this.form.process=0;
+      }
+    },
+  },
+  methods: {
+    isFloor(el) {
+      var obj = event.target;
+      el = el
+        .replace('.', '$#$') //把第一个字符'.'替换成'$#$'
+        .replace(/\./g, '') //把其余的字符'.'替换为空
+        .replace('$#$', '.') //把字符'$#$'替换回原来的'.'
+        .replace(/[^\d.]/g, '') //只能输入数字和'.'
+        .replace(/^\./g, '') //不能以'.'开头
+        .replace(/([0-9]+\.[0-9]{2})[0-9]*/, '$1'); //只保留2位小数
+      if(el=='01'||el=='02'||el=='03'||el=='04'||el=='05'||el=='06'||el=='07'||el=='08'||el=='09'){
+        el=el[1]
+      }
+      return el || null;
+    },
+    updateWeight(){
+      let krs=[]
+      let isError=false;
+      this.milestoneList.forEach(item=>{
+          if(item.weight==0||!item.weight){
+            isError=true
+          }
+          krs.push({id:item.id,weight:item.weight})
+      })
+      if(isError){
+        this.$message.error('里程碑的权重不能为空或者0')
+        return true
+      }
+      this.$axiosUser('post', '/api/pro/okr/project/milestone/weight',{project_id:this.progressData.id,items:JSON.stringify(krs)}).then(res => {
+        this.$message.success('已设置');
+        this.isShowWeight=false
+      })
+    },
+    pingJun(){
+      let weight=Math.floor((100/(this.milestoneList.length)) * 100) / 100
+      this.milestoneList.forEach(item=>{
+         item.weight=weight
+      })
+    },
+    getMilestoneList(){
+      this.$axiosUser('get', '/api/pro/okr/project/milestones',{project_id:this.progressData.id}).then(res => {
+          let milestones=res.data.data.milestones;
+          this.milestoneList=JSON.parse(JSON.stringify(milestones));
+          this.isShowWeight=true;
+      })
+    },
+    //打开Dialog的回调,用刷每次打开都初始化selected
+    openDialog() {
+      this.isOpen=this.progressData.enable_automatic;
+      this.value=this.progressData.automatic_from_plan? 1:2;
+      this.$nextTick(()=>{
+        this.form={
+          process:Number(this.progressData.process),
+          content:'',
+        };
+      })
+    },
+    close_before(done) {
+      this.close();
+      done();
+    },
+    //关闭||清空数据
+    close() {
+      this.$emit('update:visible', false);
+    },
+    // 确定
+    confirm() {
+      if(!this.isOpen&&(!this.form.process||this.form.process==0)){
+          this.$message.error('请输入完成度,并且不能为0');
+          return false;
+      }
+      let params={
+        project_id: this.progressData.id,
+        enable_automatic: this.isOpen? 1:0,
+        automatic_from_plan: this.value==1? 1:0,
+        automatic_from_milestone: this.value==2? 1:0,
+      }
+      this.$axiosUser('POST','api/pro/okr/project/process/config', params).then((res)=>{// 配置
+        if(params.enable_automatic!=1){
+           this.postQq();
+        }else{
+          this.$emit('confirm', {});
+          this.close();
+        }
+      });
+    },
+    postQq(){
+      let params={
+        target_type: 4,
+        target_id:this.progressData.id,
+        process:this.form.process,
+        content: this.form.content,
+      }
+      this.$axiosUser('POST','api/okr/process/create', params).then((res)=>{// 配置
+        this.$emit('confirm', {});
+        this.close();
+      });
+    }
+  }
+};
+</script>
+
+<style scoped lang="scss">
+  .blue-text ::v-deep .el-radio__input.is-checked .el-radio__inner {
+      border-color: #409EFF;
+      background: #409EFF;
+  }
+  .orange-text ::v-deep .el-radio__input.is-checked .el-radio__inner {
+      border-color: #FF9600;
+      background: #FF9600;
+  }
+  .red-text ::v-deep .el-radio__input.is-checked .el-radio__inner {
+      border-color: #f56c6c;
+      background: #f56c6c;
+  }
+  .blue-text ::v-deep .el-radio__input.is-checked+.el-radio__label{
+      color: #409EFF !important;
+  }
+  .orange-text ::v-deep .el-radio__input.is-checked+.el-radio__label{
+      color: #FF9600 !important;
+  }
+  .red-text ::v-deep .el-radio__input.is-checked+.el-radio__label{
+      color: #f56c6c !important;
+  }
+  .add-task-title{
+    padding: 10px;
+    position: relative;
+  }
+  .add-task-title::after{
+    content: "";
+    position: absolute;
+    width: 4px;
+    height: 14px;
+    border-radius: 5px;
+    background-color: #409EFF;
+    left: 0;
+    top: 13px;
+  }
+.dateDateil{
+  border-left:1px solid #f1f1f1;
+  border-right:1px solid #f1f1f1;
+  padding: 0 10px;
+  margin: 0 10px;
+}
+.taskItem{
+  padding: 10px 0;
+  border-top: 1px dotted #f1f1f1;
+  padding-left: 120px;
+}
+.date{
+  width: 80px;
+  background-color: #EDF1FF;
+  color: #409EFF;
+  text-align: center;
+  box-sizing: border-box;
+  border-radius: 25px;
+  margin: 0 10px;
+  line-height: 20px;
+}
+.radio{
+  width: 18px;
+  height: 18px;
+  border-radius: 100%;
+  border: 1px solid #CBCED2;
+  box-sizing: border-box;
+  position: relative;
+}
+.radio-active{
+  border: 1px solid #409EFF;
+}
+.radio-active::after{
+  content: "";
+  position: absolute;
+  width: 10px;
+  height: 10px;
+  border-radius: 100%;
+  background-color: #409EFF;
+  left: 50%;
+  top: 50%;
+  margin-left: -5px;
+  margin-top: -5px;
+}
+::v-deep .el-input-group__append{
+  padding: 0 10px;
+}
+::v-deep .el-collapse-item__header{
+  padding: 10px;
+  height: auto;
+  line-height: normal;
+}
+::v-deep .el-collapse-item__content{
+  padding: 0;
+}
+
+
+::v-deep .el-dialog__body {
+  padding: 20px;
+}
+
+
+::v-deep .el-form-item {
+  margin-bottom: 16px;
+}
+.select{
+  width: 100px;
+}
+.select ::v-deep .el-input__inner{
+  border: none;
+  border-radius: 0px;
+}
+.input ::v-deep .el-input__inner{
+  border: none;
+  border-bottom: 1px dotted #DCDFE6;
+  border-radius: 0px;
+}
+</style>

+ 439 - 0
src/okr/components/project/ProjectTj.vue

@@ -0,0 +1,439 @@
+<template>
+  <div class="br-5 scroll-bar" style="height: calc(100vh - 258px);overflow-y: scroll;">
+    <div class="flex-box-ce header">
+        <div class="tab-item" :class="{'active':0==milestone_id}" @click="milestone_id=0">项目总览</div>
+        <div class="tab-item" :class="{'active':item.id==milestone_id}" v-for="(item, index) in milestoneList" :key="index" @click="milestone_id=item.id">{{item.name}}</div>
+    </div>
+    <div class="flex-box-ce" style="margin-bottom: 10px;" v-loading="loading">
+      <div class="circular_item flex-2" style="margin-right: 10px;height: auto;">
+        <div class="circular_title_left">任务概况</div>
+        <div class="flex-box-ce flex-d-wrap" style="width: 700px;margin: 30px auto;">
+            <div v-for="(item, index) in generalizeList" :key="index" class="generalize-item cursor"  @click="openDetail(item.id)">
+              <div class="fontColorC">{{item.name}}</div>
+              <div style="font-size: 36px;font-weight: 600;margin: 5px 0;" >{{item.val}}</div>
+              <div style="width: 60px;height: 2px;" :style="{backgroundColor:item.color}"></div>
+            </div>
+        </div>
+      </div>
+      <div class="circular_item flex-1" style="height: 322px;">
+          <div class="flex-box">
+              <div class="circular_title_left">任务完成度</div>
+          </div>
+          <div style="text-align: center;margin-top: 50px;position: relative;">
+              <el-progress :width="150" :stroke-width="18" type="circle" :show-text="false" :percentage="Math.ceil(percentage)"></el-progress>
+              <div class="percent">{{percentage}}%</div>
+          </div>
+      </div>
+    </div>
+    <div class="main">
+      <div class="flex-box-ce" style="margin: 20px 0;">
+        <div class="flex-1" style="font-size: 16px;font-weight: 600;">任务明细</div>
+        <el-button type="primary" size="small" @click="exportExcel()">导出全部</el-button>
+      </div>
+      <el-table :data="tableData" style="width: 100%" :loading="loading2">
+        <el-table-column prop="name" label="人员">
+          <template slot-scope="scope"><span>{{ scope.row.name}}</span></template>
+        </el-table-column>
+        <el-table-column prop="dept" label="部门">
+            <template slot-scope="scope">
+                <span>{{ scope.row.depts}}</span>
+            </template>
+        </el-table-column>
+        <el-table-column label="负责任务总数量" prop="total"></el-table-column>
+        <el-table-column prop="finish" label="已完成"></el-table-column>
+        <el-table-column prop="o_process_avg" label="完成率">
+          <template slot-scope="scope">
+              <span>{{ scope.row.rate}}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+      <NoData v-if="tableData.length==0"></NoData>
+      <el-pagination style="text-align: center;margin-top: 20px;"  :current-page.sync="page" :page-sizes="[10, 20, 50, 100]"  layout="total,prev,pager,next,sizes" :total="total" @size-change="handleSizeChange"  @current-change="handleCurrentChange" :page-size="page_size"></el-pagination>
+    </div>
+
+
+    <!-- 里程碑进度 -->
+    <el-dialog title="任务明细" :visible.sync="isShowMp" :append-to-body="true" width="760px">
+      <div>
+          <div class="flex-box-end">
+              <el-select class="select" size="small" v-model="composite_state" placeholder="状态">
+                <el-option :key="0" label="全部" :value="0"></el-option>
+                <el-option v-for="item in taskStatus" :key="item.value" :label="item.name" :value="item.value"></el-option>
+              </el-select>
+              <el-input class="select" size="small" prefix-icon="el-icon-search" placeholder="请输入名字搜索" v-model="keyword" clearable style="width: 200px;"></el-input>
+          </div>
+          <el-table :data="taskList" style="width: 100%" :loading="loading2">
+            <el-table-column prop="name" label="任务名称" min-width="300">
+              <template slot-scope="scope">
+                  <span class="hoverBlue" @click="openTaskDetail(scope.row.id)">{{ scope.row.name}}</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="dept" label="负责人">
+                <template slot-scope="scope">
+                    <span>{{ scope.row.owner_userInfo.name}}</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="进度">
+              <template slot-scope="scope">
+                <Progress  :value="Number(scope.row.process)"></Progress>
+              </template>
+            </el-table-column>
+            <el-table-column prop="end_time" label="截止时间"></el-table-column>
+            <el-table-column prop="o_process_avg" label="状态">
+              <template slot-scope="scope">
+                  <span>{{ scope.row.stateInfo.name}}</span>
+              </template>
+            </el-table-column>
+          </el-table>
+          <NoData v-if="taskList.length==0"></NoData>
+          <el-pagination style="text-align: center;margin-top: 20px;"  :current-page.sync="page2" layout="total,prev,pager,next" :total="total2" @size-change="handleSizeChange2"  @current-change="handleCurrentChange2" :page-size="page_size2"></el-pagination>
+      </div>
+      <div class="flex-box-end" style="margin-top: 30px;">
+        <el-button @click="isShowMp=false">取消</el-button>
+        <el-button type="primary" @click="confirmProcessm('form')">确定</el-button>
+      </div>
+    </el-dialog>
+    <TaskDetail  :visible.sync="isShowTaskDetail" :taskId="taskId" @confirm="closeDetail"></TaskDetail>
+  </div>
+</template>
+
+<script>
+import {getBelongType,getYearArr,getDateStr,taskStatus} from '@/okr/utils/auth';
+import Progress from '@/okr/components/public/Progress'; //进度条
+import TaskDetail from '@/okr/components/public/TaskDetail'; //任务详情
+import {_debounce} from '@/utils/auth';
+export default {
+  name: 'ProjectTj',
+  components: {Progress,TaskDetail},
+  data() {
+    return {
+      getBelongType:getBelongType(false,true),
+      page:1,
+      page_size:10,
+      total:0,
+      page2:1,
+      page_size2:10,
+      total2:0,
+      belong_type: 0,
+      taskStatus:taskStatus(),
+      owner_id:'',
+      loading:false,
+      loading2:false,
+      // 部门可见
+
+      parameter:{
+        cycle_type:0,//	是	string	周期种类 0-不分周期直接取指定年份的 1-年度 2-季度 3-半年度 4-月度
+        year:this.$moment().format('YYYY'),//	是	string	年份
+        half_year:0,//	是	string	半年制 0-不区分 1-上半年 2-下半年
+        quarter:0,//	是	季度 0-不区分 1-第一季度 2-第二季度 3-第三季度 4-第四季度
+        month:0,//	是	月份 0-不区分 1-一月份 2-二月份 ……12-十二月份
+        dept_ids:'',//	否	部门id列表 多个部门以逗号分割
+        employee_ids:'',//	否	员工id列表 多个员工已逗号分割
+      },
+
+      percentage:0,
+      generalizeList:[
+        {name:'总数',val:10,color:'#FF9600'},
+        {name:'已完成',val:10,color:'#409EFF'},
+        {name:'进行中',val:10,color:'#67c23a'},
+        {name:'已逾期',val:10,color:'#f56c6c'},
+        {name:'已取消',val:10,color:'#5F7294'},
+        {name:'未开始',val:10,color:'#5F7294'},
+        {name:'暂停中',val:10,color:'#f56c6c'}
+      ],
+      colorList: [
+        { c1: ' #ccc', c2: '#5F7294' },
+        { c1: ' #ede737', c2: '#FF9600' },
+        { c1: '#89e398', c2: '#67c23a' },
+        { c1: ' #85E9C7', c2: '#409EFF' },
+        { c1: ' #f393a9', c2: '#f56c6c' },
+        { c1: '#e69cf3', c2: '#de43f9' }
+      ],
+
+      tableHeader:['任务名称','负责人','截止时间','状态'],
+      tableData:[],
+      projectId:0,
+      milestoneList:[],
+      milestone_id:0,
+      isShowMp:false,
+      taskList:[],
+      composite_state:0,
+      keyword:'',
+      isShowTaskDetail:false,
+      taskId:0,
+    };
+  },
+  watch:{
+    milestone_id(){
+      this.getData();
+    },
+    composite_state(){
+      this.getTaskList();
+    },
+    keyword:_debounce(function(){
+      this.getTaskList();
+    })
+
+  },
+  methods: {
+    openTaskDetail(id){
+      this.taskId=id;
+      this.isShowTaskDetail=true;
+    },
+    closeDetail(){
+
+    },
+    openDetail(id){
+      this.composite_state=id;
+      this.isShowMp=true;
+      this.getTaskList();
+    },
+    getMilestoneList(){
+      this.$axiosUser('get', '/api/pro/okr/project/milestones',{project_id:this.projectId}).then(res => {
+          let milestones=res.data.data.milestones;
+          this.milestoneList=JSON.parse(JSON.stringify(milestones));
+      })
+    },
+    exportExcel(index) {
+      let token = this.$getToken();
+      this.$axiosUser('get', '/api/pro/code').then(res => {
+        let code=res.data.data.code
+        let url=`&project_id=${this.projectId}
+                &milestone_id=${this.milestone_id}
+                `;
+        window.open(this.$serverdomain +'/api/pro/okr/download/project/statistics?code='+code+url,'_blank');
+      });
+    },
+    getData(is){
+      let params={
+          project_id:this.projectId,
+          milestone_id:this.milestone_id,//	是	string	周期种类 0-不分周期直接取指定年份的 1-年度 2-季度 3-半年度 4-月度
+          page:is? this.page:1,
+          page_size:this.page_size,
+      }
+      this.loading=true;
+      this.$axiosUser('get', '/api/pro/okr/project/statistics', params).then(res => {
+        let data=res.data.data
+        this.generalizeList=[
+          {name:'总数',val:data.total,color:'#FF9600',id:0},
+          {name:'已完成',val:data.finish,color:'#409EFF',id:6},
+          {name:'进行中',val:data.running,color:'#67c23a',id:2},
+          {name:'已逾期',val:data.delay,color:'#f56c6c',id:3},
+          {name:'已取消',val:data.cancel,color:'#5F7294',id:7},
+          {name:'未开始',val:data.waiting,color:'#5F7294',id:1},
+          {name:'暂停中',val:data.stop,color:'#f56c6c',id:4},
+          {name:'已达标',val:data.standard,color:'#409EFF',id:8},
+          {name:'审批中',val:data.reviewing,color:'#67c23a',id:5},
+        ];
+        if(data.total&&data.finish){
+          let du=data.finish/data.total*100;
+          this.percentage=(parseInt(du * 100 ) / 100 ).toFixed(2)
+        }else{
+          this.percentage=0;
+        }
+        this.tableData=res.data.data.employees;
+        this.total=res.data.data.employees_total;
+      }).finally(()=>{
+        this.loading=false;
+      })
+    },
+    getTaskList(is){
+      let params={
+          project_id:this.projectId,
+          milestone_id:this.milestone_id,//	是	string	周期种类 0-不分周期直接取指定年份的 1-年度 2-季度 3-半年度 4-月度
+          composite_state:this.composite_state,//	是	string	年份
+
+          page:is? this.page2:1,
+          page_size:this.page_size2,
+          keyword:this.keyword,
+      }
+      this.loading2=true;
+      this.$axiosUser('get', '/api/pro/okr/project/plans', params).then(res => {
+          let list=res.data.data.list
+          list.forEach(item=>{
+            item.owner_userInfo=this.$getEmployeeMapItem(item.owner_id);
+            item.stateInfo=taskStatus(item.composite_state);
+          })
+          this.taskList=list;
+          this.total2=res.data.data.total
+      }).finally(()=>{
+        this.loading2=false;
+      })
+    },
+    handleSizeChange(val) {
+      this.page=1;
+      this.page_size = val;
+      this.getData();
+    },
+    // 页面跳转
+    handleCurrentChange(val) {
+      this.page = val;
+      this.getData(true);
+    },
+    handleSizeChange2(val) {
+      this.page2=1;
+      this.page_size2 = val;
+      this.getTaskList();
+    },
+    // 页面跳转
+    handleCurrentChange2(val) {
+      this.page2 = val;
+      this.getTaskList(true);
+    }
+  },
+  created() {
+    if(this.$route.query.id){
+      this.projectId=Number(this.$route.query.id);
+      this.getMilestoneList();
+      this.getData();
+    }
+  },
+};
+</script>
+
+<style scoped lang="scss">
+  .tab-item{
+    padding: 7px 20px;
+    text-align: center;
+    background-color: #F7F8FA;
+    margin-right: 10px;
+    border-radius: 20px;
+    cursor: pointer;
+  }
+  .active{
+     background-color: #E1EBFB;
+     color: #409EFF;
+  }
+  .percent{
+    position: absolute;
+    left:50%;
+    z-index: 2;
+    width: 126px;
+    text-align: center;
+    margin-left: -63px;
+    top: 50%;
+    font-size: 24px;
+    font-weight: 700;
+    margin-top: -18px;
+  }
+  .border-l-r{
+    border-left: 1px solid #E9ECF0;
+    border-right: 1px solid #E9ECF0;
+  }
+  .input ::v-deep .el-input__inner {
+    border: none;
+    border-bottom: 1px solid #f1f1f1;
+    border-radius: 0px;
+  }
+  .generalize-item{
+    width: 140px;
+    margin-bottom: 20px;
+  }
+  .table{
+    border-radius: 10px;
+    border: 1px solid #E9ECF0;
+    margin-top: 20px;
+    overflow: hidden;
+    border-bottom: none;
+  }
+ .tableHeader{
+   background-color: #E9ECF0;
+   text-align: center;
+   padding:10px 0;
+ }
+  .tableMain{
+    background-color: #fff;
+    border-bottom: 1px solid #E9ECF0;
+  }
+  .tableMain div{
+    padding: 10px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .border-l-r{
+    border-left: 1px solid #E9ECF0;
+    border-right: 1px solid #E9ECF0;
+  }
+.main{
+}
+.select {
+  width: 100px;
+  margin-right: 6px;
+}
+.select ::v-deep .el-input__inner {
+  border-radius: 25px;
+}
+.circular_item {
+  background-color: #F7F8FA;
+  padding: 20px;
+  border-radius: 5px;
+  box-sizing: border-box;
+  height: 450px;
+}
+.selectBox {
+  background-color: #fff;
+  border-radius: 25px;
+  padding: 10px 20px;
+}
+.header {
+  padding:20px 0 10px 0px;
+}
+.circular_title_left {
+  font-size: 18px;
+  font-weight: 700;
+  margin-bottom: 6px;
+}
+
+.pie {
+  width: 500px;
+  height: 300px;
+  margin: 0 auto;
+  position: relative;
+  top: -20px;
+}
+.emphasis {
+  display: flex;
+  justify-content: center;
+}
+.emphasis:after {
+  clear: both;
+}
+.emphasis_wrap {
+  position: relative;
+}
+.emphasis_relative {
+  position: absolute;
+  width: 120px;
+  left: -60px;
+  top: -180px;
+  text-align: center;
+  font-size: 12px;
+  font-weight: bold;
+}
+
+.legend_item {
+  width: 130px;
+  font-size: 12px;
+  padding: 8px 12px;
+  border: 1px solid #f1f1f1;
+  border-radius: 5px;
+  color: #606266;
+  margin-right: 10px;
+}
+.numVal {
+  font-size: 14px;
+  padding-left: 20px;
+  font-weight: 600;
+  margin-top: 3px;
+}
+.legend_dot {
+  display: inline-block;
+  width: 10px;
+  height: 10px;
+  border: 1px solid transparent;
+  border-radius: 100px;
+  background-color: #409EFF;
+  margin: 0 3px;
+}
+</style>

+ 157 - 0
src/okr/components/project/SelectProjectGj.vue

@@ -0,0 +1,157 @@
+<template>
+  <el-popover placement="bottom-start" trigger="click" v-model="isShow"  @show="showPop">
+    <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="padding: 10px;" v-if="idText!='owner_id'">
+          <div style="margin-bottom: 15px;">项目负责人</div>
+          <el-select class="select" filterable size="small" style="width: 120px;" v-model="owner_id" clearable placeholder="负责人">
+            <el-option v-for="item in employeeOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
+          </el-select>
+      </div>
+      <div style="padding: 10px;" v-if="idText!='joiner_id'">
+          <div style="margin-bottom: 15px;">参与人</div>
+          <el-select class="select" filterable size="small"  style="width: 120px;" v-model="joiner_id" clearable placeholder="负责人">
+            <el-option v-for="item in employeeOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
+          </el-select>
+      </div>
+      <div style="padding: 10px;">
+          <div style="margin-bottom: 15px;">所属部门</div>
+          <el-cascader class="select" size="small" v-model="dept_id" :options="dept_tree" clearable filterable change-on-select placeholder="所属部门"></el-cascader>
+      </div>
+      <div style="padding: 10px;">
+          <div style="margin-bottom: 15px;">日期</div>
+          <el-date-picker class="select" style="width: 280px;" size="small" v-model="dateTime" type="daterange" value-format="yyyy-MM-dd" range-separator="~" start-placeholder="开始日期"end-placeholder="结束日期"></el-date-picker>
+      </div>
+      <div style="padding: 10px;">
+        <div style="margin-bottom: 15px;">目标排序</div>
+        <el-radio-group v-model="sort">
+          <div class="flex-box-ce flex-d-wrap">
+            <el-radio style="margin-bottom: 15px;" v-for="(item, index) in sortArr" :key="index" :label="item.code">{{ item.name }}</el-radio>
+          </div>
+        </el-radio-group>
+      </div>
+      <div class="flex-box-end">
+        <el-button size="small" plain round @click="isShow = false">取 消</el-button>
+        <el-button size="small" type="primary" plain round @click="confirm">确 定</el-button>
+      </div>
+    </div>
+    <template slot="reference">
+      <!-- :class="{update:sort!='ct_desc'}" -->
+      <div class="gd"><i class="el-icon-finished"></i></div>
+    </template>
+  </el-popover>
+</template>
+
+<script>
+export default {
+  name: 'SelectProjectGj',
+  props: {
+    formData: {
+      type: Object,
+      default: ()=>{
+        return {}
+      }
+    },
+    myTargertType:{ //从不同页面加载  1:我的项目 2:公开项目 3全部项目  4部门项目
+      type:Number,
+      default:1
+    },
+    idText:{ //从不同页面加载  1:我的项目 2:我参与的  我发布的
+      type:String,
+      default:''
+    }
+  },
+  data() {
+    return {
+      sortArr: [
+        { name: '创建时间最早', id: 0,code:'ct_asc',},
+        { name: '创建时间最晚', id: 1,code:'ct_desc',},
+        { name: '更新时间最早', id: 2,code:'ut_asc',},
+        { name: '更新时间最晚', id: 3,code:'ut_desc',},
+      ],
+      sort:'ct_desc',
+      isShow: false,
+
+      employeeOptions: this.$getEmployeeMap(),
+
+      dept_id:[],
+      owner_id:'',
+      joiner_id:'',
+      dateTime:[],
+      dept_tree: [],
+    };
+  },
+  created() {
+    if (this.$getCache('dept_tree')) {
+      this.dept_tree = this.getTreeData(this.$getCache('dept_tree'));
+    }
+  },
+  methods: {
+    // 递归判断列表,把最后的children设为undefined
+    getTreeData(data) {
+      for (var i = 0; i < data.length; i++) {
+        if (data[i].children.length < 1) {
+          // children若为空数组,则将children设为undefined
+          data[i].children = undefined;
+        } else {
+          // children若不为空数组,则继续 递归调用 本方法
+          this.getTreeData(data[i].children);
+        }
+      }
+      return data;
+    },
+    confirm() {
+      let item=this.sortArr.filter(item=>{
+        return item.id==this.sort
+      })
+      let data={
+        owner_id:this.owner_id,//	否	string	负责人id 默认为0
+        joiner_id:this.joiner_id,//	否	string	参与者id 默认为0
+        sort:this.sort,
+        dept_id:this.dept_id,
+        dateTime:this.dateTime,
+      }
+      console.log(this.dept_id)
+      this.$emit('confirm',data);
+      this.isShow = false;
+    },
+    showPop() {
+      this.sort = this.formData.sort;
+      this.dept_id = this.formData.dept_id;
+      this.owner_id = this.formData.owner_id;
+      this.joiner_id = this.formData.joiner_id;
+      this.dateTime = this.formData.dateTime;
+      this.isShow = true;
+    },
+  }
+};
+</script>
+
+<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;
+}
+.update::after{
+  content: "";
+  width: 8px;
+  height: 8px;
+  background-color: #f6be19;
+  border-radius: 100%;
+  position: absolute;
+}
+  .select {
+    border-radius: 25px;
+  }
+  .select ::v-deep .el-input__inner {
+    border-radius: 25px;
+  }
+
+</style>

+ 22 - 11
src/okr/components/public/AddTask.vue

@@ -1,7 +1,7 @@
 <template>
   <el-dialog :title="title" :visible.sync="visible_" :close-on-click-modal="false" :before-close="close_before" top="5%" append-to-body  width="660px">
     <div style="max-height: 600px;overflow-y: scroll;" class="scroll-bar">
-      <el-form label-width="80px" :model="form">
+      <el-form label-width="100px" :model="form">
         <div class="add-task-title">基本信息</div>
         <el-form-item label="任务名称">
           <el-input v-model="form.name"  maxlength="100" show-word-limit class="w270" type="textarea" autosize placeholder="请输入任务名称"></el-input>
@@ -29,14 +29,18 @@
                 <div class="showUpdate" v-else ><span class="cursor" @click="isShowRelevanceTask=true">{{motherTaskName}}</span> <i class="el-icon-error cursor"  @click="clearRelevanceTask"></i></div>
             </div>
           </el-form-item>
-          <el-form-item label="所属KR">
+          <el-form-item label="所属KR/项目">
             <div class="cursor" style="border-radius: 4px;border: 1px solid #dcdfe6;line-height: 34px;width: 400px;padding: 0 15px;">
-              <Tooltip preHtml="更改所属KR:请先取消关联母任务" v-if="target_plan_id">
+              <Tooltip preHtml="更改所属KR/项目:请先取消关联母任务" v-if="target_plan_id">
                   <span class="fontColorC">{{krName||'更改所属KR:请先取消关联母任务'}}</span>
               </Tooltip>
               <template v-else>
-                <div class="fontColorC" v-if="!target_id" @click="isShowDateSearch=true">请选择所属KR</div>
-                <div class="showUpdate" v-else ><span class="cursor" @click="isShowDateSearch=true">{{krName}}</span> <i class="el-icon-error cursor"  @click="target_id=0"></i></div>
+                <div class="fontColorC" v-if="!target_id" @click="isShowDateSearch=true">请选择所属KR/项目</div>
+                <div class="showUpdate" v-else>
+                  <span class="blue">{{selectTarget_type==2? 'KR':selectTarget_type==4? '项目':'里程碑'}}:</span>
+                  <span class="cursor" @click="isShowDateSearch=true">{{krName}}</span>
+                  <i class="el-icon-error cursor"  @click="target_id=0"></i>
+                </div>
               </template>
             </div>
           </el-form-item>
@@ -125,7 +129,7 @@
     <RelevanceTask :visible.sync="isShowRelevanceTask"  :id="target_plan_id" @confirm="ActiveRelevanceTask"></RelevanceTask>
 
     <!-- 对齐目标 -->
-    <TargetSearch :visible.sync="isShowDateSearch" title="所属KR" :showType="2" @confirm="confirmTarget"></TargetSearch>
+    <TargetSearch :visible.sync="isShowDateSearch" title="所属KR" :showSelectType="3" :showType="2" @confirm="confirmTarget"></TargetSearch>
 
     <!-- 选择负责人 -->
     <EmployeeSelector :is_filtration_creator="false" title="选择人员" :isChecKedAll="false" :isRequired="selectUserIndex==1" :multi="false" :selected="employee_selected" :visible.sync="show_employee_selector" @confirm="employee_confirm"/>
@@ -162,7 +166,7 @@ export default {
       type: Boolean,
       default: false
     },
-    target_type:{ //	计划绑定的对象种类 1-目标 2-KR 3-计划(分解计划下的子计划的时候) 0不绑定
+    target_type:{ //	0-不绑定对象 1-目标 2-KR 3-计划(分解计划下的子计划的时候) 4-项目 5-里程碑
       type: Number,
       default: 2
     },
@@ -293,7 +297,7 @@ export default {
       target_id:0,
       krName:'',
       isShowDateSearch:false,
-      
+      selectTarget_type:0,//选择的关联的类型
     };
   },
   watch: {
@@ -323,7 +327,13 @@ export default {
     getTaskDateil(id){
        this.$axiosUser('get', '/api/pro/okr/plan/info',{plan_id:id}).then(res => {
          let data=res.data.data
-         this.krName=data.kr_name;
+         if(data.kr_id){ //kr
+           this.krName=data.kr_name;
+         }else if(data.milestone_id){ //里程碑
+           this.krName=data.milestone_name;
+         }else if(data.project_id){ //项目
+           this.krName=data.project_name;
+         }
        })
     },
     clearRelevanceTask(){
@@ -336,13 +346,12 @@ export default {
       this.motherTaskName=item.name;
       this.target_plan_id=item.id;
 
-      // KR
-      // this.krName=item.name;
       this.target_id=0;
     },
     confirmTarget(item){
       this.krName=item.item.name;
       this.target_id=item.item.id;
+      this.selectTarget_type=item.type;
     },
     initData(){
       this.isShowGd=false;
@@ -350,6 +359,7 @@ export default {
       this.fileList=[];
       this.krName='';
       this.target_id=0;
+      this.selectTarget_type=0;
       this.motherTaskName='';
       this.target_plan_id=0;
       this.isShowImg=false;
@@ -585,6 +595,7 @@ export default {
       if(this.isShowGl){
         if(this.target_id){ //kr
           params.target_id=this.target_id;
+          params.target_type=this.selectTarget_type;
         }else{
           params.target_type=0;
         }

+ 5 - 1
src/okr/components/public/CopyTarget.vue

@@ -535,7 +535,11 @@ export default {
         this.form.o_id='';
         this.form.kr_id='';
         this.alignItem=e.item;
-        e.type==1? this.form.o_id=e.item.id:this.form.kr_id=e.item.id
+        if(e.type==1){
+          this.form.o_id=e.item.id
+        }else if(e.type==2){
+          this.form.kr_id=e.item.id
+        }
     },
     getTargetDateil(){
       this.$axiosUser('get', '/api/pro/okr/obj/detail', {object_id:this.id}).then(res => {

+ 1 - 1
src/okr/components/public/Schedule.vue

@@ -15,7 +15,7 @@
           </el-input>
         </el-form-item>
         <el-form-item label="进展" v-if="!isOpen">
-          <el-input size="small" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" v-model="form.content" maxlength="100" show-word-limit placeholder="请输入进展与障碍"></el-input>
+          <el-input size="small" :autosize="{ minRows: 2, maxRows: 4}" type="textarea" v-model="form.content" maxlength="500" show-word-limit placeholder="请输入进展与障碍"></el-input>
         </el-form-item>
       </el-form>
       <!-- <div style="text-align: right;" v-if="isShow"><span class="blue cursor">目标完成度自动更新设置 <i class="el-icon-arrow-down"></i></span></div> -->

+ 63 - 19
src/okr/components/public/TargetDetail.vue

@@ -51,9 +51,7 @@
                          <span v-else>{{$getDept(targetDetail.dept_id).name}}</span>
                         </div>
                      </TargetType>
-                     <!-- <SelectPeriod style="margin: 0 20px;" :dateParameter="addDateParameter" :isShowAll="false" :id="1" @confirm="dateConfirm"> -->
-                         <div style="margin: 0 20px;" class="hover-border" @click="opneSelectPeriod(targetDetail)"><i class="el-icon-alarm-clock"></i><span>{{targetDetail.year}}年 {{targetDetail.dateStr}} ({{targetDetail.start_time}}~{{targetDetail.end_time}})</span></div>
-                     <!-- </SelectPeriod> -->
+                      <div style="margin: 0 20px;" class="hover-border" @click="opneSelectPeriod(targetDetail)"><i class="el-icon-alarm-clock"></i><span>{{targetDetail.year}}年 {{targetDetail.dateStr}} ({{targetDetail.start_time}}~{{targetDetail.end_time}})</span></div>
                     </div>
                      <div v-if="targetDetail.score>=0" class="orange-box" style="width: 40px;border-radius: 25px;text-align: center;margin-right: 10px;">{{targetDetail.score}}</div>
                      <div @click="updateProgress(targetDetail,1)" class="flex-box-ce" :class="{cursor:isOperation}">
@@ -76,7 +74,6 @@
 
               <div class="main-main">
                 <template v-if="tabsIndex == 1">
-                  <!-- <div class="orange" v-if="getKrWeight!=100">当前KR权重之和未达100%,请调整!</div> -->
                   <div class="border-bottom">
                     <div class="okrs-box" v-for="(krItem, index) in krsList" :key="index">
                       <div class="flex-box">
@@ -240,8 +237,20 @@
                         <span class="okr-index">KR{{ index + 1 }}</span>
                         <div class="flex-1 clamp2 fontColorC cursor" style="padding-right: 30px;" @click="initData(2,item)">{{item.name}}</div>
                         <template v-if="item.can_edit">
-                          <el-button size="mini" type="primary" @click="handleCommandTask('a', item)" plain round>添加任务</el-button>
-                          <el-button size="mini" type="success" @click="handleCommandTask('b', item)" plain round>关联任务</el-button>
+                          <el-dropdown @command="handleCommandTask($event,item)">
+                            <el-button type="primary" size="mini" plain round icon="el-icon-plus">添加</el-button>
+                            <el-dropdown-menu slot="dropdown">
+                              <el-dropdown-item command="a">添加任务</el-dropdown-item>
+                              <el-dropdown-item command="b">添加项目</el-dropdown-item>
+                            </el-dropdown-menu>
+                          </el-dropdown>
+                          <el-dropdown @command="handleCommandTask($event,item)">
+                            <el-button style="margin-left: 10px;" type="success" size="mini" plain round icon="el-icon-link">关联</el-button>
+                            <el-dropdown-menu slot="dropdown">
+                              <el-dropdown-item command="c">关联任务</el-dropdown-item>
+                              <el-dropdown-item command="d">关联项目</el-dropdown-item>
+                            </el-dropdown-menu>
+                          </el-dropdown>
                         </template>
                       </div>
                       <template v-if="item.plans.length>0">
@@ -606,15 +615,24 @@
                 <div style="position: relative;padding: 15px 0;">
                   <div class="fontColorC" v-for="(item, index) in krsListAll2" :key="index">
                     <div class="flex-box-ce kr-item2">
-<!--                      <div style="width: 30px;height: 30px;">
-                        <i class="caret" :class="item.isShow ? 'el-icon-caret-bottom' : 'el-icon-caret-right'" @click="showTask(item)" v-if="item.plans.length>0"></i>
-                      </div> -->
-                      <!-- <span class="okr-index">KR{{ index + 1 }}</span> -->
-                      <!-- <div class="flex-1 clamp2 fontColorC cursor" style="padding-right: 30px;" @click="initData(2,item)">{{item.name}}</div> -->
                       <div class="flex-1"></div>
                       <template v-if="item.can_edit">
-                        <el-button size="mini" type="primary" @click="handleCommandTask('a', item)" plain round>添加任务</el-button>
-                        <el-button size="mini" type="success" @click="handleCommandTask('b', item)" plain round>关联任务</el-button>
+                        <el-dropdown @command="handleCommandTask($event,item)">
+                          <el-button type="primary" size="mini" plain round icon="el-icon-plus">添加</el-button>
+                          <el-dropdown-menu slot="dropdown">
+                            <el-dropdown-item command="a">添加任务</el-dropdown-item>
+                            <el-dropdown-item command="b">添加项目</el-dropdown-item>
+                          </el-dropdown-menu>
+                        </el-dropdown>
+                        <el-dropdown @command="handleCommandTask($event,item)">
+                          <el-button style="margin-left: 10px;" type="success" size="mini" plain round icon="el-icon-link">关联</el-button>
+                          <el-dropdown-menu slot="dropdown">
+                            <el-dropdown-item command="c">关联任务</el-dropdown-item>
+                            <el-dropdown-item command="d">关联项目</el-dropdown-item>
+                          </el-dropdown-menu>
+                        </el-dropdown>
+<!--                        <el-button size="mini" type="primary" @click="handleCommandTask('a', item)" plain round>添加任务</el-button>
+                        <el-button size="mini" type="success" @click="handleCommandTask('b', item)" plain round>关联任务</el-button> -->
                       </template>
                     </div>
                     <template v-if="item.plans.length>0">
@@ -624,8 +642,7 @@
                   </div>
                 </div>
               </template>
-
-              <template v-if="tabsIndex2==1">
+              <template v-if="tabsIndex2 == 1">
                   <Evolve :target_id="kr_id" :target_type="2" :isOperation="isOperation&&!krDetail.process_conf.enable_automatic&&krDetail.can_edit"></Evolve>
               </template>
               <template v-if="tabsIndex2 == 2">
@@ -756,7 +773,7 @@
     <!-- 编辑进展 -->
     <el-dialog title="编辑目标进展" :visible.sync="isShowProcess" :append-to-body="true" width="500px">
       <div>
-        <el-input v-model="processInfo.content"  maxlength="100" show-word-limit :rows="3" type="textarea" clearable placeholder="请输入进展与障碍"></el-input>
+        <el-input v-model="processInfo.content"  maxlength="500" show-word-limit :rows="3" type="textarea" clearable placeholder="请输入进展与障碍"></el-input>
       </div>
       <div class="flex-box-end" style="margin-top: 20px;">
         <el-button @click="isShowProcess=false">取消</el-button>
@@ -771,6 +788,12 @@
     <EmployeeSelector :is_filtration_creator="false" title="选择人员" :isChecKedAll="false" :isRequired="true" :multi="false" :selected="employee_selected" :visible.sync="show_employee_selector" @confirm="employee_confirm"/>
 
     <EmployeeSelector :isRequired="true" :isChecKedAll="false" :can_select_employee="false" :can_select_dept="true" :multi="true" :selected="dept_selected" :visible.sync="show_dept_selector"@confirm="dept_confirm"/>
+
+    <!-- 添加项目 -->
+    <AddProject :visible.sync="isShowProjectAdd" :kr_id="selectKrItem.id" @confirm="ActiveAddProject"></AddProject>
+
+    <!-- 对齐目标 -->
+    <TargetSearch :visible.sync="isShowProject" :showSelectType="2" @confirm="confirmProject"></TargetSearch>
   </div>
 </template>
 <script>
@@ -793,13 +816,13 @@ import Evolve from '@/okr/components/TargetDetail/Evolve'; //进展
 import Replay from '@/okr/components/TargetDetail/Replay'; //复盘
 import CopyTarget from '@/okr/components/public/CopyTarget'; //复制目标
 import RelevanceTask from '@/okr/components/public/RelevanceTask'; //关联任务
-
+import AddProject from '@/okr/components/project/AddProject'; //
 import { getPointArr,getScopeArr,getBelongType,getDateStr,taskStatus,getOperation } from '@/okr/utils/auth';
 import EmployeeSelector from '@/components/EmployeeSelector';
 import {_debounce} from '@/utils/auth';
 export default {
   name: 'TargetDetail',
-  components: {RelevanceTask,CopyTarget, AddTask,TargetSearch,Replay,Evolve,Interaction,InputBox, TargetType, SelectPeriod, Schedule, AligningTargetDetail, Rate, AddOkr, Progress, CollapseTransition, Tooltip, EmployeeSelector, Record },
+  components: {AddProject,RelevanceTask,CopyTarget, AddTask,TargetSearch,Replay,Evolve,Interaction,InputBox, TargetType, SelectPeriod, Schedule, AligningTargetDetail, Rate, AddOkr, Progress, CollapseTransition, Tooltip, EmployeeSelector, Record },
   props: {
     showDrawer: {
       type: Boolean,
@@ -927,6 +950,9 @@ export default {
       isBjQz:false,//是否能编辑权重
 
       o_pid:0,//对齐的ID
+      // 项目
+      isShowProjectAdd:false,
+      isShowProject:false,
     };
   },
   mounted() {
@@ -1004,6 +1030,16 @@ export default {
     },
   },
   methods: {
+    // 关联项目
+    confirmProject(item){
+        this.$axiosUser('post', '/api/pro/okr/project/bind',{project_id:item.item.id,kr_id:this.selectKrItem.id}).then(res => {
+             this.updateFun()
+        });
+    },
+    //添加项目
+    ActiveAddProject(){
+      this.updateFun()
+    },
     customColorMethod(index) {
       if (index ==1) {
         return '#2879ff';
@@ -1388,10 +1424,18 @@ export default {
             this.selectKrItem=item;
             this.isShowAddTask=true;
             break
-        case 'b'://关联任务
+        case 'b'://添加项目
+            this.selectKrItem=item;
+            this.isShowProjectAdd=true;
+            break
+        case 'c'://关联任务
             this.selectKrItem=item;
             this.isShowRelevanceTask=true;
             break
+        case 'd'://关联项目
+            this.selectKrItem=item;
+            this.isShowProject=true;
+            break
       }
     },
     //kr评分

+ 251 - 66
src/okr/components/public/TargetSearch.vue

@@ -1,52 +1,93 @@
 <template>
   <el-dialog :title="title" :visible.sync="visible_" :close-on-click-modal="false" :before-close="close_before" append-to-body @open="openDialog" width="760px">
-    <div class="flex-box-ce" style="padding-bottom: 10px;">
-      <el-select class="select" size="small" v-model="year" placeholder="年份">
-        <el-option  v-for="item in yearArr" :key="item.value" :label="item.label":value="item.value"></el-option>
-      </el-select>
-      <el-select class="select" size="small" v-model="dataId"  placeholder="周期">
-        <el-option  v-for="item in options" :key="item.value" :label="item.name":value="item.value"></el-option>
-      </el-select>
-<!--      <el-select class="select" size="small" v-model="value" clearable placeholder="状态">
-        <el-option  v-for="item in options" :key="item.value" :label="item.label":value="item.value"></el-option>
-      </el-select> -->
-      <el-select class="select" size="small" v-model="belong_type" placeholder="类型">
-        <el-option  v-for="item in getBelongType" :key="item.value" :label="item.label":value="item.value"></el-option>
-      </el-select>
-      <el-select class="select" style="width: 200px;" filterable size="small" v-model="owner_id" clearable placeholder="负责人">
-        <el-option  v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"></el-option>
-      </el-select>
-      <el-input class="input" style="width: 200px;" maxlength="10" show-word-limit size="small" v-model="key_word" clearable placeholder="请输入关键字"/>
-    </div>
-    <div style="height: 400px;overflow-y: scroll;" class="scroll-bar">
-      <el-collapse v-model="activeNames">
-        <el-collapse-item v-for="(item,index) in targetList" :key="index" :name='item.id' :class="{items:showType==1}">
-          <template slot="title">
-            <div style="font-size: 13px;">
-              <div class="flex-box-ce" style="margin-bottom: 10px;">
-                  <template v-if="showType!=2">
-                    <div class="radio" :class="{'radio-active':(selectId==item.id&&type==item.type)}" @click.stop="activeId(item,1)"></div>
-                    <div class="date">{{item.dateStr}}</div>
-                  </template>
-                  <Tooltip :preHtml="item.name"><span class="flex-1">{{item.name}}</span></Tooltip>
-              </div>
-              <div class="flex-box-ce fontColorC" :style="{paddingLeft:showType==2? '0px':'120px'}" >
-                <div>{{item.userInfo.name}}</div>
-                <div class="dateDateil">{{item.start_time}}~{{item.end_time}}</div>
-              </div>
-            </div>
-          </template>
-          <div v-loading="loading">
-              <div v-if="showType!=1" class="flex-box-ce taskItem" v-for="(item2,index2) in item.krs" :key="index2" :style="{paddingLeft:showType==2? '40px':'120px'}">
-                <div class="radio" style="margin-right: 10px;" :class="{'radio-active':(selectId==item2.id&&type==item2.type)}" @click.stop="activeId(item2,2)"></div>
-                <div class="flex-1">KR{{index2+1}}:{{item2.name}}</div>
-              </div>
-          </div>
-        </el-collapse-item>
-      </el-collapse>
-      <NoData v-if="targetList.length==0"></NoData>
-    </div>
-    <el-pagination style="text-align: center;margin-top: 20px;" :current-page.sync="page" :page-sizes="[10, 20, 50, 100]"  layout="total,prev,pager,next,sizes" :total="total" @size-change="handleSizeChange"  @current-change="handleCurrentChange" :page-size="page_size"></el-pagination>
+     <el-tabs v-model="activeName">
+       <el-tab-pane label="关联OKR" name="okr" v-if="showSelectType==1||showSelectType==3">
+         <div class="flex-box-ce" style="padding-bottom: 10px;margin-top: 20px;">
+           <el-select class="select" size="small" v-model="year" placeholder="年份">
+             <el-option  v-for="item in yearArr" :key="item.value" :label="item.label":value="item.value"></el-option>
+           </el-select>
+           <el-select class="select" size="small" v-model="dataId"  placeholder="周期">
+             <el-option  v-for="item in options" :key="item.value" :label="item.name":value="item.value"></el-option>
+           </el-select>
+           <el-select class="select" size="small" v-model="belong_type" placeholder="类型">
+             <el-option  v-for="item in getBelongType" :key="item.value" :label="item.label":value="item.value"></el-option>
+           </el-select>
+           <el-select class="select" style="width: 200px;" filterable size="small" v-model="owner_id" clearable placeholder="负责人">
+             <el-option  v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"></el-option>
+           </el-select>
+           <el-input class="input" style="width: 200px;" maxlength="10" show-word-limit size="small" v-model="key_word" clearable placeholder="请输入关键字"/>
+         </div>
+         <div style="height: 400px;overflow-y: scroll;" class="scroll-bar">
+           <el-collapse v-model="activeNames">
+             <el-collapse-item v-for="(item,index) in targetList" :key="index" :name='item.id' :class="{items:showType==1}">
+               <template slot="title">
+                 <div style="font-size: 13px;">
+                   <div class="flex-box-ce" style="margin-bottom: 10px;">
+                       <template v-if="showType!=2">
+                         <div class="radio" :class="{'radio-active':(selectId==item.id&&type==item.type)}" @click.stop="activeId(item,1)"></div>
+                         <div class="date">{{item.dateStr}}</div>
+                       </template>
+                       <Tooltip :preHtml="item.name"><span class="flex-1">{{item.name}}</span></Tooltip>
+                   </div>
+                   <div class="flex-box-ce fontColorC" :style="{paddingLeft:showType==2? '0px':'120px'}" >
+                     <div>{{item.userInfo.name}}</div>
+                     <div class="dateDateil">{{item.start_time}}~{{item.end_time}}</div>
+                   </div>
+                 </div>
+               </template>
+               <div v-loading="loading">
+                   <div v-if="showType!=1" class="flex-box-ce taskItem" v-for="(item2,index2) in item.krs" :key="index2" :style="{paddingLeft:showType==2? '40px':'120px'}">
+                     <div class="radio" style="margin-right: 10px;" :class="{'radio-active':(selectId==item2.id&&type==item2.type)}" @click.stop="activeId(item2,2)"></div>
+                     <div class="flex-1">KR{{index2+1}}:{{item2.name}}</div>
+                   </div>
+               </div>
+             </el-collapse-item>
+           </el-collapse>
+           <NoData v-if="targetList.length==0"></NoData>
+         </div>
+         <el-pagination style="text-align: center;margin-top: 20px;" :current-page.sync="page" :page-sizes="[10, 20, 50, 100]"  layout="total,prev,pager,next,sizes" :total="total" @size-change="handleSizeChange"  @current-change="handleCurrentChange" :page-size="page_size"></el-pagination>
+       </el-tab-pane>
+       <el-tab-pane label="关联项目" name="project" v-if="showSelectType==2||showSelectType==3">
+         <div class="flex-box-ce" style="padding-bottom: 10px;margin-top: 20px;">
+          <el-select class="select" size="small" v-model="projectData.composite_state" placeholder="状态" style="width: 100px;">
+            <el-option v-for="item in statusArr" :key="item.value" :label="item.label"  :value="item.value"></el-option>
+          </el-select>
+           <el-cascader class="select"  style="width: 200px;" size="small" v-model="projectData.dept_id" :options="dept_tree" clearable filterable change-on-select placeholder="所属部门"></el-cascader>
+           <el-select class="select"  filterable size="small" v-model="projectData.owner_id" clearable placeholder="负责人">
+             <el-option  v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"></el-option>
+           </el-select>
+           <div class="flex-1"></div>
+           <el-input class="input" style="width: 200px;" maxlength="20" show-word-limit size="small" v-model="projectData.keyword" clearable placeholder="请输入项目名称关键字"/>
+         </div>
+         <div style="height: 400px;overflow-y: scroll;" class="scroll-bar">
+           <el-collapse v-model="activeNames2" @change="openM">
+             <el-collapse-item v-for="(item,index) in projectList" :key="index" :name='item.id' :class="{items:showSelectType==2}">
+               <template slot="title">
+                 <div style="font-size: 13px;">
+                   <div class="flex-box-ce" style="margin-bottom: 10px;">
+                       <div class="radio" style="margin-right: 10px;" :class="{'radio-active':(selectId==item.id&&type==item.type)}" @click.stop="activeId(item,4)"></div>
+                       <Tooltip :preHtml="item.name"><span class="flex-1">{{item.name}}</span></Tooltip>
+                   </div>
+                   <div class="flex-box-ce fontColorC">
+                     <div>{{item.userInfo.name}}</div>
+                     <div class="dateDateil">{{item.start_date}}~{{item.end_date}}</div>
+                   </div>
+                 </div>
+               </template>
+               <div v-loading="loading" v-if="showSelectType!=2">
+                   <div class="flex-box-ce taskItem" style="padding-left: 40px;" v-for="(item2,index2) in item.tasks" :key="index2">
+                     <div class="radio" style="margin-right: 10px;" :class="{'radio-active':(selectId==item2.id&&type==item2.type)}" @click.stop="activeId(item2,5)"></div>
+                     <div class="flex-1">KR{{index2+1}}:{{item2.name}}</div>
+                   </div>
+                   <div v-if="item.statistics&&item.statistics.milestone_total==0" class="fontColorC" style="padding: 10px 0;text-align: center;">暂无里程碑</div>
+               </div>
+             </el-collapse-item>
+           </el-collapse>
+           <NoData v-if="projectList.length==0"></NoData>
+         </div>
+         <el-pagination style="text-align: center;margin-top: 20px;" :current-page.sync="page2" :page-sizes="[10, 20, 50, 100]"  layout="total,prev,pager,next,sizes" :total="total2" @size-change="handleSizeChange2"  @current-change="handleCurrentChange2" :page-size="page_size2"></el-pagination>
+       </el-tab-pane>
+     </el-tabs>
 
     <div class="flex-box-ce flex-box-end" style="margin: 15px">
       <el-button @click="close">取 消</el-button>
@@ -67,7 +108,7 @@ export default {
       type: String,
       default: '对齐目标'
     },
-    showType: {// 0显示OKR 1显示O 2显示KR
+    showType: {// 0显示OKR 1显示O 2显示KR
       type: Number,
       default: 0,
     },
@@ -91,7 +132,11 @@ export default {
     isOwner:{ //负责人是否默认选中自己
       type: Boolean,
       default: false
-    }
+    },
+    showSelectType: {// 1只能关联OKR  2能关联项目  3两个都可以
+      type: Number,
+      default: 1,
+    },
   },
   components:{CollapseTransition,Tooltip},
   name: 'TargetSearch',
@@ -100,6 +145,9 @@ export default {
       page:1,
       page_size:10,
       total:0,
+      page2:1,
+      page_size2:10,
+      total2:0,
       visible_: false,
       loading:false,
       key_word:'',
@@ -134,10 +182,30 @@ export default {
       dataId: 1,
       targetList:[],
       selectId:'',//选择对齐的ID
-      selectType:'',
       selectItme:{},
       activeNames:[],
-      type:'o'
+      activeNames2:[],
+      type:1,
+      activeName:'okr',
+
+      projectData:{
+        owner_id:'',
+        composite_state:0,
+        dept_id:0,
+        keyword:'',
+        page:1,
+        page_size:10,
+        total:0,
+      },
+      dept_tree:[],
+      statusArr: [
+        {value: 0,label: '全部状态'},
+        {value: 1,label: '未开始'},
+        {value: 2,label: '进行中'},
+        {value: 3,label: '已逾期'},
+        {value: 4,label: '已完成'},
+      ],
+      projectList:[],
     };
   },
   watch: {
@@ -165,15 +233,112 @@ export default {
       if(val&&this.showType==2){
           this.owner_id=this.$userInfo().id;
       }
-    }
+      if(this.showSelectType==2){
+        this.activeName='project';
+      }else{
+        this.activeName='okr';
+      }
+    },
+    'projectData.owner_id'(val){
+      this.getProjectList();
+    },
+    'projectData.composite_state'(val){
+      this.getProjectList();
+    },
+    'projectData.keyword': {
+      deep: true,
+      handler: _debounce(function(val) {
+        this.getProjectList();
+      })
+    },
+    'projectData.dept_id'(val){
+      this.getProjectList();
+    },
+
   },
   created() {
     let userInfo=this.$userInfo();
     if(this.isOwner&&!userInfo.is_okr_manager){
        this.owner_id=userInfo.id
     }
+    if (this.$getCache('dept_tree')) {
+      this.dept_tree = this.getTreeData(this.$getCache('dept_tree'));
+    }
   },
   methods: {
+    openM(e){
+      if(e&&e.length>0){
+        let is=true;
+        this.projectList.forEach(item=>{
+          if(item.id==e[e.length-1]&&item.statistics.milestone_total==0){
+            is=false;
+          }
+          if(item.id==e[e.length-1]&&item.tasks&&item.tasks.length>0){
+            is=false;
+          }
+        })
+        if(is){
+          this.getMilestoneList(e[e.length-1])
+        }
+      }
+    },
+
+    getMilestoneList(id){
+      this.$axiosUser('get', '/api/pro/okr/project/milestones',{project_id:id}).then(res => {
+          let milestones=res.data.data.milestones;
+          milestones.forEach(item=>{
+            item.type=5;
+          })
+          this.projectList.forEach(item=>{
+            if(item.id==id){
+              item.tasks=milestones;
+            }
+          })
+          this.activeNames2=JSON.parse(JSON.stringify(this.activeNames2))
+      })
+    },
+
+    getProjectList(is){
+      is? '':this.page2=1;
+      let data={
+        page:is? this.page2:1,
+        page_size:this.page_size2,
+        scope_type:1,//	否	string	可见范围 1-相关人员可见 2-全员可见 默认为0不区分
+        composite_state:this.projectData.composite_state,//	否	strin g	综合状态 1-未开始 2-进行中 3-逾期 4-达标 默认为0不区分
+        dept_id:this.projectData.dept_id? this.projectData.dept_id[this.projectData.dept_id.length-1]:0,//	否	string	部门id,默认为0
+        // sort:this.projectData.sort,//	否	string	排序 ct_asc-创建时间正序 ct_desc-创建时间倒序 up_asc-更新时间正序 up_desc-更新时间倒序 默认为ct_desc
+        keyword:this.projectData.keyword,//	否	string	关键字
+        visible:1,
+      }
+      if(this.projectData.owner_id){
+        data.owner_id=this.projectData.owner_id
+      }
+      this.loading = true;
+      this.$axiosUser('get','api/pro/okr/project/list',data).then(res => {
+        let list=res.data.data.list;
+        list.forEach(item=>{
+          item.userInfo=this.$getEmployeeMapItem(item.owner_id);
+          item.type=4;
+          item.tasks=[];
+        })
+        this.projectList=list;
+        this.total2=res.data.data.total;
+      }).finally(()=>{
+         this.loading = false;
+      })
+    },
+    getTreeData(data) {
+      for (var i = 0; i < data.length; i++) {
+        if (data[i].children.length < 1) {
+          // children若为空数组,则将children设为undefined
+          data[i].children = undefined;
+        } else {
+          // children若不为空数组,则继续 递归调用 本方法
+          this.getTreeData(data[i].children);
+        }
+      }
+      return data;
+    },
     handleSizeChange(val) {
       this.page=1;
       this.page_size = val;
@@ -184,6 +349,16 @@ export default {
       this.page = val;
       this.getTargetList(true);
     },
+    handleSizeChange2(val) {
+      this.page2=1;
+      this.page_size2 = val;
+      this.getProjectList();
+    },
+    // 页面跳转
+    handleCurrentChange2(val) {
+      this.page2 = val;
+      this.getProjectList(true);
+    },
     handleChange(e){
       if(e){
         let obj={};
@@ -207,12 +382,10 @@ export default {
       if(item.id==this.selectId&&item.type==this.type){
         this.selectItme={};
         this.selectId='';
-        this.selectType='';
         this.type='';
       }else{
         this.selectItme=item;
         this.selectId=item.id;
-        this.selectType=type;
         this.type=item.type;
       }
     },
@@ -220,13 +393,13 @@ export default {
     openDialog() {
      if(this.id){
        this.selectId=this.id
-       this.type='o';
+       this.type=1;
      }else{
        this.selectId=''
      }
       this.selectItme={},
-      this.selectType=''
       this.getTargetList();
+      this.getProjectList();
     },
     //目标列表
     getTargetList(is) {
@@ -261,7 +434,6 @@ export default {
         params.cycle_type=1
       }
       this.selectItme={};
-      this.selectType='';
     	this.$axiosUser('get', '/api/pro/okr/obj/list', params).then(res => {
         if (res.data.code == 1) {
           let list=res.data.data.list
@@ -270,10 +442,10 @@ export default {
             item.isShow=false;
             item.userInfo=this.$getEmployeeMapItem(item.owner_id);
             item.dateStr=getDateStr(item);
-            item.type="o"
+            item.type=1
             if(item.krs.length>0){
               item.krs.forEach((e)=>{
-                e.type="kr"
+                e.type=2
               })
             }
           })
@@ -292,15 +464,20 @@ export default {
     },
     // 确定
     confirm() {
-      let userInfo=this.$userInfo();
-      if(this.isJudge&&!userInfo.is_okr_manager){
-        let {publisher_id,owner_id}=this.selectItme;
-        if(publisher_id!=userInfo.id&&owner_id!=userInfo.id){
-          this.$message.error("不可操作");
-          return false
+      if(this.activeName=='okr'){
+        let userInfo=this.$userInfo();
+        if(this.isJudge&&!userInfo.is_okr_manager){
+          let {publisher_id,owner_id}=this.selectItme;
+          if(publisher_id!=userInfo.id&&owner_id!=userInfo.id){
+            this.$message.error("不可操作");
+            return false
+          }
         }
+        this.$emit('confirm', {item:this.selectItme,type:this.type});
+      }else{
+
+        this.$emit('confirm', {item:this.selectItme,type:this.type});
       }
-      this.$emit('confirm', {item:this.selectItme,type:this.selectType});
       this.close();
     }
   }
@@ -308,6 +485,13 @@ export default {
 </script>
 
 <style scoped lang="scss">
+ ::v-deep .el-tabs__nav .is-active {
+      font-size: 16px;
+      font-weight: 600;
+  }
+::v-deep .el-dialog__header{
+  display: none !important;
+}
 .items ::v-deep .el-collapse-item__arrow{
   display: none !important;
 }
@@ -368,6 +552,7 @@ export default {
 
 ::v-deep .el-dialog__body {
   padding: 10px 20px;
+  min-height: 630px;
 }
 .select{
   width: 100px;

+ 62 - 12
src/okr/components/public/TaskDetail.vue

@@ -134,12 +134,12 @@
                           <span slot="append">{{unit}}</span>
                         </el-input>
                         <div style="height:10px;"></div>
-                        <el-input type="textarea" v-model="content" rows="4" placeholder="请输入进展与障碍" maxlength="100" show-word-limit></el-input>
+                        <el-input type="textarea" v-model="content" rows="4" placeholder="请输入进展与障碍" maxlength="500" show-word-limit></el-input>
                         <div style="margin-top: 16px;">
                           <uploadOss :file-list="files_lh" :headers="$xtoken" :action="$action" :limit="3" :accept="$acceptImgFile" :multiple="true" :on-success="handleFilesSuccess" :before-upload="beforeFilesUpload2">
                                <span class="fontColorC"><i class="el-icon-paperclip green" style="padding-right: 5px;"></i>请添加附件</span>
                           </uploadOss>
-                          
+
                           <div v-if="files_lh_list.length>0" class="flex-box-ce flex-d-wrap">
                               <div class="files-box flex-box-ce" v-for="(item,index) in files_lh_list" :key="index">
                                 <el-image v-if="item.type=='img'" style="width: 30px; height: 30px;cursor: pointer;" :src="item.file" :preview-src-list="[item.file]"></el-image>
@@ -152,7 +152,7 @@
                                 </Tooltip>
                               </div>
                           </div>
-                          
+
                         </div>
                         <div class="flex-box-end" style="margin-top: 20px;">
                           <el-button size="mini" round @click="isShowResult=false">取消</el-button>
@@ -316,7 +316,7 @@
                   </div>
                 </div>
                 <div class="flex-box-ce kr-message-item">
-                  <div class="label"><svg-icon icon-class="#icon-biaoqian_guanlizhongxin" class="svgIcon"></svg-icon>&nbsp;所属KR
+                  <div class="label"><svg-icon icon-class="#icon-biaoqian_guanlizhongxin" class="svgIcon"></svg-icon>&nbsp;所属KR/项目
                       <el-tooltip placement="top">
                         <div slot="content">所属KR:<br/>· 在KR下添加任务时,默认属于当前KR <br/>· 所属KR开启按任务自动更新进度后,仅最顶级的母任务影响所属KR的进度</div>
                         <i class="el-icon-question fontColorD"></i>
@@ -340,6 +340,40 @@
                         </template>
                       </template>
                     </span>
+                    <span v-else-if="taskDetail.main.milestone_id" class="showUpdate flex-box-ce">
+                      <div style="max-width: 400px;" class="flex-box-ce font-flex-word">
+                          <span class="krIcon">里程碑</span>
+                          <span>{{taskDetail.main.milestone_name}}</span>
+                      </div>
+                      <template v-if="taskDetail.can_edit_normal">
+                        <Tooltip preHtml="更改所属KR:请先更改母任务的所属KR或取消关联母任务" v-if="taskDetail.pid">
+                            <i class="el-icon-edit" style="display: none;">编辑</i>
+                        </Tooltip>
+                        <template v-else>
+                           <i class="el-icon-error" style="display: none;" @click="relieveKr(5)"></i>
+                           <Tooltip preHtml="编辑">
+                              <i @click="openGlKr(1)" class="el-icon-edit" style="display: none;">编辑</i>
+                           </Tooltip>
+                        </template>
+                      </template>
+                    </span>
+                    <span v-else-if="taskDetail.main.project_id" class="showUpdate flex-box-ce">
+                      <div style="max-width: 400px;" class="flex-box-ce font-flex-word">
+                          <span class="krIcon">项目</span>
+                          <span>{{taskDetail.main.project_name}}</span>
+                      </div>
+                      <template v-if="taskDetail.can_edit_normal">
+                        <Tooltip preHtml="更改所属KR:请先更改母任务的所属KR或取消关联母任务" v-if="taskDetail.pid">
+                            <i class="el-icon-edit" style="display: none;">编辑</i>
+                        </Tooltip>
+                        <template v-else>
+                           <i class="el-icon-error" style="display: none;" @click="relieveKr(4)"></i>
+                           <Tooltip preHtml="编辑">
+                              <i @click="openGlKr(1)" class="el-icon-edit" style="display: none;">编辑</i>
+                           </Tooltip>
+                        </template>
+                      </template>
+                    </span>
                     <span v-else class="showUpdate flex-box-ce">暂无
                         <Tooltip preHtml="更改所属KR:请先取消关联母任务" v-if="taskDetail.pid">
                             <i class="el-icon-edit" style="display: none;">编辑</i>
@@ -515,10 +549,10 @@
 
 
       <!-- 关联任务 -->
-      <RelevanceTask :visible.sync="isShowRelevanceTask" :id="pid" @confirm="ActiveRelevanceTask"></RelevanceTask>
+      <RelevanceTask :visible.sync="isShowRelevanceTask"  :id="pid" @confirm="ActiveRelevanceTask"></RelevanceTask>
 
       <!-- 对齐目标 -->
-      <TargetSearch :visible.sync="isShowDateSearch" title="关联KR" :showType="2" @confirm="confirmTarget"></TargetSearch>
+      <TargetSearch :visible.sync="isShowDateSearch" title="关联KR" :showSelectType="showSelectType" :showType="2" @confirm="confirmTarget"></TargetSearch>
 
       <!-- 修改起止时间 -->
       <el-dialog title="修改起止时间" :visible.sync="isShowDqMyTarget" :append-to-body="true" width="500px">
@@ -668,6 +702,7 @@ export default {
     },
     isShowDqMyTarget(val){
       if(val){
+        console.log(this.taskDetail)
         this.time=[this.taskDetail.start_date,this.taskDetail.end_date];
       }
     },
@@ -817,6 +852,8 @@ export default {
       files_lh:[],//进展附件
       files_lh_list:[],//进展附件
       pid:0,//母任务ID
+
+      showSelectType:1,
     };
   },
   methods: {
@@ -1034,15 +1071,23 @@ export default {
       })
     },
     relieveKr(index,item){
-      if(index==1){
+      if(index==1){ //KR
         this.$axiosUser('POST', '/api/pro/okr/plan/relate/delete',{plan_id:this.id,target_type:2,target_id:this.taskDetail.main.kr_id,type:2}).then(res => {
              this.getTaskDateil();
         })
-      }else if(index==2){
+      }else if(index==2){ //其他KR
         this.$axiosUser('POST', '/api/pro/okr/plan/relate/delete',{plan_id:this.id,target_type:2,target_id:item.kr_id,type:3}).then(res => {
              this.getTaskDateil();
         })
-      }else{
+      }else if(index==4){ //项目
+        this.$axiosUser('POST', '/api/pro/okr/plan/relate/delete',{plan_id:this.id,target_type:4,target_id:this.taskDetail.main.project_id,type:2}).then(res => {
+             this.getTaskDateil();
+        })
+      }else if(index==5){ //里程碑
+        this.$axiosUser('POST', '/api/pro/okr/plan/relate/delete',{plan_id:this.id,target_type:5,target_id:this.taskDetail.main.milestone_id,type:2}).then(res => {
+             this.getTaskDateil();
+        })
+      }else{ //母任务
         this.$axiosUser('POST', '/api/pro/okr/plan/relate/delete',{plan_id:this.id,target_type:3,target_id:this.taskDetail.pid,type:1}).then(res => {
              this.getTaskDateil();
         })
@@ -1060,6 +1105,11 @@ export default {
     },
     openGlKr(index){
       this.selectInderKr=index;
+      if(index==1){
+        this.showSelectType=3;
+      }else{
+        this.showSelectType=1;
+      }
       this.isShowDateSearch=true;
     },
     //关联KR
@@ -1068,7 +1118,7 @@ export default {
           let url=this.selectInderKr==1? "/api/pro/okr/plan/relate/main":'/api/pro/okr/plan/relate/other';
           let data={
             plan_id:this.id,
-            target_type:2,
+            target_type:e.type,
             target_id:e.item.id,
           };
           this.$axiosUser('POST',url,data).then(res => {
@@ -1528,7 +1578,7 @@ export default {
   }
   .krIcon{
       position: relative;
-      width: 22px;
+      // width: 22px;
       height: 16px;
       border-radius: 25px;
       background-color: #e9f1fe;
@@ -1724,7 +1774,7 @@ export default {
     color: #89919F;
   }
   .label{
-    width: 100px;
+    width: 120px;
   }
   .kr-title{
     border-radius: 25px;

+ 147 - 92
src/okr/components/public/TaskItem.vue

@@ -1,100 +1,125 @@
 <template>
     <div>
       <CollapseTransition>
-        <div class="taskBox" v-show="isShow">
-          <template v-if="parentList.length>0">
-            <div v-for="(item,index) in parentList" :key="index">
-              <div class="flex-box-ce isSimpleShow">
-                <template v-if="isParent">
-                  <i class="caret" v-if="item.statistics.plan_total>0" :class="item.isShow? 'el-icon-caret-bottom':'el-icon-caret-right'" @click="showTask(item)"></i>
-                </template>
-                <template v-else>
-                  <i class="caret" v-if="item.child&&item.child.length>0" :class="item.isShow? 'el-icon-caret-bottom':'el-icon-caret-right'" @click="showTask(item)"></i>
-                </template>
-                <Tooltip :preHtml="item.stateInfo.name"><i :class="item.stateInfo.icon" style="margin-right: 10px;"></i></Tooltip>
-                <!-- 可操作 -->
-                <template v-if="isSimpleShow==1">
-                  <div class="flex-1 clamp2" style="padding-right: 30px;">
-                    <Tooltip :preHtml="item.name"><span class="hoverBlue" @click.prevent.stop="openDetail(item)">{{item.name}}</span></Tooltip>
-                  </div>
-                  <div class="hover-border" @click="openSelectTime(item,2)"> <svg-icon icon-class="#icon-biaoqian_wode" class="svgIcon"></svg-icon><span>{{item.userInfo.name}}</span></div>
-                  <div class="hover-border" style="margin: 0 30px;text-align: center;width: 52px;" @click="openSelectTime(item,1)">
-                    <span>{{$moment(item.end_date).format('MM/DD')}}</span>
-                  </div>
-                  <div class="flex-box-ce" v-if="item.process">
-                   <el-progress :stroke-width="3" v-if="item.process>100" :color="customColorMethod(item.level)" :show-text="false" :width="20" type="circle" :percentage="100"></el-progress>
-                   <el-progress :stroke-width="3" v-else :show-text="false" :color="customColorMethod(item.level)" :width="20" type="circle" :percentage="Number(item.process)"></el-progress>
-                    <span style="padding-left: 5px;">{{item.process}}%</span>
-                  </div>
-                  <template v-if="item.can_stop==1||item.can_cancel==1||item.can_finish==1||item.can_reset==1||isShowBinding">
-                    <el-dropdown @command="handleCommand($event,item)" trigger="click" class="hoverBlue">
-                     <i class="el-icon-more"></i>
-                     <el-dropdown-menu slot="dropdown">
-                        <el-dropdown-item :command="1" v-if="item.can_stop==1">暂停任务</el-dropdown-item>
-                        <el-dropdown-item :command="2" v-if="item.can_cancel==1">取消任务</el-dropdown-item>
-                        <el-dropdown-item :command="3" v-if="item.can_finish==1">完成任务</el-dropdown-item>
-                        <el-dropdown-item :command="4" v-if="item.can_reset==1">重置任务</el-dropdown-item>
-                        <el-dropdown-item :command="5" v-if="isShowBinding&&item.can_edit_normal==1">取消关联</el-dropdown-item>
-                      </el-dropdown-menu>
-                    </el-dropdown>
-                  </template>
-                  <div v-else style="width: 20px;height: 20px;margin-left: 30px;"></div>
-                </template>
-                <!-- 不可操作 -->
-                <template v-if="isSimpleShow==2">
-                  <div class="flex-1 clamp2" style="padding-right: 30px;">
-                    <Tooltip :preHtml="item.name"><span class="hoverBlue" @click.prevent.stop="openDetail(item)">{{item.name}}</span></Tooltip>
-                  </div>
-                  <div class="hover-border2"> <svg-icon icon-class="#icon-biaoqian_wode" class="svgIcon"></svg-icon><span>{{item.userInfo.name}}</span></div>
-                  <div class="hover-border2" style="margin: 0 30px;text-align: center;width: 52px;">
-                    <span>{{$moment(item.end_date).format('MM/DD')}}</span>
-                  </div>
-                  <div class="flex-box-ce" v-if="item.process">
-                    <el-progress :stroke-width="3" v-if="item.process>100" :color="customColorMethod(item.level)" :show-text="false" :width="20" type="circle" :percentage="100"></el-progress>
-                    <el-progress :stroke-width="3" v-else :show-text="false" :color="customColorMethod(item.level)" :width="20" type="circle" :percentage="Number(item.process)"></el-progress>
-                    <span style="padding-left: 5px;">{{item.process}}%</span>
-                  </div>
-                  <template v-if="item.can_stop==1||item.can_cancel==1||item.can_finish==1||item.can_reset==1||isShowBinding">
-                    <el-dropdown @command="handleCommand($event,item)" trigger="click" class="hoverBlue">
-                     <i class="el-icon-more"></i>
-                     <el-dropdown-menu slot="dropdown">
-                        <el-dropdown-item :command="1" v-if="item.can_stop==1">暂停任务</el-dropdown-item>
-                        <el-dropdown-item :command="2" v-if="item.can_cancel==1">取消任务</el-dropdown-item>
-                        <el-dropdown-item :command="3" v-if="item.can_finish==1">完成任务</el-dropdown-item>
-                        <el-dropdown-item :command="4" v-if="item.can_reset==1">重置任务</el-dropdown-item>
-                        <el-dropdown-item :command="5" v-if="isShowBinding&&item.can_edit_normal==1">取消关联</el-dropdown-item>
-                      </el-dropdown-menu>
-                    </el-dropdown>
-                  </template>
-                  <div v-else style="width: 20px;height: 20px;margin-left: 30px;"></div>
-                </template>
-                <!-- 已结束 -->
-                <template v-if="isSimpleShow==3">
-                    <div class="flex-1 clamp2 hoverBlue" @click.prevent.stop="openDetail(item)">
-                      <Tooltip :preHtml="item.name"><span>{{item.name}}</span></Tooltip>
-                    </div>
-                    <div class="hover-border2" v-if="item.composite_state==2||item.composite_state==3">
-                      <span v-if="item.day>0">剩余<span class="green">{{item.day}}</span>天</span>
-                      <span v-if="item.day<0">过期<span class="red">{{Math.abs(item.day)}}</span>天</span>
-                    </div>
-                </template>
-              </div>
+        <div v-show="isShow">
+          <div class="taskBox" v-if="projects.length>0">
+            <div v-for="(item,index) in projects" :key="index" class="flex-box-ce isSimpleShow">
+                <div class="flex-1 clamp2" style="padding-right: 30px;">
 
-             <template v-if="isParent">
-                <TaskItem v-if="item.statistics.plan_total>0" :isShowBinding="false" :isShow="item.isShow" :isParent="isParent" :taskId="item.id"  :isSimpleShow="isSimpleShow"  @confirm="openDetail"></TaskItem>
-              </template>
-              <template v-else>
-                <TaskItem v-if="item.child&&item.child.length>0" :isShowBinding="false" :isShow="item.isShow" :list="item.child" :isSimpleShow="isSimpleShow"  @confirm="openDetail"></TaskItem>
-              </template>
+                  <i class="el-icon-s-finance" style="margin-right: 10px;"></i>
+                  <!-- @click.prevent.stop="openDetail(item)" -->
+                  <Tooltip :preHtml="item.name"><span>{{item.name}}</span></Tooltip>
+                </div>
+                <div class="hover-border2"> <svg-icon icon-class="#icon-biaoqian_wode" class="svgIcon"></svg-icon><span>{{$getEmployeeMapItem(item.owner_id).name}}</span></div>
+                <div class="hover-border2" style="margin: 0 30px;text-align: center;width: 52px;">
+                  <span>{{$moment(item.end_date).format('MM/DD')}}</span>
+                </div>
+                <div class="flex-box-ce" v-if="item.process">
+                  <el-progress :stroke-width="3" v-if="item.process>100" color="#2879ff" :show-text="false" :width="20" type="circle" :percentage="100"></el-progress>
+                  <el-progress :stroke-width="3" v-else :show-text="false" color="#2879ff" :width="20" type="circle" :percentage="Number(item.process)"></el-progress>
+                  <span style="padding-left: 5px;">{{item.process}}%</span>
+                </div>
+                <div v-if="item.can_edit" style="width: 20px;height: 20px;margin-left: 30px;cursor: pointer;" @click="qxGl(item)">
+                   <Tooltip preHtml="取消关联"><i class="el-icon-link"></i></Tooltip>
+                </div>
+                <div v-else style="width: 20px;height: 20px;margin-left: 30px;"></div>
             </div>
-          </template>
-          <div v-else>
-            <template v-if="isParent">
-              <div class="zhu"></div>
-              <div class="zhu"></div>
-            </template>
-            <div class="orange" v-else style="margin-bottom: 8px;font-size: 13px;">用“任务”推动目标达成,合理规划安排工作</div>
+
           </div>
+          <div class="taskBox">
+                    <template v-if="parentList.length>0">
+                      <div v-for="(item,index) in parentList" :key="index">
+                        <div class="flex-box-ce isSimpleShow">
+                          <template v-if="isParent">
+                            <i class="caret" v-if="item.statistics.plan_total>0" :class="item.isShow? 'el-icon-caret-bottom':'el-icon-caret-right'" @click="showTask(item)"></i>
+                          </template>
+                          <template v-else>
+                            <i class="caret" v-if="item.child&&item.child.length>0" :class="item.isShow? 'el-icon-caret-bottom':'el-icon-caret-right'" @click="showTask(item)"></i>
+                          </template>
+                          <Tooltip :preHtml="item.stateInfo.name"><i :class="item.stateInfo.icon" style="margin-right: 10px;"></i></Tooltip>
+                          <!-- 可操作 -->
+                          <template v-if="isSimpleShow==1">
+                            <div class="flex-1 clamp2" style="padding-right: 30px;">
+                              <Tooltip :preHtml="item.name"><span class="hoverBlue" @click.prevent.stop="openDetail(item)">{{item.name}}</span></Tooltip>
+                            </div>
+                            <div class="hover-border" @click="openSelectTime(item,2)"> <svg-icon icon-class="#icon-biaoqian_wode" class="svgIcon"></svg-icon><span>{{item.userInfo.name}}</span></div>
+                            <div class="hover-border" style="margin: 0 30px;text-align: center;width: 52px;" @click="openSelectTime(item,1)">
+                              <span>{{$moment(item.end_date).format('MM/DD')}}</span>
+                            </div>
+                            <div class="flex-box-ce" v-if="item.process">
+                             <el-progress :stroke-width="3" v-if="item.process>100" :color="customColorMethod(item.level)" :show-text="false" :width="20" type="circle" :percentage="100"></el-progress>
+                             <el-progress :stroke-width="3" v-else :show-text="false" :color="customColorMethod(item.level)" :width="20" type="circle" :percentage="Number(item.process)"></el-progress>
+                              <span style="padding-left: 5px;">{{item.process}}%</span>
+                            </div>
+                            <template v-if="item.can_stop==1||item.can_cancel==1||item.can_finish==1||item.can_reset==1||isShowBinding">
+                              <el-dropdown @command="handleCommand($event,item)" trigger="click" class="hoverBlue">
+                               <i class="el-icon-more"></i>
+                               <el-dropdown-menu slot="dropdown">
+                                  <el-dropdown-item :command="1" v-if="item.can_stop==1">暂停任务</el-dropdown-item>
+                                  <el-dropdown-item :command="2" v-if="item.can_cancel==1">取消任务</el-dropdown-item>
+                                  <el-dropdown-item :command="3" v-if="item.can_finish==1">完成任务</el-dropdown-item>
+                                  <el-dropdown-item :command="4" v-if="item.can_reset==1">重置任务</el-dropdown-item>
+                                  <el-dropdown-item :command="5" v-if="isShowBinding&&item.can_edit_normal==1">取消关联</el-dropdown-item>
+                                </el-dropdown-menu>
+                              </el-dropdown>
+                            </template>
+                            <div v-else style="width: 20px;height: 20px;margin-left: 30px;"></div>
+                          </template>
+                          <!-- 不可操作 -->
+                          <template v-if="isSimpleShow==2">
+                            <div class="flex-1 clamp2" style="padding-right: 30px;">
+                              <Tooltip :preHtml="item.name"><span class="hoverBlue" @click.prevent.stop="openDetail(item)">{{item.name}}</span></Tooltip>
+                            </div>
+                            <div class="hover-border2"> <svg-icon icon-class="#icon-biaoqian_wode" class="svgIcon"></svg-icon><span>{{item.userInfo.name}}</span></div>
+                            <div class="hover-border2" style="margin: 0 30px;text-align: center;width: 52px;">
+                              <span>{{$moment(item.end_date).format('MM/DD')}}</span>
+                            </div>
+                            <div class="flex-box-ce" v-if="item.process">
+                              <el-progress :stroke-width="3" v-if="item.process>100" :color="customColorMethod(item.level)" :show-text="false" :width="20" type="circle" :percentage="100"></el-progress>
+                              <el-progress :stroke-width="3" v-else :show-text="false" :color="customColorMethod(item.level)" :width="20" type="circle" :percentage="Number(item.process)"></el-progress>
+                              <span style="padding-left: 5px;">{{item.process}}%</span>
+                            </div>
+          <!--                  <template v-if="item.can_stop==1||item.can_cancel==1||item.can_finish==1||item.can_reset==1||isShowBinding">
+                              <el-dropdown @command="handleCommand($event,item)" trigger="click" class="hoverBlue">
+                               <i class="el-icon-more"></i>
+                               <el-dropdown-menu slot="dropdown">
+                                  <el-dropdown-item :command="1" v-if="item.can_stop==1">暂停任务</el-dropdown-item>
+                                  <el-dropdown-item :command="2" v-if="item.can_cancel==1">取消任务</el-dropdown-item>
+                                  <el-dropdown-item :command="3" v-if="item.can_finish==1">完成任务</el-dropdown-item>
+                                  <el-dropdown-item :command="4" v-if="item.can_reset==1">重置任务</el-dropdown-item>
+                                  <el-dropdown-item :command="5" v-if="isShowBinding&&item.can_edit_normal==1">取消关联</el-dropdown-item>
+                                </el-dropdown-menu>
+                              </el-dropdown>
+                            </template> -->
+                            <div  style="width: 20px;height: 20px;margin-left: 30px;"></div>
+                          </template>
+                          <!-- 已结束 -->
+                          <template v-if="isSimpleShow==3">
+                              <div class="flex-1 clamp2 hoverBlue" @click.prevent.stop="openDetail(item)">
+                                <Tooltip :preHtml="item.name"><span>{{item.name}}</span></Tooltip>
+                              </div>
+                              <div class="hover-border2" v-if="item.composite_state==2||item.composite_state==3">
+                                <span v-if="item.day>0">剩余<span class="green">{{item.day}}</span>天</span>
+                                <span v-if="item.day<0">过期<span class="red">{{Math.abs(item.day)}}</span>天</span>
+                              </div>
+                          </template>
+                        </div>
+                       <template v-if="isParent">
+                          <TaskItem v-if="item.statistics.plan_total>0" :isShowBinding="false" :isShow="item.isShow" :isParent="isParent" :taskId="item.id"  :isSimpleShow="isSimpleShow"  @confirm="openDetail"></TaskItem>
+                        </template>
+                        <template v-else>
+                          <TaskItem v-if="item.child&&item.child.length>0" :isShowBinding="false" :isShow="item.isShow" :list="item.child" :isSimpleShow="isSimpleShow"  @confirm="openDetail"></TaskItem>
+                        </template>
+                      </div>
+                    </template>
+                    <div v-else>
+                      <template v-if="isParent">
+                        <div class="zhu"></div>
+                        <div class="zhu"></div>
+                      </template>
+                      <div class="orange" v-else style="margin-bottom: 8px;font-size: 13px;">用“任务”推动目标达成,合理规划安排工作</div>
+                    </div>
+                  </div>
         </div>
       </CollapseTransition>
 
@@ -162,6 +187,10 @@ export default {
         default: function(){
           return []
         }
+     },
+     isShowChild:{ //项目的详情任务有用到
+       type: Boolean,
+       default: false,
      }
   },
   data() {
@@ -224,6 +253,8 @@ export default {
         ]
       },
       isShowTaskDetail2:false,
+
+      projects:[],
     };
   },
   watch: {
@@ -231,7 +262,18 @@ export default {
       if(val){
         this.parentList=[];
         if(this.isParent){ //KR进来
+          if(!this.isShowChild){
             this.getTask();
+          }else{
+            let list=this.list;
+            list.forEach(item=>{
+              item.stateInfo=taskStatus(item.composite_state);
+              item.userInfo=this.$getEmployeeMapItem(item.owner_id);
+              item.day=this.$moment(item.end_date).diff(this.$moment().format('YYYY-MM-DD'), 'day');
+              item.isShow=false;
+            })
+            this.parentList=list;
+          }
         }else{
           let list=this.list;
           list.forEach(item=>{
@@ -245,7 +287,6 @@ export default {
       }
     },
     isShowTaskDetail(val){
-      console.log(val);
        if(!val){
          this.isShowTaskDetail2=false;
        }
@@ -275,6 +316,17 @@ export default {
         this.closeDetail();
       })
     },
+    qxGl(item){
+      this.$confirm('确定取消关联吗?','提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+          this.$axiosUser('POST', 'api/pro/okr/project/unbind', {project_id:item.id,kr_id:this.krId}).then(res => {
+            this.closeDetail();
+          })
+      }).catch(() => {});
+    },
     handleCommand(e,item){
       let url="";
       if(e==1){//暂停任务
@@ -353,6 +405,9 @@ export default {
             item.userInfo=this.$getEmployeeMapItem(item.owner_id)
           })
           this.parentList=list;
+          if(this.krId){
+            this.projects=res.data.data.projects;
+          }
       })
     },
     openDetail(item){

+ 28 - 3
src/okr/utils/auth.js

@@ -1,9 +1,7 @@
-
-
 export function onFilePreView(url) {
     window.open(url, '_blank');
 };
-//当前是否是操作者(发布者、负责人、OKR管理者)publisher_id:发布者  owner_id:负责人 
+//当前是否是操作者(发布者、负责人、OKR管理者)publisher_id:发布者  owner_id:负责人
 export function getOperation(publisher_id,owner_id){
   if(publisher_id==getCache('userInfo').id||owner_id==getCache('userInfo').id||getCache('userInfo').is_okr_manager){
     return true;
@@ -234,3 +232,30 @@ export function getPointArr(id) {
   }
   return yearArr
 }
+
+
+//项目判断是否包含  目标管理者  项目发布者  项目负责人
+export function isReturnPJ(item){
+  let is=false;
+  let userInfo= getCache('userInfo');
+  if(userInfo.is_okr_manager||item.owner_id==userInfo.id||item.publisher_id==userInfo.id){
+    is=true
+  }
+  return is
+}
+//是否是项目的相关成员
+export function isPstake(item){
+  let is=false;
+  let userInfo= getCache('userInfo');
+  if(userInfo.is_okr_manager){
+    is=true
+  }
+  if(item.stake_holder_ids){
+    item.stake_holder_ids.forEach(id=>{
+      if(userInfo.id==id){
+        is=true
+      }
+    })
+  }
+  return is
+}

+ 56 - 24
src/okr/views/okrIndex.vue

@@ -17,23 +17,22 @@
            <el-menu :default-active="activeIndex" class="el-menu-vertical-demo" ref="elMenu" :router="true" @select="activeRouter" :unique-opened="true">
              <template v-for="(item, index) in routers">
                <div :key="index">
-                 <el-menu-item :index="returnIndex(index, 0)" :route="item.children[0].path" :ref="item.children[0].path" v-if="item.name=='目标设置'||item.name=='目标复盘'">
+                 <el-menu-item :index="returnIndex(index, 0)" :route="item.children[0].path" :ref="item.children[0].path" v-if="item.label=='目标设置'||item.label=='目标复盘'">
                    <div class="flex-box-ce">
                      <svg-icon :icon-class="item.icon" class="svgIcon"></svg-icon>
-                     <span slot="title" style="margin-left: 5px;">{{ item.children[0].name }}</span>
+                     <span slot="title" style="margin-left: 5px;">{{ item.children[0].label }}</span>
                    </div>
                  </el-menu-item>
-
                  <el-submenu :index="index.toString()" :key="index" v-else>
                    <template slot="title">
                      <div class="flex-box-ce">
                        <svg-icon :icon-class="item.icon" class="svgIcon"></svg-icon>
-                       <span style="margin-left: 5px;">{{ item.name }}</span>
+                       <span style="margin-left: 5px;">{{ item.label }}</span>
                      </div>
                    </template>
                    <template v-for="(item2, index2) in item.children" v-if="!item2.meta.isHide">
                      <el-menu-item :index="returnIndex(index, index2)" :ref="item2.path" :route="item2.path" :key="index2" class="font-flex-word">
-                       <span slot="title" style="margin-left: 10px;width:120px">{{ item2.name }}</span>
+                       <span slot="title" style="margin-left: 10px;width:120px">{{ item2.label }}</span>
                      </el-menu-item>
                    </template>
                  </el-submenu>
@@ -42,7 +41,7 @@
            </el-menu>
          </el-aside>
          <el-main>
-           <router-view />
+           <keep-alive :include="keepAliveView"><router-view/></keep-alive>
          </el-main>
        </el-container>
      </LoadingAll>
@@ -59,10 +58,25 @@ export default {
       activeIndex: '0-0',
       routers: [],
       userInfo: this.$userInfo(),
+      keepAliveView:['myProject','deptProject','publicProject','allProject'],  //需要缓存的组件名称列表,用逗号分隔
+      keepAliveView2:['myProject','deptProject','publicProject','allProject'],  //需要缓存的组件名称列表,用逗号分隔
     };
   },
   watch: {
     $route(to, form) {
+      // 缓存页面,达到缓存页面条件的效果
+      if(to.name=='myProject'&&form.name!='projectDetail'){
+        this.returnArr('myProject')
+      }else if(to.name=='deptProject'&&form.name!='projectDetail'){
+        this.returnArr('deptProject')
+      }else if(to.name=='publicProject'&&form.name!='projectDetail'){
+        this.returnArr('publicProject')
+      }else if(to.name=='allProject'&&form.name!='projectDetail'){
+        this.returnArr('allProject')
+      }else{
+         this.keepAliveView=JSON.parse(JSON.stringify(this.keepAliveView2));
+      }
+
       var str = to.path;
       this.routers.map((item, index) => {
         if (item.children.length > 0) {
@@ -89,6 +103,14 @@ export default {
     }
   },
   methods: {
+    returnArr(str){
+      this.keepAliveView=this.keepAliveView.filter(item=>{
+        return item!=str;
+      })
+      setTimeout(()=>{
+         this.keepAliveView=JSON.parse(JSON.stringify(this.keepAliveView2));
+      },1000)
+    },
     getUnitList(fun){
       this.$axiosUser('get', '/api/pro/okr/kr/unit_list').then(res => {
         let data=res.data.data;
@@ -101,34 +123,28 @@ export default {
       let dept_manager=this.$getIsIdentity('dept_manager'); //是否部门管理员
       let is_okr_manager=this.userInfo.is_okr_manager;//是否目标管理员
       let routers = [
-         { name: '工作计划', children: this.returnRoutersArr('planTask'), icon: '#icon-jihua',isShow:true },
-         { name: '目标管理', children: this.returnRoutersArr('targertAdministration'), icon: '#icon-zhiyeshengyamubiao',isShow:true },
-         { name: '目标复盘', children: this.returnRoutersArr('replay'), icon: '#icon-daifucha',isShow:true },
-         // { name: '我的目标', children: this.returnRoutersArr('myTargert'), icon: '#icon-zhiyeshengyamubiao',isShow:true },
-         // { name: '我的上级', children: this.returnRoutersArr('mySuperior'), icon: '#icon-lingdao' ,isShow:is_okr_manager? false:true},
-         // { name: '我的下级', children: this.returnRoutersArr('mySubordinate'), icon: '#icon-wodexiashu',isShow:(dept_manager||is_okr_manager)? true:false},
-         // { name: '我的部门', children: this.returnRoutersArr('deptTargert'), icon: '#icon-bumen',isShow:is_okr_manager? false:true},
-         // { name: '关注的人', children: this.returnRoutersArr('attention'), icon: '#icon-tianjiaguanzhu',isShow:true },
-         // { name: '公司全部', children: this.returnRoutersArr('companyTargert'), icon: '#icon-quanbu',isShow:true },
-         // { name: '目标地图', children: this.returnRoutersArr('targetMap'), icon: '#icon-fenzuguanli',isShow:true },
+         { label: '工作计划', children: this.returnRoutersArr('planTask'), icon: '#icon-jihua',isShow:true },
+         { label: '目标管理', children: this.returnRoutersArr('targertAdministration'), icon: '#icon-zhiyeshengyamubiao',isShow:true },
+         { label: '目标复盘', children: this.returnRoutersArr('replay'), icon: '#icon-daifucha',isShow:true },
+         { label: '项目管理', children: this.returnRoutersArr('project'), icon: '#icon-xiangmu',isShow:true },
       ];
       routers[1].children.forEach(item=>{
-        if(item.name=='我的上级'||item.name=='我的部门'){
+        if(item.label=='我的上级'||item.label=='我的部门'){
           if(is_okr_manager){
             item.meta.isHide=true;
           }
         }
-        if(item.name=='我的下级'){
+        if(item.label=='我的下级'){
           if(!is_okr_manager&&!dept_manager){
             item.meta.isHide=true;
           }
         }
       })
       if(is_okr_manager){
-        routers.unshift({ name: '目标分析', children: this.returnRoutersArr('targetAnalyse'), icon: '#icon-fenxi',isShow:true })
-        routers.push({ name: '目标设置', children: this.returnRoutersArr('targetSet'), icon: '#icon-shezhi_jichushezhi',isShow:true });
+        routers.unshift({ label: '目标分析', children: this.returnRoutersArr('targetAnalyse'), icon: '#icon-fenxi',isShow:true })
+        routers.push({ label: '目标设置', children: this.returnRoutersArr('targetSet'), icon: '#icon-shezhi_jichushezhi',isShow:true });
       }else{
-        routers.push({ name: '目标分析', children: this.returnRoutersArr('targetAnalyse'), icon: '#icon-fenxi',isShow:true })
+        routers.push({ label: '目标分析', children: this.returnRoutersArr('targetAnalyse'), icon: '#icon-fenxi',isShow:true })
       }
       this.routers=routers;
       this.$nextTick(() => {
@@ -143,6 +159,7 @@ export default {
     },
     returnRoutersArr(str) {
       let routers = [];
+      let is_okr_manager=this.userInfo.is_okr_manager;//是否目标管理员
       this.$router.options.routes[0].children.some((item, i) => {
         if (item.name == 'okrIndex') {
           routers=item.children
@@ -151,9 +168,24 @@ export default {
       });
       var routersArr = [];
       routers.forEach(item => {
-        if (item.meta.groupCode == str) {
+        if (!item.hidden&&item.meta.groupCode == str) {
           //获取对应模块的路由
-          routersArr.push(item);
+          if(str=='project'){
+            if(item.name=='deptProject'){
+              if(this.userInfo.employee_detail.manage_dept_ids.length>0){
+                routersArr.push(item);
+              }
+            }else if(item.name=='allProject'){
+              if(is_okr_manager){
+                routersArr.push(item);
+              }
+            }else{
+              routersArr.push(item);
+            }
+
+          }else{
+            routersArr.push(item);
+          }
         }
       });
       return routersArr;
@@ -233,7 +265,7 @@ export default {
   height: calc(100vh - 80px);
   overflow-y: scroll;
   padding: 0 10px;
-  min-width: 1100px;
+  min-width: 1000px;
 }
 
 

+ 2 - 1
src/okr/views/planTask/planTable.vue

@@ -37,7 +37,8 @@
           </div>
           <div class="flex-box-ce" v-if="dateId">
             <div style="font-size: 18px;font-weight: 600;margin-right: 20px;">{{ dateId }}月</div>
-            <el-date-picker :picker-options="pickerOptions"  v-model="monthDateVal"  size="small"  type="daterange"  range-separator="~"  :clearable="false"  start-placeholder="开始日期"  end-placeholder="结束日期"  value-format="yyyy-MM-dd"></el-date-picker>
+            <!-- :picker-options="pickerOptions" -->
+            <el-date-picker   v-model="monthDateVal"  size="small"  type="daterange"  range-separator="~"  :clearable="false"  start-placeholder="开始日期"  end-placeholder="结束日期"  value-format="yyyy-MM-dd"></el-date-picker>
           </div>
 
           <div class="flex-box-ce flex-d-center" style="margin: 20px 0;">

+ 40 - 0
src/okr/views/project/allProject.vue

@@ -0,0 +1,40 @@
+<template>
+    <myProject :myTargertType="3"></myProject>
+</template>
+<script>
+import myProject from '@/okr/views/project/myProject'; //选择周期
+export default {
+  components:{myProject},
+  name:'allProject',
+  data() {
+    return {
+    };
+  },
+  mounted() {
+    // console.log("123")
+  }
+};
+</script>
+<style scoped lang="scss">
+.set_basics_box {
+  padding: 20px;
+  background: #fff;
+}
+/deep/ .el-message--info {
+  font-size: 20px;
+}
+.el-form-item__error {
+  left: 50px;
+}
+.tips {
+  background: #409EFF;
+  border-radius: 50%;
+  width: 14px;
+  height: 14px;
+  color: #fff;
+  display: inline-block;
+  font-size: 12px;
+  line-height: 14px;
+  text-align: center;
+}
+</style>

+ 39 - 0
src/okr/views/project/deptProject.vue

@@ -0,0 +1,39 @@
+.<template>
+    <myProject :myTargertType="4"></myProject>
+</template>
+<script>
+import myProject from '@/okr/views/project/myProject'; //选择周期
+export default {
+  components:{myProject},
+  name:'deptProject',
+  data() {
+    return {
+    };
+  },
+  mounted() {
+  }
+};
+</script>
+<style scoped lang="scss">
+.set_basics_box {
+  padding: 20px;
+  background: #fff;
+}
+/deep/ .el-message--info {
+  font-size: 20px;
+}
+.el-form-item__error {
+  left: 50px;
+}
+.tips {
+  background: #409EFF;
+  border-radius: 50%;
+  width: 14px;
+  height: 14px;
+  color: #fff;
+  display: inline-block;
+  font-size: 12px;
+  line-height: 14px;
+  text-align: center;
+}
+</style>

+ 617 - 0
src/okr/views/project/myProject.vue

@@ -0,0 +1,617 @@
+<template>
+  <div class="boxMinHeight">
+    <div class="flex-box">
+      <!-- 我的部门 -->
+      <div style="width: 160px;background-color: #fff;border-radius: 10px;margin-right: 10px;padding: 20px 10px;height: calc(100vh - 80px);" v-if="myTargertType==4">
+        <template v-if="userInfo.employee_detail.dept_list.length>0">
+          <div style="margin-bottom:10px;font-size:16px;text-align: center;" class="blue cursor">
+            <el-select v-model="dept_id" placeholder="请选择" class="selectUser" :clearable="false">
+              <el-option  v-for="item in dept_tree" :key="item.id"  :label="item.name"  :value="item.id"></el-option>
+            </el-select>
+          </div>
+          <div style="height: calc(100vh - 160px);overflow-y:auto;" class="scroll-bar">
+            <div class="flex-box-ce user-item" :class="ownerUserInfo.id==item.id? 'userActive':''" v-for="item in employeeOptions" :key="item.id" @click="ownerUserInfo=item">
+              <userImage :id="item.id" width="36px" height="36px" fontSize="14" :user_name="item.name"></userImage>
+              <Tooltip :preHtml="item.name">
+               <div class="font-flex-word" style="margin-left: 10px;font-size: 14px;">{{ item.name }}</div>
+              </Tooltip>
+            </div>
+            <div v-if="employeeOptions.length==0" style="text-align: center;margin-top: 30px;" class="fontColorC">暂无人员</div>
+            <div style="height:30px"></div>
+          </div>
+        </template>
+        <div class="fontColorC" v-else style="margin-top: 30px;text-align: center;">暂没有所属部门</div>
+      </div>
+
+      <div class="flex-1">
+        <div class="flex-box-ce flex-d-center header">
+          <div class="flex-1 flex-box-ce" style="max-width: 800px;">
+            <template v-if="myTargertType==1">
+              <div class="flex-box-ce" >
+                <userImage :id="userInfo.id" width="50px" height="50px" :user_name="userInfo.name"></userImage>
+                <span style="margin-left: 10px;font-size: 18px;font-weight: 700;color: #3F4755;">{{ userInfo.name }}</span>
+              </div>
+              <div class="flex-box-ce span-item" style="margin-left: 20px;">
+                <span :class="idText=='owner_id'? 'spanActive':''" @click="idText='owner_id'">我负责的</span>
+                <span :class="idText=='joiner_id'? 'spanActive':''" @click="idText='joiner_id'">我参与的</span>
+                <span :class="idText=='publisher_id'? 'spanActive':''" @click="idText='publisher_id'">我创建的</span>
+              </div>
+            </template>
+            <div v-if="myTargertType==2" style="font-size: 24px;font-weight: 600;padding: 9px 0;">公开项目</div>
+            <div v-if="myTargertType==3" style="font-size: 24px;font-weight: 600;padding: 9px 0;">公司全部项目</div>
+            <template v-if="myTargertType==4">
+              <div class="flex-box-ce" >
+                <userImage :id="ownerUserInfo.id" width="50px" height="50px" :user_name="ownerUserInfo.name"></userImage>
+                <span style="margin-left: 10px;font-size: 18px;font-weight: 700;color: #3F4755;">{{ ownerUserInfo.name }}</span>
+              </div>
+              <div class="flex-box-ce span-item" style="margin-left: 20px;">
+                <span :class="idText=='owner_id'? 'spanActive':''" @click="idText='owner_id'">Ta负责的</span>
+                <span :class="idText=='joiner_id'? 'spanActive':''" @click="idText='joiner_id'">Ta参与的</span>
+              </div>
+            </template>
+          </div>
+          <div class="flex-box-ce">
+            <el-button type="primary" size="small" round style="margin-right: 10px;" @click="isShowAdd=true" v-if="myTargertType==1">创建目标</el-button>
+            <el-input @input="keyinput" class="input" maxlength="20" prefix-icon="el-icon-search" style="width: 206px;" size="small" v-model="formData.keyword" clearable placeholder="请输入关键字" />
+            <el-select class="select" size="small" v-model="formData.composite_state" placeholder="类型" style="width: 100px;">
+              <el-option v-for="item in statusArr" :key="item.value" :label="item.label"  :value="item.value"></el-option>
+            </el-select>
+            <div class="selectBox">
+                <SelectProjectGj :formData="formData" :myTargertType="myTargertType" :idText="idText"   @confirm="selectPeriodGjConfirm" ></SelectProjectGj>
+            </div>
+          </div>
+        </div>
+        <div v-loading="loading">
+          <template v-if="projectList.length>0">
+            <div class="itemBox" v-for="(item,index) in projectList" :key="item.id" @click="openDetail(item)">
+              <div class="flex-box-ce flex-d-center">
+                    <div class="item-left flex-box">
+                        <div class="project-name clamp2">{{item.name}}</div>
+                        <div class="project-status" style="height: 22px;line-height: 22px;border-radius: 3px;padding: 0 5px;position: relative;top: 2px;font-size: 12px;background-color: #f1f1f1;">{{item.stateText}}</div>
+                    </div>
+                    <div class="fontColorB flex-box-ce" style="flex-shrink:0;">
+                      <div class="flex-box-ce">
+                        <svg-icon icon-class="#icon-biaoqian_wode" class="svgIcon" style="width: 1rem;height: 1rem;"></svg-icon>
+                        <span>&nbsp;{{item.userInfo.name}}</span>
+                      </div>
+                      <div class="flex-box-ce" style="margin-left: 30px;">
+                        <template v-if="item.day>30">
+                          <span>{{$moment(item.end_date).format('MM/DD')}}截止</span>
+                        </template>
+                        <template v-else>
+                          <span v-if="item.day>0" class="green">剩余{{item.day}}天</span>
+                          <span v-if="item.day==0" class="green">剩余1天</span>
+                          <span v-if="item.day<0" class="red">逾期{{Math.abs(item.day)}}天</span>
+                        </template>
+                        <Progress :inputStyle="{ height: '14px', width: '140px', lineHeight: '14px' }" :status="item.composite_state==3? 3:2" :value="Number(item.process)" style="margin-left: 10px;"></Progress>
+                      </div>
+                    </div>
+              </div>
+            </div>
+            <div style="text-align: center;background-color: #fff;padding:10px 0;border-radius: 10px;">
+              <el-pagination layout="total,prev,pager,next,sizes" :current-page.sync="formData.page" :total="total"
+                @size-change="handleSizeChange" @current-change="handleCurrentChange" :page-sizes="[10, 20, 50, 100]"
+                :page-size="formData.page_size"></el-pagination>
+            </div>
+          </template>
+          <!-- 没有列表时 -->
+          <div class="flex-box-v flex-center-center" style="height: 600px;" v-else>
+            <noData isSolt imgUrl="static/images/no_data.png" imgW="150px" imgH="100px">
+              <div v-if="myTargertType==1" style="text-align: center;margin: 0 0;line-height: 30px;">暂无项目,去<span @click="isShowAdd=true" class="blue cursor">创建项目</span></div>
+              <div v-else style="text-align: center;margin: 0 0;line-height: 30px;">暂无项目</div>
+            </noData>
+          </div>
+        </div>
+
+      </div>
+    </div>
+
+
+    <AddProject :visible.sync="isShowAdd" @confirm="ActiveAddProject"></AddProject>
+  </div>
+</template>
+<script>
+  import Tooltip from '@/components/Tooltip'; //鼠标悬浮显示文字
+  import { _debounce} from '@/utils/auth';
+  import EmployeeSelector from '@/components/EmployeeSelector';
+  import SelectProjectGj from '@/okr/components/project/SelectProjectGj'; //
+  import AddProject from '@/okr/components/project/AddProject'; //
+  import Progress from '@/okr/components/public/Progress'; //进度条
+  export default {
+    name: 'myProject',
+    components: {EmployeeSelector,Tooltip,SelectProjectGj,AddProject,Progress},
+    props:{
+      myTargertType:{ //从不同页面加载  1:我的项目 2:公开项目 3全部项目  4我的部门
+        type:Number,
+        default:1
+      }
+    },
+    data() {
+      return {
+        userInfo: this.$userInfo(),
+        isShowAdd: false,
+        idText: 'owner_id',
+        projectList:[],
+        loading: false,
+        total: 0,
+        formData: {
+          page: 1,
+          page_size: 10,
+          dateTime:[],
+          owner_id:'',//	否	string	负责人id 默认为0
+          joiner_id:'',//	否	string	参与者id 默认为0
+          publisher_id:'',//	否	string	发布者id 默认为0
+          scope_type:0,//	否	string	可见范围 1-相关人员可见 2-全员可见 默认为0不区分
+          composite_state:0,//	否	strin g	综合状态 1-未开始 2-进行中 3-逾期 4-达标 默认为0不区分
+          dept_id:'',//	否	string	部门id,默认为0
+          start_date:'',//	否	string	开始日期 格式:2024-01-01 默认为空字符串
+          end_date:'',//	否	string	结束日期 格式:2024-01-01 默认为空字符串
+          sort:'ct_desc',//	否	string	排序 ct_asc-创建时间正序 ct_desc-创建时间倒序 up_asc-更新时间正序 up_desc-更新时间倒序 默认为ct_desc
+          keyword:'',//	否	string	关键字
+        },
+        statusArr: [
+          {value: 0,label: '全部状态'},
+          {value: 1,label: '未开始'},
+          {value: 2,label: '进行中'},
+          {value: 3,label: '已逾期'},
+          {value: 4,label: '已完成'},
+        ],
+
+        // 我的部门相关
+        dept_id:'',
+        employeeOptions:[],
+        ownerUserInfo:{},
+        dept_tree:[],
+      };
+    },
+    watch: {
+      'formData.composite_state'(){
+         this.getProjectList();
+      },
+      ownerUserInfo(val){
+        this.getProjectList();
+      },
+      idText(){
+         this.getProjectList();
+      },
+      dept_id(){
+        this.get_employee_list();
+      },
+    },
+    methods: {
+      get_employee_list() {
+        this.$axiosUser('get', '/api/pro/employee/list', { dept_id: this.dept_id }, 'v2').then(res => {
+          this.employeeOptions = res.data.data.list;
+          if(this.employeeOptions.length>0){
+            this.ownerUserInfo=this.employeeOptions[0];
+          }
+        });
+      },
+      openDetail(item){
+        this.$router.push({path:'/projectDetail',query:{id:item.id}})
+      },
+      keyinput:_debounce(function(val) {
+          this.getProjectList();
+      }),
+      ActiveAddProject(){
+          this.getProjectList();
+      },
+      getProjectList(is){
+        is? '':this.formData.page=1;
+        let data={
+          page:is? this.formData.page:1,
+          page_size:this.formData.page_size,
+          scope_type:0,//	否	string	可见范围 1-相关人员可见 2-全员可见 默认为0不区分
+          composite_state:this.formData.composite_state,//	否	strin g	综合状态 1-未开始 2-进行中 3-逾期 4-达标 默认为0不区分
+          dept_id:this.formData.dept_id? this.formData.dept_id[this.formData.dept_id.length-1]:0,//	否	string	部门id,默认为0
+          sort:this.formData.sort,//	否	string	排序 ct_asc-创建时间正序 ct_desc-创建时间倒序 up_asc-更新时间正序 up_desc-更新时间倒序 默认为ct_desc
+          keyword:this.formData.keyword,//	否	string	关键字
+        }
+        if(this.formData.owner_id){
+          data.owner_id=this.formData.owner_id
+        }
+        if(this.formData.joiner_id){
+          data.joiner_id=this.formData.joiner_id
+        }
+        if(this.myTargertType==1){
+          if(this.idText=='owner_id'){
+            data.owner_id=this.$userInfo().id;
+          }
+          if(this.idText=='joiner_id'){
+            data.joiner_id=this.$userInfo().id;
+          }
+          if(this.idText=='publisher_id'){
+            data.publisher_id=this.$userInfo().id;
+          }
+        }else if(this.myTargertType==2){//公开项目
+          data.scope_type=2
+        }else if(this.myTargertType==3){//公司项目
+          data.scope_type=0
+        }else if(this.myTargertType==4){//部门项目
+          if(this.idText=='owner_id'){
+            data.owner_id=this.ownerUserInfo.id;
+          }
+          if(this.idText=='joiner_id'){
+            data.joiner_id=this.ownerUserInfo.id;
+          }
+        }
+
+
+        if(this.formData.dateTime&&this.formData.dateTime.length>0){
+          data.start_date=this.formData.dateTime[0];//	否	string	开始日期 格式:2024-01-01 默认为空字符串
+          data.end_date=this.formData.dateTime[1];//	否	string	结束日期 格式:2024-01-01 默认为空字符串
+        }
+        this.loading = true;
+        this.$axiosUser('get','api/pro/okr/project/list',data).then(res => {
+          let list=res.data.data.list;
+          list.forEach(item=>{
+            item.userInfo=this.$getEmployeeMapItem(item.owner_id);
+            item.stateText=this.returnStatus(item.composite_state);
+            item.day=this.$moment(item.end_date).diff(this.$moment().format('YYYY-MM-DD'), 'day')
+          })
+          this.projectList=list;
+          this.total=res.data.data.total;
+        }).finally(()=>{
+           this.loading = false;
+        })
+      },
+      returnStatus(index){
+        let arr=['未开始','进行中','已逾期','已完成'];
+        return arr[index-1];
+      },
+      selectPeriodGjConfirm(data){
+        this.formData.sort = data.sort;
+        this.formData.owner_id = data.owner_id;
+        this.formData.joiner_id = data.joiner_id;
+        this.formData.dateTime = data.dateTime;
+        this.formData.dept_id = data.dept_id;
+        this.getProjectList();
+      },
+      handleSizeChange(val) {
+        this.formData.page=1;
+        this.formData.page_size = val;
+        this.getProjectList();
+      },
+      // 页面跳转
+      handleCurrentChange(val) {
+        this.formData.page = val;
+        this.getProjectList(true);
+      },
+    },
+    created() {
+      if(this.myTargertType==2||this.myTargertType==3){
+        this.idText='';
+      }
+      if(this.myTargertType==4){
+          let dept_tree=[];
+          this.userInfo.employee_detail.manage_dept_ids.forEach(id=>{
+            dept_tree.push(this.$getDept(id));
+          })
+          this.dept_tree=dept_tree;
+          this.$nextTick(()=>{
+            if(dept_tree.length>0){
+              this.dept_id=dept_tree[0].id
+            }
+          })
+      }
+    },
+    mounted() {
+      this.getProjectList();
+    },
+
+
+  };
+</script>
+<style scoped lang="scss">
+  .project-name{
+      font-size: 18px;
+      font-weight: 550;
+      color: rgb(20, 28, 40);
+      max-width: 700px;
+      padding-right: 6px;
+  }
+  .width-120 {
+    width: 100px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .selectUser ::v-deep input {
+    border-radius: 20px;
+  }
+
+  .user-item {
+    padding: 4px 6px;
+    border-radius: 5px;
+    cursor: pointer;
+  }
+
+  .userActive {
+    background-color: #ecf5ff;
+    color: #409EFF;
+  }
+
+  .span-item span {
+    font-size: 14px;
+    padding: 8px 20px;
+    border-bottom: 2px solid #f1f1f1;
+    // border-radius: 3px;
+    color: #89919f;
+    cursor: pointer;
+  }
+
+  .spanActive {
+    border-bottom: 2px solid #409EFF !important;
+    color: #409EFF !important;
+  }
+
+  .okrs-box {
+    padding: 10px 0;
+    border-bottom: 1px solid #f1f1f1;
+    cursor: pointer;
+  }
+
+  .okrs-box:hover {
+    background-color: #f4f6f9;
+  }
+
+  .message-items {
+    margin-top: 10px;
+    font-size: 12px;
+  }
+
+  .message-items div {
+    margin-right: 20px;
+    cursor: pointer;
+  }
+
+  .message-items i {
+    padding-right: 5px;
+  }
+
+  .weight {
+    width: 90px;
+    margin-right: 5px;
+    padding: 5px;
+    border: 1px dotted #fff;
+    border-radius: 5px;
+    cursor: pointer;
+  }
+
+  .weight:hover {
+    border: 1px dotted #ccc;
+  }
+
+  .weight span {
+    display: inline-block;
+    width: 50px;
+    text-align: center;
+    color: #e6a23c;
+  }
+
+  .kr-item {
+    padding: 4px 0;
+    position: relative;
+    margin-bottom: 8px;
+    border-radius: 5px;
+  }
+
+  .kr-item:hover {
+    background-color: #f4f6f9;
+  }
+
+  .huan {
+    position: relative;
+    width: 24px;
+    height: 24px;
+    border-radius: 100%;
+    background-color: #E9F1FE;
+    box-sizing: border-box;
+    margin: 0 3px;
+    text-align: center;
+    line-height: 25px;
+  }
+
+  .huan span {
+    border: 2px solid #409EFF;
+    border-radius: 100%;
+    width: 12px;
+    height: 12px;
+    display: inline-block;
+  }
+
+  .add-task-title {
+    padding: 10px;
+    position: relative;
+  }
+
+  .add-task-title::after {
+    content: "";
+    position: absolute;
+    width: 4px;
+    height: 14px;
+    border-radius: 5px;
+    background-color: #409EFF;
+    left: 0;
+    top: 13px;
+  }
+
+  .inputDc {
+    position: absolute;
+    top: 0;
+    right: 0;
+    left: 0;
+    bottom: 0;
+    z-index: 9;
+    cursor: pointer;
+  }
+
+  .el-icon-more {
+    width: 20px;
+    height: 20px;
+    box-sizing: border-box;
+    text-align: center;
+    line-height: 20px;
+    cursor: pointer;
+  }
+
+  .el-icon-more:hover {
+    background-color: #606266;
+    border-radius: 25px;
+    color: #fff;
+  }
+
+  .border {
+    -webkit-appearance: none;
+    background-color: #fff;
+    background-image: none;
+    border-radius: 4px;
+    border: 1px solid #dcdfe6;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+    color: #C0C4CF;
+    font-size: inherit;
+    height: auto;
+    outline: 0;
+    padding: 0 15px;
+    padding-right: 10px;
+    line-height: 34px;
+    width: 270px;
+    position: relative;
+    cursor: pointer;
+  }
+
+  .border .font-flex-word {
+    color: #606266;
+  }
+
+  .border:hover {
+    border: 1px solid #c0c4cc;
+  }
+
+  .add-task-item {
+    position: relative;
+  }
+
+  .add-task-item:hover i {
+    cursor: pointer;
+    opacity: 1 !important;
+  }
+
+  .add-task {
+    // padding-left: 30px;
+  }
+
+  .oBox {
+    box-shadow: 0 0px 10px #F0F4FA;
+    border-radius: 3px;
+    padding: 5px 8px;
+  }
+
+  .add-task ::v-deep input {
+    border: none;
+    padding-right: 80px;
+  }
+
+  .oBox ::v-deep input {
+    border: none;
+    font-size: 18px;
+    font-weight: 600;
+    color: #141c28;
+    padding-right: 80px;
+  }
+
+  .oBox ::v-deep input::-webkit-input-placeholder {
+    font-weight: 500;
+  }
+
+  .edit {
+    display: none;
+    position: relative;
+    top: 1px;
+    padding-left: 5px;
+    transition: all .3s;
+  }
+
+  .edit:hover {
+    color: #409EFF !important;
+  }
+
+  .selectBox {
+    background-color: #fff;
+    border-radius: 25px;
+    padding: 10px 20px;
+    padding-left: 0;
+  }
+
+  .header {
+    margin-bottom: 10px;
+    border-radius: 10px;
+    padding: 10px;
+    background-color: #fff;
+  }
+
+  .caret {
+    position: absolute;
+    width: 30px;
+    height: 30px;
+    text-align: center;
+    line-height: 30px;
+    left: -30px;
+    top: 50%;
+    margin-top: -15px;
+    box-sizing: border-box;
+    cursor: pointer;
+  }
+
+  .jt {
+    width: 24px;
+    position: relative;
+    top: 2px;
+    left: 4px;
+    transform: rotateY(180deg);
+    margin-left: 0px;
+  }
+
+  .itemBox {
+    padding: 20px;
+    border-radius: 10px;
+    background-color: #fff;
+    margin-bottom: 10px;
+    box-shadow: 0 2px 12px #EBF0F6;
+    cursor: pointer;
+  }
+
+  ::v-deep .el-tabs__item {
+    color: #89919f;
+  }
+
+  ::v-deep .is-active {
+    color: #409EFF;
+    font-weight: 600;
+  }
+
+  ::v-deep .el-tabs__nav .is-active {
+    font-size: 16px;
+  }
+
+  ::v-deep .el-tabs__header {
+
+    margin: 0px;
+  }
+
+  .select {
+    margin-right: 6px;
+  }
+
+  .select ::v-deep .el-input__inner {
+    border-radius: 25px;
+  }
+
+  .input ::v-deep .el-input__inner {
+    border: none;
+    border-bottom: 1px solid #f1f1f1;
+    border-radius: 0px;
+  }
+</style>

+ 418 - 0
src/okr/views/project/projectDetail.vue

@@ -0,0 +1,418 @@
+<template>
+    <div class="br-5 all">
+      <PageHead phName="项目详情"></PageHead>
+      <header>
+        <div class="flex-box-ce">
+          <svg-icon icon-class="#icon-wenjianjia" class="svgIcon blue" style="width: 1.6rem;height: 1.6rem;"></svg-icon>
+          <!-- <ProjectInputBox :value="detailData.name" :project_id="projectId" @confirm="getProjectDateil()"></ProjectInputBox> -->
+          <div style="border-radius: 5px;font-size: 18px;font-weight: 550;padding: 0 10px;color: #141c28 !important;}">{{detailData.name}}</div>
+          <div class="flex-box-ce cursor progress-box" :class="detailData.composite_state!=3?'blue-text':'red-text'" @click="openProjectQx(1)">
+              <el-progress :color="customColor" type="circle" :percentage="Number(detailData.process)" :show-text="false" :width="16" :stroke-width="3"></el-progress>
+              <span style="padding-left: 3px;">{{detailData.process}}%</span>
+          </div>
+          <div class="flex-1"></div>
+          <div class="flex-box-ce" style="flex-shrink: 0;width: 126px;margin-left:20px">
+            <div class="flex-box-ce icon-box cursor" style="margin-right: 30px;" @click="openProjectQx(2)">
+              <svg-icon icon-class="#icon-liaotian" class="svgIcon" style="width: 1.3rem;height: 1.3rem;"></svg-icon>
+              <span>沟通</span>
+            </div>
+            <div class="flex-box-ce icon-box2 cursor" @click="openProjectQx(3)">
+              <i class="el-icon-delete" style="font-size: 1.1rem;"></i>
+              <span>删除</span>
+            </div>
+          </div>
+        </div>
+        <div class="flex-box-ce" style="margin-top: 10px;">
+            <span class="flex-box-ce fontColorC">
+              <svg-icon icon-class="#icon-biaoqian_wode" class="svgIcon" style="width: 1rem;height: 1rem;"></svg-icon>
+              <span>&nbsp;{{$getEmployeeMapItem(detailData.owner_id).name}}</span>
+            </span>
+            <span class="flex-box-ce fontColorC" style="margin-left: 30px;">
+              <i class="el-icon-date"></i>
+              <span>{{detailData.start_date}}~{{detailData.end_date}}</span>
+              <span v-if="detailData.day>0">(剩余<span class="green">{{detailData.day}}</span>天)</span>
+              <span v-if="detailData.day==0">(剩余<span class="green">1</span>天)</span>
+              <span v-if="detailData.day<0">(逾期<span class="red">{{Math.abs(detailData.day)}}</span>天)</span>
+            </span>
+        </div>
+      </header>
+      <div class="content">
+        <div class="flex-box-ce" style="background-color: #fff;box-shadow: 0 10px 10px #F3F5F8;margin: 0 -20px;padding: 0 10px;margin-top: 10px;">
+          <div class="tabs-item" :class="tabsIndex == item.code ? 'tabs-item-active' : ''" v-for="(item, index) in tabs" :key="index" @click="tabAction(item)">
+            {{ item.name }}
+          </div>
+        </div>
+        <div class="main-main">
+          <!-- 任务 -->
+          <template v-if="tabsIndex==1">
+              <div v-loading="loading">
+                <div class="flex-box-ce flex-d-center" style="margin: 20px 0;">
+                  <div class="flex-box-ce">
+                    <el-select class="select2" size="small" v-model="taskForm.composite_states" placeholder="状态">
+                      <el-option :key="0" label="全部" :value="0"></el-option>
+                      <el-option v-for="item in taskStatus" :key="item.value" :label="item.name" :value="item.value"></el-option>
+                    </el-select>
+                    <el-select class="select2" size="small" v-model="taskForm.sort" placeholder="排序">
+                      <el-option :key="0" label="默认排序" :value="0"></el-option>
+                      <el-option :key="1" label="创建时间" :value="1"></el-option>
+                      <el-option :key="2" label="开始时间" :value="2"></el-option>
+                      <el-option :key="3" label="结束时间" :value="3"></el-option>
+                    </el-select>
+                    <el-select class="select2"  collapse-tags filterable size="small" v-model="taskForm.owner_ids" clearable placeholder="人员">
+                      <el-option  v-for="item in employeeMap" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                    </el-select>
+                    <el-input prefix-icon="el-icon-search" class="input"  style="width: 200px;" size="small" v-model="keyword" clearable placeholder="请输入任务关键字" />
+                  </div>
+                  <div class="add-task" @click="isShowAddTask=true" v-if="isPstake">+ 添加任务</div>
+                </div>
+
+                <template v-if="taskList.length>0">
+                <!-- @confirm="getPastTask" -->
+                  <TaskItem isParent isShowChild :isShow="taskShow" :list="taskList" :isSimpleShow="2" ></TaskItem>
+                </template>
+                <!-- <PlanTaskItem :list="taskList" :isShowTarget="true" style="padding-left: 0;" @confirm="getPastTask(true)"></PlanTaskItem> -->
+
+                <div class="dotted-line" v-if="taskList.length==0">
+                   <div>暂无数据</div>
+                </div>
+                <el-pagination style="text-align: center;margin-top: 20px;" :current-page.sync="page" :page-sizes="[10, 20, 50, 100]"  layout="total,prev,pager,next,sizes" :total="total" @size-change="handleSizeChange"  @current-change="handleCurrentChange" :page-size="page_size"></el-pagination>
+              </div>
+          </template>
+
+          <!-- 里程碑 -->
+          <template v-if="tabsIndex==2">
+              <Milestone :isReturnPJ="isReturnPJ" :isPstake="isPstake"></Milestone>
+          </template>
+
+          <!-- 看板 -->
+          <template v-if="tabsIndex==3">
+              <KanMilestone :isReturnPJ="isReturnPJ" :isPstake="isPstake"></KanMilestone>
+          </template>
+
+          <!-- 甘特图 -->
+          <template v-if="tabsIndex==4">
+              <Dhtmlx :isReturnPJ="isReturnPJ" :isPstake="isPstake"></Dhtmlx>
+          </template>
+
+          <!-- 统计 -->
+          <template v-if="tabsIndex==5">
+              <ProjectTj :isReturnPJ="isReturnPJ" :isPstake="isPstake"></ProjectTj>
+          </template>
+
+          <!-- 沟通 -->
+          <template v-if="tabsIndex==6">
+              <Interaction ref="Interaction" style="margin-top: 30px;" v-if="tabsIndex==6" :isOperation="isPstake" :target_id="projectId" :target_type="4"></Interaction>
+          </template>
+
+          <!-- 预览 -->
+          <template v-if="tabsIndex==7">
+              <ProjectPreview></ProjectPreview>
+          </template>
+        </div>
+      </div>
+
+      <!--添加任务 -->
+      <AddTask  :visible.sync="isShowAddTask" :target_type="4" :id="projectId" :initDate="initDateVal" @confirm="getPastTask()"></AddTask>
+
+      <!-- 删除目标 -->
+      <el-dialog title="确认删除此项目吗?" :visible.sync="isShowDeleteProject" :append-to-body="true" width="500px">
+        <div style="padding: 10px;">
+           <el-radio-group v-model="radio">
+             <div style="margin-bottom: 16px;"><el-radio :label="1">不删除项目下的任务</el-radio></div>
+             <div><el-radio :label="2">同时删除项目下的所有任务</el-radio></div>
+          </el-radio-group>
+        </div>
+        <div class="flex-box-end" style="margin-top: 50px;">
+        	<el-button @click="isShowDeleteProject=false">取消</el-button>
+        	<el-button type="danger" plain @click="deleteProject()">删除</el-button>
+        </div>
+      </el-dialog>
+
+      <!-- 设置完成度 -->
+      <ProjectSchedule :visible.sync="isShowSchedule" :progressData="progressData" @confirm="getProjectDateil()"></ProjectSchedule>
+    </div>
+</template>
+
+<script>
+  import ProjectInputBox from '@/okr/components/project/ProjectInputBox'; //input组件
+  import PlanTaskItem from '@/okr/components/planTask/PlanTaskItem'; //任务内容
+  import AddTask from '@/okr/components/public/AddTask'; //添加任务
+  import Milestone from '@/okr/components/project/Milestone'; //里程碑
+  import KanMilestone from '@/okr/components/project/KanMilestone'; //看板
+  import ProjectTj from '@/okr/components/project/ProjectTj'; //统计
+  import Interaction from '@/okr/components/TargetDetail/Interaction'; //沟通
+  import ProjectPreview from '@/okr/components/project/ProjectPreview'; //预览
+  import ProjectSchedule from '@/okr/components/project/ProjectSchedule'; //完成度
+  import Dhtmlx from '@/okr/components/project/Dhtmlx'; //甘特图
+  import PageHead from '@/components/PageHead'; //头部---返回
+  import Tooltip from '@/components/Tooltip'; //鼠标悬浮显示文字文字
+  import { getYearArr,taskStatus,isReturnPJ,isPstake} from '@/okr/utils/auth';
+  import {_debounce} from '@/utils/auth';
+  export default {
+    name: 'projectDetail',
+    components: {ProjectInputBox,PlanTaskItem,AddTask,Milestone,PageHead,ProjectTj,Interaction,ProjectPreview,Dhtmlx,ProjectSchedule,Tooltip,KanMilestone},
+    data() {
+      return {
+        userInfo:this.$userInfo(),
+        tabsIndex:0,
+        projectId:0,
+        detailData:{process:0},
+        tabs: [
+          { name: '任务', code: 1 },
+          { name: '里程碑', code: 2 },
+          { name: '看板', code: 3 },
+          { name: '甘特图', code: 4 },
+          { name: '统计', code: 5 },
+          { name: '沟通', code: 6 },
+          { name: '预览', code: 7 }
+        ],
+        taskForm:{composite_states:0,sort:3,owner_ids:''},
+        taskStatus:taskStatus(),
+        employeeMap:this.$getEmployeeMap(),
+        taskList:[],
+        page:1,
+        page_size:10,
+        total:0,
+        keyword:'',
+        loading:false,
+        isShowAddTask:false,
+        initDateVal:[],
+        isShowDeleteProject:false,
+        radio:1,
+
+        isShowDateSearch:false,
+        isShowSchedule:false,
+        progressData:{},
+        customColor:'#409EFF',
+        taskShow:false,
+        isReturnPJ:false,
+        isPstake:false,//是否是相关成员
+      }
+    },
+    watch: {
+      keyword: {
+        deep: true,
+        handler: _debounce(function(val) {
+          this.getPastTask();
+        })
+      },
+      'taskForm.composite_states'(){
+          this.getPastTask();
+      },
+      'taskForm.owner_ids'(){
+          this.getPastTask();
+      },
+      'taskForm.sort'(){
+          this.getPastTask();
+      },
+      tabsIndex(val,lat){
+        if(val==1){
+          this.getPastTask();
+        }
+        if(lat==7){
+           this.getProjectDateil();
+        }
+      },
+    },
+    created() {
+        if(this.$route.query.id){
+
+          this.projectId=Number(this.$route.query.id);
+          this.getProjectDateil();
+          this.$nextTick(()=>{
+            this.tabsIndex=1;
+          })
+        }
+    },
+    methods:{
+      openProjectQx(index){
+        if(!this.isReturnPJ){
+          this.$message.error("暂无权限")
+          return false
+        }
+        if(index==1){
+          this.isShowSchedule=true
+        }else if(index==2){
+          this.openGt();
+        }else if(index==3){
+          this.isShowDeleteProject=true;
+        }
+      },
+      openGt(){
+        this.tabsIndex=6;
+        this.$nextTick(()=>{
+          this.$refs.Interaction.isShowCommunication=true;
+        })
+      },
+      getProjectDateil(){
+          this.loading=true;
+          this.$axiosUser('get', '/api/pro/okr/project/info',{project_id:this.projectId}).then(res => {
+              let data=res.data.data;
+              data.day=this.$moment(data.end_date).diff(this.$moment().format('YYYY-MM-DD'), 'day')
+              this.customColor=data.composite_state==3? '#f56c6c':'#409EFF';
+              // 进度
+              data.process_conf.process=data.process;
+              data.process_conf.id=this.projectId;
+              this.initDateVal=[data.start_date,data.end_date];
+              this.progressData=data.process_conf;
+              this.detailData=data;
+              this.isReturnPJ=isReturnPJ(data);
+              this.isPstake=isPstake(data)
+          }).finally(()=>{
+            this.loading=false;
+          })
+      },
+      deleteProject(){
+        this.$axiosUser('post', '/api/pro/okr/project/d', { project_id: this.projectId }).then(res => {
+             this.isShowDeleteProject=false;
+             this.$nextTick(()=>{
+                this.$router.go(-1);
+             })
+        });
+      },
+      handleCommand(){
+
+      },
+      tabAction(item) {
+        this.tabsIndex = item.code;
+      },
+      getPastTask(is){
+        is? '':this.page=1;
+        this.taskShow=false;
+        let data={
+          project_id:this.projectId,
+          composite_states:this.taskForm.composite_states,//	否	string	每页数据量
+          start_day:'2022-01-01',
+          end_day:this.$moment().format('YYYY-MM-DD'),
+          employee_id:this.userInfo.id,
+          page:is? this.page:1,
+          page_size:this.page_size,
+          keyword:this.keyword,
+        }
+        if(this.taskForm.sort){
+          this.taskForm.sort==1? data.sort_c=1:this.taskForm.sort==2? data.sort_s=1:data.sort_e=1
+        }
+        if(this.taskForm.owner_ids){
+          data.owner_ids=this.taskForm.owner_ids
+        }
+        this.loading = true;
+        this.$axiosUser('get', '/api/pro/okr/plan/list/project',data).then(res => {
+          let list=res.data.data.list;
+          this.total=res.data.data.total;
+          list.forEach(item=>{
+             item.isShow=false;
+             item.stateInfo=taskStatus(item.composite_state);
+             item.userInfo=this.$getEmployeeMapItem(item.owner_id)//负责人
+             item.day=this.$moment(item.end_date).diff(this.$moment().format('YYYY-MM-DD'), 'day')
+          })
+          this.taskList=list;
+          this.$nextTick(()=>{
+            this.taskShow=true;
+          })
+        }).finally(()=>{
+           this.loading = false;
+        })
+      },
+      handleSizeChange(val) {
+        this.page=1;
+        this.page_size = val;
+        this.getPastTask();
+      },
+      // 页面跳转
+      handleCurrentChange(val) {
+        this.page = val;
+        this.getPastTask(true);
+      },
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .all {
+    background-color: #fff;
+    padding: 20px;
+    padding-top: 10px;
+    height: calc(100vh - 80px);
+    box-sizing: border-box;
+  }
+  .project-name{
+      font-size: 18px;
+      font-weight: 550;
+      color: rgb(20, 28, 40);
+      min-width: 600px;
+      padding-left: 5px;
+      padding-right: 10px;
+  }
+  .progress-box{
+    border-radius: 20px;
+    padding: 3px 6px;
+    font-size: 12px;
+  }
+  .blue-text{
+    color: #409EFF;
+    background-color: #ECF5FF;
+  }
+  .red-text{
+    color: #f56c6c;
+    background-color: #FDEFEF;
+  }
+
+
+  .icon-box,.icon-box2{
+    color: #606266;
+  }
+  .icon-box:hover{
+    color: #409EFF;
+  }
+  .icon-box2:hover{
+    color: #f56c6c;
+  }
+  .tabs-item {
+    font-weight: 600;
+    color: #909399;
+    padding: 10px 15px;
+    cursor: pointer;
+    font-size: 15px;
+
+    position: relative;
+  }
+  .tabs-item-active {
+    color: #409EFF;
+  }
+  .tabs-item-active::after {
+    content: '';
+    position: absolute;
+    width: 24px;
+    height: 4px;
+    bottom: 0;
+    left: 50%;
+    margin-left: -12px;
+    background-color: #409EFF;
+    border-radius: 25px;
+  }
+  .main-main {
+    // padding-top: 10px;
+     // min-height: calc(100vh - 270px);
+  }
+  .select2 {
+    width: 100px;
+    margin-right: 10px;
+  }
+  .select2 ::v-deep .el-input__inner {
+    border-radius: 25px;
+  }
+  .input ::v-deep .el-input__inner {
+    border: none;
+    border-bottom: 1px solid #f1f1f1;
+    border-radius: 0px;
+  }
+  .add-task {
+    width: 120px;
+    text-align: center;
+    color: #fff;
+    background-color: #409EFF;
+    padding: 4px 0;
+    border-radius: 3px;
+    cursor: pointer;
+  }
+</style>

+ 39 - 0
src/okr/views/project/publicProject.vue

@@ -0,0 +1,39 @@
+<template>
+    <myProject :myTargertType="2"></myProject>
+</template>
+<script>
+import myProject from '@/okr/views/project/myProject'; //选择周期
+export default {
+  components:{myProject},
+  name:'publicProject',
+  data() {
+    return {
+    };
+  },
+  mounted() {
+  }
+};
+</script>
+<style scoped lang="scss">
+.set_basics_box {
+  padding: 20px;
+  background: #fff;
+}
+/deep/ .el-message--info {
+  font-size: 20px;
+}
+.el-form-item__error {
+  left: 50px;
+}
+.tips {
+  background: #409EFF;
+  border-radius: 50%;
+  width: 14px;
+  height: 14px;
+  color: #fff;
+  display: inline-block;
+  font-size: 12px;
+  line-height: 14px;
+  text-align: center;
+}
+</style>

+ 8 - 0
src/okr/views/project/recentlyProject.vue

@@ -0,0 +1,8 @@
+<template>
+</template>
+
+<script>
+</script>
+
+<style>
+</style>

+ 5 - 1
src/okr/views/targetMap/targetMap.vue

@@ -504,7 +504,11 @@ export default {
         this.addOkrParameter.o_id='';
         this.addOkrParameter.kr_id='';
         this.alignItem=e.item;
-        e.type==1? this.addOkrParameter.o_id=e.item.id:this.addOkrParameter.kr_id=e.item.id
+        if(e.type==1){
+          this.addOkrParameter.o_id=e.item.i
+        }else if(e.type==2){
+          this.addOkrParameter.kr_id=e.item.id
+        }
       }
     },
     openDuiQi(type){

+ 43 - 5
src/okr/views/targetMt/myTargert.vue

@@ -102,7 +102,7 @@
                 <el-option  v-for="item in employeeOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
               </el-select>
             </template>
-            <el-button type="primary" round style="margin-right: 10px;" @click="isShowAdd=true" v-if="myTargertType==1">创建目标</el-button>
+            <el-button type="primary" size="small" round style="margin-right: 10px;" @click="isShowAdd=true" v-if="myTargertType==1">创建目标</el-button>
             <div class="selectBox">
                 <div class="flex-box-ce">
                     <SelectPeriod :dateParameter="dateParameter" @confirm="dateConfirm" :id="1">
@@ -240,6 +240,8 @@
                                       <template v-if="krItem.can_edit">
                                         <el-dropdown-item command="a">添加任务</el-dropdown-item>
                                         <el-dropdown-item command="b">关联任务</el-dropdown-item>
+                                        <el-dropdown-item command="d">添加项目</el-dropdown-item>
+                                        <el-dropdown-item command="e">关联项目</el-dropdown-item>
                                       </template>
                                       <el-dropdown-item command="c" v-if="krItem.can_delete">删除</el-dropdown-item>
                                     </el-dropdown-menu>
@@ -702,6 +704,11 @@
       @confirm="attentionUser_employee_confirm"
     />
 
+    <!-- 添加项目 -->
+    <AddProject :visible.sync="isShowProjectAdd" :kr_id="selectKrItem.id" @confirm="ActiveAddProject"></AddProject>
+
+    <!-- 对齐目标 -->
+    <TargetSearch :visible.sync="isShowProject" :showSelectType="2" @confirm="confirmProject"></TargetSearch>
   </div>
 </template>
 <script>
@@ -721,6 +728,7 @@ import AddOkr from '@/okr/components/public/AddOkr'; //添加OKR
 import Rate from '@/okr/components/public/Rate'; //添加OKR
 import TargetDetail from '@/okr/components/public/TargetDetail'; //目标详情
 import Progress from '@/okr/components/public/Progress'; //进度条
+import AddProject from '@/okr/components/project/AddProject'; //
 import {_debounce} from '@/utils/auth';
 
 import CollapseTransition from '@/okr/utils/collapse-transition';
@@ -730,7 +738,7 @@ export default {
   name: 'myTargert',
   components:{RelevanceTask,AddTask,SelectPeriod,CollapseTransition,Tooltip,TargetSearch,
               SelectPeriodGj,InputBox,EmployeeSelector,Schedule,CopyTarget,TargetType,
-              AligningTargetDetail,TaskItem,AddOkr,Rate,TargetDetail,Progress},
+              AligningTargetDetail,TaskItem,AddOkr,Rate,TargetDetail,Progress,AddProject},
   props:{
     myTargertType:{ //从不同页面加载  1:默认 2:我的部门 3全部部门  4我关注的 5关注的人 6我的上级  7我的下级
       type:Number,
@@ -863,6 +871,10 @@ export default {
       attentionUser_employee_selected: { dept: [], employee: [] },
       show_attentionUser: false,
       employee_not_select: [],
+
+      // 项目
+      isShowProjectAdd:false,
+      isShowProject:false,
     };
   },
   computed:{
@@ -908,6 +920,16 @@ export default {
     }
   },
   methods: {
+    // 关联项目
+    confirmProject(item){
+        this.$axiosUser('post', '/api/pro/okr/project/bind',{project_id:item.item.id,kr_id:this.selectKrItem.id}).then(res => {
+             this.getTargetDateil(this.selectKrItem.o_id,true);
+        });
+    },
+    //添加项目
+    ActiveAddProject(){
+      this.getTargetDateil(this.selectKrItem.o_id,true);
+    },
     activeAllUser(){
       this.owner_id='';
       this.dept_id2='';
@@ -1149,13 +1171,21 @@ export default {
         this.addOkrParameter.o_id='';
         this.addOkrParameter.kr_id='';
         this.alignItem=e.item;
-        e.type==1? this.addOkrParameter.o_id=e.item.id:this.addOkrParameter.kr_id=e.item.id
+        if(e.type==1){
+          this.addOkrParameter.o_id=e.item.id
+        }else if(e.type==2){
+          this.addOkrParameter.kr_id=e.item.id
+        }
       }else if(this.selectAlignIndex==2){ //列表对齐目标
           this.updateDuiQi(e)
       }else if(this.selectAlignIndex==3){//编辑 对齐目标
         this.compileTarget.o_id='';
         this.compileTarget.kr_id='';
-        e.type==1? this.compileTarget.o_id=e.item.id:this.compileTarget.kr_id=e.item.id
+        if(e.type==1){
+          this.addOkrParameter.o_id=e.item.id
+        }else if(e.type==2){
+          this.addOkrParameter.kr_id=e.item.id
+        }
         this.compileTarget.p_name=e.item.name
       }
     },
@@ -1184,6 +1214,7 @@ export default {
          // this.isShowAdd=false;
       });
     },
+    //动态更新目标详情
     getTargetDateil(targetId,isShowText){
       let id=targetId? targetId:this.selectAlignItem.id
       this.$axiosUser('get', '/api/pro/okr/obj/list', {page:1,page_size:1,ids:JSON.stringify([id])}).then(res => {
@@ -1451,6 +1482,14 @@ export default {
                 })
             }).catch(() => {});
             break
+        case 'd'://添加项目
+            this.selectKrItem=item;
+            this.isShowProjectAdd=true;
+            break
+        case 'e'://关联项目
+            this.selectKrItem=item;
+            this.isShowProject=true;
+            break
       }
     },
     handleCommand(name,item){
@@ -1704,7 +1743,6 @@ export default {
       return data;
     },
   },
-
   mounted() {
     if(this.myTargertType==1){
       this.getTargetList();

+ 4 - 2
src/okr/views/targetMt/okrInform.vue

@@ -86,9 +86,11 @@ export default {
         this.showDetailType = item.target_type;
         this.showDetailId = item.target_id; //打开详情的ID
         this.isShowTargetDetail = true;
-      } else {
-        this.showDetailId = item.target_id; //打开详情的ID
+      } else if (item.target_type == 3){
+        this.showDetailId = item.target_id; //打开任务详情的ID
         this.isShowTaskDetail = true;
+      } else if (item.target_type == 4){  //项目
+        this.$router.push({path:'/projectDetail',query:{id:item.target_id}})
       }
     },
     //待办

+ 2 - 0
src/performance/views/assessManagement/assessDetails.vue

@@ -274,6 +274,7 @@
       </EmployeeSelector>
 
     <!-- 更多-管理-人员选择删除 -->
+
     <EmployeeSelector
       :title="packageName + ':选择被删除人员'"
       :is_filtration_creator="false"
@@ -281,6 +282,7 @@
       :can_select_dept="false"
       :isRequired="true"
       :user_employee_list="true"
+      :include_deleted="true"
       :employee_list="employee_list"
       :visible.sync="isShowUser2"
       @confirm="confirmUser2"

+ 4 - 5
src/performance/views/assessManagement/staffAssDet.vue

@@ -14,9 +14,9 @@
             <userImage class="fl" :id="remployee.id" :user_name="remployee.name" :img_url="remployee.img_url" width="50px" height="50px"></userImage>
             <div class="headTname">
               <div style="">{{ remployee.name }}</div>
-              <span v-for="(item, index) in remployee.deptList" :key="index" v-if="remployee.deptList.length>0">
+              <span v-for="(item, index) in remployee.employee_detail.dept_list" :key="index" v-if="remployee.employee_detail.dept_list.length>0">
                 <span>{{ item.dept_name }}</span>
-                <span v-if="remployee.deptList.length - index > 1">,</span>
+                <span v-if="remployee.employee_detail.dept_list.length - index > 1">,</span>
               </span>
             </div>
             <img v-if="has_finish" src="static/images/guidang.png" class="guidang">
@@ -610,7 +610,7 @@ export default {
       employeeID: 0, //考核记录ID
       employID: 0, //当前角色ID
       empDetList: {},
-      remployee: {}, //被考核人员信息
+      remployee: {employee_detail:{}}, //被考核人员信息
       flow: [], //步骤
       atPresentFlow: 0, //当前步骤ID
       dimension: [], //维度表格信息
@@ -1417,8 +1417,7 @@ export default {
             if(this.pendingList){
                this.setUpD();
             }
-            this.remployee = data.relevance_employee; //被考核人员信息
-            this.remployee.deptList = data.relevance_employee.employee_detail.dept_list; //部门
+            this.remployee =this.$getEmployeeMapItem(data.employee_id); //被考核人员信息
             this.flow = data.flow; //流程
             this.config = data.config;
             this.calc_dimension = data.calc_dimension; //是否参与权重

+ 7 - 7
src/performance/views/myPerformance/myPerformance.vue

@@ -56,10 +56,10 @@
           <userImage class="fl" :id="remployee.id" :user_name="remployee.name" :img_url="remployee.img_url" width="50px" height="50px"></userImage>
           <div class="headTname">
             <div style="">{{ remployee.name }}</div>
-            <span v-for="(item, index) in remployee.deptList" :key="index" v-if="remployee.deptList.length>0">
-              <span>{{ item.dept_name }}</span>
-              <span v-if="remployee.deptList.length - index > 1">,</span>
-            </span>
+              <span v-for="(item, index) in remployee.employee_detail.dept_list" :key="index" v-if="remployee.employee_detail.dept_list.length>0">
+                <span>{{ item.dept_name }}</span>
+                <span v-if="remployee.employee_detail.dept_list.length - index > 1">,</span>
+              </span>
           </div>
         </div>
 
@@ -605,7 +605,7 @@ export default {
       employeeID: 0, //考核记录ID
       employID: 0, //当前角色ID
       empDetList: {},
-      remployee: {}, //被考核人员信息
+      remployee: {employee_detail:{}}, //被考核人员信息
       flow: [], //步骤
       atPresentFlow: 0, //当前步骤ID
       dimension: [], //维度表格信息
@@ -1258,8 +1258,8 @@ export default {
             this.recordMemberIds = data.record_member_ids;
             this.empDetList = data;
             this.employeeID = data.id; //考核记录id
-            this.remployee = data.relevance_employee; //被考核人员信息
-            this.remployee.deptList = data.relevance_employee.employee_detail.dept_list; //部门
+            this.remployee =this.$getEmployeeMapItem(data.employee_id); //被考核人员信息
+            console.log(this.remployee)
             this.flow = data.flow; //流程
             this.config = data.config;
             this.publicity=data.publicity;

+ 39 - 1
src/performance/views/myPerformance/resultSetAll2.vue

@@ -12,6 +12,15 @@
         <span :class="{ active: activeName == 'entering' }" style="width: 80px;" @click="activeName = 'entering'">已录入({{ statusListTotal }})</span>
       </div>
       <div v-show="activeName == 'noEntering'">
+        <div style="margin-bottom: 20px;" class="flex-box-ce">
+          <el-checkbox v-model="paiXu" label="1">按指标名称排序 </el-checkbox>
+          <el-tooltip effect="dark" placement="top">
+            <template slot="content">
+              <div>指标名称排在一起,便于对多人且同个指标的结果值录入(注意:中途修改排序需重新填写结果值)</div>
+            </template>
+             <i class="el-icon-question fontColorC cursor"></i>
+          </el-tooltip>
+        </div>
         <el-table :data="noStatusList" border :header-cell-style="{ background: '#ECF5FF' }">
           <el-table-column prop="employee_name" label="姓名"></el-table-column>
           <el-table-column prop="index_name" label="指标名称"></el-table-column>
@@ -242,7 +251,9 @@ export default {
   data() {
     return {
       activeName: 'noEntering',
+      noStatusListAll:[],
       noStatusList: [],
+      noStatusListDx:[],
       noStatusListTotal: 0,
       statusList: [],
       statusListTotal: 0,
@@ -266,6 +277,7 @@ export default {
 
       employee_ids:[],
       employee_map: this.$getEmployeeMap(),
+      paiXu:false,
     };
   },
   computed:{
@@ -280,6 +292,13 @@ export default {
     },
   },
   watch: {
+    paiXu(val){
+      if(val){
+        this.noStatusList=JSON.parse(JSON.stringify(this.noStatusListDx));
+      }else{
+        this.noStatusList=JSON.parse(JSON.stringify(this.noStatusListAll));
+      }
+    },
     isResult(val) {
       if (!val) {
         this.results = []; //提交的返回结果集合
@@ -508,7 +527,26 @@ export default {
         this.statusListTotal = statusList.length;
         this.noStatusListTotal = noStatusList.length;
         this.statusList = statusList;
-        this.noStatusList = noStatusList;
+        this.noStatusList = JSON.parse(JSON.stringify(noStatusList));
+        this.noStatusListAll =JSON.parse(JSON.stringify(noStatusList));
+        let obj={},arr=[];
+        noStatusList.forEach(item=>{
+          if(obj[item.index_name]){
+            obj[item.index_name].push(item)
+          }else{
+            obj[item.index_name]=[item]
+          }
+        })
+        for (let key in obj) {
+          arr.push(obj[key]);
+        }
+        arr.sort(function(a, b){return b.length - a.length});
+        let noStatusListDx=[]
+        arr.forEach(item=>{
+          noStatusListDx.push(...item);
+        })
+        this.noStatusListDx=noStatusListDx
+
       });
     },
     //判断评分节点是否有人评了分

+ 1 - 1
src/point/views/common/applicationIntegrationPopup.vue

@@ -657,7 +657,7 @@ export default {
   height: auto;
   max-width: 500px;
   .el-cascader-node__label {
-    white-space: initial;
+    // white-space: initial;
     overflow: initial;
     text-overflow: initial;
   }

+ 1 - 1
src/point/views/common/bonusPointsPopup.vue

@@ -853,7 +853,7 @@ export default {
   height: auto;
   max-width: 500px;
   .el-cascader-node__label {
-    white-space: initial;
+    // white-space: initial;
     overflow: initial;
     text-overflow: initial;
   }

+ 1 - 1
src/point/views/common/examinePopup.vue

@@ -768,7 +768,7 @@ export default {
   height: auto;
   max-width: 500px;
   .el-cascader-node__label {
-    white-space: initial;
+    // white-space: initial;
     overflow: initial;
     text-overflow: initial;
   }

+ 1 - 1
src/point/views/statistics/custom_rank.vue

@@ -738,7 +738,7 @@ export default {
   height: auto;
   max-width: 500px;
   .el-cascader-node__label {
-    white-space: initial;
+    // white-space: initial;
     overflow: initial;
     text-overflow: initial;
   }

+ 13 - 9
src/router/index.js

@@ -194,8 +194,8 @@ const constantRouterMap = [
               jurisdiction: ['creator', 'admin', 'point_manager', 'dept_manager']
             }
           },
-          
-          
+
+
 
           // 考勤管理
           {
@@ -207,7 +207,7 @@ const constantRouterMap = [
               jurisdiction: ['creator', 'admin', 'point_manager']
             }
           },
-          
+
           // {
           //   path: '/attendance_classnew',
           //   name: '班次管理',
@@ -244,9 +244,9 @@ const constantRouterMap = [
           //     jurisdiction: ['creator', 'admin', 'point_manager']
           //   }
           // },
-          
-          
-          
+
+
+
           {
             path: '/attendance_reviewnew',
             name: '考勤审批',
@@ -256,7 +256,7 @@ const constantRouterMap = [
               jurisdiction: ['creator', 'admin', 'point_manager']
             }
           },
-          
+
           {
             path: '/attendanceStatistics',
             name: '考勤统计',
@@ -266,7 +266,7 @@ const constantRouterMap = [
               jurisdiction: ['creator', 'admin', 'point_manager']
             }
           },
-          
+
           // {
           //   path: '/attendance_datanew',
           //   name: '日打卡明细',
@@ -470,6 +470,10 @@ const constantRouterMap = [
     path: '/authredirect',
     component: () => import('@/views/authredirect'),
   },
+  {
+    path: '/demo',
+    component: () => import('@/views/demo'),
+  },
   {
     path: '/404',
     component: () => import('@/views/404'),
@@ -480,7 +484,7 @@ const constantRouterMap = [
   },
   {
     path: '*',
-    redirect: '/404'
+    redirect: '/login'
   },
 ]
 constantRouterMap[0].children.push(...performanceRouter,...okrRouter,...examineRouter);

+ 82 - 16
src/router/okrRouter.js

@@ -7,7 +7,8 @@ const routes = [{
   children: [
     {
       path: '/myTargert',
-      name: '我的目标',
+      name: 'myTargert',
+      label: '我的目标',
       component: () => import('@/okr/views/targetMt/myTargert'),
       meta: {
         groupCode: 'targertAdministration',
@@ -15,7 +16,8 @@ const routes = [{
     },
     {
       path: '/mySuperior',
-      name: '我的上级',
+      name: 'mySuperior',
+      label: '我的上级',
       component: () => import('@/okr/views/targetMt/mySuperior'),
       meta: {
         groupCode: 'targertAdministration',
@@ -23,7 +25,8 @@ const routes = [{
     },
     {
       path: '/mySubordinate',
-      name: '我的下级',
+      name: 'mySubordinate',
+      label: '我的下级',
       component: () => import('@/okr/views/targetMt/mySubordinate'),
       meta: {
         groupCode: 'targertAdministration',
@@ -31,7 +34,8 @@ const routes = [{
     },
     {
       path: '/deptTargert',
-      name: '我的部门',
+      name: 'deptTargert',
+      label: '我的部门',
       component: () => import('@/okr/views/targetMt/deptTargert'),
       meta: {
         groupCode: 'targertAdministration',
@@ -40,7 +44,8 @@ const routes = [{
 
     {
       path: '/attentionUser',
-      name: '关注的人',
+      name: 'attentionUser',
+      label: '关注的人',
       component: () => import('@/okr/views/targetMt/attentionUser'),
       meta: {
         groupCode: 'targertAdministration',
@@ -49,7 +54,8 @@ const routes = [{
 
     {
       path: '/companyTargert',
-      name: '公司全部',
+      name: 'companyTargert',
+      label: '公司全部',
       component: () => import('@/okr/views/targetMt/companyTargert'),
       meta: {
         groupCode: 'targertAdministration',
@@ -58,7 +64,8 @@ const routes = [{
 
     {
       path: '/targetMap',
-      name: '目标地图',
+      name: 'targetMap',
+      label: '目标地图',
       component: () => import('@/okr/views/targetMap/targetMap'),
       meta: {
         groupCode: 'targertAdministration',
@@ -67,7 +74,8 @@ const routes = [{
     // 工作计划
     {
       path: '/planTable',
-      name: '计划表',
+      name: 'planTable',
+      label: '计划表',
       component: () => import('@/okr/views/planTask/planTable'),
       meta: {
         groupCode: 'planTask',
@@ -75,7 +83,8 @@ const routes = [{
     },
     {
       path: '/work',
-      name: '目标规划',
+      name: 'work',
+      label: '目标规划',
       component: () => import('@/okr/views/planTask/work'),
       meta: {
         groupCode: 'planTask',
@@ -84,7 +93,8 @@ const routes = [{
     // 目标分析
     {
       path: '/targetEnact',
-      name: '目标制定',
+      name: 'targetEnact',
+      label: '目标制定',
       component: () => import('@/okr/views/targetAnalyse/targetEnact'),
       meta: {
         groupCode: 'targetAnalyse',
@@ -92,7 +102,8 @@ const routes = [{
     },
     {
       path: '/targetExecute',
-      name: '目标执行',
+      name: 'targetExecute',
+      label: '目标执行',
       component: () => import('@/okr/views/targetAnalyse/targetExecute'),
       meta: {
         groupCode: 'targetAnalyse',
@@ -100,7 +111,8 @@ const routes = [{
     },
     {
       path: '/targetScore',
-      name: '目标评分',
+      name: 'targetScore',
+      label: '目标评分',
       component: () => import('@/okr/views/targetAnalyse/targetScore'),
       meta: {
         groupCode: 'targetAnalyse',
@@ -108,7 +120,8 @@ const routes = [{
     },
     {
       path: '/taskTj',
-      name: '任务统计',
+      name: 'taskTj',
+      label: '任务统计',
       component: () => import('@/okr/views/targetAnalyse/taskTj'),
       meta: {
         groupCode: 'targetAnalyse',
@@ -116,7 +129,8 @@ const routes = [{
     },
     {
       path: '/targetSet',
-      name: '目标设置',
+      name: 'targetSet',
+      label: '目标设置',
       component: () => import('@/okr/views/targetSet/targetSet'),
       meta: {
         groupCode: 'targetSet',
@@ -124,7 +138,8 @@ const routes = [{
     },
     {
       path: '/okrInform',
-      name: 'OKR 通知',
+      name: 'okrInform',
+      label: 'OKR 通知',
       component: () => import('@/okr/views/targetMt/okrInform'),
       hidden: true, // 不在侧边栏线上
       meta: {
@@ -134,12 +149,63 @@ const routes = [{
     // 目标复盘
     {
       path: '/replay',
-      name: '目标复盘',
+      name: 'replay',
+      label: '目标复盘',
       component: () => import('@/okr/views/targetMt/replay'),
       meta: {
         groupCode: 'replay',
       }
     },
+    // 项目
+    {
+      path: '/myProject',
+      name: 'myProject',
+      label: '我的项目',
+      component: () => import('@/okr/views/project/myProject'),
+      meta: {
+        groupCode: 'project',
+      }
+    },
+    {
+      path: '/deptProject',
+      name: 'deptProject',
+      label: '我的部门',
+      component: () => import('@/okr/views/project/deptProject'),
+      meta: {
+        groupCode: 'project',
+      }
+    },
+    {
+      path: '/projectDetail',
+      name: 'projectDetail',
+      label: '项目详情',
+      component: () => import('@/okr/views/project/projectDetail'),
+      hidden: true, // 不在侧边栏线上
+      meta: {
+        groupCode: 'project',
+      }
+    },
+    {
+      path: '/publicProject',
+      name: 'publicProject',
+      label: '公开项目',
+      component: () => import('@/okr/views/project/publicProject'),
+      meta: {
+        groupCode: 'project',
+      }
+    },
+    {
+      path: '/allProject',
+      name: 'allProject',
+      label:'公司全部',
+      component: () => import('@/okr/views/project/allProject'),
+      meta: {
+        groupCode: 'project',
+      }
+    },
+
+
+
   ]
 }]
 export default routes

+ 9 - 9
src/styles/index.scss

@@ -538,11 +538,11 @@ code {
   white-space: nowrap;
 }
 .clamp2 {
-  overflow: hidden;    
-  text-overflow: ellipsis;   
-   word-break: break-all;    
-   display: -webkit-box;    
-   -webkit-line-clamp: 2;   
+  overflow: hidden;
+  text-overflow: ellipsis;
+   word-break: break-all;
+   display: -webkit-box;
+   -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
 }
 .clamp3 {
@@ -577,13 +577,13 @@ code {
   scroll-behavior: smooth;
 }
 .scroll-bar::-webkit-scrollbar {
-  width: 6px;
-  height: 6px;
+  width: 7px;
+  height: 7px;
 }
 
 /*外层轨道。可以用display:none让其不显示,也可以添加背景图片,颜色改变显示效果*/
 .scroll-bar::-webkit-scrollbar-track {
-  width: 6px;
+  width: 7px;
   background-color: #fff0;
   -webkit-border-radius: 2em;
   -moz-border-radius: 2em;
@@ -592,7 +592,7 @@ code {
 
 /*滚动条的设置*/
 .scroll-bar::-webkit-scrollbar-thumb {
-  background-color: #fff0;
+  background-color: #C1C7D4;
   background-clip: padding-box;
   -webkit-border-radius: 2em;
   -moz-border-radius: 2em;

+ 595 - 0
src/views/demo.vue

@@ -0,0 +1,595 @@
+<template>
+   <div style="height: 100%; width: 100%">
+      <div>
+          <el-select v-model="timer" placeholder="请选择">
+            <el-option
+              v-for="item in options"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value">
+            </el-option>
+          </el-select>
+      </div>
+      <div class="rightGatt" style="min-height:calc(78vh - 50px - 5px );width: 100%;overflow: hidden;" ref="gantt"></div>
+    </div>
+</template>
+<script>
+  // 引入 组件库
+  import '@/assets/js/dhtmlx.js';
+  import "@/assets/css/dhtmlxgantt.css";
+  import demoData from './ganttData.json'
+
+  export default {
+    name: 'gantt',
+    // 接受父级组件 传递的数据并规范类型
+    data() {
+      return {
+        moment: '',
+        timer: 'day', //定时
+        item: {}, //单行数据
+        searchTitle: "", //搜索标题
+        tasks: {
+          data: [
+            {
+               id: 1,//必填
+               text: "这是任务",//必填
+               type: "task",// 项目类型 task任务 project项目  milestone里程碑  
+               userName:'张',//人名
+               start_date: "2023-3-15",
+               end_date: "2023-3-30",
+               progress: 0.3,//项目任务滑块的进度
+               open: true,//是否展开显示
+               taskProgress:'0',
+             },
+             {
+               id: 2,
+               text: "这是项目",
+               type: "project",// 项目类型 task任务 project项目  milestone里程碑  
+               userName:'李',//人名
+               start_date: "2023-1-15",
+               end_date: "2023-1-20",
+               progress: 0,
+               taskProgress:'1',
+             },
+             {
+              id: 3,
+              text: "这是里程碑",
+              type: "milestone",// 项目类型 task任务 project项目  milestone里程碑  
+              userName:'陈',//人名
+              start_date: "2023-2-10",
+              end_date: "2023-2-15",
+              progress: 0,
+              taskProgress:'2',
+            },
+          ], //数据
+          links: [
+            // {
+            //    id:1,//数据id
+            //    source:1,
+            //    target:2,
+            //    type:0
+            //  },
+            //  {
+            //    id:3,
+            //    source:2,
+            //    target:3,
+            //    type:2
+            //  },
+          ],
+          // 格式 id:数据id  
+          //     source:开始链接的项目id  ----为tasks.data中数据的id
+          //     target:要链接项目的id  ----为tasks.data中数据的id
+          //     type: 0--进行-开始  `尾部链接头部`  
+          //           1--开始-开始  `头部链接头部`
+          //           2--进行-进行  `尾部链接尾部`
+          //           3--开始-进行  `头部链接尾部`
+        },
+        ganttEvent: { //销毁事件
+        },
+        options: [{
+          value: 'day',
+          label: '日'
+        }, {
+          value: 'week',
+          label: '周'
+        }, {
+          value: 'month',
+          label: '月'
+        }, {
+          value: 'year',
+          label: '年'
+        }],
+        zoomConfig:{
+          levels: [
+            {
+              name: 'day',
+              scale_height: 60,
+              scales: [{ unit: 'day', step: 1, format: '%d %M' }]
+            },
+            {
+              name: 'week',
+              scale_height: 60,
+              scales: [
+                {
+                  unit: 'week',
+                  step: 1,
+                  format: function (date) {
+                    let dateToStr = gantt.date.date_to_str('%m-%d')
+                    let endDate = gantt.date.add(date, -6, 'day')
+                    let weekNum = gantt.date.date_to_str('%W')(date) //第几周
+                    return dateToStr(endDate) + ' 至 ' + dateToStr(date)
+                  }
+                },
+                {
+                  unit: 'day',
+                  step: 1,
+                  format: '%d', // + "周%D"
+                  css: function (date) {
+                    if (date.getDay() == 0 || date.getDay() == 6) {
+                      return 'day-item weekend weekend-border-bottom'
+                    } else {
+                      return 'day-item'
+                    }
+                  }
+                }
+              ]
+            },
+            {
+              name: 'month',
+              scale_height: 60,
+              min_column_width: 18,
+              scales: [
+                { unit: 'month', format: '%Y-%m' },
+                {
+                  unit: 'day',
+                  step: 1,
+                  format: '%d',
+                  css: function (date) {
+                    if (date.getDay() == 0 || date.getDay() == 6) {
+                      return 'day-item weekend weekend-border-bottom'
+                    } else {
+                      return 'day-item'
+                    }
+                  }
+                }
+              ]
+            },
+            {
+              name: 'quarter',
+              height: 60,
+              min_column_width: 110,
+              scales: [
+                {
+                  unit: 'quarter',
+                  step: 1,
+                  format: function (date) {
+                    let yearStr = new Date(date).getFullYear() + '年'
+                    let dateToStr = gantt.date.date_to_str('%M')
+                    let endDate = gantt.date.add(gantt.date.add(date, 3, 'month'), -1, 'day')
+                    return yearStr + dateToStr(date) + ' - ' + dateToStr(endDate)
+                  }
+                },
+                {
+                  unit: 'week',
+                  step: 1,
+                  format: function (date) {
+                    let dateToStr = gantt.date.date_to_str('%m-%d')
+                    let endDate = gantt.date.add(date, 6, 'day')
+                    let weekNum = gantt.date.date_to_str('%W')(date)
+                    return dateToStr(date) + ' 至 ' + dateToStr(endDate)
+                  }
+                }
+              ]
+            },
+            {
+              name: 'year',
+              scale_height: 50,
+              min_column_width: 150,
+              scales: [
+                { unit: 'year', step: 1, format: '%Y年' },
+                { unit: 'month', format: '%Y-%m' }
+              ]
+            }
+          ]
+        }
+      }
+    },
+    watch:{
+      timer(val){
+         this.changeTime(val); //默认日格式
+      },
+    },
+    mounted() {
+      this.initGantt();
+      // this.ganttChangeDateView("d"); //默认日格式
+    },
+    methods: {
+      // 切换 年 季 月 周 日视图
+      ganttChangeDateView(type) {
+        switch (type) {
+          case 'y':
+            gantt.config.scale_unit = "year";
+            gantt.config.step = 1;
+            gantt.config.subscales = null;
+            gantt.config.date_scale = "%Y年";
+            gantt.templates.date_scale = null;
+            break ;
+          case 'm':
+            gantt.config.scale_unit = "month";
+            gantt.config.step = 1;
+            gantt.config.date_scale = "%m月";
+            gantt.templates.date_scale = null;
+            break;
+          case 'w':
+            gantt.config.scale_unit = "week";
+            gantt.config.step = 1;
+            gantt.config.date_scale = "第%w周";
+            gantt.templates.date_scale = null;
+            break;
+          case 'd':
+            gantt.config.scale_unit = "day";
+            gantt.config.step = 1;
+            gantt.config.date_scale = "%m月%d日";
+            gantt.templates.date_scale = null;
+            gantt.config.subscales = null;
+            break;
+        }
+        gantt.render();
+      },
+      // 是否全屏
+      changeFull() {
+        gantt.ext.fullscreen.toggle();
+      },
+       changeTime(val){
+       gantt.ext.zoom.setLevel(val)
+      },
+      // 定位到今日线
+      changeToday() {
+        this.$nextTick(() => {
+          let ganTT = document.getElementsByClassName('gantt_marker today')
+          gantt.scrollTo(ganTT[0].offsetLeft - 300, null);
+        })
+      },
+      initGantt() {
+        gantt.config.branch_loading = true
+        //日期格式化
+        gantt.config.date_format = "%Y-%m-%d";
+        // gantt.config.order_branch = true;
+        // gantt.config.order_branch_free = true;
+
+        //左侧是否自适应
+        gantt.config.autofit = true;
+        gantt.config.drag_links = true; //连线
+        gantt.config.readonly = false; //只读
+        // gantt.config.layout = { //拖拽布局
+        //   css: "gantt_container",
+        //   rows: [{
+        //       cols: [{
+        //           view: "grid",
+        //           id: "grid",
+        //           scrollX: "scrollHor",
+        //           scrollY: "scrollVer"
+        //         },
+        //         {
+        //           resizer: true,
+        //           width: 1
+        //         },
+        //         {
+        //           view: "timeline",
+        //           id: "timeline",
+        //           scrollX: "scrollHor",
+        //           scrollY: "scrollVer"
+        //         },
+        //         {
+        //           view: "scrollbar",
+        //           scroll: "y",
+        //           id: "scrollVer"
+        //         }
+        //       ]
+        //     },
+        //     {
+        //       view: "scrollbar",
+        //       scroll: "x",
+        //       id: "scrollHor",
+        //       height: 20
+        //     }
+        //   ]
+        // };
+        gantt.config.start_on_monday = true;
+        gantt.config.work_time = true;
+        gantt.config.fit_tasks = true; //自动调整图表坐标轴区间用于适配task的长度
+
+        // 汉化
+        gantt.locale = {
+          date: {
+            month_full: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"],
+            month_short: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
+            day_full: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
+            day_short: ["日", "一", "二", "三", "四", "五", "六"]
+          },
+          labels: {
+            dhx_cal_today_button: "今天",
+            day_tab: "日",
+            week_tab: "周",
+            month_tab: "月",
+            new_event: "新建日程",
+            icon_save: "保存",
+            icon_cancel: "关闭",
+            icon_details: "详细",
+            icon_edit: "编辑",
+            icon_delete: "删除",
+            confirm_closing: "请确认是否撤销修改!", //Your changes will be lost, are your sure?
+            confirm_deleting: "是否删除计划?",
+            section_description: "描述:",
+            section_time: "时间范围:",
+            section_type: "类型:",
+            section_text: "计划名称:",
+            section_test: "测试:",
+            section_projectClass: "项目类型:",
+            taskProjectType_0: "项目任务",
+            taskProjectType_1: "普通任务",
+            section_head: "负责人:",
+            section_priority: "优先级:",
+            taskProgress: "任务状态",
+            taskProgress_0: "未开始",
+            taskProgress_1: "进行中",
+            taskProgress_2: "已完成",
+            taskProgress_3: "已延期",
+            taskProgress_4: "搁置中",
+            section_template: "Details",
+            column_text: "计划名称",
+            column_start_date: "开始时间",
+            column_duration: "持续时间",
+            column_add: "",
+            column_priority: "难度",
+            link: "关联",
+            confirm_link_deleting: "将被删除",
+            message_ok: "确定",
+            message_cancel: "取消",
+            link_start: "(开始)",
+            link_end: "(结束)",
+            type_task: "任务",
+            type_project: "项目",
+            type_milestone: "里程碑",
+            minutes: "分钟",
+            hours: "小时",
+            days: "天",
+            weeks: "周",
+            months: "月",
+            years: "年"
+          }
+        };
+
+        gantt.serverList("priority", [{
+            key: 0,
+            label: "最高"
+          },
+          {
+            key: 1,
+            label: "较高"
+          },
+          {
+            key: 2,
+            label: "普通"
+          },
+          {
+            key: 3,
+            label: "较低"
+          },
+          {
+            key: 4,
+            label: "最低"
+          },
+        ]);
+        // 左侧显示列名
+        gantt.config.columns = [{
+            name: "text",
+            min_width: 100,
+            max_width: 200,
+            label: "任务",
+            align: "left",
+            resize: true,
+            tree: true,
+            editor: {
+              type: 'text',
+              map_to: 'text'
+            },
+          },
+          {
+            name: "id",
+            label: "",
+            hide: true
+          },
+          {
+            name: "start_date",
+            label: "开始时间",
+            width: 120,
+            resize: true,
+            align: "left"
+          },
+          {
+            name: "userName",
+            min_width: 100,
+            height: 40,
+            label: "负责人",
+            resize: true,
+            align: "left",
+            template: (item) => {
+              // if (this.ganttDealById(gantt.serverList('staff'), item.head_id)) {
+              //   return `<span class='userIcon' style='background-color:${item.color ? item.color : "#6666"}'>${this.ganttDealById(gantt.serverList('staff'), item.head_id).slice(0, 1)}</span>${this.ganttDealById(gantt.serverList('staff'), item.head_id)}`
+              // }
+            }
+          },
+          {
+            name: "taskProgress",
+            label: "任务状态",
+            align: "center",
+            min_width: 110,
+            editor: {
+              type: "select",
+              map_to: "taskProgress",
+              options: [{
+                  key: "0",
+                  label: gantt.locale.labels.taskProgress_0
+                },
+                {
+                  key: "1",
+                  label: gantt.locale.labels.taskProgress_1
+                },
+                {
+                  key: "2",
+                  label: gantt.locale.labels.taskProgress_2
+                },
+                {
+                  key: "3",
+                  label: gantt.locale.labels.taskProgress_3
+                },
+                {
+                  key: "4",
+                  label: gantt.locale.labels.taskProgress_4,
+                }
+              ]
+            },
+            template: (obj) => {
+              let val = '';
+              switch (obj.taskProgress) {
+                case "0":
+                  val = `<div class='taskProgress color_bg_1'>未开始</div>`;
+                  break;
+                case "1":
+                  val = `<div class='taskProgress color_bg_2'>进行中</div>`;
+                  break;
+                case "2":
+                  val = `<div class='taskProgress color_bg_3'>已完成</div>`;
+                  break;
+                case "3":
+                  val = `<div class='taskProgress color_bg_4'>已延期</div>`;
+                  break;
+                case "4":
+                  val = `<div class='taskProgress color_bg_5'>搁置中</div>`;
+                  break;
+              };
+              return val
+            }
+          }
+
+        ]
+        gantt.config.lightbox.sections = [
+            {
+              name: 'text',
+              height: 70,
+              map_to: 'text',
+              type: 'textarea',
+              focus: true,
+              width: '*'
+            },
+            {
+              name: 'time',
+              height: 40,
+              map_to: 'auto',
+              type: 'duration',
+              time_format: ["%Y", "%m", "%d"],
+            },
+            {
+              name: 'projectClass',
+              height: 30,
+              map_to: 'proTemplate',
+              type: 'template',
+            },
+            {
+              name: 'head',
+              height: 22,
+              map_to: 'head_id',
+              type: 'select',
+              options: gantt.serverList("staff", []),
+            },
+            {
+              name: 'description',
+              height: 70,
+              map_to: 'description',
+              type: 'textarea'
+            },
+            {
+              name: 'priority',
+              height: 40,
+              map_to: 'priority',
+              type: 'radio',
+              options: gantt.serverList("priority")
+            },
+        ]
+        gantt.config.smart_scales = true;
+        gantt.plugins({
+          click_drag: true,
+          drag_timeline: true, // 拖动图
+          marker: true, // 时间标记
+          fullscreen: true, // 全屏
+          tooltip: true, // 鼠标经过时信息
+          undo: true // 允许撤销
+        })
+        gantt.init(this.$refs.gantt);
+        gantt.parse(this.tasks);
+      },
+    }
+  };
+</script>
+<style scoped lang="scss">
+
+  .ant-layout {
+    background: #f8f9f9;
+  }
+
+  ​ .ant-layout-header {
+    background: #fdffff;
+    color: rgb(29, 28, 28);
+    border: 1px solid #dee0e0;
+  }
+
+  ​ .header {
+    // position: fixed;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  ​ .content {
+    background: #fdffff;
+    height: 99vh;
+  }
+
+  ​ // 中间
+
+  .centerContent {
+    position: relative;
+    background: #fdffff;
+    width: 100%;
+    overflow-y: auto;
+    display: flex;
+    justify-content: space-between;
+
+    .selectDate {
+      width: 100px;
+      position: fixed;
+      top: 18%;
+      right: 9%;
+      z-index: 99;
+      display: flex;
+      justify-content: space-between;
+    }
+  }
+  /deep/ .color_bg_1{
+    background-color:#60a3bc !important;
+  }
+  .color_bg_2{
+    background-color:#079992 ;
+  }
+  .color_bg_3{
+    background-color:#78e08f ;
+  }
+  .color_bg_4{
+    background-color:#e55039 ;
+  }
+  .color_bg_5{
+    background-color:#f6b93b ;
+  }
+
+</style>

+ 202 - 0
src/views/demo1.vue

@@ -0,0 +1,202 @@
+<template>
+  <section class="my-gantt">
+    <div class="time-box">
+      <el-radio-group v-model="timeState" @change="changeTime">
+        <el-radio-button v-for="(time, t_index) in timeList" :key="t_index" :label="time.code" size="default" border>{{ time.name }}</el-radio-button>
+      </el-radio-group>
+    </div>
+    <div id="gantt_here" class="gantt-container"></div>
+  </section>
+</template>
+
+<script>
+  import {
+    gantt
+  } from 'dhtmlx-gantt'
+  import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
+  import demoData from './ganttData.json'
+  export default {
+    name: "ganttChart",
+    data() {
+      return {
+        timeList: [
+          {
+              name: "周",
+              code: "week",
+          },
+          {
+            name: '月',
+            code: 'month'
+          },
+          {
+            name: '季',
+            code: 'quarter'
+          },
+          {
+            name: '年',
+            code: 'year'
+          }
+        ],
+        timeState: 'month',
+        zoomConfig: {
+          levels: [{
+              name: 'day',
+              scale_height: 60,
+              scales: [{
+                unit: 'day',
+                step: 1,
+                format: '%d %M'
+              }]
+            },
+            {
+              name: 'week',
+              scale_height: 60,
+              scales: [{
+                  unit: 'week',
+                  step: 1,
+                  format: function(date) {
+                    let dateToStr = gantt.date.date_to_str('%m-%d')
+                    let endDate = gantt.date.add(date, -6, 'day')
+                    let weekNum = gantt.date.date_to_str('%W')(date) //第几周
+                    return dateToStr(endDate) + ' 至 ' + dateToStr(date)
+                  }
+                },
+                {
+                  unit: 'day',
+                  step: 1,
+                  format: '%d', // + "周%D"
+                  css: function(date) {
+                    if (date.getDay() == 0 || date.getDay() == 6) {
+                      return 'day-item weekend weekend-border-bottom'
+                    } else {
+                      return 'day-item'
+                    }
+                  }
+                }
+              ]
+            },
+            {
+              name: 'month',
+              scale_height: 60,
+              min_column_width: 18,
+              startDate:new Date(2023,12, 1),
+              endDate:new Date(2024,2,29),
+              scales: [
+                {
+                  step: 1,
+                  unit: 'month',
+                  format: '%Y-%m'
+                },
+                {
+                  unit: 'day',
+                  step: 1,
+                  format: '%d',
+                  css: function(date) {
+                    if (date.getDay() == 0 || date.getDay() == 6) {
+                      return 'day-item weekend weekend-border-bottom'
+                    } else {
+                      return 'day-item'
+                    }
+                  }
+                }
+              ]
+            },
+            {
+              name: 'quarter',
+              height: 60,
+              min_column_width: 110,
+              scales: [{
+                  unit: 'quarter',
+                  step: 1,
+                  format: function(date) {
+                    let yearStr = new Date(date).getFullYear() + '年'
+                    let dateToStr = gantt.date.date_to_str('%M')
+                    let endDate = gantt.date.add(gantt.date.add(date, 3, 'month'), -1, 'day')
+                    return yearStr + dateToStr(date) + ' - ' + dateToStr(endDate)
+                  }
+                },
+                {
+                  unit: 'week',
+                  step: 1,
+                  format: function(date) {
+                    let dateToStr = gantt.date.date_to_str('%m-%d')
+                    let endDate = gantt.date.add(date, 6, 'day')
+                    let weekNum = gantt.date.date_to_str('%W')(date)
+                    return dateToStr(date) + ' 至 ' + dateToStr(endDate)
+                  }
+                }
+              ]
+            },
+            {
+              name: 'year',
+              scale_height: 50,
+              min_column_width: 150,
+              scales: [{
+                  unit: 'year',
+                  step: 1,
+                  format: '%Y年'
+                },
+                {
+                  unit: 'month',
+                  format: '%Y-%m'
+                }
+              ]
+            }
+          ]
+        }
+      }
+    },
+    mounted() {
+      this.initGantt();
+    },
+    methods: {
+      //初始化甘特图
+      initGantt() {
+        gantt.config.grid_width = 350
+        gantt.config.add_column = false //添加符号
+
+        //时间轴图表中,如果不设置,只有行边框,区分上下的任务,设置之后带有列的边框,整个时间轴变成格子状。
+        gantt.config.autofit = false
+        gantt.config.row_height = 60
+        gantt.config.bar_height = 34
+        gantt.config.fit_tasks = true //自动延长时间刻度,以适应所有显示的任务
+        gantt.config.auto_types = true //将包含子任务的任务转换为项目,将没有子任务的项目转换回任务
+        // gantt.config.xml_date = '%Y-%m-%d' //甘特图时间数据格式
+
+        // gantt.config.start_date = new Date(2012, 1, 1);
+        // gantt.config.end_date = new Date(2024, 12, 31);
+        gantt.config.readonly = true //是否只读
+        gantt.i18n.setLocale('cn') //设置语言
+        gantt.init('gantt_here') //初始化
+        gantt.parse(demoData) //填充数据
+
+        gantt.ext.zoom.init(this.zoomConfig) //配置初始化扩展
+        gantt.ext.zoom.setLevel('month') //切换到指定的缩放级别
+      },
+      changeTime() {
+        gantt.ext.zoom.setLevel(this.timeState)
+      }
+    },
+  }
+</script>
+<style scoped lang="scss">
+  .my-gantt {
+    height: 800px;
+    width: 100vw;
+
+    .time-box {
+      text-align: center;
+      margin-bottom: 20px;
+    }
+
+    ::v-deep .gantt-container {
+      width: 100%;
+      height: 100%;
+
+      .weekend {
+        background: #ff9e2f;
+        color: #fff;
+      }
+    }
+  }
+</style>

+ 886 - 0
src/views/demo2.vue

@@ -0,0 +1,886 @@
+<template>
+   <div style="height: 100%; width: 100%">
+
+          <div class="content">
+               <div style="margin: -5px 0px 5px;display: flex;justify-content: space-between;">
+                     <a-input allowClear v-model="searchTitle" placeholder="请输入任务名称"></a-input>
+                     <a-button icon="search" type="primary" @click="searchDataClick">搜索</a-button>
+                 </div>
+               <!-- 中间 内容 -->
+               <div class="centerContent">
+                   <!-- 甘特图 -->
+                   <div class="selectDate">
+                       <a-button @click="changeToday">今</a-button>
+                       <!-- 日期切换 -->
+                       <a-select  default-value="d" style="width: 55px;margin-left: 5px;" @change="ganttChangeDateView">
+                           <a-select-option value="y">年</a-select-option>
+                           <a-select-option value="m">月</a-select-option>
+                           <a-select-option value="w">周</a-select-option>
+                           <a-select-option value="d">日</a-select-option>
+                         </a-select>
+                       <a-button style="margin-left: 5px;" @click="changeFull"><a-icon type="fullscreen" /></a-button>
+                     </div>
+                   <div class="rightGatt" style="min-height:calc(78vh - 50px - 5px );width: 100%;overflow: hidden;" ref="gantt"></div>
+                 </div>
+             </div>
+         </div>
+
+    </div>
+</template>
+<script>
+  import {gantt} from '@/assets/js/dhtmlx';
+  import "@/assets/css/dhtmlxgantt.css";
+  import moment from 'moment'​
+  export default {
+    name: "ganttChart",
+    data() {
+      return {
+        moment,
+        timer: null, //定时
+        item: {}, //单行数据
+        searchTitle: "", //搜索标题
+        tasks: {
+          data: [
+          ], //数据
+          links: [
+              {
+                id:'111',//数据id
+                source:'1'
+                target:'2'
+                type:'0'
+              },//
+              {
+                id:'222'
+                source:'2'
+                target:'1'
+                type:'1'
+              },
+          ], //关联项目数据
+        },
+        savetasks: {
+          data: [],
+          links: []
+        }, //暂存空数据
+        ganttServerStaff: [], //设置gantt成员数据
+        selectStaff: [], //下拉成员
+        staff: [], //成员
+        ganttEvent: { //销毁事件
+        },
+        urls: {
+          staff: "", //项目成员
+          tasklinks: '', //gantt数据
+          linksEdit: '', //修改和新增连接
+          linksDelete: '', //删除连接
+          addTask: '', //新增项目PUT
+          editTask: '', //编辑项目put请求 tid
+          deleteTask: '', //删除项目delete tid
+        }
+      }
+    },
+    watch: {
+      searchTitle(newVal, oldVal) {
+        this.searchTitle = newVal;
+      }
+    },
+    mounted() {
+
+      this.$nextTick(() => {
+        this.ganttChangeEvent(); //交互事件
+        this.initGantt(); //初始化
+
+        this.createTodayLine(); //今日线
+        // this.ganttServerList(); //服务数据
+      })
+      this.onQuery(); //查询数据
+      this.ganttChangeDateView("d"); //默认日格式
+    },
+    methods: {
+      /*
+        甘特图
+       */
+      // 初始化gantt
+      initGantt() {
+        // 清空之前的配置
+        // gantt.clearAll();
+
+        // 启用动态加载
+        gantt.config.branch_loading = true
+        //日期格式化
+        gantt.config.xml_date = "%Y-%m-%d";
+        gantt.config.order_branch = true;
+        gantt.config.order_branch_free = true;
+        //左侧是否自适应
+        gantt.config.autofit = true;
+        gantt.config.drag_links = true; //连线
+        gantt.config.readonly = false; //只读
+        gantt.config.date_scale = "%m月%d日"; //右侧显示列名
+        gantt.config.layout = { //拖拽布局
+          css: "gantt_container",
+          rows: [{
+              cols: [{
+                  view: "grid",
+                  id: "grid",
+                  scrollX: "scrollHor",
+                  scrollY: "scrollVer"
+                },
+                {
+                  resizer: true,
+                  width: 1
+                },
+                {
+                  view: "timeline",
+                  id: "timeline",
+                  scrollX: "scrollHor",
+                  scrollY: "scrollVer"
+                },
+                {
+                  view: "scrollbar",
+                  scroll: "y",
+                  id: "scrollVer"
+                }
+              ]
+            },
+            {
+              view: "scrollbar",
+              scroll: "x",
+              id: "scrollHor",
+              height: 20
+            }
+          ]
+        };
+        gantt.config.start_on_monday = true;
+        gantt.config.work_time = true;
+        gantt.config.fit_tasks = true; //自动调整图表坐标轴区间用于适配task的长度
+
+        ​
+        //汉化
+        gantt.locale = {
+          date: {
+            month_full: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"],
+            month_short: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
+            day_full: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
+            day_short: ["日", "一", "二", "三", "四", "五", "六"]
+          },
+          labels: {
+            dhx_cal_today_button: "今天",
+            day_tab: "日",
+            week_tab: "周",
+            month_tab: "月",
+            new_event: "新建日程",
+            icon_save: "保存",
+            icon_cancel: "关闭",
+            icon_details: "详细",
+            icon_edit: "编辑",
+            icon_delete: "删除",
+            confirm_closing: "请确认是否撤销修改!", //Your changes will be lost, are your sure?
+            confirm_deleting: "是否删除计划?",
+            section_description: "描述:",
+            section_time: "时间范围:",
+            section_type: "类型:",
+            section_text: "计划名称:",
+            section_test: "测试:",
+            section_projectClass: "项目类型:",
+            taskProjectType_0: "项目任务",
+            taskProjectType_1: "普通任务",
+            section_head: "负责人:",
+            section_priority: '优先级:',
+            taskProgress: '任务状态',
+            taskProgress_0: "未开始",
+            taskProgress_1: "进行中",
+            taskProgress_2: "已完成",
+            taskProgress_3: "已延期",
+            taskProgress_4: "搁置中",
+            section_template: 'Details',
+            /* grid columns */
+            column_text: "计划名称",
+            column_start_date: "开始时间",
+            column_duration: "持续时间",
+            column_add: "",
+            column_priority: "难度",
+            /* link confirmation */
+            link: "关联",
+            confirm_link_deleting: "将被删除",
+            message_ok: '确定',
+            message_cancel: '取消',
+            link_start: " (开始)",
+            link_end: " (结束)",
+            ​
+            type_task: "任务",
+            type_project: "项目",
+            type_milestone: "里程碑",
+            ​
+            minutes: "分钟",
+            hours: "小时",
+            days: "天",
+            weeks: "周",
+            months: "月",
+            years: "年"
+          }
+        }
+
+        gantt.serverList("priority", [{
+            key: 0,
+            label: "最高"
+          },
+          {
+            key: 1,
+            label: "较高"
+          },
+          {
+            key: 2,
+            label: "普通"
+          },
+          {
+            key: 3,
+            label: "较低"
+          },
+          {
+            key: 4,
+            label: "最低"
+          },
+        ]);
+
+        //左侧显示列名
+        gantt.config.columns = [{
+            name: "text",
+            min_width: 100,
+            max_width: 200,
+            label: "任务",
+            align: "left",
+            resize: true,
+            tree: true,
+            editor: {
+              type: 'text',
+              map_to: 'text'
+            }
+          },
+          {
+            name: "id",
+            label: "",
+            hide: true
+          },
+          {
+            name: "start_date",
+            label: "开始时间",
+            width: 120,
+            resize: true,
+            align: "left"
+          },
+          {
+            name: "head",
+            min_width: 100,
+            height: 40,
+            label: "负责人",
+            resize: true,
+            align: "left",
+            // editor: {
+            //   map_to: "head_id", type: "select", options: gantt.serverList("staff"),
+            // },
+            template: (item) => {
+              if (this.ganttDealById(gantt.serverList('staff'), item.head_id)) {
+                return `<span class='userIcon' style='background-color:${item.color ? item.color : "#6666"}'>${this.ganttDealById(gantt.serverList('staff'), item.head_id).slice(0, 1)}</span>${this.ganttDealById(gantt.serverList('staff'), item.head_id)}`
+              }
+            }
+          },
+          // { name: "end_date", label: "结束时间", align: "center" },
+          {
+            name: "taskProgress",
+            label: "任务状态",
+            align: "center",
+            min_width: 110,
+            editor: {
+              type: "select",
+              map_to: "taskProgress",
+              options: [{
+                  key: "0",
+                  label: gantt.locale.labels.taskProgress_0
+                },
+                {
+                  key: "1",
+                  label: gantt.locale.labels.taskProgress_1
+                },
+                {
+                  key: "2",
+                  label: gantt.locale.labels.taskProgress_2
+                },
+                {
+                  key: "3",
+                  label: gantt.locale.labels.taskProgress_3
+                },
+                {
+                  key: "4",
+                  label: gantt.locale.labels.taskProgress_4
+                },
+              ],
+            },
+            template: function(obj) {
+              let re = '';
+              switch (obj.taskProgress) {
+                case "0":
+                  re = `<div class='taskProgress color_bg_1' >未开始</div>`
+                  break;
+                case "1":
+                  re = `<div class='taskProgress color_bg_2' >进行中</div>`
+                  break;
+                case "2":
+                  re = `<div class='taskProgress color_bg_3'  >已完成</div>`
+                  break;
+                case "3":
+                  re = `<div  class='taskProgress color_bg_4'>已延期</div>`
+                  break;
+                case "4":
+                  re = `<div  class='taskProgress color_bg_5' >搁置中</div>`
+                  break;
+              }
+              return re​
+            }
+          }, ​
+        ];​
+
+        //弹出层
+        gantt.config.lightbox.sections = [{
+            name: "text",
+            height: 70,
+            map_to: "text",
+            type: "textarea",
+            focus: true,
+            width: "*"
+          },
+          {
+            name: "time",
+            height: 40,
+            map_to: "auto",
+            type: "duration",
+            time_format: ["%Y", "%m", "%d"],
+          },
+          {
+            name: "projectClass",
+            height: 30,
+            map_to: "proTemplate",
+            type: "template",
+
+          },
+          {
+            name: "head",
+            height: 22,
+            map_to: "head_id",
+            type: "select",
+            options: gantt.serverList('staff', []),
+          },
+          {
+            name: "description",
+            height: 70,
+            map_to: "description",
+            type: "textarea"
+          },
+          {
+            name: "priority",
+            height: 40,
+            map_to: "priority",
+            type: "radio",
+            options: gantt.serverList("priority")
+          }, ​
+        ];​
+        gantt.config.smart_scales = true;
+        gantt.plugins({
+          click_drag: true,
+          drag_timeline: true, // 拖动图
+          marker: true, // 时间标记
+          fullscreen: true, // 全屏
+          tooltip: true, // 鼠标经过时信息
+          undo: true // 允许撤销
+        })
+        gantt.init(this.$refs.gantt);
+
+      },
+
+
+      // gantt数据服务列表
+      ganttServerList() {
+        this.getProjectStaff(); //获取项目成员
+        // 项目难度
+        gantt.serverList("priority", [{
+            key: 0,
+            label: "最高"
+          },
+          {
+            key: 1,
+            label: "较高"
+          },
+          {
+            key: 2,
+            label: "普通"
+          },
+          {
+            key: 3,
+            label: "较低"
+          },
+          {
+            key: 4,
+            label: "最低"
+          },
+        ]);
+
+      },
+      // gantt交互事件注册
+      ganttChangeEvent() {
+        // gantt渲染
+        this.ganttEvent.onGanttReady = gantt.attachEvent("onGanttReady", () => {
+
+          //弹窗标题 日期范围
+          gantt.templates.task_time = function(start, end, task) {
+            return "周期:" + moment(start).format('YYYY-MM-DD') + " 至 " + moment(end).format('YYYY-MM-DD');
+          };
+          // 浮窗
+          gantt.templates.tooltip_text = (start, end, task) => {
+            return "<b>项目名称:</b> " + task.text + "<br><b>负责人:</b>" + task.head + "<br/><b>开始时间:</b> " +
+              moment(start).format('YYYY-MM-DD') +
+              "<br/><b>结束时间:</b> " +
+              moment(new Date(end).valueOf() - 1000 * 60 * 60 * 24).format('YYYY-MM-DD');
+          }
+          //弹窗标题 计划名称
+          gantt.templates.task_text = function(start, end, task) {
+            return task.text;
+          };
+          gantt.templates.timeline_cell_class = function(task, date) {
+            if (!gantt.isWorkTime({
+                task: task,
+                date: date
+              })) {
+              return "weekend";
+            } else {
+              return 'weekday'
+            }
+          };
+          gantt.templates.task_end_date = (date) => {
+            return gantt.templates.task_date(this.moment(new Date(date.valueOf() - 1000 * 60 * 60 * 24)).format(
+              "YYYY-MM-DD"));
+          };
+          gantt.templates.grid_date_format = (date, column) => {
+            if (column === "end_date") {
+              return this.moment(new Date(date.valueOf() - 1000 * 60 * 60 * 24)).format("YYYY-MM-DD");
+            } else {
+              return this.moment(date).format("YYYY-MM-DD");
+            }
+          }
+        });
+        // 修改默认弹窗
+        gantt.attachEvent("onBeforeLightbox", (id) => {
+          var task = gantt.getTask(id);
+          task.proTemplate = `${gantt.locale.labels.taskProjectType_0}`
+          return true;
+        });​
+        //添加后触发
+        this.ganttEvent.onAfterTaskAdd = gantt.attachEvent("onAfterTaskAdd", (id, item) => {
+          clearTimeout(this.timer)
+          this.timer = setTimeout(() => {
+            this.dealProject("add", item)
+          }, 500)​
+        });
+        // 修改任务
+        this.ganttEvent.onAfterTaskUpdate = gantt.attachEvent("onAfterTaskUpdate", (id, data) => {
+          clearTimeout(this.timer)
+          this.timer = setTimeout(() => {
+            this.dealProject("edit", data);
+            gantt.render()
+          }, 500)​
+        });
+        // 删除项目
+        this.ganttEvent.onAfterTaskDelete = gantt.attachEvent("onAfterTaskDelete", (id, data) => {
+          clearTimeout(this.timer)
+          this.timer = setTimeout(() => {
+
+            this.dealProject("delete", data);
+            gantt.render();
+          }, 500)
+        });
+        // 移动项目
+        this.ganttEvent.onAfterTaskDrag = gantt.attachEvent("onAfterTaskDrag", (id, mode, e) => {
+          clearTimeout(this.timer)
+          this.timer = setTimeout(() => {
+            var task = gantt.getTask(id);
+            this.dealProject("edit", task);
+            gantt.render()
+          }, 500)
+        });
+        // 用户完成拖动并释放鼠标
+        this.ganttEvent.onAfterTaskChanged = gantt.attachEvent("onAfterTaskChanged", (id, mode, task) => {
+          clearTimeout(this.timer)
+          this.timer = setTimeout(() => {
+            gantt.render();
+          }, 500)
+        });​
+        // 删除连接项目关系
+        this.ganttEvent.onAfterLinkDelete = gantt.attachEvent("onAfterLinkDelete", (id, item) => {
+          clearTimeout(this.timer)
+          this.timer = setTimeout(() => {
+
+            let param = Object.assign({}, {
+              projectId: this.$store.state.project_data.id
+            }, item)
+            this.axios.$delete(this.urls.linksDelete, param).then(res => {
+              res.code == 200 && this.$message.success("解除成功!")
+              res.code != 200 && this.$message.error("解除失败!")
+            })
+            gantt.render();
+          }, 500)
+        });
+        // 修改连接项目关系
+        this.ganttEvent.onAfterLinkUpdate = gantt.attachEvent("onAfterLinkUpdate", (id, item) => {
+          clearTimeout(this.timer)
+          this.timer = setTimeout(() => {
+            ​
+            let param = Object.assign({}, {
+              projectId: this.$store.state.project_data.id
+            }, item)
+            this.axios.$put(this.urls.linksEdit, param).then(res => {
+              res.code == 200 && this.$message.success("关联成功!")
+              res.code != 200 && this.$message.error("关联失败!")​
+            })
+            gantt.render()
+          }, 500)
+        });
+        // 新增连接项目关系
+        this.ganttEvent.onBeforeLinkAdd = gantt.attachEvent("onBeforeLinkAdd", (id, item) => {
+          this.timer = setTimeout(() => {
+            clearTimeout(this.timer)
+            let param = Object.assign({}, {
+              projectId: this.$store.state.project_data.id
+            }, item)
+            this.axios.$put(this.urls.linksEdit, param).then(res => {
+              res.code == 200 && this.$message.success("关联成功!");
+              res.code != 200 && this.$message.error("关联失败!");
+
+            })
+            gantt.render()
+          }, 20)​
+        });
+        // 保存新增
+        this.ganttEvent.onLightboxSave = gantt.attachEvent("onLightboxSave", (id, item) => {
+          if (!item.text) {
+            this.$message.error("请填写计划名称!");
+            return false;
+          }
+          return true;
+        });​
+      },
+      // 处理id 对应名称label
+      ganttDealById(list, id) {
+        for (let i = 0; i < list.length; i++) {
+          if (list[i].key == id)
+            return list[i].label;
+        }
+        return "";
+      },
+      // 切换 年 季 月 周 日视图
+      ganttChangeDateView(type) {
+        switch (type) {
+          case 'y':
+            gantt.config.scale_unit = "year";
+            gantt.config.step = 1;
+            gantt.config.subscales = null;
+            gantt.config.date_scale = "%Y年";
+            gantt.templates.date_scale = null;
+            break;​
+          case 'm':
+            gantt.config.scale_unit = 'month';
+            gantt.config.step = 1;
+            gantt.config.date_scale = "%m月";
+            gantt.templates.date_scale = null;
+
+            break;
+          case 'w':
+            gantt.config.scale_unit = 'week';
+            gantt.config.step = 1;
+            gantt.config.date_scale = "第%w周";
+            gantt.templates.date_scale = null;
+
+            break;
+          case 'd':
+            gantt.config.scale_unit = 'day';
+            gantt.config.step = 1;
+            gantt.config.date_scale = "%m月%d日";
+            gantt.templates.date_scale = null;
+            gantt.config.subscales = null;
+
+            break;
+        }
+        gantt.render();
+      },
+      // 今日线
+      createTodayLine() {
+        var dateToStr = gantt.date.date_to_str("%Y年%M%d日");
+        var markerId = gantt.addMarker({
+          id: 'markerLine',
+          start_date: new Date(),
+          css: "today",
+          text: "今日",
+          title: dateToStr(new Date())
+        });
+        gantt.updateMarker(markerId);
+      },
+      // 是否全屏
+      changeFull() {
+        gantt.ext.fullscreen.toggle();
+      },
+      // 定位到今日线
+      changeToday() {
+        this.$nextTick(() => {
+          let ganTT = document.getElementsByClassName('gantt_marker today')
+          gantt.scrollTo(ganTT[0].offsetLeft - 300, null);
+        })
+      },
+      /*
+    操作
+    */
+      //  获取项目成员
+      getProjectStaff(projectID) {
+        this.axios.get(this.urls.staff, {
+          params: {
+            projectId: projectID ? projectID : this.$store.state.project_data.id
+          },
+        }).then(res => {
+          let staffArr = [];
+          res.data.code = 200 && res.data.result.forEach((item, index) => {
+            staffArr[index] = {};
+            staffArr[index].key = item.id;
+            staffArr[index].label = item.realname;
+          })
+          this.selectStaff = res.data.result;
+          // 补充gantt数据
+          this.ganttServerStaff = staffArr;
+          gantt.updateCollection("staff", staffArr);
+          gantt.render()
+        })
+      },
+      //计算进度
+      evalProgess(start, end, update) {
+        if (start && end && update) {
+          let start_date = this.moment(start).format("YYYY-MM-DD");
+          let end_date = this.moment(end).format("YYYY-MM-DD");
+          let update_date = this.moment(update).format("YYYY-MM-DD");
+
+          if ((new Date(end_date) - new Date(update_date) > 0)) {
+            let process = (new Date(update_date) - new Date(start_date)) / (new Date(end_date) - new Date(start_date));
+            return process.toFixed(2)
+          } else {
+            return 0
+          }
+        }​
+        return 0
+      },
+      // 获取数据
+      onQuery(param) {
+        gantt.clearAll();
+        gantt.parse(this.savetasks);
+        gantt.render();
+        param = Object.assign({}, {
+          projectId: this.$store.state.project_data.id ? this.$store.state.project_data.id : '1'
+        }, param)
+        this.axios.get(this.urls.tasklinks, {
+          params: param
+        }).then(res => {
+          let result = res.data.result["taskList"];
+          let pushArr = [];
+          this.tasks.links = res.data.result["ganttchartList"]; //连接项目
+          this.tasks.data = [];
+          result.forEach((item, index) => {
+            this.tasks.data[index] = {};
+            this.tasks.data[index].id = item.tid ? item.tid : ''; //项目id
+            this.tasks.data[index].text = item.title ? item.title : '空标题'; //标题
+            this.tasks.data[index].start_date = item.startTime; //开始时间
+            // 负责人--成员
+            this.tasks.data[index].head_id = item.headRole?.id ? item.headRole?.id : ''; //负责人id
+            this.tasks.data[index].head = item.headRole?.realname ? item.headRole?.realname : ''; //负责人
+            this.tasks.data[index].progress = this.evalProgess(item.startTime, item.endTime, item
+              .updateTime) //项目进展
+            // 后台时间加一天 显示减一天 处理条形图时间左闭右开
+            this.tasks.data[index].end_date = this.moment(new Date(item.endTime).valueOf() + 1000 * 60 * 60 *
+              24).format("YYYY-MM-DD"); //结束时间
+            // this.tasks.data[index].end_date = item.endTime;//结束时间
+            this.tasks.data[index].priority = item.priority ? item.priority : ''; //任务优先级
+            this.tasks.data[index].projectClass = item.type ? item.type : ''; //类型 0项目任务 1 普通任务
+            ​
+            this.tasks.data[index].taskProgress = item.taskStatus.toString(); //任务状态
+            this.tasks.data[index].description = item.describe ? item.describe : ''; //描述
+            this.tasks.data[index].color = item.stateDictionary.color ? item.stateDictionary.color : ''; //颜色
+            if (item.taskList && item.taskList.length != 0) {
+              this.tasks.data[index].render = "split"; //显示在单行
+              this.tasks.data[index].open = true; //展开
+              item.taskList.forEach((_item) => {
+                pushArr.push({
+                  id: _item.tid,
+                  text: _item.title ? _item.title : '空标题',
+                  start_date: _item.startTime,
+                  end_date: _item.endTime,
+                  head_id: _item.headId,
+                  head: _item.head,
+                  priority: _item.priority,
+                  projectClass: _item.type,
+                  taskProgress: _item.taskStatus.toString(),
+                  description: _item.describe,
+                  parent: _item.mainTaskId,
+                });
+              })
+            }
+          });
+          this.tasks.data = this.tasks.data.concat(pushArr)
+
+          this.$nextTick(() => {
+            gantt.parse(this.tasks);
+            gantt.render();
+            gantt.refreshData();
+          })​
+        })
+      },
+      ​
+      // 项目新增 修改tid  删除tid
+      dealProject(type, data) {
+        let param = {};
+        if (type != 'add') {
+          param.tid = data.id;
+          param.title = data.text;
+          param.startTime = this.moment(data.start_date).format("YYYY-MM-DD");
+          param.endTime = this.moment(data.end_date).format("YYYY-MM-DD");
+
+          param.describe = data.description;
+          param.priority = data.priority;
+
+          param.head = data.head_id;
+          param.taskStatus = data.taskProgress;
+          param.projectId = this.$store.state.project_data.id;
+        } else {
+
+          param.title = data.text;
+          param.startTime = this.moment(data.start_date).format("YYYY-MM-DD");
+          param.endTime = this.moment(data.end_date).format("YYYY-MM-DD");
+
+          param.describe = data.description;
+          param.priority = data.priority;
+
+          param.head = data.head_id;
+          param.taskStatus = data.taskProgress;
+          param.projectId = this.$store.state.project_data.id;
+        }
+        let formdata = new FormData();
+        for (let i in param) {
+          formdata.append(i, param[i])
+        }
+        switch (type) {
+          case "add": //新增
+            this.axios.put(this.urls.addTask, formdata).then(res => {
+
+              res.data.code == 200 && this.$message.success("新增成功!")
+              res.data.code != 200 && this.$message.error("新增失败!")
+            }).catch(err => {
+              this.$message.error("请求失败!")
+            })
+            break;
+          case "edit":
+            this.axios.put(this.urls.editTask, formdata).then(res => {
+              res.data.code == 200 && this.$message.success("修改成功!")
+              res.data.code != 200 && this.$message.error("修改失败!")
+            }).catch(err => {
+              this.$message.error("请求失败!")
+            })
+            break;
+          case "delete":
+            this.axios.delete(this.urls.deleteTask, formdata).then(res => {
+              res.data.code == 200 && this.$message.success("删除成功!")
+              res.data.code != 200 && this.$message.error("删除失败!")
+            }).catch(err => {
+              this.$message.error("请求失败!")
+            })
+            break;​
+        }
+      },
+      selecthead(val) {
+
+        this.searchHead = val; //id
+      },
+      // 搜索判断数据
+      hasSubstr(parentId, type) {
+        let task = gantt.getTask(parentId);
+        if (type == 'tilte') {
+          if (task.text.toLowerCase().indexOf(this.searchTitle) !== -1)
+            return true;
+        }
+        // }  
+      },
+      //点击按钮搜索
+      searchDataClick() {
+
+        if (this.searchTitle) {
+          this.ganttEvent.onBeforeTaskDisplay = gantt.attachEvent("onBeforeTaskDisplay", (id, task) => {
+            if (this.hasSubstr(id, 'tilte')) {
+              return true;
+            }
+            return false;
+          });
+          gantt.refreshData()
+          gantt.render()
+        } else {
+          this.ganttEvent.onBeforeTaskDisplay = gantt.attachEvent("onBeforeTaskDisplay", (id, task) => {
+            return true
+          })
+          gantt.refreshData()
+          gantt.render()
+        }
+      },
+    },
+    ​
+    destroyed() {
+      // 销毁gantt事件
+      for (let i in this.ganttEvent) {
+        gantt.detachEvent(this.ganttEvent[i])
+      }
+      gantt.ext.tooltips.tooltip.hide();
+    }
+  }
+</script>
+<style scoped lang="scss">
+  @import url(./gantt.css);
+
+  .ant-layout {
+    background: #f8f9f9;
+  }
+
+  ​ .ant-layout-header {
+    background: #fdffff;
+    color: rgb(29, 28, 28);
+    border: 1px solid #dee0e0;
+  }
+
+  ​ .header {
+    // position: fixed;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  ​ .content {
+    background: #fdffff;
+    height: 99vh;
+  }
+
+  ​ // 中间
+
+  .centerContent {
+    position: relative;
+    background: #fdffff;
+    width: 100%;
+    overflow-y: auto;
+    display: flex;
+    justify-content: space-between;
+
+    .selectDate {
+      width: 100px;
+      position: fixed;
+      top: 18%;
+      right: 9%;
+      z-index: 99;
+      display: flex;
+      justify-content: space-between;
+    }
+  }
+</style>

+ 11 - 0
src/views/ganttData.json

@@ -0,0 +1,11 @@
+{
+	"data":[
+		{"id":11, "text":"Project #1", "start_date":"01-04-2019", "duration":"1", "progress": 0.6, "open": true},
+		{"id":1, "text":"Project #2", "start_date":"01-04-2019", "duration":"18", "progress": 0.4, "open": true},
+		{"id":2, "text":"Task #1", "start_date":"02-04-2023", "duration":"8", "parent":"1", "progress":0.5}
+	],
+	"links":[
+		{"id":"1","source":"1","target":"2","type":"1"},
+		{"id":"2","source":"2","target":"1","type":"0"}
+	]
+}

BIN
static/images/icon_fw2zsw6wfdi.zip


BIN
static/images/no_data.png


BIN
static/images/下箭头.png


BIN
static/images/任务.png


BIN
static/images/右箭头.png


BIN
static/images/里程碑.png


BIN
static/images/里程碑2.png


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor