import { defineComponent, h, ref, onMounted, onUnmounted, PropType, watch } from 'vue' import ResizeObserver from 'resize-observer-polyfill' import { useDrag } from './drag' import './index.less' interface CatTableColumn { prop: string; label: string; width: number; } /** * 通用表格组件(开发中) */ export default defineComponent({ props: { columns: { type: Array as PropType, default: () => ([]) }, rows: { default: () => ([]) } }, setup(props) { const { dragStart, drag, dragEnd } = useDrag(); const tableElement = ref(); const headerElement = ref(); const bodyElement = ref(); const headerColumns = ref([]); const scrollbarWidth = ref(0); // 滚动条宽度 const tableStyle = () => ({ width: headerColumns.value.reduce((pre, cur) => pre += cur.width, 0) + 'px' }) const renderColGroup = () => h('colgroup', headerColumns.value.map((column) => h('col', { width: column.width }))); const renderHeader = () => h('div', { class: 'cat-table__wrapper' }, [ h('table', { ref: headerElement, class: 'cat-table__header', cellspacing: 0, cellpadding: 0, style: tableStyle(), }, [ renderColGroup(), h('thead', h('tr', headerColumns.value.map((column) => h('th', h('div', { class: 'cell' }, column.label))))) ]) ]) const renderBody = () => h('div', { class: 'cat-table__wrapper' }, [ h('table', { ref: bodyElement, class: 'cat-table__body', cellspacing: 0, cellpadding: 0, style: tableStyle(), }, [ renderColGroup(), h('tbody', props.rows.map((row) => { return h('tr', headerColumns.value.map((column) => h('td', h('div', { class: 'cell' }, row[column.prop])))) })) ]), ...scrollbarWidth.value > 0 ? [renderScrollbar()] : [], ]) const renderScrollbar = () => h('div', { class: 'cat-scrollbar', }, h('div', { class: 'cat-scrollbar__thumb', draggable: true, ondragstart: dragStart, ondrag: drag, ondragend: dragEnd, style: { width: scrollbarWidth.value + 'px' }, })) const columnResize = (() => { let timer = 0; let prevWidth = 0; // 记录上次宽度 return () => { const table = tableElement.value; if (table) { const miniWidth = 48; // 限制列宽最小宽度 headerColumns.value = []; // 计算出列宽的组合数据 const colgroup = props.columns.reduce((pre, cur) => { if (cur.width > miniWidth) { pre.width -= cur.width; pre.length -= 1; } return pre; }, { width: table.clientWidth, length: props.columns.length, }) // 列宽平均分配 props.columns.forEach((column) => { const item = { ...column }; if (!item.width) { const width = Math.floor(colgroup.width / colgroup.length); item.width = width > miniWidth ? width : miniWidth; } headerColumns.value.push(item) }) // 判断是否显示滚动条 if (colgroup.width !== prevWidth) { clearTimeout(timer); scrollbarWidth.value = 0; timer = window.setTimeout(() => { prevWidth = colgroup.width; const body = bodyElement.value; if (body) { const ratio = table.clientWidth / body.clientWidth; if (ratio < 1) { scrollbarWidth.value = table.clientWidth * ratio; } } }, 200) } } } })() watch(() => props.columns, () => columnResize()); onMounted(() => { const el = tableElement.value; if (el) { // 监听元素变化 const resizeObserver = new ResizeObserver(columnResize); resizeObserver.observe(el); onUnmounted(() => { resizeObserver.unobserve(el); }) } }) return () => h('div', { ref: tableElement, class: 'cat-table' }, [ ...props.columns.length ? [renderHeader()] : [], ...props.rows.length ? [renderBody()] : [], ]) } })