index.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import { defineComponent, h, ref, onMounted, onUnmounted, PropType, watch } from 'vue'
  2. import ResizeObserver from 'resize-observer-polyfill'
  3. import { useDrag } from './drag'
  4. import './index.less'
  5. interface CatTableColumn {
  6. prop: string;
  7. label: string;
  8. width: number;
  9. }
  10. /**
  11. * 通用表格组件(开发中)
  12. */
  13. export default defineComponent({
  14. props: {
  15. columns: {
  16. type: Array as PropType<CatTableColumn[]>,
  17. default: () => ([])
  18. },
  19. rows: {
  20. default: () => ([])
  21. }
  22. },
  23. setup(props) {
  24. const { dragStart, drag, dragEnd } = useDrag();
  25. const tableElement = ref<HTMLDivElement>();
  26. const headerElement = ref<HTMLTableElement>();
  27. const bodyElement = ref<HTMLTableElement>();
  28. const headerColumns = ref<CatTableColumn[]>([]);
  29. const scrollbarWidth = ref(0); // 滚动条宽度
  30. const tableStyle = () => ({
  31. width: headerColumns.value.reduce((pre, cur) => pre += cur.width, 0) + 'px'
  32. })
  33. const renderColGroup = () => h('colgroup', headerColumns.value.map((column) => h('col', { width: column.width })));
  34. const renderHeader = () => h('div', {
  35. class: 'cat-table__wrapper'
  36. }, [
  37. h('table', {
  38. ref: headerElement,
  39. class: 'cat-table__header',
  40. cellspacing: 0,
  41. cellpadding: 0,
  42. style: tableStyle(),
  43. }, [
  44. renderColGroup(),
  45. h('thead', h('tr', headerColumns.value.map((column) => h('th', h('div', { class: 'cell' }, column.label)))))
  46. ])
  47. ])
  48. const renderBody = () => h('div', {
  49. class: 'cat-table__wrapper'
  50. }, [
  51. h('table', {
  52. ref: bodyElement,
  53. class: 'cat-table__body',
  54. cellspacing: 0,
  55. cellpadding: 0,
  56. style: tableStyle(),
  57. }, [
  58. renderColGroup(),
  59. h('tbody', props.rows.map((row) => {
  60. return h('tr', headerColumns.value.map((column) => h('td', h('div', { class: 'cell' }, row[column.prop]))))
  61. }))
  62. ]),
  63. ...scrollbarWidth.value > 0 ? [renderScrollbar()] : [],
  64. ])
  65. const renderScrollbar = () => h('div', {
  66. class: 'cat-scrollbar',
  67. }, h('div', {
  68. class: 'cat-scrollbar__thumb',
  69. draggable: true,
  70. ondragstart: dragStart,
  71. ondrag: drag,
  72. ondragend: dragEnd,
  73. style: {
  74. width: scrollbarWidth.value + 'px'
  75. },
  76. }))
  77. const columnResize = (() => {
  78. let timer = 0;
  79. let prevWidth = 0; // 记录上次宽度
  80. return () => {
  81. const table = tableElement.value;
  82. if (table) {
  83. const miniWidth = 48; // 限制列宽最小宽度
  84. headerColumns.value = [];
  85. // 计算出列宽的组合数据
  86. const colgroup = props.columns.reduce((pre, cur) => {
  87. if (cur.width > miniWidth) {
  88. pre.width -= cur.width;
  89. pre.length -= 1;
  90. }
  91. return pre;
  92. }, {
  93. width: table.clientWidth,
  94. length: props.columns.length,
  95. })
  96. // 列宽平均分配
  97. props.columns.forEach((column) => {
  98. const item = { ...column };
  99. if (!item.width) {
  100. const width = Math.floor(colgroup.width / colgroup.length);
  101. item.width = width > miniWidth ? width : miniWidth;
  102. }
  103. headerColumns.value.push(item)
  104. })
  105. // 判断是否显示滚动条
  106. if (colgroup.width !== prevWidth) {
  107. clearTimeout(timer);
  108. scrollbarWidth.value = 0;
  109. timer = window.setTimeout(() => {
  110. prevWidth = colgroup.width;
  111. const body = bodyElement.value;
  112. if (body) {
  113. const ratio = table.clientWidth / body.clientWidth;
  114. if (ratio < 1) {
  115. scrollbarWidth.value = table.clientWidth * ratio;
  116. }
  117. }
  118. }, 200)
  119. }
  120. }
  121. }
  122. })()
  123. watch(() => props.columns, () => columnResize());
  124. onMounted(() => {
  125. const el = tableElement.value;
  126. if (el) {
  127. // 监听元素变化
  128. const resizeObserver = new ResizeObserver(columnResize);
  129. resizeObserver.observe(el);
  130. onUnmounted(() => {
  131. resizeObserver.unobserve(el);
  132. })
  133. }
  134. })
  135. return () => h('div', {
  136. ref: tableElement,
  137. class: 'cat-table'
  138. }, [
  139. ...props.columns.length ? [renderHeader()] : [],
  140. ...props.rows.length ? [renderBody()] : [],
  141. ])
  142. }
  143. })