index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. <template>
  2. <div>
  3. <avue-crud
  4. v-model:search="search"
  5. :option="option"
  6. @search-change="searchChange"
  7. @search-reset="resetChange"
  8. />
  9. <div class="btns">
  10. <div>
  11. <el-button type="primary" @click="exportToPNG">导出PNG图片</el-button>
  12. <el-button type="primary" @click="exportToPDF">导出PDF文件</el-button>
  13. </div>
  14. <!-- <el-button type="params" v-print="printObj" @click="toPrint"
  15. >打印</el-button
  16. > -->
  17. <div class="illustration">
  18. <div style="display: flex; align-items: center; margin-right: 5px">
  19. <div class="round1"></div>
  20. :完成
  21. </div>
  22. <div style="display: flex; align-items: center">
  23. <div class="round2"></div>
  24. :未完成
  25. </div>
  26. </div>
  27. </div>
  28. <div class="main-content">
  29. <div ref="ganttRef" id="gantt_here" class="gantt-container"></div>
  30. </div>
  31. </div>
  32. </template>
  33. <script setup>
  34. import { gantt } from "dhtmlx-gantt";
  35. import "dhtmlx-gantt/codebase/dhtmlxgantt.css";
  36. import html2canvas from "html2canvas";
  37. import { htmlPdf } from "@/utils/htmlPDF.js";
  38. import { useCrud } from "@/hooks/userCrud";
  39. const ganttRef = ref(null);
  40. const { form, data, option, search, page, toDeleteIds, Methords, Utils } =
  41. useCrud({
  42. src: "/api/v1/process/census/queryApsResultGant",
  43. pageStr: "no",
  44. });
  45. const exportToPDF = () => {
  46. var fileName = "甘特图pdf";
  47. const fileList = document.getElementsByClassName("gantt_here");
  48. htmlPdf(fileName, document.querySelector("#gantt_here"), fileList, 1111);
  49. };
  50. const exportToPNG = async () => {
  51. const chartContainer = ganttRef.value;
  52. const canvas = await html2canvas(chartContainer);
  53. const img = canvas.toDataURL("image/png");
  54. const a = document.createElement("a");
  55. a.href = img;
  56. a.download = "甘特图.png";
  57. a.click();
  58. };
  59. // const printUrl = ref("");
  60. // const toPrint = async () => {
  61. // const chartContainer = ganttRef.value;
  62. // const canvas = await html2canvas(chartContainer);
  63. // const img = canvas.toDataURL("image/png");
  64. // printUrl.value = img;
  65. // };
  66. // const printObj = ref({
  67. // id: "gantt_here",
  68. // preview: true,
  69. // popTitle: "甘特图",
  70. // url: printUrl.value,
  71. // });
  72. const { dataList, createRow, updateRow, deleteRow, searchChange, resetChange } =
  73. Methords;
  74. option.value = Object.assign(option.value, {
  75. selection: false,
  76. menu: true,
  77. menuWidth: 100,
  78. addBtn: false,
  79. filterBtn: false,
  80. searchShowBtn: true,
  81. columnBtn: false,
  82. gridBtn: false,
  83. editBtn: false,
  84. viewBtn: false,
  85. delBtn: false,
  86. column: [
  87. {
  88. label: "日期范围",
  89. prop: "searchTime",
  90. search: true,
  91. hide: true,
  92. type: "date",
  93. format: "YYYY-MM-DD",
  94. valueFormat: "YYYY-MM-DD",
  95. searchRange: true,
  96. startPlaceholder: "开始日期",
  97. endPlaceholder: "结束日期",
  98. },
  99. {
  100. label: "订单名称",
  101. prop: "name",
  102. search: true,
  103. },
  104. {
  105. label: "时间维度",
  106. prop: "timeType",
  107. search: true,
  108. type: "select",
  109. searchValue: "1",
  110. dicData: [
  111. {
  112. dictLabel: "小时",
  113. dictValue: "0",
  114. },
  115. {
  116. dictLabel: "日",
  117. dictValue: "1",
  118. },
  119. {
  120. dictLabel: "月",
  121. dictValue: "2",
  122. },
  123. ],
  124. props: {
  125. label: "dictLabel",
  126. value: "dictValue",
  127. },
  128. },
  129. {
  130. label: "订单状态",
  131. prop: "type",
  132. search: true,
  133. type: "select",
  134. dicData: [
  135. {
  136. dictLabel: "订单",
  137. dictValue: "0",
  138. },
  139. {
  140. dictLabel: "工单",
  141. dictValue: "1",
  142. },
  143. {
  144. dictLabel: "流转卡号",
  145. dictValue: "2",
  146. },
  147. {
  148. dictLabel: "工序",
  149. dictValue: "3",
  150. },
  151. ],
  152. props: {
  153. label: "dictLabel",
  154. value: "dictValue",
  155. },
  156. },
  157. ],
  158. });
  159. const getCurrentMonthStartAndEndDates = () => {
  160. // 获取当前日期
  161. let now = new Date();
  162. // 获取当前月份的第一天
  163. let startDate = new Date(now.getFullYear(), now.getMonth(), 1);
  164. // 获取当前月份的最后一天
  165. let endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0);
  166. // 格式化日期为'YYYY-MM-DD'格式
  167. function formatDate(date) {
  168. let year = date.getFullYear();
  169. let month = String(date.getMonth() + 1).padStart(2, "0");
  170. let day = String(date.getDate()).padStart(2, "0");
  171. return `${year}-${month}-${day}`;
  172. }
  173. // 返回包含开始和结束日期的数组
  174. return [formatDate(startDate), formatDate(endDate)];
  175. };
  176. const demoData = {
  177. data: [
  178. {
  179. id: 11,
  180. text: "Project #1",
  181. start_date: "2024-07-01 08:00:00",
  182. endTime: "2024-09-30 09:00:00",
  183. startTime: "2024-07-30 08:00:00",
  184. duration: "11",
  185. progress: 0.8,
  186. open: true,
  187. },
  188. ],
  189. };
  190. //初始化甘特图
  191. const initGantt = (type) => {
  192. gantt.plugins({
  193. tooltip: true,
  194. marker: true,
  195. critical_path: true,
  196. export_api: true,
  197. });
  198. gantt.templates.task_text = function (start, end, task) {
  199. return (
  200. "<span style='padding:0 5px;font-size:15px;color: #000; display: inline-block;width: 100%; white-space: nowrap;overflow: hidden; text-overflow: ellipsis; '>" +
  201. task.text +
  202. "</span>"
  203. );
  204. };
  205. gantt.templates.progress_text = function (start, end, task) {
  206. return (
  207. "<span style='text-align:left;'>" +
  208. Math.round(task.progress * 100) +
  209. "% </span>"
  210. );
  211. };
  212. gantt.templates.task_class = function (start, end, task) {
  213. return "taskClass";
  214. };
  215. if (type) {
  216. gantt.config.duration_unit = type;
  217. }
  218. gantt.config.show_today = true;
  219. gantt.config.grid_width = 350;
  220. gantt.config.add_column = false;
  221. gantt.config.duration_step = 1;
  222. gantt.config.resize_rows = true;
  223. gantt.config.columns = [
  224. {
  225. tree: true,
  226. name: "text",
  227. label: "订单名称",
  228. width: "240",
  229. },
  230. {
  231. name: "startTime",
  232. label: "开始时间",
  233. align: "center",
  234. width: "160",
  235. template(task) {
  236. return task.startTime;
  237. },
  238. },
  239. {
  240. name: "endTime",
  241. label: "结束时间",
  242. align: "center",
  243. width: "160",
  244. template(task) {
  245. return task.endTime;
  246. },
  247. },
  248. ];
  249. gantt.config.autofit = true;
  250. gantt.config.resize_rows = true;
  251. gantt.config.row_height = 60;
  252. gantt.config.bar_height = 40;
  253. gantt.config.min_column_width = 60;
  254. gantt.config.xml_date = "%Y-%m-%d %H:%i:%s"; //甘特图时间格式
  255. gantt.config.readonly = true; //是否只读
  256. gantt.i18n.setLocale("cn"); //设置语言
  257. gantt.templates.tooltip_text = function (start, end, task) {
  258. const text = `名称: ${task.text}<br/>总数: ${task.num}<br/>完成进度: ${task.progress * 100}%`;
  259. return !task.parent
  260. ? `${text}<br/>交期时间: ${task.deliverWhen}`
  261. : `${text}<br/>计划开始时间: ${task.startTime}<br/>计划结束时间: ${task.endTime}`; // eslint-disable-line
  262. };
  263. const dateToStr = gantt.date.date_to_str(gantt.config.task_date);
  264. const today = new Date(new Date().setHours(0, 0, 0, 0));
  265. gantt.addMarker({
  266. start_date: today,
  267. css: "today",
  268. text: "今日",
  269. title: `Today: ${dateToStr(today)}`,
  270. });
  271. gantt.init("gantt_here"); //初始化
  272. };
  273. const setTime = () => {
  274. search.value = {
  275. searchTime: getCurrentMonthStartAndEndDates(),
  276. timeType: "1",
  277. };
  278. };
  279. function convertDateString1(str) {
  280. // 将输入的日期字符串转换为 Date 对象
  281. let date = new Date(str);
  282. // 获取年、月、日
  283. let year = date.getFullYear();
  284. let month = date.toLocaleString("en-us", { month: "short" });
  285. let day = date.getDate();
  286. // 构造新的日期字符串格式
  287. let formattedDate = `${date.toDateString().slice(0, 3)} ${month} ${day} ${year} 00:00:00 GMT+0800 (GMT+08:00)`;
  288. return formattedDate;
  289. }
  290. function convertDateString2(str) {
  291. // 将输入的日期字符串转换为 Date 对象
  292. let date = new Date(str);
  293. // 获取年、月、日
  294. let year = date.getFullYear();
  295. let month = date.toLocaleString("en-us", { month: "short" });
  296. let day = date.getDate();
  297. // 构造新的日期字符串格式
  298. let formattedDate = `${date.toDateString().slice(0, 3)} ${month} ${day} ${year} 24:00:00 GMT+0800 (GMT+08:00)`;
  299. return formattedDate;
  300. }
  301. // watchEffect(() => {
  302. // //此api后续弃用
  303. // // gantt.parse({ data: data.value });
  304. // gantt.parse(demoData);
  305. // });
  306. const setGantt = () => {
  307. gantt.clearAll();
  308. if (search?.value.searchTime) {
  309. gantt.config.start_date = convertDateString1(search?.value.searchTime[0]);
  310. gantt.config.end_date = convertDateString2(search?.value.searchTime[1]);
  311. gantt.config.show_tasks_outside_timescale = true;
  312. }
  313. switch (search.value.timeType) {
  314. case "0":
  315. gantt.config.duration_unit = "hour";
  316. gantt.config.scale_height = 28 * 4;
  317. gantt.config.scales = [
  318. {
  319. unit: "year",
  320. step: 1,
  321. format: "%Y年",
  322. css: function (date) {
  323. return "year";
  324. },
  325. },
  326. {
  327. unit: "month",
  328. step: 1,
  329. format: "%m月",
  330. css: function (date) {
  331. return "month";
  332. },
  333. },
  334. {
  335. unit: "day",
  336. step: 1,
  337. format: "%d日",
  338. css: function (date) {
  339. return "day";
  340. },
  341. },
  342. {
  343. unit: "hour",
  344. step: 1,
  345. format: "%H时",
  346. css: function (date) {
  347. return "hour";
  348. },
  349. },
  350. ];
  351. gantt.init("gantt_here");
  352. gantt.render();
  353. gantt.parse({ data: useData.value });
  354. break;
  355. case "1":
  356. gantt.config.scale_height = 28 * 4;
  357. gantt.config.scales = [
  358. {
  359. unit: "year",
  360. step: 1,
  361. format: "%Y年",
  362. css: function (date) {
  363. return "year";
  364. },
  365. },
  366. {
  367. unit: "month",
  368. step: 1,
  369. format: "%m月",
  370. css: function (date) {
  371. return "month";
  372. },
  373. },
  374. {
  375. unit: "day",
  376. step: 1,
  377. format: "%d日",
  378. css: function (date) {
  379. return "day";
  380. },
  381. },
  382. ];
  383. gantt.config.duration_unit = "day";
  384. gantt.init("gantt_here");
  385. gantt.render();
  386. gantt.parse({ data: useData.value });
  387. break;
  388. case "2":
  389. // initGantt("month");
  390. gantt.config.scale_height = 28 * 4;
  391. gantt.config.scales = [
  392. {
  393. unit: "year",
  394. step: 1,
  395. format: "%Y年",
  396. css: function (date) {
  397. return "year";
  398. },
  399. },
  400. {
  401. unit: "month",
  402. step: 1,
  403. format: "%m月",
  404. css: function (date) {
  405. return "month";
  406. },
  407. },
  408. ];
  409. gantt.config.duration_unit = "month";
  410. gantt.init("gantt_here");
  411. gantt.parse({ data: useData.value });
  412. gantt.render();
  413. break;
  414. }
  415. };
  416. const useData = ref([]);
  417. const setUseData = (data) => {
  418. useData.value = [];
  419. const data1 = [...data];
  420. data1.forEach((element) => {
  421. element.start_date = element.startTime;
  422. if (element.progress != 1) {
  423. element.color = "orange";
  424. } else {
  425. element.color = "green";
  426. }
  427. });
  428. const data2 = JSON.parse(JSON.stringify(data1));
  429. useData.value = data2;
  430. };
  431. onMounted(() => {
  432. setTime();
  433. dataList();
  434. initGantt();
  435. });
  436. watch(
  437. () => data.value,
  438. () => {
  439. if (data.value) {
  440. setUseData(data.value);
  441. setGantt();
  442. }
  443. },
  444. { immediate: true, deep: true }
  445. );
  446. </script>
  447. <style scoped lang="scss">
  448. .btns {
  449. margin: 7px 0;
  450. display: flex;
  451. align-items: center;
  452. justify-content: space-between;
  453. .illustration {
  454. display: flex;
  455. padding-right: 5%;
  456. align-items: center;
  457. width: 240px;
  458. justify-content: space-between;
  459. .round2 {
  460. width: 20px;
  461. height: 20px;
  462. background-color: orange;
  463. border-radius: 10px;
  464. }
  465. .round1 {
  466. width: 20px;
  467. height: 20px;
  468. background-color: green;
  469. border-radius: 10px;
  470. }
  471. }
  472. }
  473. :deep(.avue-crud__body) {
  474. display: none;
  475. }
  476. .main-content {
  477. width: 100%;
  478. height: 600px;
  479. .gantt-container {
  480. width: 100%;
  481. height: 100%;
  482. }
  483. }
  484. .ganttStyle {
  485. border-radius: 50%;
  486. }
  487. </style>