index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. <template>
  2. <div class="project-gstc-gantt">
  3. <GSTC v-if="loadFinish" ref="gstc" :config="config" @state="emitState"/>
  4. <Dropdown class="project-gstc-dropdown-eye" @on-click="tapView">
  5. <Icon class="project-gstc-dropdown-icon" type="md-eye" />
  6. <DropdownMenu slot="list">
  7. <DropdownItem name="now">{{$L('现在')}}</DropdownItem>
  8. <DropdownItem name="day" :class="{'project-gstc-dropdown-period':period=='day'}">{{$L('天视图')}}</DropdownItem>
  9. <DropdownItem name="week" :class="{'project-gstc-dropdown-period':period=='week'}">{{$L('周视图')}}</DropdownItem>
  10. <DropdownItem name="month" :class="{'project-gstc-dropdown-period':period=='month'}">{{$L('月视图')}}</DropdownItem>
  11. </DropdownMenu>
  12. </Dropdown>
  13. <Dropdown class="project-gstc-dropdown-filtr" @on-click="tapProject">
  14. <Icon class="project-gstc-dropdown-icon" :class="{filtr:filtrProjectId>0}" type="md-funnel" />
  15. <DropdownMenu slot="list">
  16. <DropdownItem :name="0" :class="{'project-gstc-dropdown-period':filtrProjectId==0}">{{$L('全部')}}</DropdownItem>
  17. <DropdownItem v-for="(item, index) in projectLabel" :key="index" :name="item.id" :class="{'project-gstc-dropdown-period':filtrProjectId==item.id}">{{item.title}}</DropdownItem>
  18. </DropdownMenu>
  19. </Dropdown>
  20. <div class="project-gstc-close" @click="$emit('on-close')"><Icon type="md-close" /></div>
  21. <div class="project-gstc-edit" :class="{info:editShowInfo, visible:editData.length > 0}">
  22. <div class="project-gstc-edit-info">
  23. <Table class="tableFill" size="small" max-height="600" :columns="editColumns" :data="editData"></Table>
  24. <div class="project-gstc-edit-btns">
  25. <Button :loading="editLoad > 0" size="small" type="text" @click="editSubmit(false)">{{$L('取消')}}</Button>
  26. <Button :loading="editLoad > 0" size="small" type="primary" @click="editSubmit(true)">{{$L('保存')}}</Button>
  27. <Icon type="md-arrow-dropright" class="zoom" @click="editShowInfo=false"/>
  28. </div>
  29. </div>
  30. <div class="project-gstc-edit-small">
  31. <div class="project-gstc-edit-text" @click="editShowInfo=true">{{$L('未保存计划时间')}}: {{editData.length}}</div>
  32. <Button :loading="editLoad > 0" size="small" type="text" @click="editSubmit(false)">{{$L('取消')}}</Button>
  33. <Button :loading="editLoad > 0" size="small" type="primary" @click="editSubmit(true)">{{$L('保存')}}</Button>
  34. </div>
  35. </div>
  36. </div>
  37. </template>
  38. <style lang="scss">
  39. .project-gstc-gantt {
  40. position: absolute;
  41. top: 15px;
  42. left: 15px;
  43. right: 15px;
  44. bottom: 15px;
  45. z-index: 1;
  46. transform: translateZ(0);
  47. background-color: #fdfdfd;
  48. border-radius: 3px;
  49. .gantt-schedule-timeline-calendar {
  50. background-color: transparent;
  51. padding: 12px;
  52. }
  53. .gantt-schedule-timeline-calendar__list-column-header-resizer-container {
  54. line-height: 90px;
  55. max-width: 200px;
  56. overflow: hidden;
  57. text-overflow: ellipsis;
  58. white-space: nowrap;
  59. }
  60. .project-gstc-dropdown-eye,
  61. .project-gstc-dropdown-filtr {
  62. position: absolute;
  63. top: 46px;
  64. left: 276px;
  65. .project-gstc-dropdown-icon {
  66. cursor: pointer;
  67. color: #999;
  68. font-size: 20px;
  69. &.filtr {
  70. color: #058ce4;
  71. }
  72. }
  73. .project-gstc-dropdown-period {
  74. color: #058ce4;
  75. }
  76. }
  77. .project-gstc-dropdown-eye {
  78. left: 238px;
  79. }
  80. .project-gstc-close {
  81. position: absolute;
  82. top: 8px;
  83. left: 12px;
  84. cursor: pointer;
  85. &:hover {
  86. i {
  87. transform: scale(1) rotate(45deg);
  88. }
  89. }
  90. i {
  91. color: #666666;
  92. font-size: 28px;
  93. transform: scale(0.92);
  94. transition: all .2s;
  95. }
  96. }
  97. .project-gstc-edit {
  98. position: absolute;
  99. bottom: 6px;
  100. right: 6px;
  101. background: #ffffff;
  102. border-radius: 4px;
  103. opacity: 0;
  104. transform: translate(120%, 0);
  105. transition: all 0.2s;
  106. &.visible {
  107. opacity: 1;
  108. transform: translate(0, 0);
  109. }
  110. &.info {
  111. .project-gstc-edit-info {
  112. display: block;
  113. }
  114. .project-gstc-edit-small {
  115. display: none;
  116. }
  117. }
  118. .project-gstc-edit-info {
  119. display: none;
  120. border: 1px solid #e4e4e4;
  121. background: #ffffff;
  122. padding: 6px;
  123. border-radius: 4px;
  124. width: 500px;
  125. .project-gstc-edit-btns {
  126. margin: 12px 6px 4px;
  127. display: flex;
  128. align-items: center;
  129. justify-content: flex-end;
  130. .ivu-btn {
  131. margin-right: 8px;
  132. font-size: 13px;
  133. }
  134. .zoom {
  135. font-size: 20px;
  136. color: #444444;
  137. cursor: pointer;
  138. &:hover {
  139. color: #57a3f3;
  140. }
  141. }
  142. }
  143. }
  144. .project-gstc-edit-small {
  145. border: 1px solid #e4e4e4;
  146. background: #ffffff;
  147. padding: 6px 12px;
  148. display: flex;
  149. align-items: center;
  150. .project-gstc-edit-text {
  151. cursor: pointer;
  152. text-decoration: underline;
  153. color: #444444;
  154. margin-right: 8px;
  155. &:hover {
  156. color: #57a3f3;
  157. }
  158. }
  159. .ivu-btn {
  160. margin-left: 4px;
  161. font-size: 13px;
  162. }
  163. }
  164. }
  165. }
  166. .gantt-notice-box {
  167. .ivu-notice-desc-btn {
  168. margin-top: 6px;
  169. text-align: right;
  170. .ivu-btn {
  171. margin-left: 4px;
  172. font-size: 13px;
  173. }
  174. }
  175. }
  176. </style>
  177. <script>
  178. import GSTC from "./GSTC";
  179. import ItemMovement from "gantt-schedule-timeline-calendar/dist/ItemMovement.plugin.js"
  180. import CalendarScroll from "gantt-schedule-timeline-calendar/dist/CalendarScroll.plugin.js"
  181. import WeekendHighlight from "gantt-schedule-timeline-calendar/dist/WeekendHighlight.plugin.js"
  182. /**
  183. * 甘特图
  184. */
  185. export default {
  186. name: 'ProjectGantt',
  187. components: { GSTC },
  188. props: {
  189. projectLabel: {
  190. default: []
  191. },
  192. },
  193. data () {
  194. return {
  195. loadFinish: false,
  196. period: 'day',
  197. rows: {},
  198. items: {},
  199. config: {},
  200. editColumns: [],
  201. editData: [],
  202. editShowInfo: false,
  203. editLoad: 0,
  204. filtrProjectId: 0,
  205. }
  206. },
  207. mounted() {
  208. this.editColumns = [
  209. {
  210. title: this.$L('任务名称'),
  211. key: 'label',
  212. minWidth: 150,
  213. ellipsis: true,
  214. }, {
  215. title: this.$L('原计划时间'),
  216. minWidth: 135,
  217. align: 'center',
  218. render: (h, params) => {
  219. if (params.row.notime === true) {
  220. return h('span', '-');
  221. }
  222. return h('div', {
  223. style: {},
  224. }, [
  225. h('div', $A.formatDate('Y-m-d H:i', Math.round(params.row.backTime.start / 1000))),
  226. h('div', $A.formatDate('Y-m-d H:i', Math.round(params.row.backTime.end / 1000)))
  227. ]);
  228. }
  229. }, {
  230. title: this.$L('新计划时间'),
  231. minWidth: 135,
  232. align: 'center',
  233. render: (h, params) => {
  234. return h('div', {
  235. style: {},
  236. }, [
  237. h('div', $A.formatDate('Y-m-d H:i', Math.round(params.row.newTime.start / 1000))),
  238. h('div', $A.formatDate('Y-m-d H:i', Math.round(params.row.newTime.end / 1000)))
  239. ]);
  240. }
  241. }
  242. ];
  243. //
  244. this.initData();
  245. this.loadFinish = true;
  246. //
  247. this.$watch("projectLabel",
  248. (projectLabel) => {
  249. this.initData();
  250. },
  251. {deep: true}
  252. );
  253. },
  254. methods: {
  255. initData() {
  256. let isUpdate = this.loadFinish == true;
  257. this.rows = {};
  258. this.items = {};
  259. let index = 0;
  260. this.projectLabel.forEach((item) => {
  261. if (this.filtrProjectId > 0) {
  262. if (item.id != this.filtrProjectId) {
  263. return;
  264. }
  265. }
  266. item.taskLists.forEach((taskData) => {
  267. index++;
  268. let notime = taskData.startdate == 0 || taskData.enddate == 0;
  269. let times = this.getTimeObj(taskData);
  270. let start = times.start;
  271. let end = times.end;
  272. //
  273. let color = '#058ce4';
  274. if (taskData.complete) {
  275. color = '#c1c1c1';
  276. } else {
  277. if (taskData.level === 1) {
  278. color = '#ff0000';
  279. }else if (taskData.level === 2) {
  280. color = '#BB9F35';
  281. }else if (taskData.level === 3) {
  282. color = '#449EDD';
  283. }else if (taskData.level === 4) {
  284. color = '#84A83B';
  285. }
  286. }
  287. //
  288. let label = `<div class="gantt-schedule-timeline-calendar-title${taskData.complete?' complete-title':''}">${taskData['title']}</div>`;
  289. if (taskData.overdue) {
  290. label = `<div class="gantt-schedule-timeline-calendar-overdue"><em>${this.$L('已超期')}</em></div>${label}`;
  291. }
  292. label = `${label}<div class="gantt-schedule-timeline-calendar-goto"></div>`;
  293. this.rows[index] = {
  294. id: index,
  295. label: label,
  296. taskId: taskData['id'],
  297. complete: taskData.complete,
  298. overdue: taskData.overdue,
  299. };
  300. let tempTime = { start, end };
  301. let findData = this.editData.find((t) => { return t.id == taskData['id'] });
  302. if (findData) {
  303. findData.backTime = $A.cloneData(tempTime)
  304. tempTime = $A.cloneData(findData.newTime);
  305. }
  306. this.items[taskData['id']] = {
  307. rowId: index,
  308. id: taskData['id'],
  309. label: taskData['title'],
  310. time: tempTime,
  311. notime: notime,
  312. style: { background: color },
  313. };
  314. });
  315. });
  316. //
  317. if (Object.keys(this.rows).length == 0 && this.filtrProjectId == 0) {
  318. this.$Modal.warning({
  319. title: this.$L("温馨提示"),
  320. content: this.$L('任务列表为空,请先添加任务。'),
  321. onOk: () => {
  322. this.$emit('on-close');
  323. },
  324. });
  325. }
  326. //
  327. if (isUpdate) {
  328. this.$refs.gstc.getState().update('config.list.rows', this.rows);
  329. this.$refs.gstc.getState().update('config.chart.items', this.items);
  330. return;
  331. }
  332. //
  333. this.config = {
  334. plugins: [ItemMovement({
  335. moveable: 'x',
  336. resizeable: true,
  337. collisionDetection: true
  338. }), CalendarScroll(), WeekendHighlight()],
  339. height: this.$el.clientHeight - 24,
  340. list: {
  341. toggle: {
  342. display: false
  343. },
  344. rows: this.rows,
  345. columns: {
  346. percent: 100,
  347. resizer: {
  348. inRealTime: true,
  349. dots: 0
  350. },
  351. data: {
  352. label: {
  353. id: "label",
  354. data: "label",
  355. width: 300,
  356. isHTML: true,
  357. header: {
  358. content: this.$L("任务名称")
  359. }
  360. }
  361. }
  362. }
  363. },
  364. chart: {
  365. time: {
  366. period: 'day',
  367. additionalSpaces: {
  368. hour: { before: 24, after: 24, period: 'hour' },
  369. day: { before: 1, after: 1, period: 'month' },
  370. week: { before: 1, after: 1, period: 'year' },
  371. month: { before: 6, after: 6, period: 'year' },
  372. year: { before: 12, after: 12, period: 'year' }
  373. }
  374. },
  375. items: this.items
  376. },
  377. actions: {
  378. "list-column-row": [this.actionRow],
  379. 'chart-timeline-items-row-item': [this.actionResizing]
  380. },
  381. locale: this.getLanguage() == 'en' ? {} : {
  382. name: "zh-cn",
  383. weekdays: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
  384. weekdaysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
  385. weekdaysMin: ["日", "一", "二", "三", "四", "五", "六"],
  386. months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
  387. monthsShort: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
  388. ordinal: function(t, e) {
  389. switch (e) {
  390. case "W":
  391. return "".concat(t, "周");
  392. default:
  393. return "".concat(t, "日")
  394. }
  395. },
  396. weekStart: 1,
  397. yearStart: 4,
  398. formats: {
  399. LT: "HH:mm",
  400. LTS: "HH:mm:ss",
  401. L: "YYYY/MM/DD",
  402. LL: "YYYY年M月D日",
  403. LLL: "YYYY年M月D日Ah点mm分",
  404. LLLL: "YYYY年M月D日ddddAh点mm分",
  405. l: "YYYY/M/D",
  406. ll: "YYYY年M月D日",
  407. lll: "YYYY年M月D日 HH:mm",
  408. llll: "YYYY年M月D日dddd HH:mm"
  409. },
  410. relativeTime: {
  411. future: "%s内",
  412. past: "%s前",
  413. s: "几秒",
  414. m: "1 分钟",
  415. mm: "%d 分钟",
  416. h: "1 小时",
  417. hh: "%d 小时",
  418. d: "1 天",
  419. dd: "%d 天",
  420. M: "1 个月",
  421. MM: "%d 个月",
  422. y: "1 年",
  423. yy: "%d 年"
  424. },
  425. },
  426. };
  427. },
  428. actionRow(element, data) {
  429. let onClick = (event) => {
  430. //打开任务
  431. let id = this.rows[data.rowId].taskId;
  432. if (event.target.className == 'gantt-schedule-timeline-calendar-goto') {
  433. id && this.$refs.gstc.getGstc().api.scrollToTime(this.items[id].time.start)
  434. } else {
  435. id && this.taskDetail(id);
  436. }
  437. }
  438. element.addEventListener('click', onClick);
  439. return {
  440. update(element, newData) {
  441. data = newData;
  442. },
  443. destroy(element, data) {
  444. element.removeEventListener('click', onClick);
  445. }
  446. };
  447. },
  448. actionResizing(element, data) {
  449. let thas = this;
  450. return {
  451. update(element, newData) {
  452. data = newData;
  453. if (data.item.isResizing) {
  454. let original = thas.getRawTime(data.item.id);
  455. if (Math.abs(original.end - data.item.time.end) > 1000 || Math.abs(original.start - data.item.time.start) > 1000) {
  456. //修改时间(变化超过1秒钟)
  457. let backTime = $A.cloneData(original);
  458. let newTime = $A.cloneData(data.item.time);
  459. let findData = thas.editData.find((item) => { return item.id == data.item.id });
  460. if (findData) {
  461. findData.newTime = newTime;
  462. } else {
  463. thas.editData.push({
  464. id: data.item.id,
  465. label: data.item.label,
  466. notime: data.item.notime,
  467. backTime,
  468. newTime,
  469. })
  470. }
  471. }
  472. }
  473. }
  474. };
  475. },
  476. getRawTime(taskId) {
  477. let times = null;
  478. this.projectLabel.some((item) => {
  479. item.taskLists.some((taskData) => {
  480. if (taskData.id == taskId) {
  481. times = this.getTimeObj(taskData);
  482. return true;
  483. }
  484. });
  485. if (times) {
  486. return true;
  487. }
  488. });
  489. return times;
  490. },
  491. getTimeObj(taskData) {
  492. let start = taskData.startdate || taskData.indate;
  493. let end = taskData.enddate || (taskData.indate + 86400);
  494. if (end == start) {
  495. end = Math.round(new Date($A.formatDate('Y-m-d 23:59:59', end)).getTime()/1000);
  496. }
  497. end = Math.max(end, start + 60);
  498. start*= 1000;
  499. end*= 1000;
  500. return {start, end};
  501. },
  502. editSubmit(save) {
  503. this.editData.forEach((item) => {
  504. if (!this.items[item.id]) {
  505. return;
  506. }
  507. if (save) {
  508. this.editLoad++;
  509. let timeStart = $A.formatDate('Y-m-d H:i', Math.round(item.newTime.start / 1000));
  510. let timeEnd = $A.formatDate('Y-m-d H:i', Math.round(item.newTime.end / 1000));
  511. let ajaxData = {
  512. act: 'plannedtime',
  513. taskid: item.id,
  514. content: timeStart + "," + timeEnd,
  515. };
  516. $A.apiAjax({
  517. url: 'project/task/edit',
  518. data: ajaxData,
  519. complete: () => {
  520. this.editLoad--;
  521. },
  522. error: () => {
  523. alert(this.$L('网络繁忙,请稍后再试!'));
  524. this.items[item.id].time = item.backTime;
  525. this.$refs.gstc.updateTime(item.id, item.backTime);
  526. },
  527. success: (res) => {
  528. if (res.ret === 1) {
  529. $A.triggerTaskInfoListener(ajaxData.act, res.data);
  530. $A.triggerTaskInfoChange(ajaxData.taskid);
  531. } else {
  532. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  533. this.items[item.id].time = item.backTime;
  534. this.$refs.gstc.updateTime(item.id, item.backTime);
  535. }
  536. }
  537. });
  538. } else {
  539. this.items[item.id].time = item.backTime;
  540. this.$refs.gstc.updateTime(item.id, item.backTime);
  541. }
  542. });
  543. this.editData = [];
  544. },
  545. emitState(GSTCState) {
  546. GSTCState.subscribe('config.plugin.ItemMovement.item', data => {
  547. if (!data) return;
  548. GSTCState.update(`config.chart.items.${data.id}.isResizing`, !(data.waiting || data.moving || data.resizing));
  549. });
  550. },
  551. tapView(e) {
  552. if ("now" === e) {
  553. var i = this.$refs.gstc.getGstc()
  554. return i.api.scrollToTime(i.api.time.date().valueOf())
  555. }
  556. this.period = e;
  557. this.$refs.gstc.setPeriod(e);
  558. },
  559. tapProject(e) {
  560. this.filtrProjectId = $A.runNum(e);
  561. this.initData();
  562. }
  563. }
  564. }
  565. </script>