mixin.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. import XEUtils from 'xe-utils';
  2. import { getRefElem } from '../../src/util';
  3. import { getAbsolutePos, addClass, removeClass, hasControlKey } from '../../../ui/src/dom';
  4. function getCheckboxRangeRows($xeTable, evnt, params, targetTrElem, trRect, offsetClientTop, moveRange) {
  5. const props = $xeTable;
  6. const reactData = $xeTable;
  7. const internalData = $xeTable;
  8. const { showOverflow } = props;
  9. const { fullAllDataRowIdData, isResizeCellHeight } = internalData;
  10. const rowOpts = $xeTable.computeRowOpts;
  11. const cellOpts = $xeTable.computeCellOpts;
  12. const defaultRowHeight = $xeTable.computeDefaultRowHeight;
  13. const { row } = params;
  14. let countHeight = 0;
  15. let rangeRows = [];
  16. let moveSize = 0;
  17. const isDown = moveRange > 0;
  18. const { scrollYLoad } = reactData;
  19. const { afterFullData } = internalData;
  20. if (isDown) {
  21. moveSize = offsetClientTop + moveRange;
  22. }
  23. else {
  24. moveSize = (trRect.height - offsetClientTop) + Math.abs(moveRange);
  25. }
  26. if (scrollYLoad) {
  27. const _rowIndex = $xeTable.getVTRowIndex(row);
  28. const isCustomCellHeight = isResizeCellHeight || cellOpts.height || rowOpts.height;
  29. if (!isCustomCellHeight && showOverflow) {
  30. if (isDown) {
  31. rangeRows = afterFullData.slice(_rowIndex, _rowIndex + Math.ceil(moveSize / defaultRowHeight));
  32. }
  33. else {
  34. rangeRows = afterFullData.slice(_rowIndex - Math.floor(moveSize / defaultRowHeight), _rowIndex + 1);
  35. }
  36. }
  37. else {
  38. if (isDown) {
  39. for (let i = _rowIndex; i < afterFullData.length; i++) {
  40. const item = afterFullData[i];
  41. const rowid = $xeTable.getRowid(item);
  42. const rowRest = fullAllDataRowIdData[rowid] || {};
  43. countHeight += rowRest.resizeHeight || cellOpts.height || rowOpts.height || rowRest.height || defaultRowHeight;
  44. rangeRows.push(item);
  45. if (countHeight > moveSize) {
  46. return rangeRows;
  47. }
  48. }
  49. }
  50. else {
  51. for (let len = _rowIndex; len >= 0; len--) {
  52. const item = afterFullData[len];
  53. const rowid = $xeTable.getRowid(item);
  54. const rowRest = fullAllDataRowIdData[rowid] || {};
  55. countHeight += rowRest.resizeHeight || cellOpts.height || rowOpts.height || rowRest.height || defaultRowHeight;
  56. rangeRows.push(item);
  57. if (countHeight > moveSize) {
  58. return rangeRows;
  59. }
  60. }
  61. }
  62. }
  63. }
  64. else {
  65. const siblingProp = isDown ? 'next' : 'previous';
  66. while (targetTrElem && countHeight < moveSize) {
  67. const rowNodeRest = $xeTable.getRowNode(targetTrElem);
  68. if (rowNodeRest) {
  69. rangeRows.push(rowNodeRest.item);
  70. countHeight += targetTrElem.offsetHeight;
  71. targetTrElem = targetTrElem[`${siblingProp}ElementSibling`];
  72. }
  73. }
  74. }
  75. return rangeRows;
  76. }
  77. function handleMoveSelected($xeTable, evnt, args, isLeftArrow, isUpArrow, isRightArrow, isDwArrow) {
  78. const internalData = $xeTable;
  79. const { afterFullData, visibleColumn } = internalData;
  80. const params = Object.assign({}, args);
  81. const _rowIndex = $xeTable.getVTRowIndex(params.row);
  82. const _columnIndex = $xeTable.getVTColumnIndex(params.column);
  83. evnt.preventDefault();
  84. if (isUpArrow && _rowIndex > 0) {
  85. // 移动到上一行
  86. params.rowIndex = _rowIndex - 1;
  87. params.row = afterFullData[params.rowIndex];
  88. }
  89. else if (isDwArrow && _rowIndex < afterFullData.length - 1) {
  90. // 移动到下一行
  91. params.rowIndex = _rowIndex + 1;
  92. params.row = afterFullData[params.rowIndex];
  93. }
  94. else if (isLeftArrow && _columnIndex) {
  95. // 移动到左侧单元格
  96. params.columnIndex = _columnIndex - 1;
  97. params.column = visibleColumn[params.columnIndex];
  98. }
  99. else if (isRightArrow && _columnIndex < visibleColumn.length - 1) {
  100. // 移动到右侧单元格
  101. params.columnIndex = _columnIndex + 1;
  102. params.column = visibleColumn[params.columnIndex];
  103. }
  104. $xeTable.scrollToRow(params.row, params.column).then(() => {
  105. params.cell = $xeTable.getCellElement(params.row, params.column);
  106. $xeTable.handleSelected(params, evnt);
  107. });
  108. return params;
  109. }
  110. function handleCheckboxRangeEvent($xeTable, evnt, params) {
  111. const internalData = $xeTable;
  112. const { elemStore } = internalData;
  113. const bodyScrollElem = getRefElem(elemStore['main-body-scroll']);
  114. const leftScrollElem = getRefElem(elemStore['left-body-scroll']);
  115. const rightScrollElem = getRefElem(elemStore['right-body-scroll']);
  116. const { column, cell } = params;
  117. if (column.type === 'checkbox') {
  118. let bodyWrapperElem = bodyScrollElem;
  119. if (leftScrollElem && column.fixed === 'left') {
  120. bodyWrapperElem = leftScrollElem;
  121. }
  122. else if (rightScrollElem && column.fixed === 'right') {
  123. bodyWrapperElem = rightScrollElem;
  124. }
  125. if (!bodyWrapperElem) {
  126. return;
  127. }
  128. const bodyRect = bodyWrapperElem.getBoundingClientRect();
  129. const el = $xeTable.$refs.refElem;
  130. const disX = evnt.clientX;
  131. const disY = evnt.clientY;
  132. const checkboxRangeElem = bodyWrapperElem.querySelector('.vxe-table--checkbox-range');
  133. const trElem = cell.parentNode;
  134. const selectRecords = $xeTable.getCheckboxRecords();
  135. let lastRangeRows = [];
  136. const marginSize = 1;
  137. const startTop = evnt.clientY - bodyRect.y + bodyWrapperElem.scrollTop;
  138. const startLeft = evnt.clientX - bodyRect.x + bodyWrapperElem.scrollLeft;
  139. const startScrollTop = bodyWrapperElem.scrollTop;
  140. const rowHeight = trElem.offsetHeight;
  141. const trRect = trElem.getBoundingClientRect();
  142. const offsetClientTop = disY - trRect.y;
  143. let mouseScrollTimeout = null;
  144. let isMouseScrollDown = false;
  145. let mouseScrollSpaceSize = 1;
  146. const triggerEvent = (type, evnt) => {
  147. $xeTable.dispatchEvent(`checkbox-range-${type}`, {
  148. records: () => $xeTable.getCheckboxRecords(),
  149. reserves: () => $xeTable.getCheckboxReserveRecords()
  150. }, evnt);
  151. };
  152. const handleChecked = (evnt) => {
  153. const { clientX, clientY } = evnt;
  154. const offsetLeft = clientX - disX;
  155. const offsetTop = clientY - disY + (bodyWrapperElem.scrollTop - startScrollTop);
  156. let rangeHeight = Math.abs(offsetTop);
  157. let rangeWidth = Math.abs(offsetLeft);
  158. let rangeTop = startTop;
  159. let rangeLeft = startLeft;
  160. if (offsetTop < marginSize) {
  161. // 向上
  162. rangeTop += offsetTop;
  163. if (rangeTop < marginSize) {
  164. rangeTop = marginSize;
  165. rangeHeight = startTop;
  166. }
  167. }
  168. else {
  169. // 向下
  170. rangeHeight = Math.min(rangeHeight, bodyWrapperElem.scrollHeight - startTop - marginSize);
  171. }
  172. if (offsetLeft < marginSize) {
  173. // 向左
  174. rangeLeft += offsetLeft;
  175. if (rangeWidth > startLeft) {
  176. rangeLeft = marginSize;
  177. rangeWidth = startLeft;
  178. }
  179. }
  180. else {
  181. // 向右
  182. rangeWidth = Math.min(rangeWidth, bodyWrapperElem.clientWidth - startLeft - marginSize);
  183. }
  184. checkboxRangeElem.style.height = `${rangeHeight}px`;
  185. checkboxRangeElem.style.width = `${rangeWidth}px`;
  186. checkboxRangeElem.style.left = `${rangeLeft}px`;
  187. checkboxRangeElem.style.top = `${rangeTop}px`;
  188. checkboxRangeElem.style.display = 'block';
  189. const rangeRows = getCheckboxRangeRows($xeTable, evnt, params, trElem, trRect, offsetClientTop, offsetTop < marginSize ? -rangeHeight : rangeHeight);
  190. // 至少滑动 10px 才能有效匹配
  191. if (rangeHeight > 10 && rangeRows.length !== lastRangeRows.length) {
  192. const isControlKey = hasControlKey(evnt);
  193. lastRangeRows = rangeRows;
  194. if (isControlKey) {
  195. rangeRows.forEach((row) => {
  196. $xeTable.handleBatchSelectRows([row], selectRecords.indexOf(row) === -1);
  197. });
  198. }
  199. else {
  200. $xeTable.setAllCheckboxRow(false);
  201. $xeTable.handleCheckedCheckboxRow(rangeRows, true, false);
  202. }
  203. triggerEvent('change', evnt);
  204. }
  205. };
  206. // 停止鼠标滚动
  207. const stopMouseScroll = () => {
  208. clearTimeout(mouseScrollTimeout);
  209. mouseScrollTimeout = null;
  210. };
  211. // 开始鼠标滚动
  212. const startMouseScroll = (evnt) => {
  213. stopMouseScroll();
  214. mouseScrollTimeout = setTimeout(() => {
  215. if (mouseScrollTimeout) {
  216. const { scrollLeft, scrollTop, clientHeight, scrollHeight } = bodyWrapperElem;
  217. const topSize = Math.ceil(mouseScrollSpaceSize * 50 / rowHeight);
  218. if (isMouseScrollDown) {
  219. if (scrollTop + clientHeight < scrollHeight) {
  220. $xeTable.scrollTo(scrollLeft, scrollTop + topSize);
  221. startMouseScroll(evnt);
  222. handleChecked(evnt);
  223. }
  224. else {
  225. stopMouseScroll();
  226. }
  227. }
  228. else {
  229. if (scrollTop) {
  230. $xeTable.scrollTo(scrollLeft, scrollTop - topSize);
  231. startMouseScroll(evnt);
  232. handleChecked(evnt);
  233. }
  234. else {
  235. stopMouseScroll();
  236. }
  237. }
  238. }
  239. }, 50);
  240. };
  241. addClass(el, 'drag--range');
  242. document.onmousemove = evnt => {
  243. evnt.preventDefault();
  244. evnt.stopPropagation();
  245. const { clientY } = evnt;
  246. const { boundingTop } = getAbsolutePos(bodyWrapperElem);
  247. // 如果超过可视区,触发滚动
  248. if (clientY < boundingTop) {
  249. isMouseScrollDown = false;
  250. mouseScrollSpaceSize = boundingTop - clientY;
  251. if (!mouseScrollTimeout) {
  252. startMouseScroll(evnt);
  253. }
  254. }
  255. else if (clientY > boundingTop + bodyWrapperElem.clientHeight) {
  256. isMouseScrollDown = true;
  257. mouseScrollSpaceSize = clientY - boundingTop - bodyWrapperElem.clientHeight;
  258. if (!mouseScrollTimeout) {
  259. startMouseScroll(evnt);
  260. }
  261. }
  262. else if (mouseScrollTimeout) {
  263. stopMouseScroll();
  264. }
  265. handleChecked(evnt);
  266. };
  267. document.onmouseup = (evnt) => {
  268. stopMouseScroll();
  269. removeClass(el, 'drag--range');
  270. checkboxRangeElem.removeAttribute('style');
  271. document.onmousemove = null;
  272. document.onmouseup = null;
  273. triggerEvent('end', evnt);
  274. };
  275. triggerEvent('start', evnt);
  276. }
  277. }
  278. export default {
  279. methods: {
  280. // 处理 Tab 键移动
  281. moveTabSelected(args, isLeft, evnt) {
  282. const $xeTable = this;
  283. const props = $xeTable;
  284. const internalData = $xeTable;
  285. const { editConfig } = props;
  286. const { afterFullData, visibleColumn } = internalData;
  287. const editOpts = $xeTable.computeEditOpts;
  288. const rowOpts = $xeTable.computeRowOpts;
  289. const currentRowOpts = $xeTable.computeCurrentRowOpts;
  290. const columnOpts = $xeTable.computeColumnOpts;
  291. const currentColumnOpts = $xeTable.computeCurrentColumnOpts;
  292. let targetRow;
  293. let targetRowIndex;
  294. let targetColumnIndex;
  295. const params = Object.assign({}, args);
  296. const _rowIndex = $xeTable.getVTRowIndex(params.row);
  297. const _columnIndex = $xeTable.getVTColumnIndex(params.column);
  298. evnt.preventDefault();
  299. if (isLeft) {
  300. // 向左
  301. if (_columnIndex <= 0) {
  302. // 如果已经是第一列,则移动到上一行
  303. if (_rowIndex > 0) {
  304. targetRowIndex = _rowIndex - 1;
  305. targetRow = afterFullData[targetRowIndex];
  306. targetColumnIndex = visibleColumn.length - 1;
  307. }
  308. }
  309. else {
  310. targetColumnIndex = _columnIndex - 1;
  311. }
  312. }
  313. else {
  314. if (_columnIndex >= visibleColumn.length - 1) {
  315. // 如果已经是第一列,则移动到上一行
  316. if (_rowIndex < afterFullData.length - 1) {
  317. targetRowIndex = _rowIndex + 1;
  318. targetRow = afterFullData[targetRowIndex];
  319. targetColumnIndex = 0;
  320. }
  321. }
  322. else {
  323. targetColumnIndex = _columnIndex + 1;
  324. }
  325. }
  326. const targetColumn = visibleColumn[targetColumnIndex];
  327. if (targetColumn) {
  328. if (targetRow) {
  329. params.rowIndex = targetRowIndex;
  330. params.row = targetRow;
  331. }
  332. else {
  333. params.rowIndex = _rowIndex;
  334. }
  335. params.columnIndex = targetColumnIndex;
  336. params.column = targetColumn;
  337. params.cell = $xeTable.getCellElement(params.row, params.column);
  338. if (rowOpts.isCurrent && currentRowOpts.isFollowSelected) {
  339. $xeTable.triggerCurrentRowEvent(evnt, params);
  340. }
  341. if (columnOpts.isCurrent && currentColumnOpts.isFollowSelected) {
  342. $xeTable.triggerCurrentColumnEvent(evnt, params);
  343. }
  344. if (editConfig) {
  345. if (editOpts.trigger === 'click' || editOpts.trigger === 'dblclick') {
  346. if (editOpts.mode === 'row') {
  347. $xeTable.handleEdit(params, evnt);
  348. }
  349. else {
  350. $xeTable.scrollToRow(params.row, params.column)
  351. .then(() => {
  352. $xeTable.handleSelected(params, evnt);
  353. });
  354. }
  355. }
  356. }
  357. else {
  358. $xeTable.scrollToRow(params.row, params.column)
  359. .then(() => {
  360. $xeTable.handleSelected(params, evnt);
  361. });
  362. }
  363. }
  364. },
  365. // 处理当前行方向键移动
  366. moveCurrentRow(isUpArrow, isDwArrow, evnt) {
  367. const $xeTable = this;
  368. const props = $xeTable;
  369. const reactData = $xeTable;
  370. const internalData = $xeTable;
  371. const { treeConfig } = props;
  372. const { currentRow } = reactData;
  373. const { afterFullData } = internalData;
  374. const treeOpts = $xeTable.computeTreeOpts;
  375. const childrenField = treeOpts.children || treeOpts.childrenField;
  376. let targetRow;
  377. if (currentRow) {
  378. if (treeConfig) {
  379. const { index, items } = XEUtils.findTree(afterFullData, item => item === currentRow, { children: childrenField });
  380. if (isUpArrow && index > 0) {
  381. targetRow = items[index - 1];
  382. }
  383. else if (isDwArrow && index < items.length - 1) {
  384. targetRow = items[index + 1];
  385. }
  386. }
  387. else {
  388. const _rowIndex = $xeTable.getVTRowIndex(currentRow);
  389. if (isUpArrow && _rowIndex > 0) {
  390. targetRow = afterFullData[_rowIndex - 1];
  391. }
  392. else if (isDwArrow && _rowIndex < afterFullData.length - 1) {
  393. targetRow = afterFullData[_rowIndex + 1];
  394. }
  395. }
  396. }
  397. else {
  398. targetRow = afterFullData[0];
  399. }
  400. if (targetRow) {
  401. evnt.preventDefault();
  402. const params = {
  403. $table: $xeTable,
  404. row: targetRow,
  405. rowIndex: $xeTable.getRowIndex(targetRow),
  406. $rowIndex: $xeTable.getVMRowIndex(targetRow)
  407. };
  408. $xeTable.scrollToRow(targetRow)
  409. .then(() => $xeTable.triggerCurrentRowEvent(evnt, params));
  410. }
  411. },
  412. // 处理当前列方向键移动
  413. moveCurrentColumn(isLeftArrow, isRightArrow, evnt) {
  414. const $xeTable = this;
  415. const reactData = $xeTable;
  416. const internalData = $xeTable;
  417. const { currentColumn } = reactData;
  418. const { visibleColumn } = internalData;
  419. let targetCol = null;
  420. if (currentColumn) {
  421. const _columnIndex = $xeTable.getVTColumnIndex(currentColumn);
  422. if (isLeftArrow && _columnIndex > 0) {
  423. targetCol = visibleColumn[_columnIndex - 1];
  424. }
  425. else if (isRightArrow && _columnIndex < visibleColumn.length - 1) {
  426. targetCol = visibleColumn[_columnIndex + 1];
  427. }
  428. }
  429. else {
  430. targetCol = visibleColumn[0];
  431. }
  432. if (targetCol) {
  433. evnt.preventDefault();
  434. const params = {
  435. $table: $xeTable,
  436. column: targetCol,
  437. columnIndex: $xeTable.getColumnIndex(targetCol),
  438. $columnIndex: $xeTable.getVMColumnIndex(targetCol)
  439. };
  440. $xeTable.scrollToColumn(targetCol)
  441. .then(() => $xeTable.triggerCurrentColumnEvent(evnt, params));
  442. }
  443. },
  444. // 处理可编辑方向键移动
  445. moveArrowSelected(args, isLeftArrow, isUpArrow, isRightArrow, isDwArrow, evnt) {
  446. const $xeTable = this;
  447. const props = $xeTable;
  448. const { highlightCurrentRow, highlightCurrentColumn } = props;
  449. const rowOpts = $xeTable.computeRowOpts;
  450. const currentRowOpts = $xeTable.computeCurrentRowOpts;
  451. const columnOpts = $xeTable.computeColumnOpts;
  452. const currentColumnOpts = $xeTable.computeCurrentColumnOpts;
  453. const params = handleMoveSelected($xeTable, evnt, args, isLeftArrow, isUpArrow, isRightArrow, isDwArrow);
  454. if (rowOpts.isCurrent || highlightCurrentRow) {
  455. if (currentRowOpts.isFollowSelected) {
  456. $xeTable.triggerCurrentRowEvent(evnt, params);
  457. }
  458. else {
  459. // 当前行按键上下移动
  460. if ((isUpArrow || isDwArrow) && (rowOpts.isCurrent || highlightCurrentRow)) {
  461. $xeTable.moveCurrentRow(isUpArrow, isDwArrow, evnt);
  462. }
  463. }
  464. }
  465. if (columnOpts.isCurrent || highlightCurrentColumn) {
  466. if (currentColumnOpts.isFollowSelected) {
  467. $xeTable.triggerCurrentColumnEvent(evnt, params);
  468. }
  469. else {
  470. // 当前行按键左右移动
  471. if ((isLeftArrow || isRightArrow) && (columnOpts.isCurrent || highlightCurrentColumn)) {
  472. $xeTable.moveCurrentColumn(isLeftArrow, isRightArrow, evnt);
  473. }
  474. }
  475. }
  476. },
  477. moveEnterSelected(args, isLeftArrow, isUpArrow, isRightArrow, isDwArrow, evnt) {
  478. const $xeTable = this;
  479. const props = $xeTable;
  480. const { highlightCurrentRow, highlightCurrentColumn } = props;
  481. const rowOpts = $xeTable.computeRowOpts;
  482. const currentRowOpts = $xeTable.computeCurrentRowOpts;
  483. const columnOpts = $xeTable.computeColumnOpts;
  484. const currentColumnOpts = $xeTable.computeCurrentColumnOpts;
  485. const params = handleMoveSelected($xeTable, evnt, args, isLeftArrow, isUpArrow, isRightArrow, isDwArrow);
  486. if (((rowOpts.isCurrent || highlightCurrentRow) && currentRowOpts.isFollowSelected)) {
  487. $xeTable.triggerCurrentRowEvent(evnt, params);
  488. }
  489. if ((columnOpts.isCurrent || highlightCurrentColumn) && currentColumnOpts.isFollowSelected) {
  490. $xeTable.triggerCurrentColumnEvent(evnt, params);
  491. }
  492. },
  493. // 处理可编辑方向键移动
  494. moveSelected(args, isLeftArrow, isUpArrow, isRightArrow, isDwArrow, evnt) {
  495. const $xeTable = this;
  496. handleMoveSelected($xeTable, evnt, args, isLeftArrow, isUpArrow, isRightArrow, isDwArrow);
  497. },
  498. handleCellMousedownEvent(evnt, params) {
  499. const $xeTable = this;
  500. const props = $xeTable;
  501. const { editConfig, checkboxConfig, mouseConfig } = props;
  502. const checkboxOpts = $xeTable.computeCheckboxOpts;
  503. const mouseOpts = $xeTable.computeMouseOpts;
  504. const editOpts = $xeTable.computeEditOpts;
  505. if (mouseConfig && mouseOpts.area && $xeTable.triggerCellAreaModnEvent) {
  506. return $xeTable.triggerCellAreaModnEvent(evnt, params);
  507. }
  508. else {
  509. if (checkboxConfig && checkboxOpts.range) {
  510. handleCheckboxRangeEvent($xeTable, evnt, params);
  511. }
  512. if (mouseConfig && mouseOpts.selected) {
  513. if (!editConfig || editOpts.mode === 'cell') {
  514. $xeTable.handleSelected(params, evnt);
  515. }
  516. }
  517. }
  518. }
  519. }
  520. };