From 11836748b73632ca1a28bc2a54a0098044feb5d8 Mon Sep 17 00:00:00 2001 From: Gary Gu <garygu@Garydebijibendiannao.local> Date: Wed, 28 May 2025 15:20:59 +0800 Subject: [PATCH] feat: 甘特图嵌入 --- src/components/Gantt.vue | 660 ++++++++++++++++++ yarn.lock | 5 package.json | 1 src/views/Home.vue | 1458 ++++++++++++++++++++------------------- 4 files changed, 1,417 insertions(+), 707 deletions(-) diff --git a/package.json b/package.json index aa6a8bd..eac4f4c 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "axios": "^1.2.1", "crypto-js": "^4.1.1", "dayjs": "^1.11.7", + "dhtmlx-gantt": "^9.0.11", "echarts": "^5.4.1", "echarts-gl": "2.0.9", "element-ui": "^2.15.12", diff --git a/src/components/Gantt.vue b/src/components/Gantt.vue new file mode 100644 index 0000000..d65cee0 --- /dev/null +++ b/src/components/Gantt.vue @@ -0,0 +1,660 @@ +<template> + <div class="gantt-box"> + <div class="gantt-header"> + <div> + <el-button size="small" type="primary" @click="yearClick">年</el-button> + <el-button size="small" type="primary" @click="monthClick">月</el-button> + <el-button size="small" type="primary" @click="weekClick">周</el-button> + <!-- <el-button size="small" type="primary" @click="dayClick">日</el-button> --> + </div> + <div> + <el-button style="margin-right: 20px" size="small" type="primary" v-if="!columnsShow" @click="toggle('1')">展开详情</el-button> + <el-button style="margin-right: 20px" size="small" type="primary" v-else @click="toggle('2')">关闭详情</el-button> + <span>备注:</span> + <span>进行中</span><span class="color" style="background-color: rgb(211, 211, 0)"></span>, <span>待进行</span + ><span class="color" style="background-color: rgb(170, 170, 127)"></span>, <span>延期未完工</span + ><span class="color" style="background-color: rgb(255, 0, 0)"></span>, <span>延期已完工</span + ><span class="color" style="background-color: #8bc34a"></span>, <span>已完工/提前完工</span + ><span class="color" style="background-color: rgb(14, 172, 81)"></span> + </div> + </div> + <div ref="gantt" style="height: 400px; font-size: 12px"></div> + </div> +</template> + +<script> + import axios from "axios"; + import { gantt } from "dhtmlx-gantt"; + import "dhtmlx-gantt/codebase/dhtmlxgantt.css"; + + export default { + name: "Gantt", + components: {}, + data() { + return { + taskslist: [], + // 甘特图配置 + tasks: { + data: [], + }, + columnsShow: false, + }; + }, + mounted() { + this.init(); //初始化 + this.getProjectqryDataColl(); //获取数据 + }, + methods: { + // 将毫秒值转换为普通的日期格式 yyyy-MM-dd + formatDate(time) { + const myTime = new Date(time); + const yyyy = myTime.getFullYear(); + let MM = myTime.getMonth() + 1; + if (MM < 10) { + MM = "0" + MM; + } + let dd = myTime.getDate(); + if (dd < 10) { + dd = "0" + dd; + } + const str = yyyy + "-" + MM + "-" + dd; + return str; + }, + // 获取两个时间相减的天数 + getDays(strDateStart, strDateEnd) { + var strSeparator = "-"; + //日期分隔符 var oDate1; var oDate2; + var iDays; + var oDate1 = strDateStart.split(strSeparator); + var oDate2 = strDateEnd.split(strSeparator); + var strDateS = new Date(oDate1[0], oDate1[1] - 1, oDate1[2]); + var strDateE = new Date(oDate2[0], oDate2[1] - 1, oDate2[2]); + var iDays = parseInt(Math.abs(strDateS - strDateE) / 1000 / 60 / 60 / 24); + //把相差的毫秒数转换为天数 + return iDays; + }, + getIntervalDays(timestamp) { + const date1 = new Date(); + const date2 = new Date(timestamp); + const diff = Math.abs(date2.getTime() - date1.getTime()); // 计算时间差(毫秒) + return Math.floor(diff / (1000 * 60 * 60 * 24)); // 将时间差转换为天数 + }, + // 计算时间进度 + getIntervalDays2(timestamp1, timestamp2) { + const now = new Date(); + const date1 = new Date(timestamp1); + const date2 = new Date(timestamp2); + const diff = Math.abs(date2.getTime() - date1.getTime()); // 计算时间差(毫秒) + const total = Math.floor(diff / (1000 * 60 * 60 * 24)); // 将时间差转换为天数 + const diff2 = Math.abs(now.getTime() - date1.getTime()); // 计算时间差(毫秒) + const real = Math.floor(diff2 / (1000 * 60 * 60 * 24)); // 将时间差转换为天数 + return Math.floor((real / total) * 100); + }, + + clickdataAll() { + let clickdata = []; + let colors = ""; + this.taskslist.map((item) => { + var arr = this.formatDate(item.begin1).split("-"); + var arr1 = this.formatDate(item.over1).split("-"); + var startTime = arr[2] + "-" + arr[1] + "-" + arr[0]; + var endTime = arr1[2] + "-" + arr1[1] + "-" + arr1[0]; + + var arr2 = []; + var arr3 = []; + var startTime1 = ""; + var endTime1 = ""; + var duration1 = ""; + if (item.begin2 != null) { + startTime1 = this.formatDate(item.begin2); + } + if (item.over2 != null) { + endTime1 = this.formatDate(item.over2); + } + + if (startTime1 != "" && endTime1 != "") { + duration1 = this.getDays(startTime1, endTime1); + } + if (item.zt == "0") { + colors = "#aaaa7f"; + } + if (item.zt == "1") { + colors = "#d3d300"; + } + if (item.zt == "2" || item.zt == "3") { + colors = "#0eac51"; + } + if (item.zt == "5") { + colors = "#ff0000"; + } + if (item.zt == "4") { + colors = "#8bc34a"; + } + clickdata.push({ + id: item.id, + text: item.project, + start_date: startTime, + end_date: endTime, + parent: item.parent, + duration: "4", + start_date1: startTime1, + end_date1: endTime1, + duration1: duration1, + progress: 0, + open: true, + color: colors, + remark: item.remark, + }); + }); + gantt.clearAll(); + this.tasks.data = clickdata; + gantt.parse(this.tasks); + gantt.refreshData(); + this.ganttLoading = false; + }, + + clearDirtyData() { + let baseList = []; + let baseList2 = []; + let baseList3 = []; + this.taskslist.map((item) => { + item.used = false; + item.baseTitle = ""; + }); + // 先筛选父级 + this.taskslist.map((item) => { + if (!item.used && !item.parent) { + item.used = true; + item.baseTitle = "[" + item.project + "]"; + baseList.push(item); + } + }); + baseList.map((item) => { + this.taskslist.map((item2) => { + if (!item2.used && item2.parent == item.id) { + item2.used = true; + item2.baseTitle = item.baseTitle + "->[" + item2.project + "]"; + baseList2.push(item2); + } + }); + }); + + baseList2.map((item) => { + this.taskslist.map((item2) => { + if (!item2.used && item2.parent == item.id) { + item2.used = true; + item2.baseTitle = item.baseTitle + "->[" + item2.project + "]"; + baseList3.push(item2); + } + }); + }); + + baseList3.map((item) => { + this.taskslist.map((item2) => { + if (!item2.used && item2.parent == item.id) { + item2.used = true; + item2.baseTitle = item.baseTitle + "->[" + item2.project + "]"; + } + }); + }); + + let copyTaskslist = []; + this.taskslist.map((item) => { + if (item.baseTitle) { + copyTaskslist.push(item); + } + }); + this.taskslist = copyTaskslist; + }, + // 获取工程进度项目列表 + getProjectqryDataColl() { + axios.get("https://api.zhihuibuild.com/zuul/zuul/jinchan-device/project/qryProjectProgress").then(({ data }) => { + // 接口获取data + if (data.code == "200") { + if (data.body.length != 0) { + this.taskslist = data.body; + this.clearDirtyData(); + let cache = []; + + this.taskslist.map((item, index) => { + item.zIndex = index + 1; + // 重新判断状态 + let nowTime = new Date().getTime(); + if (!item.begin2) { + //未开始 + item.zt = 0; + } else { + if (!item.over2) { + //未结束 + if (item.over1 >= nowTime) { + //判断预计工期是否超过当前时间 + item.zt = 1; //进行中 + } else { + item.zt = 5; //延期未完工 + } + } else { + if (item.over1 >= item.over2) { + //判断预计工期是否超过实际工期 + item.zt = 3; //提前完工2、3 + } else { + item.zt = 4; //延期完工 + } + } + } + if (item.zt == "5") { + cache.push([cache.length + 1, item.project, this.getIntervalDays(item.over1)]); + } + }); + + cache.sort(function (a, b) { + return b[2] - a[2]; + }); + cache.map((item, index) => { + item[0] = index + 1; + item[2] = item[2] + "天"; + }); + + setTimeout(() => { + this.clickdataAll(); + }, 1000); + } + } + }); + }, + yearClick() { + gantt.config.scales = [ + { + unit: "year", + step: 1, + date: " %Y", + }, + ]; + gantt.init(this.$refs.gantt); + }, + monthClick() { + gantt.config.scales = [ + { + unit: "year", + step: 1, + date: " %Y", + }, + { + unit: "month", + step: 1, + date: "%F", + }, + ]; + gantt.init(this.$refs.gantt); + }, + weekClick() { + gantt.config.scales = [ + { + unit: "year", + step: 1, + date: " %Y", + }, + { + unit: "month", + step: 1, + date: "%F", + }, + { + unit: "week", + step: 1, + date: "%W周", + }, + ]; + gantt.init(this.$refs.gantt); + }, + dayClick() { + gantt.config.scales = [ + { + unit: "year", + step: 1, + date: " %Y", + }, + { + unit: "month", + step: 1, + date: "%F", + }, + { + unit: "day", + step: 1, + date: "%j日", + }, + ]; + gantt.init(this.$refs.gantt); + }, + riliClick() { + gantt.config.scales = [ + { + unit: "month", + step: 1, + date: " %Y - %F", + }, + { + unit: "day", + step: 1, + date: "%j日 周%D", + }, + ]; + gantt.init(this.$refs.gantt); + }, + toggle(val) { + if (val == 1) { + this.columnsShow = true; + } else { + this.columnsShow = false; + } + //左侧显示列名 + if (val == "1") { + gantt.config.columns = [ + { + name: "text", + label: "任务名称", + tree: true, + align: "left", + resize: true, + width: "*", + min_width: 220, + }, + { + name: "start_date", + label: "计划开始", + align: "center", + resize: true, + width: "*", + min_width: 100, + }, + { + name: "end_date", + label: "计划结束", + align: "center", + resize: true, + width: "*", + min_width: 100, + }, + { + name: "duration", + label: "工期", + align: "center", + }, + { + name: "start_date1", + label: "实际开始", + align: "center", + resize: true, + width: "*", + min_width: 100, + }, + { + name: "end_date1", + label: "实际结束", + align: "center", + resize: true, + width: "*", + min_width: 100, + }, + { + name: "duration1", + label: "实际工期", + align: "center", + }, + { + name: "zt", + label: "", + align: "center", + resize: false, + }, + ]; + } else { + gantt.config.columns = [ + { + name: "text", + label: "任务名称", + tree: true, + align: "left", + resize: true, + width: 250, + }, + ]; + } + + // 初始化 + gantt.init(this.$refs.gantt); + // 数据解析 + gantt.parse(this.tasks); + }, + // 初始化 + init() { + // 自动延长时间刻度 + gantt.config.fit_tasks = true; + gantt.config.show_links = false; + gantt.config.drag_progress = false; // 取消进度条 + gantt.config.row_height = 25; //进度条容器高 + gantt.config.scale_height = 50; + gantt.config.autofit = true; //左侧是否自适应 + gantt.i18n.setLocale("cn"); // 设置中文 + gantt.config.readonly = true; // 设置为只读 + //自适应甘特图的尺寸大小, 使得在不出现滚动条的情况下, 显示全部任务 + gantt.config.autosize = false; + gantt.config.open_split_tasks = true; + // 允许拖放 + gantt.config.drag_project = true; + // 设置甘特图时间的起始结束时间,并允许显示超过时间刻度任务 + // gantt.config.start_date = new Date(`${new Date().getFullYear()-1}`,'01'); + // gantt.config.end_date = new Date(`${new Date().getFullYear()+1}`,'01'); + gantt.config.show_tasks_outside_timescale = true; + //开启提示:鼠标悬浮在gantt行上显示 + gantt.plugins({ + tooltip: true, + // quick_info: true,// 快速信息框 + multiselect: true, // 激活多任务选择 + }); + + gantt.templates.tooltip_text = function (start, end, task) { + return ( + "<b>项目: <span style='color:" + + task.color + + "'>" + + task.text + + "</span><br/><span>实际开始:" + + task.start_date1 + + "</span> " + + "</span><br/><span>实际结束:" + + task.end_date1 + + "</span> " + + "</span><br/><br/><span>计划开始:" + + gantt.templates.tooltip_date_format(start) + + "</span> " + + "<br/><span>计划结束:" + + gantt.templates.tooltip_date_format(end) + + "</span> " + + (task.remark ? `<br/><span>备注:${task.remark}</span>` : "") + ); + }; + // 按月 + gantt.config.scales = [ + { + unit: "year", + step: 1, + date: " %Y", + }, + { + unit: "month", + step: 1, + date: "%F", + }, + ]; + + //左侧显示列名 + gantt.config.columns = [ + { + name: "text", + label: "任务名称", + tree: true, + align: "left", + resize: true, + width: 250, + }, + ]; + // 初始化 + gantt.init(this.$refs.gantt); + + // 数据解析 + gantt.parse(this.tasks); + }, + }, + }; +</script> +<style lang="scss" scoped> + .gantt-box { + background-color: rgba(0, 0, 0, 0.2); + margin: 10px; + color: #fff; + + .gantt-header { + padding-top: 5px; + margin: 10px; + height: 40px; + display: flex; + justify-content: space-between; + align-items: center; + + .color { + width: 36px; + height: 16px; + vertical-align: text-top; + display: inline-block; + margin-right: 5px; + margin-left: 5px; + } + } + + /* 滚动条样式*/ + ::-webkit-scrollbar { + width: 10px; + height: 10px; + background-color: transparent; + } + + ::-webkit-scrollbar-thumb { + background-color: #73abb1; + border-radius: 6px; + } + + ::-webkit-scrollbar-track { + background-color: #373f4a; + opacity: 0.9; + } + } +</style> +<style lang="scss"> + body { + background-color: rgb(19, 90, 144); + } + + /* 自定义甘特图样式 */ + .gantt_grid_data .gantt_row.gantt_selected, + .gantt_grid_data .gantt_row.odd.gantt_selected, + .gantt_task_row.gantt_selected { + background-color: #409eff !important; + } + + .gantt_grid_data .gantt_row.odd:hover, + .gantt_grid_data .gantt_row:hover { + background-color: #409eff !important; + } + + .gantt_grid_scale .gantt_grid_head_cell { + color: #fff !important; + } + + .gantt_grid_scale, + .gantt_task_scale, + .gantt_task_vscroll { + background-color: transparent; + } + + /* 大背景透明 */ + .gantt_container { + background-color: transparent; + } + + /* 左标题文字白色 */ + .gantt_grid_data .gantt_cell { + color: #fff; + } + + /* 顶部日期白色 */ + .gantt_task .gantt_task_scale .gantt_scale_cell { + color: #fff; + } + + /* 偶数行背景透明 */ + .gantt_row.odd, + .gantt_task_row.odd { + background-color: transparent; + } + + /* 奇数行背景透明 */ + .gantt_row, + .gantt_task_row { + background-color: transparent; + } + + /* 表格边框*/ + .gantt_cell { + border-right: 1px solid #0de8ec; + } + .gantt_task_cell { + border-right: 1px solid #0de8ec; + } + + .gantt_row, + .gantt_task_row { + border-bottom: 1px solid #0de8ec; + } + + .gantt_grid_scale, + .gantt_task_scale { + border-bottom: 1px solid #0de8ec; + } + + .gantt_scale_line { + border-top: 1px solid #0de8ec; + } + + .gantt_layout_cell_border_right { + border-right: 1px solid #0de8ec; + } + + .gantt_layout_cell_border_bottom { + border-bottom: 1px solid #0de8ec; + } + + .gantt_layout_cell_border_left { + border-left: 1px solid #0de8ec; + } + + .gantt_layout_cell_border_top { + border-top: 1px solid #0de8ec; + } + + .gantt_task_content { + text-shadow: 0 0 4px #333; + overflow: visible; + transform: translateY(-12px); + } + + /* 进度条高度 */ + .gantt_task_line { + height: 10px !important; + transform: translateY(10px); + } + + .gantt_tooltip { + width: 180px; + } +</style> diff --git a/src/views/Home.vue b/src/views/Home.vue index 747f117..068b371 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -1,43 +1,70 @@ <template> -<div class="home-container"> - <!-- 顶部导航 --> - <div class="top-nav"> - <div class="nav-items"> - <div class="nav-item mr-[8px]" :class="{ active: currentView === 'model' }" @click="handleChange('model')">模型</div> - <div class="nav-item mr-[8px]" :class="{ active: currentView === '720' }" @click="handleChange('720')">720全景</div> - <div class="nav-item" :class="{ active: currentView === 'project' }" @click="handleChange('project')">项目实况</div> - </div> - <div class="time-info"> - <div class="time-info-item"> - <i class="el-icon-date" style="color: #FFBF00;font-size: 20px;"></i> - <div class="text-white text-[16px] ml-[4px]">工程倒计时/天</div> - <div class="text-[#FFBF00] text-[24px] font-bold ml-[28px]">{{endDate}}</div> - </div> - <div class="time-line"> - </div> - </div> - </div> - <div class="bottom-nav"> - <div class="content-wrapper mb-[20px]"> - <!-- 720菜单 --> - <div class="side-menu z-[100]" v-if="currentView === '720'"> - <el-cascader popper-class="custom-dropdown" v-model="panoValue" :options="panoList" @change="handlePanoChange" :props="{ value: 'id',label: 'label',children: 'Children'}"/> - </div> - <!-- 项目实况--> - <div class="side-menu z-[100]" v-if="currentView === 'project'"> - <el-cascader popper-class="custom-dropdown" v-model="liveValue" :options="liveList" @change="handleLiveChange" :props="{ value: 'id',label: 'label',children: 'JSCProjectMonitorList'}"/> + <div class="home-container"> + <!-- 顶部导航 --> + <div class="top-nav"> + <div class="nav-items"> + <div class="nav-item mr-[8px]" :class="{ active: currentView === 'model' }" @click="handleChange('model')">模型</div> + <div class="nav-item mr-[8px]" :class="{ active: currentView === '720' }" @click="handleChange('720')">720全景</div> + <div class="nav-item" :class="{ active: currentView === 'project' }" @click="handleChange('project')">项目实况</div> + </div> + <div class="time-info"> + <div class="time-info-item"> + <i class="el-icon-date" style="color: #ffbf00; font-size: 20px"></i> + <div class="text-white text-[16px] ml-[4px]">工程倒计时/天</div> + <div class="text-[#FFBF00] text-[24px] font-bold ml-[28px]">{{ endDate }}</div> + </div> + <div class="time-line"> </div> + </div> </div> + <div class="bottom-nav"> + <div class="content-wrapper mb-[20px]"> + <!-- 720菜单 --> + <div class="side-menu z-[100]" v-if="currentView === '720'"> + <el-cascader + popper-class="custom-dropdown" + v-model="panoValue" + :options="panoList" + @change="handlePanoChange" + :props="{ value: 'id', label: 'label', children: 'Children' }" + /> + </div> + <!-- 项目实况--> + <div class="side-menu z-[100]" v-if="currentView === 'project'"> + <el-cascader + popper-class="custom-dropdown" + v-model="liveValue" + :options="liveList" + @change="handleLiveChange" + :props="{ value: 'id', label: 'label', children: 'JSCProjectMonitorList' }" + /> + </div> - - <!-- 主要内容区域 --> - <div class="main-content" :style="isFullScreen ? fullScreenStyle : ''" v-loading="isLoading"> - <!-- 模型 / 720 --> - <div class="w-full h-full" :style="isFullScreen ? fullScreenStyle : 'width: 100%; height: 100%;'" v-if="currentView === '720' || currentView === 'model'"> - <iframe id="model-iframe" class="content-frame w-full h-full" src="" frameborder="0" :style="isFullScreen ? fullScreenModelStyle : ''" v-if="currentView === 'model'"></iframe> - <iframe ref="panoiframe" id="panoviewpreview" class="pano-frame w-full h-full" :style="isFullScreen ? fullScreenPanoStyle : ''" v-if="currentView === '720'"></iframe> - </div> - <!-- 720全景图版本选择 --> - <!--<div class="w-full h-[120px] bg-[rgba(19,40,64,0.8)] absolute bottom-0 left-0 right-0 flex items-center py-[10px]" :style="isFullScreen ? 'z-index: 100; height:' : ''" v-if="currentView === '720'"> + <!-- 主要内容区域 --> + <div class="main-content" :style="isFullScreen ? fullScreenStyle : ''" v-loading="isLoading"> + <!-- 模型 / 720 --> + <div + class="w-full h-full" + :style="isFullScreen ? fullScreenStyle : 'width: 100%; height: 100%;'" + v-if="currentView === '720' || currentView === 'model'" + > + <iframe + id="model-iframe" + class="content-frame w-full h-full" + src="" + frameborder="0" + :style="isFullScreen ? fullScreenModelStyle : ''" + v-if="currentView === 'model'" + ></iframe> + <iframe + ref="panoiframe" + id="panoviewpreview" + class="pano-frame w-full h-full" + :style="isFullScreen ? fullScreenPanoStyle : ''" + v-if="currentView === '720'" + ></iframe> + </div> + <!-- 720全景图版本选择 --> + <!--<div class="w-full h-[120px] bg-[rgba(19,40,64,0.8)] absolute bottom-0 left-0 right-0 flex items-center py-[10px]" :style="isFullScreen ? 'z-index: 100; height:' : ''" v-if="currentView === '720'"> <div v-for="item in panoVersionList" :key="item.value" :style="isFullScreen ? 'z-index: 100;' : ''"> <div class="w-[160px] h-[100px] mr-[10px] ml-[10px] cursor-pointer border-[1px] border-[#3068A5] rounded-[2px]" @click="panoPreview(panoObj, item)"> <img :src="getItemImg(item)" alt="全景图" class="w-full h-full" /> @@ -45,706 +72,723 @@ </div> </div>--> - <!-- 放大镜 --> - <div class="w-[80px] h-[80px] bg-[#008C99] absolute top-[-40px] right-[-40px] rounded-[50%] z-[100]" v-if="currentView === 'model' || currentView === '720'" @click="handleZoomOut"> - <img src="../assets/images/backgrounds/zoomin.png" alt="放大" class="w-[16px] h-[16px] mt-[50px] ml-[15px] cursor-pointer" v-if="isFullScreen" /> - <img src="../assets/images/backgrounds/zoomout.png" alt="缩小" class="w-[16px] h-[16px] mt-[50px] ml-[15px] cursor-pointer" v-else /> - </div> + <!-- 放大镜 --> + <div + class="w-[80px] h-[80px] bg-[#008C99] absolute top-[-40px] right-[-40px] rounded-[50%] z-[100]" + v-if="currentView === 'model' || currentView === '720'" + @click="handleZoomOut" + > + <img src="../assets/images/backgrounds/zoomin.png" alt="放大" class="w-[16px] h-[16px] mt-[50px] ml-[15px] cursor-pointer" v-if="isFullScreen" /> + <img src="../assets/images/backgrounds/zoomout.png" alt="缩小" class="w-[16px] h-[16px] mt-[50px] ml-[15px] cursor-pointer" v-else /> + </div> + </div> + <!-- 项目 --> + <div class="w-full h-full" v-if="currentView === 'project'"> + <video + controls + muted + controlslist="nodownload" + autoplay + class="video-box" + ref="videoElement" + style="width: 100%; height: 100%; background-color: #000" + ></video> + </div> + </div> </div> - <!-- 项目 --> - <div class="w-full h-full" v-if="currentView === 'project'"> - <video controls muted controlslist="nodownload" autoplay class="video-box" ref="videoElement" style="width: 100%; height: 100%; background-color: #000;"></video> - </div> - </div> - - </div> - <div class="chart-content"> - <div -class="w-full h-[4vh] mb-[1vh]" :style="{ + <div class="chart-content"> + <div + class="w-full h-[4vh] mb-[1vh]" + :style="{ backgroundImage: `url(${bottomImage})`, backgroundRepeat: 'no-repeat', backgroundPosition: 'center center', - backgroundSize: '100% 100%' - }"> - </div> - <div class="chart-container" :style="sectionStyle"> - <img src="../assets/images/backgrounds/shigongjinduchart.png" alt="施工进度" class="w-full h-full" /> - </div> + backgroundSize: '100% 100%', + }" + > + </div> + <div class="chart-container" :style="sectionStyle"> + <Gantt /> + </div> + </div> </div> - </div> - -</div> </template> <script> - import flvjs from 'flv.js' -import { - nextTick -} from 'vue' -import { - HomeAPI -} from "../api/home"; -import { - getProjectId, - getToken, - getProjectEndDate -} from "../utils/getToken.js"; -export default { - name: 'Home', - components: {}, - props: { - selectedId: { - type: String, - default: "", + import flvjs from "flv.js"; + import { nextTick } from "vue"; + import { HomeAPI } from "../api/home"; + import { getProjectId, getToken, getProjectEndDate } from "../utils/getToken.js"; + import Gantt from "@/components/Gantt.vue"; + export default { + name: "Home", + components: { Gantt }, + props: { + selectedId: { + type: String, + default: "", + }, }, - }, - data() { - return { - liveValue:[], - playerStatus: '未就绪', - errorMessage:'', - flvPlayer:null, - videoSrc: "", - currentView: 'model', - chartOptions: { - id: 'shigongjinduChart', - options: { - title: { - text: '施工进度' - } - } - }, - bottomImage: new URL('@/assets/images/titles/shigong.png', - import.meta.url).href, - isLoading: false, - panoVersionList: [], - isFullScreen: false, - fullScreenStyle: '', - fullScreenPanoStyle: '', - fullScreenModelStyle: '', - panoList: [{ - value: 'zhinan', - label: '指南', - children: [{ - value: 'shejiyuanze', - label: '设计原则' - }, { - value: 'daohang', - label: '导航' - }] - }, { - value: 'zujian', - label: '组件', - children: [{ - value: 'basic', - label: 'Basic', - }, { - value: 'form', - label: 'Form', - }, { - value: 'data', - label: 'Data', - }, { - value: 'notice', - label: 'Notice', - }, { - value: 'navigation', - label: 'Navigation', - }, { - value: 'others', - label: 'Others', - }], - }], - panoValue: [], - liveList: [], - backgroundImage: new URL('@/assets/images/backgrounds/cover_bg.png', - import.meta.url).href, - endDate: 0, - } - }, - computed: { - sectionStyle() { - return { - backgroundImage: `url(${this.backgroundImage})`, - backgroundRepeat: 'no-repeat', - backgroundPosition: 'center center', - backgroundSize: '100% 100%' - } - }, - projectId() { - return getProjectId() - }, - projectEndDate() { - return getProjectEndDate() - } - }, - watch: { - projectEndDate:{ - deep: true, - handler (newVal) { - console.log('newVal', newVal) - const date = new Date(newVal); - // 获取当前日期作为Date对象 - const now = new Date(); - // 计算两个日期的差异(毫秒) - const diffTime = Math.abs(now - date); - // 将毫秒转换为天数 - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - this.endDate = diffDays - } - }, - selectedId: { - handler(newVal) { - console.log("实况界面接收到的数据:", newVal); - // 处理数据变化的逻辑 - this.getProjectLive(newVal); - }, - deep: true, // 深度监听对象内部变化 - immediate: true, // 立即执行一次 - }, - }, - mounted() { - setTimeout(() => { - console.log('projectEndDate', getProjectEndDate()) - this.modelShow() - }, 1000) - }, - updated() { - if (this.$refs.panoiframe) { - // IE - if (this.$refs.panoiframe.attachEvent) { - this.$refs.panoiframe.attachEvent('onload', () => { - console.log('loading') - // 加载成功 - this.isLoading = false; - }); - } else { - this.$refs.panoiframe.onload = () => { - console.log('loading2') - // 加载成功 - this.isLoading = false; + data() { + return { + liveValue: [], + playerStatus: "未就绪", + errorMessage: "", + flvPlayer: null, + videoSrc: "", + currentView: "model", + chartOptions: { + id: "shigongjinduChart", + options: { + title: { + text: "施工进度", + }, + }, + }, + bottomImage: new URL("@/assets/images/titles/shigong.png", import.meta.url).href, + isLoading: false, + panoVersionList: [], + isFullScreen: false, + fullScreenStyle: "", + fullScreenPanoStyle: "", + fullScreenModelStyle: "", + panoList: [ + { + value: "zhinan", + label: "指南", + children: [ + { + value: "shejiyuanze", + label: "设计原则", + }, + { + value: "daohang", + label: "导航", + }, + ], + }, + { + value: "zujian", + label: "组件", + children: [ + { + value: "basic", + label: "Basic", + }, + { + value: "form", + label: "Form", + }, + { + value: "data", + label: "Data", + }, + { + value: "notice", + label: "Notice", + }, + { + value: "navigation", + label: "Navigation", + }, + { + value: "others", + label: "Others", + }, + ], + }, + ], + panoValue: [], + liveList: [], + backgroundImage: new URL("@/assets/images/backgrounds/cover_bg.png", import.meta.url).href, + endDate: 0, }; - } - } -}, - - methods: { - getItemImg (item) { - return `${window.ProjectConfig.panoBaseUrl}/Panorama${this.panoObj.PbUrl}/vtour/panos/${item.PsScenename}.tiles/thumb.jpg` - }, - handleZoomOut() { - console.log('放大') - this.isFullScreen = !this.isFullScreen - if (this.isFullScreen) { - const clientX = document.documentElement.clientWidth || document.body.clientWidth - const clientY = document.documentElement.clientHeight || document.body.clientHeight - this.fullScreenStyle = `width: ${clientX - 40}px; height: ${clientY - 120}px; margin:20px; position: fixed; top: 80px; left: 0;z-index: 100;background-color: #fff;overflow: hidden;` - if (this.currentView === '720') { - this.fullScreenPanoStyle = `width: 100%; height: 100%; z-index: 100; ` - } else if(this.currentView === 'model'){ - this.fullScreenModelStyle = `width: ${clientX - 40}px; height: ${clientY - 120}px; margin:20px; position: fixed; top: 80px; left: 0;z-index: 100;background-color: #fff;overflow: hidden;` + }, + computed: { + sectionStyle() { + return { + backgroundImage: `url(${this.backgroundImage})`, + backgroundRepeat: "no-repeat", + backgroundPosition: "center center", + backgroundSize: "100% 100%", + }; + }, + projectId() { + return getProjectId(); + }, + projectEndDate() { + return getProjectEndDate(); + }, + }, + watch: { + projectEndDate: { + deep: true, + handler(newVal) { + console.log("newVal", newVal); + const date = new Date(newVal); + // 获取当前日期作为Date对象 + const now = new Date(); + // 计算两个日期的差异(毫秒) + const diffTime = Math.abs(now - date); + // 将毫秒转换为天数 + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + this.endDate = diffDays; + }, + }, + selectedId: { + handler(newVal) { + console.log("实况界面接收到的数据:", newVal); + // 处理数据变化的逻辑 + this.getProjectLive(newVal); + }, + deep: true, // 深度监听对象内部变化 + immediate: true, // 立即执行一次 + }, + }, + mounted() { + setTimeout(() => { + console.log("projectEndDate", getProjectEndDate()); + this.modelShow(); + }, 1000); + }, + updated() { + if (this.$refs.panoiframe) { + // IE + if (this.$refs.panoiframe.attachEvent) { + this.$refs.panoiframe.attachEvent("onload", () => { + console.log("loading"); + // 加载成功 + this.isLoading = false; + }); + } else { + this.$refs.panoiframe.onload = () => { + console.log("loading2"); + // 加载成功 + this.isLoading = false; + }; + } } - } - }, - handleChange(view) { - this.isLoading = true - this.currentView = view - if (view === 'model') { - nextTick(() => { - this.modelShow() - }) - } else if (view === '720') { - nextTick(() => { - this.panoShow() - }) - } else if (view === 'project') { - nextTick(() => { - this.getProjectLive(this.selectedId) - }) - } - }, - handlePanoChange(value) { - console.log('value', value) - this.isLoading = true - this.panoList.forEach(item => { - if (item.LabelId === value[0]) { - item.Children.forEach(item2 => { - if (item2.PbGuid === value[1]) { - this.panoObj = item2 - console.log('panoObj', this.panoObj) - this.getPanoPhase(item2) - } - }) - } - }) - }, - modelShow() { - const iframe = document.getElementById('model-iframe') - const token = getToken() - const projectId = this.projectId - let ifrSrc = '' - console.log('模型中查看') - // ifrSrc = `${window.ProjectConfig.modelUrl}?token=${token}&projectId=${projectId}&isPreview=1&edit=false` - ifrSrc = window.ProjectConfig.modelUrl - iframe.src = ifrSrc - console.log('加载iframe地址', ifrSrc) - this.isLoading = false - }, - panoShow() { - const data = { - projectId: this.projectId - } - HomeAPI.GetProjectPanoramaList(data).then(res => { - console.log('全景图', res) - if (res.Ret === 1) { - const result = res.Data - if (result.length > 0) { - result.forEach(element => { - element.id = element.LabelId - element.label = element.LabelName - element.Children.forEach(item => { - item.id = item.PbGuid - item.label = item.PbName - }); - }); - this.panoList = result - this.panoValue = [result[0].id, result[0].Children[0].id] - this.panoObj = result[0].Children[0] - this.getPanoPhase() - } - } - }) + }, - }, - getPanoPhase() { - const data = { - pbguid: this.panoObj.PbGuid - } - HomeAPI.GetScenesByPbGuid(data).then(res => { - console.log('全景图阶段', res) - if (res.Ret === 1) { - this.panoVersionList = res.Data - this.panoPreview(this.panoObj, this.panoVersionList[0]) - } - }) - }, - panoPreview(item, scene) { - // /LinkShare/PanoShare/:PanoId/:organizeId/:StartScene - // PanoId:全景图的id,organizeId:项目id,StartScene:初始场景id - const panoFrame = document.getElementById('panoviewpreview') - nextTick(() => { - if(panoFrame.src){ - panoFrame.removeAttribute("src"); //先移除上一次的src地址 - } - setTimeout(()=>{ - let ifrSrc = '' - ifrSrc = `${window.ProjectConfig.panoUrl}/#/LinkShare/PanoShare/${scene.PbGuid}/${this.projectId}/${scene.PsScenename}` - panoFrame.setAttribute("src",ifrSrc); - }, 300) - - }) - // removepano('krpanoSWFObject') - // const basepath = `${window.ProjectConfig.panoUrl}/Panorama${item.PbUrl}/vtour/` - // const xmlurl = `${basepath}tour.xml?r=${(Math.random() * 100000 + 1)}` - // let setting = {} - // if (scene) { - // const scenename = 'scene_' + scene.PsScenename - // setting = { - // startscene: scenename - // } - // } - // this.$nextTick(() => { - // embedpano({ - // xml: xmlurl, - // target: 'panoviewpreview', - // basepath, - // vars: setting, - // html5: 'auto', - // passQueryParameters: true, - // }) - // }) - }, - sceneShow() { - const iframe = document.getElementById('scene-iframe') - let ifrSrc = '' - ifrSrc = window.ProjectConfig.sceneUrl - iframe.src = ifrSrc - this.isLoading = false - }, - mounted() { - this.isLoading = true - this.modelShow() - }, - /** - * 获取项目实况数据 - * @param {string} bindId - 标段ID - */ - getProjectLive(bindId) { - const params = { - bindId, - }; - HomeAPI.GetJSCProjectMonitor(params).then(res => { - if (res.Ret === 1) { - const result = res.Data - if (result.length > 0) { - console.log('实况数据', result); - result.forEach(element => { - element.id = element.BindId - element.label = element.BindName - element.JSCProjectMonitorList.forEach(item => { - item.id = item.MonitorUrl - item.label = item.MonitorName - }); - }); - } - this.isLoading = false - this.liveList = result - this.liveValue = [result[0].id, result[0].JSCProjectMonitorList[0].id] - this.videoSrc = this.liveList[0].JSCProjectMonitorList[0].MonitorUrl - this.loadVideo(); - } - }) - }, - /** - * 处理实况的实时变化 - * @param value - */ - handleLiveChange(value) { - console.log('实况变化', value); - this.videoSrc = value[1] - this.loadVideo(); - }, - // 检查FLV支持 - checkFlvSupport() { - if (!flvjs.isSupported()) { - this.errorMessage = '当前浏览器不支持FLV播放' - this.playerStatus = '不支持' - return false - } - this.playerStatus = '已就绪' - return true - }, - /** - * 销毁播放器实例 - */ - destroyPlayer() { - if (this.flvPlayer) { - try { - this.flvPlayer.pause() - this.flvPlayer.unload() - this.flvPlayer.detachMediaElement() - this.flvPlayer.destroy() - this.flvPlayer = null - } catch (error) { - console.error('销毁播放器时出错:', error) - } - } - this.isReady = false - }, - /** - * 加载视频 - */ - loadVideo() { - if (!this.videoSrc.trim()) { - this.errorMessage = '请播放有效的视频URL' - return - } + methods: { + getItemImg(item) { + return `${window.ProjectConfig.panoBaseUrl}/Panorama${this.panoObj.PbUrl}/vtour/panos/${item.PsScenename}.tiles/thumb.jpg`; + }, + handleZoomOut() { + console.log("放大"); + this.isFullScreen = !this.isFullScreen; + if (this.isFullScreen) { + const clientX = document.documentElement.clientWidth || document.body.clientWidth; + const clientY = document.documentElement.clientHeight || document.body.clientHeight; + this.fullScreenStyle = `width: ${clientX - 40}px; height: ${ + clientY - 120 + }px; margin:20px; position: fixed; top: 80px; left: 0;z-index: 100;background-color: #fff;overflow: hidden;`; + if (this.currentView === "720") { + this.fullScreenPanoStyle = `width: 100%; height: 100%; z-index: 100; `; + } else if (this.currentView === "model") { + this.fullScreenModelStyle = `width: ${clientX - 40}px; height: ${ + clientY - 120 + }px; margin:20px; position: fixed; top: 80px; left: 0;z-index: 100;background-color: #fff;overflow: hidden;`; + } + } + }, + handleChange(view) { + this.isLoading = true; + this.currentView = view; + if (view === "model") { + nextTick(() => { + this.modelShow(); + }); + } else if (view === "720") { + nextTick(() => { + this.panoShow(); + }); + } else if (view === "project") { + nextTick(() => { + this.getProjectLive(this.selectedId); + }); + } + }, + handlePanoChange(value) { + console.log("value", value); + this.isLoading = true; + this.panoList.forEach((item) => { + if (item.LabelId === value[0]) { + item.Children.forEach((item2) => { + if (item2.PbGuid === value[1]) { + this.panoObj = item2; + console.log("panoObj", this.panoObj); + this.getPanoPhase(item2); + } + }); + } + }); + }, + modelShow() { + const iframe = document.getElementById("model-iframe"); + const token = getToken(); + const projectId = this.projectId; + let ifrSrc = ""; + console.log("模型中查看"); + // ifrSrc = `${window.ProjectConfig.modelUrl}?token=${token}&projectId=${projectId}&isPreview=1&edit=false` + ifrSrc = window.ProjectConfig.modelUrl; + iframe.src = ifrSrc; + console.log("加载iframe地址", ifrSrc); + this.isLoading = false; + }, + panoShow() { + const data = { + projectId: this.projectId, + }; + HomeAPI.GetProjectPanoramaList(data).then((res) => { + console.log("全景图", res); + if (res.Ret === 1) { + const result = res.Data; + if (result.length > 0) { + result.forEach((element) => { + element.id = element.LabelId; + element.label = element.LabelName; + element.Children.forEach((item) => { + item.id = item.PbGuid; + item.label = item.PbName; + }); + }); + this.panoList = result; + this.panoValue = [result[0].id, result[0].Children[0].id]; + this.panoObj = result[0].Children[0]; + this.getPanoPhase(); + } + } + }); + }, + getPanoPhase() { + const data = { + pbguid: this.panoObj.PbGuid, + }; + HomeAPI.GetScenesByPbGuid(data).then((res) => { + console.log("全景图阶段", res); + if (res.Ret === 1) { + this.panoVersionList = res.Data; + this.panoPreview(this.panoObj, this.panoVersionList[0]); + } + }); + }, + panoPreview(item, scene) { + // /LinkShare/PanoShare/:PanoId/:organizeId/:StartScene + // PanoId:全景图的id,organizeId:项目id,StartScene:初始场景id + const panoFrame = document.getElementById("panoviewpreview"); + nextTick(() => { + if (panoFrame.src) { + panoFrame.removeAttribute("src"); //先移除上一次的src地址 + } + setTimeout(() => { + let ifrSrc = ""; + ifrSrc = `${window.ProjectConfig.panoUrl}/#/LinkShare/PanoShare/${scene.PbGuid}/${this.projectId}/${scene.PsScenename}`; + panoFrame.setAttribute("src", ifrSrc); + }, 300); + }); + // removepano('krpanoSWFObject') + // const basepath = `${window.ProjectConfig.panoUrl}/Panorama${item.PbUrl}/vtour/` + // const xmlurl = `${basepath}tour.xml?r=${(Math.random() * 100000 + 1)}` + // let setting = {} + // if (scene) { + // const scenename = 'scene_' + scene.PsScenename + // setting = { + // startscene: scenename + // } + // } + // this.$nextTick(() => { + // embedpano({ + // xml: xmlurl, + // target: 'panoviewpreview', + // basepath, + // vars: setting, + // html5: 'auto', + // passQueryParameters: true, + // }) + // }) + }, + sceneShow() { + const iframe = document.getElementById("scene-iframe"); + let ifrSrc = ""; + ifrSrc = window.ProjectConfig.sceneUrl; + iframe.src = ifrSrc; + this.isLoading = false; + }, + mounted() { + this.isLoading = true; + this.modelShow(); + }, + /** + * 获取项目实况数据 + * @param {string} bindId - 标段ID + */ + getProjectLive(bindId) { + const params = { + bindId, + }; + HomeAPI.GetJSCProjectMonitor(params).then((res) => { + if (res.Ret === 1) { + const result = res.Data; + if (result.length > 0) { + console.log("实况数据", result); + result.forEach((element) => { + element.id = element.BindId; + element.label = element.BindName; + element.JSCProjectMonitorList.forEach((item) => { + item.id = item.MonitorUrl; + item.label = item.MonitorName; + }); + }); + } + this.isLoading = false; + this.liveList = result; + this.liveValue = [result[0].id, result[0].JSCProjectMonitorList[0].id]; + this.videoSrc = this.liveList[0].JSCProjectMonitorList[0].MonitorUrl; + this.loadVideo(); + } + }); + }, + /** + * 处理实况的实时变化 + * @param value + */ + handleLiveChange(value) { + console.log("实况变化", value); + this.videoSrc = value[1]; + this.loadVideo(); + }, + // 检查FLV支持 + checkFlvSupport() { + if (!flvjs.isSupported()) { + this.errorMessage = "当前浏览器不支持FLV播放"; + this.playerStatus = "不支持"; + return false; + } + this.playerStatus = "已就绪"; + return true; + }, + /** + * 销毁播放器实例 + */ + destroyPlayer() { + if (this.flvPlayer) { + try { + this.flvPlayer.pause(); + this.flvPlayer.unload(); + this.flvPlayer.detachMediaElement(); + this.flvPlayer.destroy(); + this.flvPlayer = null; + } catch (error) { + console.error("销毁播放器时出错:", error); + } + } + this.isReady = false; + }, + /** + * 加载视频 + */ + loadVideo() { + if (!this.videoSrc.trim()) { + this.errorMessage = "请播放有效的视频URL"; + return; + } - if (!this.checkFlvSupport()) { - return - } - this.errorMessage = '' - this.playerStatus = '加载中...' + if (!this.checkFlvSupport()) { + return; + } + this.errorMessage = ""; + this.playerStatus = "加载中..."; - try { - // 销毁旧的播放器实例 - this.destroyPlayer() + try { + // 销毁旧的播放器实例 + this.destroyPlayer(); - // 创建FLV播放器 - this.flvPlayer = flvjs.createPlayer({ - type: 'flv', - url: this.videoSrc, - isLive: false, // 如果是直播流,设置为true - cors: true, - withCredentials: false - }, { - enableWorker: false, - enableStashBuffer: true, - stashInitialSize: 128, - autoCleanupSourceBuffer: true - }) + // 创建FLV播放器 + this.flvPlayer = flvjs.createPlayer( + { + type: "flv", + url: this.videoSrc, + isLive: false, // 如果是直播流,设置为true + cors: true, + withCredentials: false, + }, + { + enableWorker: false, + enableStashBuffer: true, + stashInitialSize: 128, + autoCleanupSourceBuffer: true, + }, + ); - // 绑定事件监听器 - // this.bindPlayerEvents() + // 绑定事件监听器 + // this.bindPlayerEvents() - // 绑定到video元素 - this.flvPlayer.attachMediaElement(this.$refs.videoElement) + // 绑定到video元素 + this.flvPlayer.attachMediaElement(this.$refs.videoElement); - // 开始加载 - this.flvPlayer.load() - - } catch (error) { - console.error('加载视频失败:', error) - this.errorMessage = '视频加载失败: ' + error.message - this.playerStatus = '加载失败' - } - }, - } - -} + // 开始加载 + this.flvPlayer.load(); + } catch (error) { + console.error("加载视频失败:", error); + this.errorMessage = "视频加载失败: " + error.message; + this.playerStatus = "加载失败"; + } + }, + }, + }; </script> <style lang="scss" scoped> -.home-container { - display: flex; - flex-direction: column; - height: calc(100vh - 100px); - overflow: hidden; -} - -.top-nav { - height: 5vh; - display: flex; - justify-content: space-between; - align-items: center; - color: white; -} - -.bottom-nav { - flex: 1; -} - -.nav-items { - display: flex; - gap: 20px; -} - -.nav-item { - background: rgba(112, 119, 140, 0.3); - border-radius: 2px; - border: 1px solid rgba(112, 124, 140, 0.6); - padding: 5px 16px; - cursor: pointer; -} - -.nav-item.active { - background: rgba(255, 191, 0, 0.2); - border-radius: 2px; - border: 1px solid #FFBF00; -} - -.time-info { - display: flex; - flex-direction: column; - align-items: center; - width: 200px; -} - -.time-info-item { - display: flex; - align-items: center; - -} - -.time-line { - width: 200px; - height: 1px; - background: linear-gradient(270deg, rgba(255, 191, 0, 0) 0%, #FFBF00 100%); -} - -.content-wrapper { - display: flex; - overflow: hidden; - position: relative; - height: 98%; -} - -.chart-content { - width: 100%; - height: calc(100% - 52% - 20px); -} - -.side-menu { - width: 160px; - color: white; - position: absolute; - top: 10px; - left: 10px; - right: auto; - bottom: auto; - - ::v-deep .el-cascader { - width: 100%; - height: 100%; - - .el-cascader-node__label { - color: #fff; + .home-container { + display: flex; + flex-direction: column; + height: calc(100vh - 100px); + overflow: hidden; } - .el-input .el-input__inner { - background: rgba(33, 72, 115, 0.9); + .top-nav { + height: 5vh; + display: flex; + justify-content: space-between; + align-items: center; + color: white; } - } -} -.menu-dropdown { - padding: 15px; -} - -.menu-select { - width: 100%; - padding: 8px; - background-color: #0a1931; - color: white; - border: 1px solid #2a4d7d; -} - -.menu-list { - border-top: 1px solid #2a4d7d; -} - -.menu-item { - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; -} - -.menu-item:hover { - background-color: rgba(255, 255, 255, 0.1); -} - -.arrow-icon { - border: solid white; - border-width: 0 2px 2px 0; - display: inline-block; - padding: 3px; - transform: rotate(-45deg); -} - -.submenu { - background-color: #152b4a; -} - -.submenu-item { - padding: 10px 30px; - font-size: 14px; - cursor: pointer; -} - -.submenu-item:hover { - background-color: rgba(255, 255, 255, 0.1); -} - -.main-content { - flex: 1; - background-color: #fff; - width: 100%; - height: 100%; -} - -.content-header { - color: white; - margin-bottom: 20px; -} - -.chart-container { - padding: 20px; - width: 100%; - height: calc(100% - 5vh); -} - -.chart-legend { - display: flex; - gap: 20px; - margin-bottom: 20px; - color: white; -} - -.legend-item { - display: flex; - align-items: center; - gap: 8px; -} - -.dot { - width: 10px; - height: 10px; - border-radius: 50%; -} - -.dot.blue { - background-color: #00ffff; -} - -.dot.yellow { - background-color: #ffd700; -} - -.chart-area { - height: 400px; - margin-bottom: 20px; -} - -.chart-tabs { - display: flex; - gap: 10px; -} - -.tab { - padding: 8px 16px; - color: white; - cursor: pointer; - border-radius: 4px; -} - -.tab.active { - background-color: #2a4d7d; -} -</style><style lang="scss"> -.custom-dropdown { - background-color: rgba(33, 72, 115, 0.9); - border-radius: 2px; - border: 1px solid #3068A5; - - .el-cascader-menu { - color: #fff; - - .el-cascader-node__label { - color: #fff; + .bottom-nav { + flex: 1; } - } - .el-cascader .el-input .el-input__inner:focus, - .el-cascader .el-input.is-focus .el-input__inner { - height: 28px; - line-height: 28px; - } + .nav-items { + display: flex; + gap: 20px; + } - .el-cascader .el-input .el-input__inner { - height: 28px; - line-height: 28px; - background: rgba(33, 72, 115, 0.9); - } + .nav-item { + background: rgba(112, 119, 140, 0.3); + border-radius: 2px; + border: 1px solid rgba(112, 124, 140, 0.6); + padding: 5px 16px; + cursor: pointer; + } - .el-input__icon { - line-height: 38px; - } + .nav-item.active { + background: rgba(255, 191, 0, 0.2); + border-radius: 2px; + border: 1px solid #ffbf00; + } - .el-cascader-node:not(.is-disabled):focus, - .el-cascader-node:not(.is-disabled):hover { - background: #3068A5; - } + .time-info { + display: flex; + flex-direction: column; + align-items: center; + width: 200px; + } - .popper__arrow { - display: none; - } + .time-info-item { + display: flex; + align-items: center; + } - .el-scrollbar__wrap { - overflow: hidden; - margin-bottom: 0px !important; - margin-right: 0px !important; - } -} + .time-line { + width: 200px; + height: 1px; + background: linear-gradient(270deg, rgba(255, 191, 0, 0) 0%, #ffbf00 100%); + } + + .content-wrapper { + display: flex; + overflow: hidden; + position: relative; + height: 98%; + } + + .chart-content { + width: 100%; + height: calc(100% - 52% - 20px); + } + + .side-menu { + width: 160px; + color: white; + position: absolute; + top: 10px; + left: 10px; + right: auto; + bottom: auto; + + ::v-deep .el-cascader { + width: 100%; + height: 100%; + + .el-cascader-node__label { + color: #fff; + } + + .el-input .el-input__inner { + background: rgba(33, 72, 115, 0.9); + } + } + } + + .menu-dropdown { + padding: 15px; + } + + .menu-select { + width: 100%; + padding: 8px; + background-color: #0a1931; + color: white; + border: 1px solid #2a4d7d; + } + + .menu-list { + border-top: 1px solid #2a4d7d; + } + + .menu-item { + padding: 12px 20px; + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + } + + .menu-item:hover { + background-color: rgba(255, 255, 255, 0.1); + } + + .arrow-icon { + border: solid white; + border-width: 0 2px 2px 0; + display: inline-block; + padding: 3px; + transform: rotate(-45deg); + } + + .submenu { + background-color: #152b4a; + } + + .submenu-item { + padding: 10px 30px; + font-size: 14px; + cursor: pointer; + } + + .submenu-item:hover { + background-color: rgba(255, 255, 255, 0.1); + } + + .main-content { + flex: 1; + background-color: #fff; + width: 100%; + height: 100%; + } + + .content-header { + color: white; + margin-bottom: 20px; + } + + .chart-container { + padding: 20px; + width: 100%; + height: calc(100% - 5vh); + } + + .chart-legend { + display: flex; + gap: 20px; + margin-bottom: 20px; + color: white; + } + + .legend-item { + display: flex; + align-items: center; + gap: 8px; + } + + .dot { + width: 10px; + height: 10px; + border-radius: 50%; + } + + .dot.blue { + background-color: #00ffff; + } + + .dot.yellow { + background-color: #ffd700; + } + + .chart-area { + height: 400px; + margin-bottom: 20px; + } + + .chart-tabs { + display: flex; + gap: 10px; + } + + .tab { + padding: 8px 16px; + color: white; + cursor: pointer; + border-radius: 4px; + } + + .tab.active { + background-color: #2a4d7d; + }</style +><style lang="scss"> + .custom-dropdown { + background-color: rgba(33, 72, 115, 0.9); + border-radius: 2px; + border: 1px solid #3068a5; + + .el-cascader-menu { + color: #fff; + + .el-cascader-node__label { + color: #fff; + } + } + + .el-cascader .el-input .el-input__inner:focus, + .el-cascader .el-input.is-focus .el-input__inner { + height: 28px; + line-height: 28px; + } + + .el-cascader .el-input .el-input__inner { + height: 28px; + line-height: 28px; + background: rgba(33, 72, 115, 0.9); + } + + .el-input__icon { + line-height: 38px; + } + + .el-cascader-node:not(.is-disabled):focus, + .el-cascader-node:not(.is-disabled):hover { + background: #3068a5; + } + + .popper__arrow { + display: none; + } + + .el-scrollbar__wrap { + overflow: hidden; + margin-bottom: 0px !important; + margin-right: 0px !important; + } + } </style> diff --git a/yarn.lock b/yarn.lock index fe7d6cd..9a6edfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -619,6 +619,11 @@ defined "^1.0.0" minimist "^1.2.6" +dhtmlx-gantt@^9.0.11: + version "9.0.11" + resolved "https://registry.yarnpkg.com/dhtmlx-gantt/-/dhtmlx-gantt-9.0.11.tgz#4492c3d42b65b4d98c005f8bdfbb0ad9b2d87290" + integrity sha512-tbJ4RYud/uSw4MpdBoTxolW7DzCtOnLNzxpFptP0Zqw1yaaNhU7s4Jfq3RnnkmD5WEdFx9bJ+6w6LovegFDHJw== + didyoumean@^1.2.2: version "1.2.2" resolved "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" -- Gitblit v1.9.3