GanttChart.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. <!-- 事项甘特图 -->
  2. <template>
  3. <div class="gantt-chart">
  4. <div class="condition-legend-bar">
  5. <Select
  6. class="system-select"
  7. width="217"
  8. :isReadOnly="true"
  9. tipPlace="top"
  10. caption="系统名称:"
  11. v-model="systemId"
  12. :selectdata="systemList"
  13. :placeholder="'请选择'"
  14. @change="changeSystem"
  15. />
  16. <div class="legend-container">
  17. <div v-for="(item) in legends" :key="'key_' + item.id" class="item-legend">
  18. <div :style="{'background': item.backgroundColor, 'border-color': item.borderColor}"></div>
  19. <div>{{item.text}}</div>
  20. </div>
  21. <div>
  22. <el-radio-group v-model="timeType" @change="changeView">
  23. <el-radio style="line-height: 32px;margin-right: 16px;" label="month">月视图</el-radio>
  24. <el-radio style="line-height: 32px;" label="day">日视图</el-radio>
  25. </el-radio-group>
  26. </div>
  27. </div>
  28. </div>
  29. <div class="main-gantt-container">
  30. <div class="task-title">
  31. <div class="title">任务标题</div>
  32. <div>
  33. <Tree :data="treeData" @openNode="toggleNode" />
  34. </div>
  35. </div>
  36. <div class="chart-container" id="chartContainer" ref="chartContainer">
  37. <div id="ganttContainer"></div>
  38. </div>
  39. <el-dialog :title="detailTitle" :visible.sync="showDetail" width="840px">
  40. <div class="dialog-container">
  41. <div class="row task-status">
  42. <div>
  43. <Task class="icon" />
  44. <div class="title">任务状态</div>
  45. </div>
  46. <div>进行中</div>
  47. </div>
  48. <div class="row">
  49. <div>
  50. <PlanTime class="icon" />
  51. <div class="title">计划时间</div>
  52. </div>
  53. <div class="info">{{this.ganttDetail.planTime}}</div>
  54. </div>
  55. <div class="row">
  56. <div>
  57. <RealTime class="icon" />
  58. <div class="title">实际执行时间</div>
  59. </div>
  60. <div class="info">{{this.ganttDetail.realTime}}</div>
  61. </div>
  62. <div class="row picture">
  63. <div>
  64. <Picture class="icon" />
  65. <div class="title">照片</div>
  66. </div>
  67. <div>
  68. <div>
  69. <div class="num">12张</div>
  70. <div class="more" @click="() => showImgDetail = true">查看更多</div>
  71. </div>
  72. <div class="pictrue-container">
  73. <div v-for="(item) in pictures" :key="'id_' + item.id">
  74. <img :src="item.imgSrc" alt />
  75. <div class="info">{{item.title}}</div>
  76. </div>
  77. </div>
  78. </div>
  79. </div>
  80. <div class="row picture">
  81. <div>
  82. <Report class="icon" />
  83. <div class="title">报告</div>
  84. </div>
  85. <div>
  86. <div>
  87. <div class="num">2张</div>
  88. </div>
  89. <div class="pictrue-container">
  90. <div v-for="(item) in reports" :key="'id_' + item.id">
  91. <img :src="item.imgSrc" alt />
  92. <div class="info">{{item.title}}</div>
  93. </div>
  94. </div>
  95. </div>
  96. </div>
  97. <div class="row test-content">
  98. <div>
  99. <Menu class="icon" />
  100. <div class="title">检测内容</div>
  101. </div>
  102. <el-table :data="tableData" max-height="300">
  103. <el-table-column property="sbjc" label="设备简称"></el-table-column>
  104. <el-table-column property="sbjbm" label="设备编号"></el-table-column>
  105. <el-table-column property="cnt" label="设备数"></el-table-column>
  106. <el-table-column property="wzjc" width="300" label="空间位置"></el-table-column>
  107. </el-table>
  108. </div>
  109. </div>
  110. </el-dialog>
  111. <div class="img-detail-container">
  112. <el-dialog :title="'图片预览'" :visible.sync="showImgDetail" width="1260px">
  113. <div class="detail-container">
  114. <div class="pictures-menu">
  115. <!-- <div v-for="(item) in pictureList" :key="'id_' + item.id" class="item"> -->
  116. <div class="item">
  117. <!-- <img :src="item.url" alt=""> -->
  118. <img src="../../assets/images/login_back.png" alt />
  119. <div class="name">图层名称1.jpg</div>
  120. </div>
  121. <div class="item">
  122. <img src="../../assets/images/login_back.png" alt />
  123. <div class="name">图层名称1.jpg</div>
  124. </div>
  125. </div>
  126. <div class="cur-img-container">
  127. <img src="../../assets/images/login_back.png" alt />
  128. </div>
  129. </div>
  130. </el-dialog>
  131. </div>
  132. </div>
  133. </div>
  134. </template>
  135. <script>
  136. import { queryGanttChart, queryGanttDetail } from "../../api/ganttChart";
  137. import { querySystemList, queryDetailData } from "../../api/coreDeviceReport";
  138. import { Tree } from "meri-design";
  139. import _ from "lodash";
  140. import moment from "moment";
  141. // import '../../utils/ganttChart/g.js';
  142. import { GanttChartDay } from "../../utils/ganttChart/GanttChart_day.js";
  143. import { GanttChartMonth } from "../../utils/ganttChart/GanttChart_month.js";
  144. import { parse } from "path";
  145. import Task from "../../assets/svg/task.svg";
  146. import PlanTime from "../../assets/svg/plan_time.svg";
  147. import RealTime from "../../assets/svg/real_time.svg";
  148. import Picture from "../../assets/svg/picture.svg";
  149. import Report from "../../assets/svg/report.svg";
  150. import Menu from "../../assets/svg/menu.svg";
  151. export default {
  152. data() {
  153. return {
  154. systemList: [], // 系统列表
  155. systemId: "", // 系统名称
  156. legends: [
  157. {
  158. id: 1,
  159. text: "按时完成",
  160. backgroundColor: "#E7E9EA",
  161. borderColor: "#C3C7CB"
  162. },
  163. {
  164. id: 2,
  165. text: "未开始/进行中",
  166. backgroundColor: "rgba(91, 143, 249, 0.2)",
  167. borderColor: "#5B8FF9"
  168. },
  169. {
  170. id: 3,
  171. text: "逾期完成",
  172. backgroundColor: "#FBCE99",
  173. borderColor: "#F58300"
  174. },
  175. {
  176. id: 4,
  177. text: "逾期未完成",
  178. backgroundColor: "#FBB8B5",
  179. borderColor: "#F54E45"
  180. }
  181. ], // 图例
  182. timeType: "month", // 甘特图时间类型
  183. treeData: [], // 树结构数据
  184. startTime: null, // 甘特图开始时间
  185. endTime: null, // 甘特图结束时间
  186. // 弹窗相关
  187. showDetail: false, // 弹框显示状态
  188. detailTitle: "", // 弹窗标题
  189. pictures: [
  190. {
  191. id: 1,
  192. imgSrc: require("../../assets/imgs/analysis/picture.png"),
  193. title: "2019年5月高压用具检测报告.jpg"
  194. },
  195. {
  196. id: 2,
  197. imgSrc: require("../../assets/imgs/analysis/picture.png"),
  198. title: "2019年5月高压用具检测报告.jpg"
  199. }
  200. ], // 照片
  201. reports: [
  202. {
  203. id: 1,
  204. imgSrc: require("../../assets/imgs/analysis/report_pdf.png"),
  205. title: "2019年5月高压用具检测报告.pdf"
  206. },
  207. {
  208. id: 2,
  209. imgSrc: require("../../assets/imgs/analysis/report_word.png"),
  210. title: "2019年5月高压用具检测报告.docx"
  211. }
  212. ], // 报告
  213. ganttDetail: {}, // 甘特图明细
  214. tableData: [], // 表数据
  215. showImgDetail: false, // 查看更多图片弹框显示状态
  216. };
  217. },
  218. components: {
  219. Task,
  220. PlanTime,
  221. RealTime,
  222. Picture,
  223. Report,
  224. Menu
  225. },
  226. computed: {},
  227. mounted() {
  228. this.initChartTime();
  229. this.getSystemList();
  230. },
  231. methods: {
  232. /**
  233. * 初始化甘特图请求时间
  234. */
  235. initChartTime() {
  236. let endTime = new Date().getTime(),
  237. startTime = endTime - 1000*60*60*24*365;
  238. this.startTime = startTime;
  239. this.endTime = endTime;
  240. },
  241. /**
  242. * 获取系统列表数据
  243. */
  244. getSystemList() {
  245. querySystemList().then(res => {
  246. if (res.result === "success") {
  247. let data = res.data;
  248. let newData = [];
  249. _.forEach(data, item => {
  250. newData.push({
  251. id: item.code,
  252. name: item.name
  253. });
  254. });
  255. this.systemList = newData;
  256. const { query } = this.$route;
  257. if (!_.isEmpty(query) && query.smsxt) {
  258. this.systemId = query.smsxt;
  259. } else {
  260. this.systemId = newData[0].id;
  261. }
  262. this.getGanttChartData();
  263. }
  264. });
  265. },
  266. /**
  267. * 处理请求数据的时间
  268. */
  269. disQueryTime() {
  270. let startTime, endTime;
  271. let curTime = new Date(),
  272. curYear = curTime.getFullYear(),
  273. curMonth = curTime.getMonth() + 1;
  274. if (this.timeType === "month") {
  275. startTime = `${curYear}0101000000`;
  276. endTime = `${curYear + 1}0101000000`;
  277. } else {
  278. startTime = `${curYear}${curMonth}01000000`;
  279. if (curMonth + 1 > 12) {
  280. endTime = `${curYear + 1}0101000000`;
  281. } else {
  282. endTime = `${curYear}${curMonth + 1}01000000`;
  283. }
  284. }
  285. return [startTime, endTime];
  286. },
  287. /**
  288. * 切换系统
  289. */
  290. changeSystem() {
  291. if (window.gc) {
  292. gc.gCanvas.destroy();
  293. window.gc = null;
  294. }
  295. this.getGanttChartData();
  296. },
  297. /**
  298. * 切换视图
  299. */
  300. changeView() {
  301. console.log('timeType', this.timeType)
  302. if (window.gc) {
  303. gc.gCanvas.destroy();
  304. window.gc = null;
  305. }
  306. this.getGanttChartData();
  307. },
  308. /**
  309. * 获取甘特图数据
  310. */
  311. getGanttChartData() {
  312. let time = this.disQueryTime();
  313. let param = {
  314. smsxt: this.systemId,
  315. plazaId: 1000423,
  316. startDate: this.startTime, // time[0]
  317. endDate: this.endTime // time[1]
  318. };
  319. queryGanttChart("/data/base/queryGanttChart", param).then(res => {
  320. const { dsfList, wbList, zwList, result } = res;
  321. console.log("res", res);
  322. if (result === "success") {
  323. let newData = [],
  324. data = [wbList, zwList, dsfList],
  325. name = ["维保", "专维", "第三方检测"];
  326. _.forEach(data, (item, index) => {
  327. newData.push({
  328. index: index,
  329. id: index + 1,
  330. name: name[index],
  331. open: true,
  332. // children: name[index] === '专维'?[] : this.disGanttData(dsfList)
  333. children: this.disGanttData(data[index])
  334. });
  335. });
  336. this.treeData = newData;
  337. let taskData = _.map(_.cloneDeep(newData), item => {
  338. return {
  339. id: item.id,
  340. type: item.name,
  341. open: item.open,
  342. dataList: _.map(item.children, _item => {
  343. return {
  344. id: _item.id,
  345. title: _item.name,
  346. tasks: _item.originData
  347. };
  348. })
  349. };
  350. });
  351. console.log("taskData", taskData);
  352. if (!window.gc) {
  353. let width = this.$refs.chartContainer.offsetWidth;
  354. let handleFn = null;
  355. if (this.timeType === 'month') {
  356. handleFn = GanttChartMonth
  357. } else {
  358. handleFn = GanttChartDay
  359. }
  360. window.gc = new handleFn({
  361. chartParentContainer: "chartContainer",
  362. chartContainer: "ganttContainer",
  363. viewWidth: width,
  364. cWidth: 3 * width,
  365. tasks: taskData,
  366. daysCount: this.timeType === 'month'? 365 : parseInt((3 * width) / 40),
  367. callback: this.showDialog,
  368. pageToCallback: (data)=>{
  369. const { startAt, endAt } = data;
  370. this.startTime = new Date(startAt).getTime();
  371. this.endTime = new Date(endAt).getTime();
  372. this.getGanttChartData();
  373. }
  374. });
  375. gc.main();
  376. } else {
  377. gc.changeTasks(taskData)
  378. }
  379. }
  380. });
  381. },
  382. /**
  383. * 处理甘特图数据
  384. */
  385. disGanttData(arr) {
  386. let data = [];
  387. _.forEach(arr, item => {
  388. data.push({
  389. id: item.id,
  390. name: item.name,
  391. checked: "uncheck",
  392. originData: _.map(item.data, _item => {
  393. // _item.startDate = moment.unix((new Date().getTime() - 1000*60*60*24*7)/1000).format('YYYY-MM-DD HH:mm:ss');
  394. _item.startDate = moment.unix(_item.startDate / 1000).format("YYYY-MM-DD HH:mm:ss");
  395. // _item.endDate = moment.unix(new Date().getTime()/1000).format('YYYY-MM-DD HH:mm:ss');
  396. _item.endDate = moment.unix(_item.endDate / 1000).format("YYYY-MM-DD HH:mm:ss");
  397. _item.description = _item.parentname;
  398. return _item;
  399. })
  400. });
  401. });
  402. return data;
  403. },
  404. /**
  405. * 展开收起节点
  406. */
  407. toggleNode(status, item) {
  408. console.log("status", status, item);
  409. window.gc.toggle(item.index);
  410. },
  411. /**
  412. * 显示弹窗
  413. */
  414. showDialog(task) {
  415. this.showDetail = true;
  416. this.detailTitle = task._pdata.description;
  417. this.getGanttDetailData();
  418. this.getPictureOrReportData(0);
  419. this.getPictureOrReportData(1);
  420. },
  421. /**
  422. * 获取甘特图详情
  423. */
  424. getGanttDetailData() {
  425. let param = {
  426. plazaId: 1000423,
  427. type: 2,
  428. id: 1078467
  429. };
  430. queryGanttDetail("/data/base/queryGanttChartDetails", param).then(res => {
  431. const { result, data } = res;
  432. if (result === "success") {
  433. const {
  434. status,
  435. planStartDate,
  436. planEndDate,
  437. realStartDate,
  438. realEndDate
  439. } = data;
  440. let newData = {
  441. status,
  442. color: "",
  443. planTime: this.dealDetailTime(planStartDate, planEndDate),
  444. realTime: this.dealDetailTime(realStartDate, realEndDate)
  445. };
  446. this.ganttDetail = newData;
  447. this.tableData = data.assetList;
  448. }
  449. });
  450. },
  451. /**
  452. * 处理时间
  453. */
  454. dealDetailTime(startTime, endTime) {
  455. let start = moment.unix(startTime / 1000).format("YYYY.MM.DD HH:mm:ss"),
  456. end = moment.unix(endTime / 1000).format("YYYY.MM.DD HH:mm:ss");
  457. return `${start}-${end}`;
  458. },
  459. /**
  460. * 获取甘特图的图片/报告详情
  461. */
  462. getPictureOrReportData(type) {
  463. let param = {
  464. file_type: 1,
  465. file_type_id: 1985,
  466. type: type
  467. };
  468. queryDetailData("/data/base/queryFileDetails", param).then(res => {
  469. console.log("photo", res);
  470. });
  471. }
  472. }
  473. };
  474. </script>
  475. <style lang='less' scoped>
  476. .gantt-chart {
  477. padding-left: 16px;
  478. padding-right: 16px;
  479. padding-top: 12px;
  480. padding-bottom: 16px;
  481. width: 100%;
  482. height: 100%;
  483. background: #fff;
  484. .condition-legend-bar {
  485. margin-bottom: 12px;
  486. display: flex;
  487. justify-content: space-between;
  488. }
  489. .legend-container {
  490. display: flex;
  491. .item-legend {
  492. padding-left: 18px;
  493. position: relative;
  494. font-size: 14px;
  495. color: #646c73;
  496. line-height: 32px;
  497. &:not(:nth-of-type(4)) {
  498. margin-right: 20px;
  499. }
  500. > div:first-child {
  501. position: absolute;
  502. left: 0;
  503. top: calc(50% - 6px);
  504. width: 12px;
  505. height: 12px;
  506. border: 1px solid;
  507. }
  508. &:nth-of-type(4) {
  509. margin-right: 32px;
  510. }
  511. }
  512. }
  513. .main-gantt-container {
  514. width: 100%;
  515. height: calc(100% - 44px);
  516. background: #f2f5f7;
  517. border: 1px solid rgba(239, 240, 241, 1);
  518. border-radius: 4px;
  519. display: flex;
  520. .task-title {
  521. width: 404px;
  522. height: 100%;
  523. border-right: 1px solid #eff0f1;
  524. .title {
  525. padding-left: 32px;
  526. padding-top: 15px;
  527. padding-bottom: 15px;
  528. font-size: 14px;
  529. color: #646c73;
  530. line-height: 19px;
  531. border-bottom: 1px solid #eff0f1;
  532. }
  533. > div:nth-of-type(2) {
  534. max-height: calc(100% - 50px);
  535. overflow: auto;
  536. }
  537. }
  538. .chart-container {
  539. flex: 1;
  540. overflow: hidden;
  541. background: #fff;
  542. }
  543. }
  544. .dialog-container {
  545. max-height: 580px;
  546. overflow: auto;
  547. .row {
  548. display: flex;
  549. > div:first-child {
  550. margin-right: 24px;
  551. display: flex;
  552. align-items: center;
  553. }
  554. &:not(:last-of-type) {
  555. margin-bottom: 14px;
  556. }
  557. .info {
  558. font-size: 14px;
  559. color: #1f2429;
  560. line-height: 22px;
  561. }
  562. }
  563. .icon {
  564. margin-right: 6px;
  565. width: 16px;
  566. }
  567. .title {
  568. width: 84px;
  569. font-size: 14px;
  570. color: #646c73;
  571. line-height: 22px;
  572. font-weight: 400;
  573. }
  574. .task-status > div:nth-of-type(2) {
  575. width: 58px;
  576. height: 24px;
  577. background: rgba(91, 143, 249, 0.2);
  578. border-radius: 2px;
  579. color: #5b8ff9;
  580. line-height: 24px;
  581. text-align: center;
  582. }
  583. .picture {
  584. margin-bottom: 19px !important;
  585. > div:first-child {
  586. align-items: baseline;
  587. }
  588. > div:nth-of-type(2) {
  589. flex: 1;
  590. }
  591. > div:nth-of-type(2) > div:first-child {
  592. margin-bottom: 12px;
  593. display: flex;
  594. }
  595. .num,
  596. .more {
  597. font-size: 14px;
  598. line-height: 22px;
  599. font-weight: 400;
  600. }
  601. .num {
  602. margin-right: 22px;
  603. color: #1f2429;
  604. }
  605. .more {
  606. color: #025baa;
  607. cursor: pointer;
  608. }
  609. }
  610. .pictrue-container {
  611. > div {
  612. padding: 10px 16px;
  613. width: 100%;
  614. border-radius: 4px;
  615. border: 1px solid rgba(233, 233, 233, 1);
  616. display: flex;
  617. align-items: center;
  618. > img {
  619. width: 24px;
  620. margin-right: 15px;
  621. }
  622. }
  623. > div:not(:last-child) {
  624. margin-bottom: 8px;
  625. }
  626. }
  627. .test-content {
  628. display: block;
  629. > div:first-child {
  630. margin-bottom: 13px;
  631. }
  632. }
  633. }
  634. .detail-container {
  635. display: flex;
  636. height: 600px;
  637. }
  638. .pictures-menu {
  639. margin-right: 21px;
  640. padding-top: 16px;
  641. padding-bottom: 16px;
  642. width: 180px;
  643. height: 100%;
  644. overflow: auto;
  645. .item {
  646. > img {
  647. width: 180px;
  648. border: 4px solid rgba(245, 246, 247, 1);
  649. border-radius: 4px;
  650. }
  651. .name {
  652. font-size: 12px;
  653. color: #1f2429;
  654. line-height: 16px;
  655. margin-top: 12px;
  656. text-align: center;
  657. }
  658. &:not(:last-of-type) {
  659. margin-bottom: 20px;
  660. }
  661. }
  662. }
  663. .cur-img-container {
  664. padding: 20px;
  665. flex: 1;
  666. height: 100%;
  667. background: #f5f6f7;
  668. display: flex;
  669. justify-content: center;
  670. align-items: center;
  671. > img {
  672. max-width: 730px;
  673. max-height: 530px;
  674. }
  675. }
  676. }
  677. </style>