gantt.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. <template>
  2. <div class="ganttContainerBox">
  3. <div ref="ganttContainer" style="height: 100%;">
  4. </div>
  5. <!-- 任务详情信息弹出框 -->
  6. <el-dialog :class="''" :title="title" v-if="addFormVisible" append-to-body
  7. :visible.sync="addFormVisible" :close-on-click-modal="false" customClass="customWidth" width="840px" :top="'6vh'">
  8. <taskComponent ref="thskComponents" :integrationTask="integrationTask" :showOrNot="showOrNot"
  9. @closeBounced="closeBounced" :showMmeiLaiDe="true" :showMmeiLaiDeData="showMmeiLaiDeData"></taskComponent>
  10. <div slot="title" v-if="addForm.parentTid != null">
  11. <el-page-header @back="backToParentTask" :title="$t('parenttask')"
  12. :content="addForm.parentTname"></el-page-header>
  13. </div>
  14. </el-dialog>
  15. </div>
  16. </template>
  17. <script>
  18. import { gantt } from 'dhtmlx-gantt';
  19. // import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
  20. // import 'dhtmlx-gantt/codebase/locale/locale_cn' // 本地化
  21. import taskComponent from "@/components/taskComponent.vue"
  22. export default {
  23. name: 'gantt',
  24. components: {
  25. taskComponent
  26. },
  27. props: {
  28. tasks: {
  29. type: Object,
  30. default() {
  31. return { data: [], links: [] }
  32. }
  33. },
  34. stafforpro: '',
  35. valueDate: [],
  36. },
  37. data() {
  38. return {
  39. containerRect: null,
  40. taskFormVisible: false,
  41. addForm: {},
  42. addLoading: false,
  43. title: '创建计划',
  44. commentList: [],
  45. dynamicTab: true,
  46. showOrNot: false,
  47. integrationTask: null,
  48. taskComponentFlg: false,
  49. addFormVisible: false,
  50. integrationTaskNingwai: {},
  51. integrationTask: {},
  52. user: JSON.parse(sessionStorage.getItem("user")),
  53. showMmeiLaiDeData: {},
  54. };
  55. },
  56. created: function () {
  57. // gantt.clearAll()
  58. // console.log("tasks",this.$props.tasks);
  59. },
  60. methods: {
  61. detaliTaskExposure(row) {
  62. const { taskId, ganttData } = row
  63. this.title = '编辑计划'
  64. this.showMmeiLaiDeData = JSON.parse(ganttData || '{}')
  65. setTimeout(() => {
  66. this.editTask({ taskId })
  67. }, 1000)
  68. },
  69. getDistanceToParent(element, parent) {
  70. let distance = 0;
  71. while (element && element !== parent) {
  72. distance += element.offsetTop; // 获取当前元素相对于父元素顶部的距离
  73. element = element.offsetParent; // 获取上一级父元素
  74. }
  75. return distance;
  76. },
  77. closeBounced(obj) {
  78. console.log(obj, '<======== 点击事件')
  79. if (!obj.backToParentTaskSub) {
  80. this.addFormVisible = false
  81. this.taskComponentFlg = false
  82. if (obj.submitInsert) {
  83. this.$emit('closeBounced', obj)
  84. }
  85. if(obj.deleteTask) {
  86. this.$emit('closeBounced', obj)
  87. }
  88. }
  89. },
  90. backToParentTask() {
  91. console.log('点击, <======== 点击了backToParentTask')
  92. },
  93. addTask(row) {
  94. this.showOrNot = true
  95. this.addForm = {
  96. projectId: '', groupId: '', stagesId: '', taskLevel: 0, planHours: 8, taskType: 0, startDate: row.date
  97. }
  98. const userIdList = (row.userId && row.userId.split(',')) || []
  99. const executorListFront = userIdList.map(item => {
  100. return {
  101. executorId: item,
  102. planHours: 8
  103. }
  104. })
  105. let obj = {
  106. create: true,
  107. addForm: this.addForm,
  108. executorListFront,
  109. stage: this.addForm,
  110. integrationTaskNingwai: this.integrationTaskNingwai,
  111. taskVue: true,
  112. meetingId: this.addForm.meetingId
  113. }
  114. this.integrationTask = obj
  115. this.addFormVisible = true
  116. },
  117. editTask(row) {
  118. this.showOrNot = true
  119. this.http.post('/task/getTask', { id: row.taskId },
  120. res => {
  121. if (res.code == "ok") {
  122. const data = res.data
  123. if(this.user.roleId != 2283 && !data.executorId) {
  124. this.$message({
  125. message: '这条数据只有区域经理才能分配',
  126. type: 'warning'
  127. });
  128. return
  129. }
  130. this.addForm = {
  131. id: data.id
  132. }
  133. this.integrationTask = {
  134. id: data.id,
  135. task: data,
  136. num: 1,
  137. curProjectId: data.projectId || '',
  138. create: false,
  139. integrationTaskNingwai: {
  140. groupId: data.groupId,
  141. isDesc: false,
  142. order: "seq",
  143. projectId: data.projectId || '',
  144. },
  145. taskVue: data.projectId ? false : true,
  146. meetingId: this.addForm.meetingId
  147. }
  148. this.addFormVisible = true
  149. } else {
  150. this.$message({ message: res.msg, type: "error" });
  151. }
  152. },
  153. error => {
  154. this.$message({ message: error, type: "error" });
  155. });
  156. },
  157. handleEmptyClick(row) {
  158. if (row.taskId) {
  159. // 编辑任务
  160. this.title = '编辑计划'
  161. this.editTask(row)
  162. } else {
  163. // 新增任务
  164. this.title = '创建计划'
  165. this.addTask(row)
  166. }
  167. },
  168. },
  169. mounted: function () {
  170. const userInfo = JSON.parse(sessionStorage.getItem("user"));
  171. gantt.clearAll()
  172. gantt.locale = {
  173. date: {
  174. month_full: [this.$t('yiYue'), this.$t('erYue'), this.$t('sanYue'), this.$t('siYue'), this.$t('thisTWuyue'), this.$t('liuYue'), this.$t('qiYue'), this.$t('baYue'), this.$t('jiuYue'), this.$t('shiYue'), this.$t('shiYiYue'), this.$t('shiErYue')],
  175. month_short: [this.$t('1Yue'), this.$t('2Yue'), this.$t('3Yue'), this.$t('4Yue'), this.$t('5Yue'), this.$t('6Yue'), this.$t('7Yue'), this.$t('8Yue'), this.$t('9Yue'), this.$t('10Yue'), this.$t('11Yue'), this.$t('12Yue')],
  176. day_full: [this.$t('xingQiRi'), this.$t('xingQiYi'), this.$t('xingQiEr'), this.$t('xingQiSan'), this.$t('xingQiSi'), this.$t('xingQiWu'), this.$t('xingQiLiu')],
  177. day_short: [this.$t('ri'), "一", "二", "三", "四", "五", "六"]
  178. },
  179. labels: {
  180. dhx_cal_today_button: this.$t('jinTian'),
  181. day_tab: this.$t('weekDay.day'),
  182. week_tab: this.$t('zhou'),
  183. month_tab: this.$t('weekDay.month'),
  184. new_event: this.$t('xinJianRiCheng'),
  185. icon_save: this.$t('save'),
  186. icon_cancel: this.$t('Shutdown'),
  187. icon_details: this.$t('xiangXi'),
  188. icon_edit: this.$t('bian-ji'),
  189. icon_delete: this.$t('btn.delete'),
  190. confirm_closing: this.$t('qingQueRenShiFouCheXiaoXiuGai'), //Your changes will be lost, are your sure?
  191. confirm_deleting: this.$t('shiFouShanChuRiCheng'),
  192. section_description: this.$t('other.describe'),
  193. section_time: this.$t('shiJianFanWei'),
  194. section_type: this.$t('types'),
  195. /* grid columns */
  196. column_text: this.$t('renWuMing'),
  197. column_start_date: this.$t('starttimes'),
  198. column_duration: this.$t('chiXuShiJian'),
  199. column_add: "",
  200. /* link confirmation */
  201. link: this.$t('guanLian'),
  202. confirm_link_deleting: this.$t('jiangBeiShanChu'),
  203. link_start: this.$t('kaiShi'),
  204. link_end: this.$t('jieShu'),
  205. type_task: this.$t('other.task'),
  206. type_project: this.$t('other.project'),
  207. type_milestone: this.$t('other.milestone'),
  208. minutes: this.$t('fenZhong'),
  209. hours: this.$t('time.hour'),
  210. days: this.$t('time.day'),
  211. weeks: this.$t('zhou'),
  212. months: this.$t('weekDay.month'),
  213. years: this.$t('nian')
  214. }
  215. };
  216. // gantt.config.autosize = true;
  217. // gantt.config.duration_unit = "hour";
  218. gantt.config.fit_tasks = true;
  219. gantt.config.drag_move = false;
  220. gantt.config.xml_date = "%Y-%m-%d";
  221. gantt.config.columns = [
  222. { name: "text", label: (this.stafforpro == this.$t('anRenYuanChaKan') ? this.$t('lable.name') : this.$t('headerTop.projectName')), align: "left", tree: true },
  223. // {name:"time",label:"计划工时(h)", align: "center" }
  224. // {name:"start_date", label:"开始时间", width:'*' , align: "center" },
  225. // {name:"duration", label:"工时(天)", width:'*' , align: "center" }
  226. ];
  227. gantt.config.scale_unit = "month"; //按月显示
  228. gantt.config.date_scale = "%F, %Y"; //设置时间刻度的格式(X轴) 多个尺度
  229. gantt.config.scale_height = 50; //设置时间刻度的高度和网格的标题
  230. gantt.config.open_tree_initially = true;
  231. gantt.config.subscales = [
  232. { unit: "day", step: 1, date: "周%D,%d" }
  233. ];
  234. gantt.config.buttons_left = []
  235. gantt.config.buttons_right = ["gantt_cancel_btn"]
  236. gantt.config.drag_links = false
  237. gantt.config.drag_resize = false
  238. gantt.config.drag_progress = false
  239. gantt.config.details_on_dblclick = false
  240. gantt.config.lightbox.sections = [
  241. { name: "description", height: 76, map_to: "text", type: "textarea", focus: true }
  242. ];
  243. gantt.config.start_date = new Date(this.valueDate[0]);
  244. gantt.config.end_date = new Date(this.valueDate[1]);
  245. //设置任务条样式
  246. gantt.templates.task_class = function (start, end, item) {
  247. const { taskPlan, taskStatus } = item
  248. if(taskStatus == 5 || taskStatus == 6) {
  249. return "reject_line"
  250. }
  251. if(taskPlan == 1 && taskStatus == 0) {
  252. return "success_line"
  253. }
  254. if(taskPlan == 1 && taskStatus == 2) {
  255. return "draft_line"
  256. }
  257. return item.taskPlanType == 3 ? "error_line" : "person_line"
  258. };
  259. const that = this
  260. // 设置任务条内容显示
  261. gantt.templates.task_text = function (start, end, task) {
  262. const { leaderOrManager, taskPlan, taskStatus, checkFirstId, checkSecondId } = task
  263. const userIds = that.user.id
  264. // 都审核通过
  265. if(taskPlan == 1 && taskStatus == 0 && task.text == '请假') {
  266. return `<div class="task_text">
  267. <div style="background: '#ff5757'">${task.text}</div>
  268. </div>`
  269. }
  270. if(taskPlan == 1 && taskStatus == 0) {
  271. return `<div class="task_text">
  272. <div class="exclamation-circle circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">V</div>
  273. <div class="exclamation-circle circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">V</div>
  274. <div>${task.text}</div>
  275. </div>`
  276. }
  277. // 小组长
  278. // if (userInfo.projectLeaderType == 1) {
  279. if (leaderOrManager == 1) {
  280. return `<div class="task_text">
  281. ${task.taskStatus == 3 ? `<div class="circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">!</div> <div class="circle"></div>` : ''}
  282. ${task.taskStatus == 4 ? `<div class="exclamation-circle circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">V</div> <div class="circle"></div>` : ''}
  283. ${task.taskStatus == 5 ? `<div class="exclamation-circle circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">!</div> <div class="circle"></div>` : ''}
  284. ${task.taskStatus == 6 ? `<div class="exclamation-circle circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">V</div> <div class="exclamation-circle circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">!</div>` : ''}
  285. <div>${task.text}</div>
  286. </div>`;
  287. }
  288. // 审核人
  289. // if (userInfo.projectLeaderType == 2) {
  290. if (leaderOrManager == 2) {
  291. const oneRight = `<div class="exclamation-circle circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">V</div> <div class="circle"></div>`
  292. const twoPairs = `<div class="exclamation-circle circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">V</div> <div class="circle exclamation-circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">V</div>`
  293. const toExamine = `<div class="task_text statuss">
  294. ${task.taskStatus == 3 || task.taskStatus == 4 ? `<div class="circle">
  295. <div class="pendingReviewOfCornerMarkers">!</div>
  296. </div>` : ''}`
  297. const twoEmptyCircles = `<div class="circle"></div><div class="circle"></div>`
  298. return `<div class="task_text">
  299. ${(userIds == checkFirstId && task.taskStatus == 3) ? toExamine : (userIds == checkFirstId && task.taskStatus == 4) ? oneRight : ''}
  300. ${(userIds == checkSecondId && task.taskStatus == 4) ? toExamine : (userIds == checkSecondId && task.taskStatus == 3) ? twoEmptyCircles : ''}
  301. ${task.taskStatus == 5 ? `<div class="exclamation-circle circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">!</div> <div class="circle"></div>` : ''}
  302. ${task.taskStatus == 6 ? `<div class="exclamation-circle circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">V</div> <div class="exclamation-circle circle" style="color: ${task.taskPlanType == 3 ? '#f56c6c' : '#8ecaf8'}">!</div>` : ''}
  303. <div>${task.text}</div>
  304. <div>${task.text}</div>
  305. </div>`
  306. }
  307. // 正常人
  308. return `<div class="task_text">
  309. <div>${task.text}</div>
  310. </div>`;
  311. };
  312. gantt.config.grid_width = 350;
  313. gantt.plugins({ tooltip: true });
  314. gantt.templates.tooltip_text = function (start, end, task) {
  315. return
  316. };
  317. gantt.ext.tooltips.attach({
  318. selector: '.gantt_grid [' + gantt.config.task_attribute + ']',
  319. onmouseenter: (event, node) => {
  320. if (node.textContent.length > 19) {
  321. let sdom = document.createElement('span')
  322. sdom.innerText = node.innerText
  323. sdom.className = 'tooltiptext'
  324. node.appendChild(sdom)
  325. }
  326. },
  327. onmousemove: () => { },
  328. onmouseleave: (event, node) => {
  329. let sdom = document.getElementsByClassName('tooltiptext')[0]
  330. if (sdom) { node.removeChild(sdom) }
  331. },
  332. global: true
  333. })
  334. // 双击事件
  335. this.$refs.ganttContainer.addEventListener('dblclick', (event) => {
  336. const taskIdStr = gantt.locate(event);
  337. if(userInfo.projectLeaderType != 1 && userInfo.projectLeaderType != 2 && userInfo.projectLeaderType != 3) {
  338. return
  339. }
  340. this.showMmeiLaiDeData = {}
  341. if (taskIdStr && gantt.isTaskExists(taskIdStr)) {
  342. // 编辑任务
  343. const rows = gantt.getTask(taskIdStr)
  344. if (rows.id.indexOf('任务') != '-1') {
  345. const value = taskIdStr.split('任务_')[1]
  346. const taskId = value.split('_')[0]
  347. this.showMmeiLaiDeData = rows
  348. this.handleEmptyClick({ taskId })
  349. }
  350. } else {
  351. if(userInfo.projectLeaderType == 2) {
  352. return
  353. }
  354. // 获取 gantt_grid_data 容器
  355. const ganttGrid = document.querySelector('.gantt_grid_data');
  356. if (!ganttGrid) return;
  357. // // 计算点击位置
  358. const gridRect = ganttGrid.getBoundingClientRect();
  359. // const clickY = event.clientY - gridRect.top; // 相对 gantt_grid_data 的 Y 轴偏移量
  360. // // 获取任务行
  361. // const rows = ganttGrid.querySelectorAll('.gantt_row');
  362. // const rowHeight = rows.length > 0 ? rows[0].offsetHeight : 34; // 获取行高 (默认 40px)
  363. // // 计算索引
  364. // const rowIndex = Math.floor(clickY / rowHeight);
  365. // if (rowIndex >= rows.length || rowIndex < 0) return;
  366. // // 获取任务 ID
  367. // const rowElement = rows[rowIndex];
  368. // 新写的
  369. const rows = ganttGrid.querySelectorAll('.gantt_row');
  370. const clickedElement = event.toElement; // 当前点击的元素
  371. const parentElement = clickedElement.closest('.gantt_task_row');
  372. const yzhozhi = this.getDistanceToParent(parentElement) - 219 // 219 固定值
  373. const yCoordinate = +((yzhozhi / 35).toFixed(0)) * 35
  374. let rowIndexs = 0
  375. for(const em in rows) {
  376. const str = (rows[em].style && rows[em].style.top) || '-1px'
  377. const nums = str.split('px')[0]
  378. if(nums == yCoordinate) {
  379. rowIndexs = em
  380. }
  381. }
  382. const rowElement = rows[rowIndexs]
  383. console.log(yCoordinate, rowIndexs)
  384. console.log(rowElement, '<,,,,,,,,,,=============== rowElement')
  385. const taskIdFromRow = rowElement.getAttribute('task_id'); // 获取任务 ID
  386. if (taskIdFromRow && gantt.isTaskExists(taskIdFromRow)) {
  387. const taskData = gantt.getTask(taskIdFromRow);
  388. console.log(taskData, '<=== taskData')
  389. if (taskData.userId) {
  390. // 新增任务
  391. let rollingDistance = 0
  392. const scrollElement = this.$refs.ganttContainer.querySelector('.gantt_hor_scroll');
  393. if (scrollElement) {
  394. rollingDistance = scrollElement.scrollLeft || 0
  395. }
  396. const userId = taskData.userId;
  397. const gridX = event.clientX - gridRect.left; // 相对 gantt_grid_data 的 X 轴偏移量
  398. const date = gantt.dateFromPos((gridX - 349) + rollingDistance);
  399. console.log('点击的日期:', date, this.dayjs(date).format('YYYY-MM-DD'));
  400. this.handleEmptyClick({ userId, date: this.dayjs(date).format('YYYY-MM-DD') })
  401. }
  402. }
  403. }
  404. });
  405. gantt.init(this.$refs.ganttContainer);
  406. this.containerRect = this.$refs.ganttContainer.getBoundingClientRect();
  407. gantt.parse(this.$props.tasks);
  408. }
  409. }
  410. </script>
  411. <style>
  412. @import "~dhtmlx-gantt/codebase/dhtmlxgantt.css";
  413. .person_line {
  414. background: #8ecaf8;
  415. border: 1px solid;
  416. @include border_color("border_color");
  417. }
  418. .error_line {
  419. background: #ff5757;
  420. border: 1px solid;
  421. border-color: #ff5757;
  422. }
  423. .success_line {
  424. background: #5cb87a;
  425. border: 1px solid;
  426. border-color: #5cb87a;
  427. }
  428. .draft_line {
  429. background: #909399;
  430. border: 1px solid;
  431. border-color: #909399;
  432. }
  433. .reject_line {
  434. background: #FFBD4D;
  435. border: 1px solid;
  436. border-color: #FFBD4D;
  437. }
  438. .task_text {
  439. display: flex;
  440. flex-direction: row;
  441. align-items: center;
  442. white-space: nowrap;
  443. overflow: hidden;
  444. text-overflow: ellipsis;
  445. width: 100%;
  446. }
  447. .task_text>div {
  448. flex: 0 0 auto;
  449. }
  450. .circle {
  451. width: 10px;
  452. height: 10px;
  453. border-radius: 50%;
  454. border: 2px solid #fff;
  455. margin-left: 4px;
  456. display: flex;
  457. justify-content: center;
  458. align-items: center;
  459. }
  460. .exclamation-circle {
  461. background: #fff;
  462. font-size: 12px;
  463. font-weight: bold;
  464. }
  465. .pendingReviewOfCornerMarkers {
  466. width: 100%;
  467. height: 100%;
  468. border-radius: 50%;
  469. display: flex;
  470. justify-content: center;
  471. align-items: center;
  472. background: #fdba6e;
  473. font-weight: bold;
  474. }
  475. .statuss .circle {
  476. border-color: #fdba6e;
  477. }
  478. /* .gantt_tooltip{
  479. z-index: 10000;
  480. top: 10px !important;
  481. left: 10px !important;
  482. } */
  483. .tooltiptext {
  484. visibility: visible;
  485. background-color: #fff;
  486. color: #454545;
  487. box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
  488. border-left: 1px solid rgba(0, 0, 0, .07);
  489. border-top: 1px solid rgba(0, 0, 0, .07);
  490. font-size: 8pt;
  491. line-height: 20px;
  492. padding: 10px;
  493. /* opacity: 0;
  494. transition: 0.5s; */
  495. position: absolute;
  496. z-index: 10000;
  497. bottom: 120%;
  498. left: 25px;
  499. max-width: 300px;
  500. /* word-wrap: break-word;
  501. word-break: break-all; */
  502. white-space: pre-wrap;
  503. }
  504. </style>