123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668 |
- <template>
- <div class="ganttContainerBox">
- <div ref="ganttContainer" style="height: 100%;">
- </div>
- <!-- 任务详情信息弹出框 -->
- <el-dialog :class="''" :title="title" v-if="addFormVisible" append-to-body
- :visible.sync="addFormVisible" :close-on-click-modal="false" customClass="customWidth" width="840px" :top="'6vh'">
- <taskComponent ref="thskComponents" :integrationTask="integrationTask" :showOrNot="showOrNot"
- @closeBounced="closeBounced" :showMmeiLaiDe="true" :showMmeiLaiDeData="showMmeiLaiDeData"></taskComponent>
- <div slot="title" v-if="addForm.parentTid != null">
- <el-page-header @back="backToParentTask" :title="$t('parenttask')"
- :content="addForm.parentTname"></el-page-header>
- </div>
- </el-dialog>
- <!-- tooltip -->
- <div
- v-if="tooltip.visible"
- :style="`top:${tooltip.y}px;left:${tooltip.x}px;`"
- class="custom-tooltip-customize"
- >
- <div><b>开始时间:</b> {{ tooltip.task.start_date }}</div>
- <div><b>结束时间:</b> {{ tooltip.task.end_date }}</div>
- <div><b>计划工时:</b> {{ tooltip.task.dayDifference }} 天 {{ tooltip.task.time }} 小时</div>
- <div><b>项目名称:</b> {{ tooltip.task.projectName }}</div>
- <div><b>计划名称:</b> {{ tooltip.task.planName }}</div>
- </div>
- </div>
- </template>
- <script>
- import { gantt } from 'dhtmlx-gantt';
- // import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
- // import 'dhtmlx-gantt/codebase/locale/locale_cn' // 本地化
- import taskComponent from "@/components/taskComponent.vue"
- export default {
- name: 'gantt',
- components: {
- taskComponent
- },
- props: {
- tasks: {
- type: Object,
- default() {
- return { data: [], links: [] }
- }
- },
- stafforpro: '',
- valueDate: [],
- },
- data() {
- return {
- containerRect: null,
- taskFormVisible: false,
- addForm: {},
- addLoading: false,
- title: '创建计划',
- commentList: [],
- dynamicTab: true,
- showOrNot: false,
- integrationTask: null,
- taskComponentFlg: false,
- addFormVisible: false,
- integrationTaskNingwai: {},
- integrationTask: {},
- user: JSON.parse(sessionStorage.getItem("user")),
- showMmeiLaiDeData: {},
- tooltip: {
- visible: false,
- task: null,
- x: 0,
- y: 0,
- }
- };
- },
- created: function () {
- // gantt.clearAll()
- // console.log("tasks",this.$props.tasks);
- },
- methods: {
- detaliTaskExposure(row) {
- const { taskId, ganttData } = row
- this.title = '编辑计划'
- this.showMmeiLaiDeData = JSON.parse(ganttData || '{}')
- setTimeout(() => {
- this.editTask({ taskId })
- }, 1000)
- },
- getDistanceToParent(element, parent) {
- let distance = 0;
- while (element && element !== parent) {
- distance += element.offsetTop; // 获取当前元素相对于父元素顶部的距离
- element = element.offsetParent; // 获取上一级父元素
- }
- return distance;
- },
- closeBounced(obj) {
- console.log(obj, '<======== 点击事件')
- if (!obj.backToParentTaskSub) {
- this.addFormVisible = false
- this.taskComponentFlg = false
- if (obj.submitInsert) {
- this.$emit('closeBounced', obj)
- }
- if(obj.deleteTask) {
- this.$emit('closeBounced', obj)
- }
- }
- },
- backToParentTask() {
- console.log('点击, <======== 点击了backToParentTask')
- },
- addTask(row) {
- this.showOrNot = true
- this.addForm = {
- projectId: '', groupId: '', stagesId: '', taskLevel: 0, planHours: 8, taskType: 0, startDate: row.date
- }
- const userIdList = (row.userId && row.userId.split(',')) || []
- const executorListFront = userIdList.map(item => {
- return {
- executorId: item,
- planHours: 8
- }
- })
- let obj = {
- create: true,
- addForm: this.addForm,
- executorListFront,
- stage: this.addForm,
- integrationTaskNingwai: this.integrationTaskNingwai,
- taskVue: true,
- meetingId: this.addForm.meetingId
- }
- this.integrationTask = obj
- this.addFormVisible = true
- },
- editTask(row) {
- this.showOrNot = true
- this.http.post('/task/getTask', { id: row.taskId },
- res => {
- if (res.code == "ok") {
- const data = res.data
- if(this.user.roleId != 2283 && !data.executorId) {
- this.$message({
- message: '这条数据只有区域经理才能分配',
- type: 'warning'
- });
- return
- }
- this.addForm = {
- id: data.id
- }
- this.integrationTask = {
- id: data.id,
- task: data,
- num: 1,
- curProjectId: data.projectId || '',
- create: false,
- integrationTaskNingwai: {
- groupId: data.groupId,
- isDesc: false,
- order: "seq",
- projectId: data.projectId || '',
- },
- taskVue: data.projectId ? false : true,
- meetingId: this.addForm.meetingId
- }
- this.addFormVisible = true
- } else {
- this.$message({ message: res.msg, type: "error" });
- }
- },
- error => {
- this.$message({ message: error, type: "error" });
- });
- },
- handleEmptyClick(row) {
- if (row.taskId) {
- // 编辑任务
- this.title = '编辑计划'
- this.editTask(row)
- } else {
- // 新增任务
- this.title = '创建计划'
- this.addTask(row)
- }
- },
- // 定位数据
- focusAndSelectTaskFull(taskId) {
- if (gantt.isTaskExists(taskId)) {
- var task = gantt.getTask(taskId);
- gantt.showTask(taskId);
- gantt.selectTask(taskId);
- // 检查日期有效性
- if (!(task.start_date instanceof Date) || !(task.end_date instanceof Date)) {
- console.error("Invalid date format in task:", task);
- return;
- }
- var midTime = (task.start_date.getTime() + task.end_date.getTime()) / 2;
- var midDate = new Date(midTime);
- gantt.scrollTo(gantt.posFromDate(midDate) - 200, gantt.getScrollState().y);
- }
- },
- observeTaskLines(gantt) {
- console.log(gantt, '<==== gantt')
- const taskContainer = gantt.$task_data;
- this.observer = new MutationObserver(() => {
- this.bindTooltipEvents();
- });
- this.observer.observe(taskContainer, {
- childList: true,
- subtree: true
- });
- this.bindTooltipEvents(); // 初次执行
- },
- bindTooltipEvents() {
- console.log('开始执行')
- const taskLines = this.$refs.ganttContainer.querySelectorAll(".gantt_task_line");
- taskLines.forEach(line => {
- const taskId = line.getAttribute("task_id");
- if (!taskId || line.dataset.bound === "true") return;
- line.dataset.bound = "true";
- line.addEventListener("mouseenter", (e) => {
- const rect = line.getBoundingClientRect();
- const task = gantt.getTask(taskId);
- if(!task.taskId) {
- this.tooltip.visible = false;
- return
- }
- console.log(task)
- const list = task.text.split('/')
- const d1 = this.dayjs(task.start_date);
- const d2 = this.dayjs(task.end_date);
- this.tooltip.task = {
- ...task,
- end_date: this.dayjs(task.endDateStr).format('YYYY-MM-DD HH:mm:ss'),
- start_date: this.dayjs(task.startDateStr).format('YYYY-MM-DD HH:mm:ss'),
- projectName: list[0],
- planName: list[1],
- dayDifference: d2.diff(d1, 'day'),
- };
- this.tooltip.x = rect.left + window.scrollX;
- this.tooltip.y = (rect.top - 180) - 10 + window.scrollY;
- this.tooltip.visible = true;
- });
- line.addEventListener("mouseleave", () => {
- this.tooltip.visible = false;
- });
- });
- },
- bindTaskLineEvents() {
- console.log("bindTaskLineEvents", this.$refs.ganttContainer)
- if(!this.$refs.ganttContainer) {
- return
- }
- const taskLines = this.$refs.ganttContainer.querySelectorAll(".gantt_task_line");
- taskLines.forEach(line => {
- const taskId = line.getAttribute("task_id");
- if (!taskId) return;
- // 避免重复绑定
- if (line.dataset.tooltipBound) return;
- line.dataset.tooltipBound = "true";
- line.addEventListener("mouseenter", (e) => {
- const task = gantt.getTask(taskId);
- this.tooltip.task = task;
- this.tooltip.visible = true;
- const updateMouse = (moveEvent) => {
- this.tooltip.x = moveEvent.pageX;
- this.tooltip.y = moveEvent.pageY;
- };
- document.addEventListener("mousemove", updateMouse);
- line.addEventListener("mouseleave", () => {
- this.tooltip.visible = false;
- document.removeEventListener("mousemove", updateMouse);
- }, { once: true });
- });
- });
- }
- },
- mounted: function () {
- const userInfo = JSON.parse(sessionStorage.getItem("user"));
- gantt.clearAll()
- gantt.locale = {
- date: {
- 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')],
- 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')],
- day_full: [this.$t('xingQiRi'), this.$t('xingQiYi'), this.$t('xingQiEr'), this.$t('xingQiSan'), this.$t('xingQiSi'), this.$t('xingQiWu'), this.$t('xingQiLiu')],
- day_short: [this.$t('ri'), "一", "二", "三", "四", "五", "六"]
- },
- labels: {
- dhx_cal_today_button: this.$t('jinTian'),
- day_tab: this.$t('weekDay.day'),
- week_tab: this.$t('zhou'),
- month_tab: this.$t('weekDay.month'),
- new_event: this.$t('xinJianRiCheng'),
- icon_save: this.$t('save'),
- icon_cancel: this.$t('Shutdown'),
- icon_details: this.$t('xiangXi'),
- icon_edit: this.$t('bian-ji'),
- icon_delete: this.$t('btn.delete'),
- confirm_closing: this.$t('qingQueRenShiFouCheXiaoXiuGai'), //Your changes will be lost, are your sure?
- confirm_deleting: this.$t('shiFouShanChuRiCheng'),
- section_description: this.$t('other.describe'),
- section_time: this.$t('shiJianFanWei'),
- section_type: this.$t('types'),
- /* grid columns */
- column_text: this.$t('renWuMing'),
- column_start_date: this.$t('starttimes'),
- column_duration: this.$t('chiXuShiJian'),
- column_add: "",
- /* link confirmation */
- link: this.$t('guanLian'),
- confirm_link_deleting: this.$t('jiangBeiShanChu'),
- link_start: this.$t('kaiShi'),
- link_end: this.$t('jieShu'),
- type_task: this.$t('other.task'),
- type_project: this.$t('other.project'),
- type_milestone: this.$t('other.milestone'),
- minutes: this.$t('fenZhong'),
- hours: this.$t('time.hour'),
- days: this.$t('time.day'),
- weeks: this.$t('zhou'),
- months: this.$t('weekDay.month'),
- years: this.$t('nian')
- }
- };
- // gantt.config.autosize = true;
- // gantt.config.duration_unit = "hour";
- gantt.config.fit_tasks = true;
- gantt.config.drag_move = false;
- gantt.config.xml_date = "%Y-%m-%d";
- gantt.config.columns = [
- { name: "text", label: (this.stafforpro == this.$t('anRenYuanChaKan') ? this.$t('lable.name') : this.$t('headerTop.projectName')), align: "left", tree: true },
- // {name:"time",label:"计划工时(h)", align: "center" }
- // {name:"start_date", label:"开始时间", width:'*' , align: "center" },
- // {name:"duration", label:"工时(天)", width:'*' , align: "center" }
- ];
- gantt.config.scale_unit = "month"; //按月显示
- gantt.config.date_scale = "%F, %Y"; //设置时间刻度的格式(X轴) 多个尺度
- gantt.config.scale_height = 50; //设置时间刻度的高度和网格的标题
- gantt.config.open_tree_initially = true;
- gantt.config.subscales = [
- { unit: "day", step: 1, date: "周%D,%d" }
- ];
- gantt.config.buttons_left = []
- gantt.config.buttons_right = ["gantt_cancel_btn"]
- gantt.config.drag_links = false
- gantt.config.drag_resize = false
- gantt.config.drag_progress = false
- gantt.config.details_on_dblclick = false
- gantt.config.lightbox.sections = [
- { name: "description", height: 76, map_to: "text", type: "textarea", focus: true }
- ];
- gantt.config.start_date = new Date(this.valueDate[0]);
- gantt.config.end_date = new Date(this.valueDate[1]);
- const that = this
- // 设置任务条内容显示
- gantt.templates.task_text = function (start, end, task) {
- const { leaderOrManager, taskPlan, taskStatus, checkFirstId, checkSecondId } = task
- const userIds = that.user.id
- // 都审核通过
- if(taskPlan == 1 && taskStatus == 0 && task.text == '请假') {
- return `<div class="task_text">
- <div style="background: '#ff5757'">${task.text}</div>
- </div>`
- }
- if(taskPlan == 1 && taskStatus == 0) {
- return `<div class="task_text">
- <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'}">V</div>
- <div>${task.text}</div>
- </div>`
- }
- // 更改为统一的
- if([3,4,5,6].includes(taskStatus)) {
- return `<div class="task_text">
- ${task.taskStatus == 3 ? `<div class="circle"></div> <div class="circle"></div>` : ''}
- ${task.taskStatus == 4 ? `<div class="exclamation-circle circle" style="color: #8ecaf8">V</div> <div class="circle"></div>` : ''}
- ${task.taskStatus == 5 ? `<div class="exclamation-circle circle" style="color: #f56c6c">!</div> <div class="circle"></div>` : ''}
- ${task.taskStatus == 6 ? `<div class="exclamation-circle circle" style="color: #8ecaf8">V</div> <div class="exclamation-circle circle" style="color: #f56c6c">!</div>` : ''}
- <div>${task.text}</div>
- </div>`;
- }
- // 正常人
- return `<div class="task_text">
- <div>${task.text}</div>
- </div>`;
- };
- gantt.config.grid_width = 350;
- gantt.plugins({ tooltip: true });
- gantt.templates.tooltip_text = function (start, end, task) {
- return
- };
- gantt.ext.tooltips.attach({
- selector: '.gantt_grid [' + gantt.config.task_attribute + ']',
- onmouseenter: (event, node) => {
- if (node.textContent.length > 19) {
- let sdom = document.createElement('span')
- sdom.innerText = node.innerText
- sdom.className = 'tooltiptext'
- node.appendChild(sdom)
- }
- },
- onmousemove: () => { },
- onmouseleave: (event, node) => {
- let sdom = document.getElementsByClassName('tooltiptext')[0]
- if (sdom) { node.removeChild(sdom) }
- },
- global: true
- })
- // 双击事件
- this.$refs.ganttContainer.addEventListener('dblclick', (event) => {
- const taskIdStr = gantt.locate(event);
- // if(userInfo.projectLeaderType != 1 && userInfo.projectLeaderType != 2 && userInfo.projectLeaderType != 3) {
- // return
- // }
- this.showMmeiLaiDeData = {}
- if (taskIdStr && gantt.isTaskExists(taskIdStr)) {
- // 编辑任务
- const rows = gantt.getTask(taskIdStr)
- if (rows.id.indexOf('任务') != '-1') {
- const value = taskIdStr.split('任务_')[1]
- const taskId = value.split('_')[0]
- this.showMmeiLaiDeData = rows
- this.handleEmptyClick({ taskId })
- }
- } else {
- if(userInfo.projectLeaderType == 2) {
- return
- }
- // 获取 gantt_grid_data 容器
- const ganttGrid = document.querySelector('.gantt_grid_data');
- if (!ganttGrid) return;
- // // 计算点击位置
- const gridRect = ganttGrid.getBoundingClientRect();
- // const clickY = event.clientY - gridRect.top; // 相对 gantt_grid_data 的 Y 轴偏移量
- // // 获取任务行
- // const rows = ganttGrid.querySelectorAll('.gantt_row');
- // const rowHeight = rows.length > 0 ? rows[0].offsetHeight : 34; // 获取行高 (默认 40px)
- // // 计算索引
- // const rowIndex = Math.floor(clickY / rowHeight);
- // if (rowIndex >= rows.length || rowIndex < 0) return;
- // // 获取任务 ID
- // const rowElement = rows[rowIndex];
- // 新写的
- const rows = ganttGrid.querySelectorAll('.gantt_row');
- const clickedElement = event.toElement; // 当前点击的元素
- const parentElement = clickedElement.closest('.gantt_task_row');
- const yzhozhi = this.getDistanceToParent(parentElement) - 219 // 219 固定值
- const yCoordinate = +((yzhozhi / 35).toFixed(0)) * 35
- let rowIndexs = 0
- for(const em in rows) {
- const str = (rows[em].style && rows[em].style.top) || '-1px'
- const nums = str.split('px')[0]
- if(nums == yCoordinate) {
- rowIndexs = em
- }
- }
- const rowElement = rows[rowIndexs]
- console.log(yCoordinate, rowIndexs)
- console.log(rowElement, '<,,,,,,,,,,=============== rowElement')
- const taskIdFromRow = rowElement.getAttribute('task_id'); // 获取任务 ID
- if (taskIdFromRow && gantt.isTaskExists(taskIdFromRow)) {
- const taskData = gantt.getTask(taskIdFromRow);
- console.log(taskData, '<=== taskData')
- if (taskData.userId) {
- // 新增任务
- let rollingDistance = 0
- const scrollElement = this.$refs.ganttContainer.querySelector('.gantt_hor_scroll');
- if (scrollElement) {
- rollingDistance = scrollElement.scrollLeft || 0
- }
- const userId = taskData.userId;
- const gridX = event.clientX - gridRect.left; // 相对 gantt_grid_data 的 X 轴偏移量
- const date = gantt.dateFromPos((gridX - 349) + rollingDistance);
- console.log('点击的日期:', date, this.dayjs(date).format('YYYY-MM-DD'));
- this.handleEmptyClick({ userId, date: this.dayjs(date).format('YYYY-MM-DD') })
- }
- }
- }
- });
- gantt.init(this.$refs.ganttContainer);
- this.containerRect = this.$refs.ganttContainer.getBoundingClientRect();
- gantt.parse(this.$props.tasks);
- // 等渲染完成后绑定每个任务条事件
- setTimeout(() => {
- this.observeTaskLines(gantt); // 开始监听任务条变化
- }, 2000)
- setTimeout(() => {
- const list = JSON.parse(localStorage.getItem('ganttChartTaskId') || '[]')
- if(list[0]) {
- this.focusAndSelectTaskFull(list[0])
- }
- localStorage.removeItem('ganttChartTaskId')
- }, 1000)
- }
- }
- </script>
- <style>
- @import "~dhtmlx-gantt/codebase/dhtmlxgantt.css";
- .person_line {
- background: #8ecaf8;
- border: 1px solid;
- @include border_color("border_color");
- }
- .error_line {
- background: #ff5757;
- border: 1px solid;
- border-color: #ff5757;
- }
- .success_line {
- background: #5cb87a;
- border: 1px solid;
- border-color: #5cb87a;
- }
- .draft_line {
- background: #909399;
- border: 1px solid;
- border-color: #909399;
- }
- .reject_line {
- background: #FFBD4D;
- border: 1px solid;
- border-color: #FFBD4D;
- }
- .task_text {
- display: flex;
- flex-direction: row;
- align-items: center;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- width: 100%;
- }
- .task_text>div {
- flex: 0 0 auto;
- }
- .circle {
- width: 10px;
- height: 10px;
- border-radius: 50%;
- border: 2px solid #fff;
- margin-left: 4px;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .exclamation-circle {
- background: #fff;
- font-size: 12px;
- font-weight: bold;
- }
- .pendingReviewOfCornerMarkers {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- display: flex;
- justify-content: center;
- align-items: center;
- background: #fdba6e;
- font-weight: bold;
- }
- .statuss .circle {
- border-color: #fdba6e;
- }
- /* .gantt_tooltip{
- z-index: 10000;
- top: 10px !important;
- left: 10px !important;
- } */
- .tooltiptext {
- visibility: visible;
- background-color: #fff;
- color: #454545;
- 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;
- line-height: 20px;
- padding: 10px;
- /* opacity: 0;
- transition: 0.5s; */
- position: absolute;
- z-index: 10000;
- bottom: 120%;
- left: 25px;
- max-width: 300px;
- /* word-wrap: break-word;
- word-break: break-all; */
- white-space: pre-wrap;
- }
- .custom-tooltip-customize {
- position: fixed;
- z-index: 9999;
- background: rgba(0, 0, 0, 0.7);
- color: #fff;
- padding: 20px;
- border-radius: 6px;
- }
- .custom-tooltip-customize div {
- margin-bottom: 10px;
- line-height: 20px;
- }
- .custom-tooltip-customize div:last-child {
- margin-bottom: 0;
- }
- </style>
|