sortableTable.html 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. {{define "component/sortableTableTrigger"}}
  2. <a-icon type="drag"
  3. class="sortable-icon"
  4. style="cursor: move;"
  5. @mouseup="mouseUpHandler"
  6. @mousedown="mouseDownHandler"
  7. @click="clickHandler" />
  8. {{end}}
  9. {{define "component/sortableTable"}}
  10. <script>
  11. const DRAGGABLE_ROW_CLASS = 'draggable-row';
  12. const findParentRowElement = (el) => {
  13. if (!el || !el.tagName) {
  14. return null;
  15. } else if (el.classList.contains(DRAGGABLE_ROW_CLASS)) {
  16. return el;
  17. } else if (el.parentNode) {
  18. return findParentRowElement(el.parentNode);
  19. } else {
  20. return null;
  21. }
  22. }
  23. Vue.component('a-table-sortable', {
  24. data() {
  25. return {
  26. sortingElementIndex: null,
  27. newElementIndex: null,
  28. };
  29. },
  30. props: ['data-source', 'customRow'],
  31. inheritAttrs: false,
  32. provide() {
  33. const sortable = {}
  34. Object.defineProperty(sortable, "setSortableIndex", {
  35. enumerable: true,
  36. get: () => this.setCurrentSortableIndex,
  37. });
  38. Object.defineProperty(sortable, "resetSortableIndex", {
  39. enumerable: true,
  40. get: () => this.resetSortableIndex,
  41. });
  42. return {
  43. sortable,
  44. }
  45. },
  46. render: function (createElement) {
  47. return createElement('a-table', {
  48. class: {
  49. 'ant-table-is-sorting': this.isDragging(),
  50. },
  51. props: {
  52. ...this.$attrs,
  53. 'data-source': this.records,
  54. customRow: (record, index) => this.customRowRender(record, index),
  55. },
  56. on: this.$listeners,
  57. nativeOn: {
  58. drop: (e) => this.dropHandler(e),
  59. },
  60. scopedSlots: this.$scopedSlots,
  61. }, this.$slots.default, )
  62. },
  63. created() {
  64. this.$memoSort = {};
  65. },
  66. methods: {
  67. isDragging() {
  68. const currentIndex = this.sortingElementIndex;
  69. return currentIndex !== null && currentIndex !== undefined;
  70. },
  71. resetSortableIndex(e, index) {
  72. this.sortingElementIndex = null;
  73. this.newElementIndex = null;
  74. this.$memoSort = {};
  75. },
  76. setCurrentSortableIndex(e, index) {
  77. this.sortingElementIndex = index;
  78. },
  79. dragStartHandler(e, index) {
  80. if (!this.isDragging()) {
  81. e.preventDefault();
  82. return;
  83. }
  84. const hideDragImage = this.$el.cloneNode(true);
  85. hideDragImage.id = "hideDragImage-hide";
  86. hideDragImage.style.opacity = 0;
  87. e.dataTransfer.setDragImage(hideDragImage, 0, 0);
  88. },
  89. dragStopHandler(e, index) {
  90. const hideDragImage = document.getElementById('hideDragImage-hide');
  91. if (hideDragImage) hideDragImage.remove();
  92. this.resetSortableIndex(e, index);
  93. },
  94. dragOverHandler(e, index) {
  95. if (!this.isDragging()) {
  96. return;
  97. }
  98. e.preventDefault();
  99. const currentIndex = this.sortingElementIndex;
  100. if (index === currentIndex) {
  101. this.newElementIndex = null;
  102. return;
  103. }
  104. const row = findParentRowElement(e.target);
  105. if (!row) {
  106. return;
  107. }
  108. const rect = row.getBoundingClientRect();
  109. const offsetTop = e.pageY - rect.top;
  110. if (offsetTop < rect.height / 2) {
  111. this.newElementIndex = Math.max(index - 1, 0);
  112. } else {
  113. this.newElementIndex = index;
  114. }
  115. },
  116. dropHandler(e) {
  117. if (this.isDragging()) {
  118. this.$emit('onsort', this.sortingElementIndex, this.newElementIndex);
  119. }
  120. },
  121. customRowRender(record, index) {
  122. const parentMethodResult = this.customRow?.(record, index) || {};
  123. const newIndex = this.newElementIndex;
  124. const currentIndex = this.sortingElementIndex;
  125. return {
  126. ...parentMethodResult,
  127. attrs: {
  128. ...(parentMethodResult?.attrs || {}),
  129. draggable: true,
  130. },
  131. on: {
  132. ...(parentMethodResult?.on || {}),
  133. dragstart: (e) => this.dragStartHandler(e, index),
  134. dragend: (e) => this.dragStopHandler(e, index),
  135. dragover: (e) => this.dragOverHandler(e, index),
  136. },
  137. class: {
  138. ...(parentMethodResult?.class || {}),
  139. [DRAGGABLE_ROW_CLASS]: true,
  140. ['dragging']: this.isDragging()
  141. ? (newIndex === null ? index === currentIndex : index === newIndex)
  142. : false,
  143. },
  144. };
  145. }
  146. },
  147. computed: {
  148. records() {
  149. const newIndex = this.newElementIndex;
  150. const currentIndex = this.sortingElementIndex;
  151. if (!this.isDragging() || newIndex === null || currentIndex === newIndex) {
  152. return this.dataSource;
  153. }
  154. if (this.$memoSort.newIndex === newIndex) {
  155. return this.$memoSort.list;
  156. }
  157. let list = [...this.dataSource];
  158. list.splice(newIndex, 0, list.splice(currentIndex, 1)[0]);
  159. this.$memoSort = {
  160. newIndex,
  161. list,
  162. };
  163. return list;
  164. }
  165. }
  166. });
  167. Vue.component('table-sort-trigger', {
  168. template: `{{template "component/sortableTableTrigger"}}`,
  169. props: ['item-index'],
  170. inject: ['sortable'],
  171. methods: {
  172. mouseDownHandler(e) {
  173. if (this.sortable) {
  174. this.sortable.setSortableIndex(e, this.itemIndex);
  175. }
  176. },
  177. mouseUpHandler(e) {
  178. if (this.sortable) {
  179. this.sortable.resetSortableIndex(e, this.itemIndex);
  180. }
  181. },
  182. clickHandler(e) {
  183. e.preventDefault();
  184. },
  185. }
  186. })
  187. </script>
  188. <style>
  189. @media only screen and (max-width: 767px) {
  190. .sortable-icon {
  191. display: none;
  192. }
  193. }
  194. .ant-table-is-sorting .draggable-row td {
  195. background-color: #ffffff !important;
  196. }
  197. .dark .ant-table-is-sorting .draggable-row td {
  198. background-color: var(--dark-color-surface-100) !important;
  199. }
  200. .ant-table-is-sorting .dragging td {
  201. background-color: rgb(232 244 242) !important;
  202. color: rgba(0, 0, 0, 0.3);
  203. }
  204. .dark .ant-table-is-sorting .dragging td {
  205. background-color: var(--dark-color-table-hover) !important;
  206. color: rgba(255, 255, 255, 0.3);
  207. }
  208. .ant-table-is-sorting .dragging {
  209. opacity: 1;
  210. box-shadow: 1px -2px 2px #008771;
  211. transition: all 0.2s;
  212. }
  213. .ant-table-is-sorting .dragging .ant-table-row-index {
  214. opacity: 0.3;
  215. }
  216. </style>
  217. {{end}}