index.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. <template>
  2. <div class="app-container">
  3. <div class="u-gantt-container u-flex">
  4. <div>
  5. <el-table
  6. :data="tableData"
  7. stripe
  8. :cell-class-name="tableCellClassName"
  9. :header-cell-class-name="tableCellClassName"
  10. row-key="id"
  11. default-expand-all
  12. :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
  13. @expand-change="expandChange"
  14. :row-style="{ height: uStyle.ganttHeight + 'px' }"
  15. :header-row-style="{ height: uStyle.ganttHeight + 'px' }"
  16. @cell-click="cellClick"
  17. @row-dblclick="rowDblclick"
  18. >
  19. <template v-for="(item, index) in configColumns">
  20. <el-table-column
  21. v-if="item.name !== 'add'"
  22. :prop="item.name"
  23. :label="item.label"
  24. :width="item.width"
  25. :align="item.align"
  26. :key="index"
  27. show-overflow-tooltip
  28. >
  29. </el-table-column>
  30. <el-table-column
  31. v-if="item.name === 'add'"
  32. :prop="item.name"
  33. :label="item.label"
  34. :align="item.align"
  35. :width="item.width"
  36. :key="index"
  37. >
  38. <template slot-scope="scope">
  39. <!-- <i class="el-icon-plus" v-if="!scope.row.pid" @click="addRow(scope.row, scope.$index)"></i> -->
  40. <span v-if="!scope.row.pid" @click="addRow(scope.row, scope.$index)" class="add-btn">+</span>
  41. </template>
  42. </el-table-column>
  43. <!-- :fixed="item.fixed" -->
  44. </template>
  45. </el-table>
  46. </div>
  47. <!-- <div class="u-gantt-area" ref="uGantt"> -->
  48. <div class="u-gantt" ref="uGantt">
  49. <div class="u-gantt-title u-flex" >
  50. <div v-for="(item, index) in uGanttColumns" :key="index" class="item-title" :style="{'height': uStyle.ganttHeight + 'px', 'lineHeight': uStyle.ganttHeight + 'px'}">{{ item.label}}</div>
  51. </div>
  52. <div class="u-gantt-content">
  53. <div class="u-gantt-bg">
  54. <div v-for="(item, tr) in ganttLists" :key="tr" class="item-tr u-flex" :style="{'height': uStyle.ganttHeight + 'px'}">
  55. <!-- :style="{'display': item.show}" -->
  56. <div class="item-td" v-for="(item, td) in uGanttColumns" :key="td"></div>
  57. </div>
  58. </div>
  59. <div class="u-gantt-task">
  60. <div class="u-gantt-task-content" v-for="(item, index) in ganttLists" :key="index">
  61. <!-- <span class="resize" ref="`resize${index}}`" @mousedown="dragControllerDiv(index)"></span> -->
  62. <!-- 计划 -->
  63. <div class="u-gantt-plan" ref="uGanttTaskItem" :style="setPlanStyle(item)">
  64. <div class="u-gantt-task-drag" ref="uGanttDrag"></div>
  65. <div class="u-gantt-task-resize resize-left" ref="uGanttResizeL"></div>
  66. <div class="u-gantt-task-text">{{ item.task_text }}</div>
  67. <div class="u-gantt-task-resize resize-right" ref="uGanttResizeR"></div>
  68. </div>
  69. <!-- 实际 -->
  70. <div v-if="hasReality" class="u-gantt-reality" :style="setRealityStyle(item)"></div>
  71. </div>
  72. </div>
  73. </div>
  74. </div>
  75. <!-- </div> -->
  76. </div>
  77. <!-- 自定义内容:弹窗 -->
  78. <slot/>
  79. </div>
  80. </template>
  81. <script>
  82. export default {
  83. props: {
  84. configColumns: {
  85. type: Array,
  86. default: []
  87. },
  88. taskLists: {
  89. type: Array,
  90. default: []
  91. },
  92. uStyle: {
  93. type: Object,
  94. default: () => ({
  95. ganttHeight: 40, // 一行的高度(整体一行的)
  96. planHeight: 26, // 计划甘特条的高度
  97. planBg: '#44c2e5', // 计划甘特条的颜色
  98. realityHeight: 10, // 实际甘特条的高度
  99. realityBgIncomplete: '#ffaf58', // 实际甘特条进行中的颜色
  100. realityBgCompleted: '#9BDF90', // 实际甘特条已完成的颜色
  101. // #e5de44
  102. top: 4, // 甘特条离上线条距离
  103. })
  104. },
  105. hasReality: {
  106. type: Boolean,
  107. default: false
  108. }
  109. },
  110. data(){
  111. return {
  112. // 甘特图部分时间表头
  113. uGanttColumns: [],
  114. // 表格使用数据
  115. tableData: [],
  116. // 甘特图显示的
  117. ganttLists: [],
  118. }
  119. },
  120. watch: {
  121. taskLists: {
  122. handler(newValue, oldValue){
  123. console.log(newValue, oldValue)
  124. if(newValue.length > 0){
  125. newValue.forEach(res=>{
  126. if(!res.end_date){
  127. res.end_date = res.start_date
  128. }
  129. })
  130. console.log(newValue)
  131. this.changeTableTree()
  132. this.getStartEndTime()
  133. this.$nextTick(() => {
  134. this.uGanttDrag()
  135. this.uGanttResizeL()
  136. this.uGanttResizeR()
  137. })
  138. // this.$forceUpdate()
  139. } else {
  140. console.log(this.setDefaultDate())
  141. this.uGanttColumns = this.setDefaultDate()
  142. }
  143. },
  144. immediate: true,
  145. // deep: true
  146. }
  147. },
  148. created (){
  149. // this.changeTableTree()
  150. // this.getStartEndTime()
  151. },
  152. mounted () {
  153. // this.uGanttDrag()
  154. // this.uGanttResizeL()
  155. // this.uGanttResizeR()
  156. },
  157. methods: {
  158. // 没有数据时设置默认当月日期
  159. setDefaultDate() {
  160. // 获取标准时间
  161. var nowdays = new Date()
  162. // 获取当年
  163. var currentYear = new Date().getFullYear()
  164. // 获取当月
  165. var currentMonth = new Date().getMonth() + 1
  166. // 获取当天
  167. var nowDay = new Date().getDate()
  168. // 获取当前月有多少天
  169. var currentMonthNum = new Date(currentYear, currentMonth, 0).getDate()
  170. // console.log(nowDay, currentMonth, currentMonthNum)
  171. //
  172. // let now = new Date(nowDay)
  173. // console.log(nowdays, now)
  174. // 当前月份所有日期集合
  175. let currentMonthArr = []
  176. for (let i = 1; i <= currentMonthNum; i++) {
  177. // let day = nowdays.setDate(i);
  178. // console.log(day)
  179. // 年月日(yyyy-MM-dd)
  180. let dateLabel = (currentMonth < 10 ? '0' + currentMonth : currentMonth) + "月" + (i < 10 ? '0' + i : i) + "日";
  181. let dateName = currentYear + "-" + (currentMonth < 10 ? '0' + currentMonth : currentMonth) + "-" + (i < 10 ? '0' + i : i);
  182. // let day =
  183. currentMonthArr.push({label: dateLabel, name: dateName});
  184. }
  185. console.log(currentMonthArr)
  186. return currentMonthArr
  187. },
  188. // 获取最开始和最末尾时间
  189. getStartEndTime(){
  190. // 开始日期(实际和计划都)排序选择最开始的一天
  191. // var hasSjStartArr = this.taskLists.filter(res=> res.reality_startDate) // 因为计划开始日期一定有,所以就不判断了
  192. var allStartDataArr = []
  193. this.taskLists.forEach(res=>{
  194. // console.log(res.reality_startDate)
  195. if(res.reality_startDate){allStartDataArr.push(res.reality_startDate)}
  196. allStartDataArr.push(res.start_date)
  197. })
  198. // 从小到大排序
  199. allStartDataArr.sort(function(a, b) {
  200. return new Date(a) - new Date(b)
  201. })
  202. // console.log(allStartDataArr)
  203. // 结束日期(实际和计划都)排序选择最后的一天
  204. var allEndDataArr = []
  205. this.taskLists.forEach(res=>{
  206. allEndDataArr.push(res.reality_endDate)
  207. allEndDataArr.push(res.end_date)
  208. })
  209. // 从大到小排序
  210. allEndDataArr.sort(function(a, b) {
  211. return new Date(b) - new Date(a)
  212. })
  213. // console.log(allEndDataArr)
  214. // 获取最开始和最结束时间
  215. const start_date = allStartDataArr[0]
  216. const end_date = allEndDataArr[0]
  217. // const end_date = setTimeData[setTimeData.length - 1].end_date
  218. // 判断两个日期之间多少天--bug:少一天
  219. var betweenDays = (new Date(end_date).getTime() - new Date(start_date).getTime()) / (1000 * 60 * 60 * 24)
  220. // console.log(betweenDays)
  221. var final_date = betweenDays > 30 ? 2 : (30 - betweenDays)
  222. // 最前面往前一天,最后面往后两天
  223. var addStart = this.addDate(start_date, -1)
  224. var addEnd = this.addDate(end_date, final_date)
  225. this.uGanttColumns = this.getBetweenDate(addStart, addEnd)
  226. },
  227. /**
  228. * 计算某个日期几天后的日期
  229. * @param {*} date 相加前的时间
  230. * @param {*} days 需要相加的天数
  231. */
  232. addDate(date, days) {
  233. var date = new Date(date);
  234. date.setDate(date.getDate() + days);
  235. var year = date.getFullYear();
  236. var month = date.getMonth() + 1;
  237. var day = date.getDate();
  238. var mm = "'" + month + "'";
  239. var dd = "'" + day + "'";
  240. if(mm.length == 3) {
  241. month = "0" + month;
  242. }
  243. if(dd.length == 3) {
  244. day = "0" + day;
  245. }
  246. var time = year + "-" + month + "-" + day
  247. return time;
  248. },
  249. /** 获取两个日期之间的间隔日期
  250. * @param { Date | number | string } startTime 开始时间 标准时间格式 时间戳格式 字符串格式(2008-08-08,2008-8-8,2008-08-8,2008-8-08)
  251. * @param { Date | number | string } endTime 结束时间 标准时间格式 时间戳格式 字符串格式(2008-08-08,2008-8-8,2008-08-8,2008-8-08)
  252. * */
  253. getBetweenDate(startTime, endTime){
  254. // 校验时间格式
  255. if (typeof startTime === 'string') {
  256. const reg = /^\d{4}-(0?[1-9]|1[0-2])-((0?[1-9])|((1|2)[0-9])|30|31)$/g
  257. if (!reg.test(startTime)) throw `开始时间:${startTime}时间格式错误`
  258. }
  259. if (typeof endTime === 'string') {
  260. const reg = /^\d{4}-(0?[1-9]|1[0-2])-((0?[1-9])|((1|2)[0-9])|30|31)$/g
  261. if (!reg.test(endTime)) throw `结束时间:${endTime}时间格式错误`
  262. }
  263. let start = new Date(startTime)
  264. let end = new Date(endTime)
  265. const resultTime = []
  266. // 当 开始时间小于结束时间的时候进入循环
  267. while (start <= end) {
  268. let getDay = start.getDate()
  269. // 月份需要加 1
  270. let getMonth = start.getMonth() + 1
  271. let getYear = start.getFullYear()
  272. /**
  273. * 拼接时间格式
  274. * (getMonth >= 10 ? `${getMonth}` : `0${getMonth}`) 自动给 小于10的时间前面补0
  275. */
  276. let setTime =
  277. `${getYear}-` +
  278. (getMonth >= 10 ? `${getMonth}` : `0${getMonth}`) +
  279. '-' +
  280. (getDay >= 10 ? `${getDay}` : `0${getDay}`)
  281. let dateLabel = (getMonth < 10 ? '0' + getMonth : getMonth) + "月" + (getDay < 10 ? '0' + getDay : getDay) + "日";
  282. resultTime.push({label: dateLabel, name: setTime})
  283. /**
  284. * 重新设置开始时间
  285. * 使用 setFullYear() 方法会自动将时间累加,返回的是时间戳格式
  286. * 使用 new Date() 将时间重新设置为标准时间
  287. * getMonth - 1 将月份时间重新还原
  288. */
  289. start = new Date(start.setFullYear(getYear, getMonth - 1, getDay + 1))
  290. }
  291. return resultTime
  292. },
  293. // 将一维数组改成多维数组
  294. changeTableTree(){
  295. // console.log(this.taskLists)
  296. this.tableData = this.changeDataTree(this.taskLists, 'id', 'pid')
  297. // this.tableData = this.handleTree(this.taskLists, 'id', "pid")
  298. // console.log(this.tableData)
  299. this.ganttLists = []
  300. this.changeTaskSort(this.tableData)
  301. },
  302. // 将一维数组转成table使用的二维数组(时间问题没改成多维)
  303. changeDataTree(Array, id, pid){
  304. let data = JSON.parse(JSON.stringify(Array));
  305. data.forEach(item => {
  306. console.log(item[pid])
  307. if(item[pid]){
  308. // 有父级的情况
  309. var index = data.findIndex(res => res[id] === item[pid])
  310. data[index].children = data[index].children || []
  311. data[index].children.push(item)
  312. } else {
  313. item.children = item.children || undefined;
  314. }
  315. })
  316. console.log(data)
  317. return data.filter(res => !res[pid])
  318. },
  319. // 将组好的多维数组转成一维数组--是为了左右对应
  320. changeTaskSort(arr){
  321. for(var i = 0; i < arr.length; i++){
  322. // arr[i].show = 'flex'
  323. if(Array.isArray(arr[i].children)){
  324. this.ganttLists.push(arr[i]);
  325. this.changeTaskSort(arr[i].children);
  326. }else{
  327. this.ganttLists.push(arr[i]);
  328. }
  329. }
  330. },
  331. // 绘制计划甘特条
  332. setPlanStyle(item){
  333. var startIndex = this.uGanttColumns.findIndex(res => res.name === item.start_date)
  334. var endIndex = this.uGanttColumns.findIndex(res => res.name === item.end_date)
  335. if(endIndex !== -1){
  336. var trIndex = this.ganttLists.findIndex(res => res.id === item.id)
  337. // console.log(trIndex, this.ganttLists)
  338. var style = {
  339. left: 70 * startIndex + 'px',
  340. width: 70 * (endIndex - startIndex + 1) + 'px',
  341. top: (this.uStyle.ganttHeight * trIndex + this.uStyle.top) + 'px',
  342. height: this.uStyle.planHeight + 'px',
  343. lineHeight: this.uStyle.planHeight + 'px',
  344. background: this.uStyle.planBg
  345. }
  346. return style
  347. }
  348. },
  349. // 绘制实际的甘特条
  350. setRealityStyle(item){
  351. // 如果没有实际开始 直接返回
  352. if(!item.reality_startDate){
  353. return
  354. }
  355. console.log(item.reality_startDate, item.reality_endDate)
  356. var startIndex = this.uGanttColumns.findIndex(res => res.name === item.reality_startDate)
  357. var endIndex
  358. // 如果没有结束时间,则显示到的当天,如果有则显示赋值的
  359. if(item.reality_endDate){
  360. endIndex = this.uGanttColumns.findIndex(res => res.name === item.reality_endDate)
  361. } else {
  362. var year = new Date().getFullYear();
  363. var month = new Date().getMonth() + 1;
  364. var day = new Date().getDate();
  365. var mm = "'" + month + "'";
  366. var dd = "'" + day + "'";
  367. if(mm.length == 3) {
  368. month = "0" + month;
  369. }
  370. if(dd.length == 3) {
  371. day = "0" + day;
  372. }
  373. var time = year + "-" + month + "-" + day
  374. endIndex = this.uGanttColumns.findIndex(res => res.name === time)
  375. }
  376. var trIndex = this.ganttLists.findIndex(res => res.id === item.id)
  377. // console.log(trIndex, this.ganttLists)
  378. var style = {
  379. left: 70 * startIndex + 'px',
  380. width: 70 * (endIndex - startIndex + 1) + 'px',
  381. top: (this.uStyle.ganttHeight * (trIndex+1) - 10) + 'px',
  382. height: this.uStyle.realityHeight,
  383. // jhpqStatus:0未完成 1已完成
  384. background: item.jhpqStatus === '0' ? this.uStyle.realityBgIncomplete : this.uStyle.realityBgCompleted
  385. }
  386. return style
  387. },
  388. // 拖拽位移事件
  389. uGanttDrag(){
  390. var _this = this
  391. // 当前甘特条可移动部分
  392. var uGanttDrag = this.$refs.uGanttDrag;
  393. // 当前甘特条
  394. var uGanttTaskItem = this.$refs.uGanttTaskItem;
  395. // 整个甘特图
  396. var uGantt = this.$refs.uGantt
  397. // console.log(this.$refs)
  398. //当前甘特区域宽度
  399. for (let i = 0; i < uGanttDrag.length; i++) {
  400. // 鼠标按下
  401. uGanttDrag[i].onmousedown = function (e) {
  402. // 整个甘特图区域滚动宽度(非展示宽度,加上看不见的滚动区域)
  403. var scrollWidth = uGantt.scrollWidth
  404. const uGanttWidth = uGantt.offsetWidth // 放在down外值不对,不知道为啥
  405. /**如果这些参数放在move设置就是会变的,放在down中就是固定值 */
  406. // 当前甘特条距离左侧距离
  407. const offsetLeft = uGanttTaskItem[i].offsetLeft
  408. // 当前甘特条宽度
  409. const offsetWidth = uGanttTaskItem[i].offsetWidth
  410. // 设置上次移动结束滚动条位置,用来判断
  411. const oldScrollLeft = uGantt.scrollLeft
  412. // 甘特图距离窗口左侧的距离
  413. var ganttWindowLeft = uGantt.getBoundingClientRect().left
  414. // 鼠标按下位置-用于计算
  415. var startX = e.clientX;
  416. var endX, moveLen
  417. // 向上取证获取当前是第几行
  418. var ganttIndex = Math.trunc(uGanttTaskItem[i].offsetTop / _this.uStyle.ganttHeight)
  419. // console.log(uGantt.scrollLeft)
  420. // console.log(startX)
  421. // 移动事件
  422. document.onmousemove = function (e) {
  423. endX = e.clientX;
  424. // 设置当前甘特条距离左侧的距离(使得左侧移动,而宽度不变,右侧对应向右移,产生移动效果)
  425. // console.log(oldScrollLeft, uGantt.scrollLeft)
  426. var distance
  427. if(uGantt.scrollLeft === oldScrollLeft){
  428. // 如果滚动条没有滚动
  429. distance = offsetLeft + ( endX - startX )
  430. } else {
  431. // 如果滚动条滚动了
  432. distance = offsetLeft + ( endX - startX ) + (uGantt.scrollLeft - oldScrollLeft)
  433. }
  434. // 判断超出左右距离时设置临界值
  435. if(distance > (scrollWidth - offsetWidth)){
  436. // 超出右边距离
  437. moveLen = scrollWidth - offsetWidth
  438. } else if(distance < 0) {
  439. // 超出左边距离
  440. moveLen = 0
  441. } else {
  442. moveLen = distance
  443. }
  444. uGanttTaskItem[i].style.left = moveLen + 'px'
  445. // 移动过程鼠标变成拖拽形状
  446. uGanttDrag[i].style.cursor = 'move'
  447. // 监听位置进行滚动条滚动
  448. // 鼠标当前位置
  449. var pointRight = ganttWindowLeft + uGanttWidth - endX
  450. // console.log(document.documentElement.clientWidth)
  451. if(pointRight < 50){
  452. uGantt.scrollLeft += 70
  453. }
  454. // 鼠标当前位置位于左侧边缘
  455. if(endX - ganttWindowLeft < 50){
  456. uGantt.scrollLeft -= 70
  457. }
  458. // uGantt.scrollHeight // 滚动条的高度
  459. }
  460. document.onmouseup = function (evt) {
  461. // 同样:不能停在一段的中间位置,所以计算使其整段整段的形式显示
  462. // console.log(uGanttTaskItem[i].offsetLeft)
  463. // 移动到最后一列的时候:时间往前+1天
  464. var StartLeft = Math.round(moveLen/70) * 70
  465. // var startNum
  466. if(StartLeft <= 0){
  467. var currentStartData = _this.addDate(_this.uGanttColumns[0].name, -1)
  468. _this.uGanttColumns = _this.getBetweenDate(currentStartData, _this.uGanttColumns[_this.uGanttColumns.length - 1].name)
  469. uGanttTaskItem[i].style.left = (Math.round(moveLen/70) + 1) * 70 + 'px';
  470. // startNum = (uGanttTaskItem[i].offsetLeft)/70
  471. } else {
  472. uGanttTaskItem[i].style.left = Math.round(moveLen/70) * 70 + 'px';
  473. }
  474. // 获取最终左侧停在第几列
  475. var startNum = (uGanttTaskItem[i].offsetLeft)/70
  476. // console.log(startNum)
  477. // var StartNum0 = startNum === 0 ? startNum + 1 : startNum
  478. // console.log(isStartNum)
  479. // 获取最终右侧停在第几列
  480. var endNum = (offsetWidth / 70) + startNum
  481. // 找到对应的时间强制赋值刷新
  482. _this.$set(_this.ganttLists[ganttIndex], 'start_date', _this.uGanttColumns[startNum].name)
  483. _this.$set(_this.ganttLists[ganttIndex], 'end_date', _this.uGanttColumns[endNum - 1].name)
  484. // 移动到最后一列的时候:时间+2天
  485. if(endNum >= _this.uGanttColumns.length){
  486. var currentEndData = _this.addDate(_this.uGanttColumns[endNum - 1].name, 2)
  487. _this.uGanttColumns = _this.getBetweenDate(_this.uGanttColumns[0].name, currentEndData)
  488. }
  489. // 鼠标变回手型
  490. uGanttDrag[i].style.cursor = 'pointer'
  491. // 释放
  492. document.onmousemove = null;
  493. document.onmouseup = null;
  494. document.releaseCapture && document.releaseCapture();
  495. }
  496. document.setCapture && document.setCapture();
  497. return false;
  498. }
  499. }
  500. },
  501. // 改变大小
  502. uGanttResizeL(){
  503. var _this = this
  504. var resize = this.$refs.uGanttResizeL;
  505. var uGanttTaskItem = this.$refs.uGanttTaskItem;
  506. for (let i = 0; i < resize.length; i++) {
  507. resize[i].onmousedown = function (e) {
  508. var startX = e.clientX;
  509. var ganttIndex = Math.trunc(uGanttTaskItem[i].offsetTop / _this.uStyle.ganttHeight)
  510. const offsetLeft = uGanttTaskItem[i].offsetLeft;
  511. const offsetWidth = uGanttTaskItem[i].offsetWidth
  512. var moveLen
  513. var ganttWidth
  514. document.onmousemove = function (e) {
  515. var endX = e.clientX;
  516. moveLen = offsetLeft - (startX - endX);
  517. ganttWidth = (startX - endX) + offsetWidth
  518. if (ganttWidth < 70){
  519. ganttWidth = 70
  520. moveLen = offsetLeft + offsetWidth - 70
  521. }
  522. uGanttTaskItem[i].style.left = moveLen + 'px';
  523. uGanttTaskItem[i].style.width = ganttWidth + 'px';
  524. }
  525. document.onmouseup = function (evt) {
  526. // 移动到最后一列的时候:时间往前+1天
  527. var StartLeft = Math.round(moveLen/70) * 70
  528. if(StartLeft <= 0){
  529. var currentStartData = _this.addDate(_this.uGanttColumns[0].name, -1)
  530. _this.uGanttColumns = _this.getBetweenDate(currentStartData, _this.uGanttColumns[_this.uGanttColumns.length - 1].name)
  531. uGanttTaskItem[i].style.left = (Math.round(moveLen/70) + 1) * 70 + 'px';
  532. // startNum = (uGanttTaskItem[i].offsetLeft)/70
  533. } else {
  534. uGanttTaskItem[i].style.left = Math.round(moveLen/70) * 70 + 'px';
  535. }
  536. uGanttTaskItem[i].style.width = Math.round(ganttWidth/70) * 70 + 'px';
  537. var num = (uGanttTaskItem[i].offsetLeft)/70
  538. // console.log(num)
  539. // 普通赋值
  540. // _this.ganttLists[0].end_date = _this.uGanttColumns[num - 1].name
  541. // 强制赋值刷新
  542. // console.log(ganttIndex)
  543. _this.$set(_this.ganttLists[ganttIndex], 'start_date', _this.uGanttColumns[num].name)
  544. // 强制刷新
  545. // _this.$forceUpdate()
  546. document.onmousemove = null;
  547. document.onmouseup = null;
  548. document.releaseCapture && document.releaseCapture();
  549. }
  550. document.setCapture && document.setCapture();
  551. return false;
  552. }
  553. }
  554. },
  555. // 改变大小
  556. uGanttResizeR(){
  557. // 方法域(onmousedown等方法中有自己的this,所以在外面重新赋值this,让内部方法调用)
  558. var _this = this
  559. // 1.获取ref
  560. var resize = this.$refs.uGanttResizeR;
  561. var uGanttTaskItem = this.$refs.uGanttTaskItem;
  562. // 整个甘特图
  563. var uGantt = this.$refs.uGantt
  564. // 2.循环监听获取当前点击的事件
  565. // 上面设置onmousedown方法会出现第一次点击无法调用的问题,所以在mounted中写(不知道为什么有类似监听的效果)
  566. // 因为ref有好几个,获取的数组,所以这里要循环
  567. for (let i = 0; i < resize.length; i++) {
  568. // 不可以设置var resize = resize[i] 下面统一使用resize的方式 =>这样会有问题
  569. // 1.鼠标按下事件:
  570. // 用[resize[i].],而不是全局document监听,这样才能监听到是哪儿一个甘特条触发了事件
  571. resize[i].onmousedown = function (e) {
  572. // 1.整个甘特图区域滚动宽度(非展示宽度,加上看不见的滚动区域)
  573. var scrollWidth = uGantt.scrollWidth
  574. const uGanttWidth = uGantt.offsetWidth
  575. // 2.获取当前点击的甘特条是第几行的,也就是对应左侧项目组中的index
  576. var ganttIndex = Math.trunc(uGanttTaskItem[i].offsetTop / _this.uStyle.ganttHeight)
  577. // 3.获取按下位置距离
  578. var startX = e.clientX;
  579. // 4.获取当前右侧拖拽区域距离父元素左侧距离(也就是右侧拖拽区域距离甘特图左边的距离:注意父子关系)
  580. var offsetLeft = resize[i].offsetLeft;
  581. // 转言之:上面那个就是当前甘特条的宽度--也就可以写作下面的方式
  582. // var offsetLeft = uGanttTaskItem[i].offsetWidth;
  583. // 5.甘特图距离窗口左侧的距离
  584. var ganttWindowLeft = uGantt.getBoundingClientRect().left
  585. // 6.定义拖拽参数--主要是为了onmouseup能调用
  586. var moveLen
  587. // 7.鼠标移动事件--注意:这里是document全局,这样就可以实现超出位置依旧可以控制拖拽
  588. document.onmousemove = function (e) {
  589. // 1.获取移动过程中实时位置
  590. var endX = e.clientX;
  591. // 2.设置当前甘特条的宽度:一开始甘特条的宽度 +(拖拽区域(鼠标点)末位置-初始位置)
  592. moveLen = offsetLeft + (endX - startX);
  593. // 3.判断如果小于1格,就不再减少
  594. if (moveLen < 70) moveLen = 70;
  595. // 这一步好像是没啥用啊?
  596. // resize[i].style.left = moveLen;
  597. // 设置甘特条随move变化:即实时宽度也为moveLen即可
  598. uGanttTaskItem[i].style.width = moveLen + 'px';
  599. // 4.监听位置进行滚动条滚动
  600. // 鼠标当前位置
  601. var pointRight = ganttWindowLeft + uGanttWidth - endX
  602. // 如果位置靠右小于50,滚动条滚动
  603. // if(pointRight < 50){
  604. // uGantt.scrollLeft += 70
  605. // }
  606. }
  607. // 鼠标抬起事件
  608. document.onmouseup = function (evt) {
  609. // 1.因为时间一段一段的,设置少于35(70的一半)最终结果向左靠,反之向右靠
  610. uGanttTaskItem[i].style.width = Math.round(moveLen/70) * 70 + 'px';
  611. // 2.获取当前甘特条最后位置在第几列--用于赋值
  612. var num = (uGanttTaskItem[i].offsetLeft + uGanttTaskItem[i].offsetWidth)/70
  613. // 移动到最后一列的时候:时间+2天
  614. if(num >= _this.uGanttColumns.length){
  615. var currentEndData = _this.addDate(_this.uGanttColumns[num - 1].name, 2)
  616. _this.uGanttColumns = _this.getBetweenDate(_this.uGanttColumns[0].name, currentEndData)
  617. }
  618. // 普通赋值
  619. // _this.ganttLists[0].end_date = _this.uGanttColumns[num - 1].name
  620. // 3.强制赋值刷新--获取列对应的时间,赋值给左侧的第ganttIndex个项目的end_date
  621. _this.$set(_this.ganttLists[ganttIndex], 'end_date', _this.uGanttColumns[num - 1].name)
  622. // 强制刷新
  623. // _this.$forceUpdate()
  624. // 4.释放鼠标事件
  625. document.onmousemove = null;
  626. document.onmouseup = null;
  627. // 5.从当前线程中的窗口释放鼠标捕获,并恢复通常的鼠标输入处理
  628. document.releaseCapture && document.releaseCapture();
  629. }
  630. // 当前线程的给定窗口设置鼠标捕获
  631. document.setCapture && document.setCapture();
  632. return false;
  633. }
  634. }
  635. },
  636. tableCellClassName({ row, column, rowIndex, columnIndex }){
  637. //把每一行的索引放进row--可以用于获取index
  638. // row.index = rowIndex;
  639. // 设置新增列
  640. if(columnIndex === this.configColumns.length - 1 && column.property === 'add'){
  641. return 'add-btn-cell u-table-cell'
  642. }
  643. return 'u-table-cell'
  644. },
  645. // 展开隐藏事件
  646. expandChange(row, isExpend){
  647. console.log(row, isExpend)
  648. if(isExpend){
  649. // 展开
  650. var index = this.ganttLists.findIndex(res=>res.id === row.id)
  651. this.ganttLists.splice(index+1, 0, ...row.children)
  652. this.$forceUpdate()
  653. } else {
  654. // 隐藏
  655. this.ganttLists = this.ganttLists.filter(res=>res.pid !== row.id)
  656. this.$forceUpdate()
  657. }
  658. },
  659. // 双击事件
  660. rowDblclick(row, column, event){
  661. // console.log(row, column, event)
  662. // 深拷贝:直接赋值会有还没点确定,甘特图部分就已经改变的bug
  663. // this.currentDbEdit = JSON.parse(JSON.stringify(row))
  664. // this.$emit('dbclick', JSON.parse(JSON.stringify(row)))
  665. const dbClickRow = JSON.parse(JSON.stringify(row))
  666. const dbClickColumn = JSON.parse(JSON.stringify(column))
  667. this.$emit('dbclick', dbClickRow, dbClickColumn)
  668. },
  669. // 单元格点击事件
  670. cellClick(row,column,event,cell){
  671. // console.log(row,column,event,cell)
  672. const clickRow = JSON.parse(JSON.stringify(row))
  673. const clickColumn = JSON.parse(JSON.stringify(column))
  674. this.$emit('cellClick', clickRow, clickColumn)
  675. },
  676. // 添加事件
  677. addRow(row, index){
  678. // console.log(row, index)
  679. this.$emit('addRow', row, index)
  680. // var current = this.ganttLists[index].children
  681. // console.log(current)
  682. // var copy = current[current.length - 1]
  683. // this.ganttLists[index].children.push(copy)
  684. // this.$forceUpdate()
  685. },
  686. // 将数据返回
  687. getSaveData(){
  688. return this.ganttLists
  689. }
  690. }
  691. }
  692. </script>
  693. <style lang="scss">
  694. .u-flex{
  695. display: flex;
  696. }
  697. .u-gantt-container{
  698. background-color: #fff;
  699. // width: calc(100vw - 240px);
  700. .el-table{
  701. user-select: none;
  702. // border-top: 1px solid #ebebeb;
  703. // border-left: 1px solid #ebebeb;
  704. // width: auto;
  705. .u-table-cell{
  706. padding: 0 !important;
  707. height: 40px !important;
  708. line-height: 40px !important;
  709. box-sizing: border-box !important;
  710. }
  711. .el-table--enable-row-transition .el-table__body td.el-table__cell{
  712. padding: 0 !important;
  713. }
  714. //
  715. .add-btn-cell .cell{
  716. padding-left: 0;
  717. padding-right: 0;
  718. }
  719. .add-btn{
  720. font-size: 28px;
  721. font-weight: bolder;
  722. color: #44c2e5;
  723. }
  724. }
  725. .u-gantt-area{
  726. flex: 1;
  727. overflow: hidden;
  728. }
  729. .u-gantt{
  730. flex: 1;
  731. border-top: 1px solid #ebebeb;
  732. border-left: 1px solid #ebebeb;
  733. // width: calc(100% - 371px);
  734. overflow: auto;
  735. }
  736. .u-gantt-title{
  737. .item-title{
  738. width: 70px;
  739. // height: 40px;
  740. // line-height: 40px;
  741. text-align: center;
  742. border-right: 1px solid #ebebeb;
  743. border-bottom: 1px solid #ebebeb;
  744. font-size: 12px;
  745. color: #787878;
  746. flex-shrink: 0; // 设置宽度不够时,元素不压缩而是超出滚动
  747. }
  748. }
  749. .u-gantt-content{
  750. position: relative;
  751. // 底层表格线框
  752. .u-gantt-bg{
  753. // position: absolute;
  754. .item-tr{
  755. // height: 40px;
  756. }
  757. .item-td{
  758. width: 70px;
  759. // height: 40px;
  760. border-right: 1px solid #ebebeb;
  761. border-bottom: 1px solid #ebebeb;
  762. flex-shrink: 0; // 设置宽度不够时,元素不压缩而是超出滚动
  763. }
  764. }
  765. // 项目甘特条
  766. .u-gantt-task{
  767. // position: absolute;
  768. // top: 0;
  769. // left: 0;
  770. // right: 0;
  771. // bottom: 0;
  772. // background-color: #b72222;
  773. .u-gantt-task-content{
  774. .u-gantt-plan{
  775. position: absolute;
  776. top: 0;
  777. left: 0;
  778. width: 0;
  779. height: 0;
  780. line-height: 0;
  781. background-color: #44c2e5;
  782. border-radius: 0;
  783. .u-gantt-task-drag{
  784. position: absolute;
  785. top: 0;
  786. left: 1px;
  787. right: 1px;
  788. bottom: 0;
  789. cursor: pointer;
  790. }
  791. .u-gantt-task-resize{
  792. position: absolute;
  793. width: 8px;
  794. height: 26px;
  795. top: 0;
  796. cursor: w-resize;
  797. }
  798. .resize-left{
  799. left: -7px;
  800. }
  801. .u-gantt-task-text{
  802. color: #fff;
  803. text-align: center;
  804. }
  805. .resize-right{
  806. right: -7px;
  807. }
  808. }
  809. .u-gantt-reality{
  810. position: absolute;
  811. top: 0;
  812. left: 0;
  813. width: 0;
  814. height: 6px;
  815. // line-height: 6px;
  816. background-color: #e5de44;
  817. border-radius: 0;
  818. }
  819. }
  820. }
  821. }
  822. }
  823. </style>