UNPKG

cloud-ui.vusion

Version:
618 lines (606 loc) 29.5 kB
import { ellipsisTitle } from 'proto-ui.vusion/src/base/directives'; import { getStyle, getScrollSize } from '../base/utils/style'; // import { deepCopy } from '../base/utils/index'; import { cloneDeep } from 'lodash'; export default { name: 'u-resize-table', props: { showHeader: { type: Boolean, default: true }, data: { type: Array, default() { return []; } }, // minWidth: { type: Number, default: 46 }, color: { type: String, default: '' }, ellipsis: { type: Boolean, default: true }, // 应该支持多选的形式 暂未支持 应用场景不多 defaultSort: { type: Object, default() { return { title: undefined, order: undefined, }; }, }, width: [String, Number], // 宽度设置 height: [String, Number], // 高度设置 maxHeight: [String, Number], minHeight: [String, Number], pattern: { type: String, default: 'normal' }, limit: { type: [String, Number], default: 5 }, // 配合 pattern 值是limit使用 limitText: { type: String, default: '查看更多' }, allText: { type: String, default: '收起' }, allChecked: { type: Boolean, default: false }, loading: { type: Boolean, default: false }, loadText: { type: String, default: '' }, // 加载状态显示的文本 noDataText: { type: String, default: '暂无数据' }, // 无数据时的提示文案 border: { type: Boolean, default: false }, // 显示边框 defaultText: { type: String, default: '-' }, // 无数据显示内容 visible: { type: Boolean, default: true }, sortMethod: Function, // 排序自定义 sortRemoteMethod: Function, // 异步排序自定义 filterMethod: Function, // 过滤自定义 rowClassName: { type: Function, default() { return ''; } }, // 自定义表格单行的样式 }, data() { return { itemColumns: [], initPoint: { pageX: 0, pageY: 0, }, currentDiff: { offsetX: 0, offsetY: 0, }, currentIndex: undefined, isDown: false, // 判断是否mousedown direction: undefined, // 移动的方向 minIndexs: [], // 存放列的宽度达到最小宽度限制列的索引 sumOffset: 0, bodyTableHeight: undefined, scrollWidth: undefined, isYScroll: false, tdata: [], copyData: [], filterData: [], allSel: this.allChecked, // type类型是selection currentSortColumn: undefined, // 表示当前排序列,目前支持单选 currentSort: this.defaultSort, copyTdata: [], // tdata的复制版本主要用来过滤 filterTdata: undefined, // 用来记录当前filter列过滤后符合条件的所有数据 currentFilter: { title: undefined, value: undefined, column: undefined, }, maxBodyHeight: undefined, minBodyHeight: undefined, }; }, directives: { ellipsisTitle }, created() { this.$on('add-item-vm', (itemVM) => { itemVM.parentVM = this; this.itemColumns.push(itemVM); }); this.$on('remove-item-vm', (itemVM) => { itemVM.parentVM = undefined; this.itemColumns.splice(this.itemColumns.indexOf(itemVM), 1); }); document.body.addEventListener('mousemove', this.onMouseMove); document.body.addEventListener('mouseup', this.onMouseUp); }, mounted() { // 需要在没有给表格列赋值初始化的情况下 获取每列的宽度 if (this.pattern === 'limit') this.tdata = this.initTableData(this.limit); else this.tdata = this.initTableData(); window.addEventListener('resize', this.onResize, false); this.$nextTick(() => { this.itemColumns.forEach((item, index) => { if (!item.digitWidth) item.copyWidth = item.currentWidth = item.digitWidth = parseFloat(getStyle(this.$refs.thColumn[index], 'width')); }); }); this.handleSize(); // 计算样式 }, watch: { defaultSort(newValue) { this.currentSort = newValue; }, data: { deep: true, handler(newValue) { if (this.pattern === 'limit') this.tdata = this.initTableData(this.limit); else this.tdata = this.initTableData(); const flag = this.itemColumns.some((column) => column.type === 'filter'); if (flag) { // 在有filter列的情况下 数据如果发生变化是需要对数据进行过滤显示的 let columnIndex; if (this.currentFilter.title === undefined) { this.itemColumns.some((item, index) => { if (item.filter) { this.currentFilter.title = item.title; this.currentFilter.value = item.value; this.currentFilter.column = item; columnIndex = index; return true; } return false; }); } else { this.itemColumns.some((column, index) => { if (column.title === this.currentFilter.title) { this.currentFilter.column = column; columnIndex = index; return true; } return false; }); } const column = this.currentFilter.column; const value = this.currentFilter.value; this.filterTdata = this.tdata = this.copyTdata.filter((item) => { if (this.filterMethod || column.filterMethod) { const filterMethod = this.filterMethod || column.filterMethod; return filterMethod(value, item[column.label], item, column); } else return item[column.label] === value; }); } if (this.pattern === 'limit') this.tdata = this.tdata.slice(0, this.limit); if (this.currentSortColumn && (!this.currentSortColumn.sortRemoteMethod || !this.sortRemoteMethod)) { const order = this.defaultSort.order === 'asc' ? -1 : 1; this.sortData(this.currentSortColumn, order, 'change'); } this.handleResize(); }, }, }, computed: { expandedColumn() { return this.itemColumns.filter((column) => column.type === 'expand')[0]; }, allDisabled() { return this.tdata.every((item) => item.disabled); }, }, methods: { rowClsName(index) { return this.rowClassName && this.rowClassName(index, this.tdata[index]); }, onMouseDown(e, index) { // this.pauseEvent(e); this.isDown = true; this.initPoint.pageX = e.clientX; this.initPoint.pageY = e.clientY; this.currentIndex = index; this.itemColumns.forEach((item) => item.currentWidth = item.digitWidth); }, onMouseMove(e) { this.pauseEvent(e); // 指定位置点击 才能触发move事件 // 移动规则: 如果移动的距离不能被右边的列均分,则优先将移动距离给靠近移动列的其他列 例如右边有3列,移动距离2,则右边的移动距离是1,1,0 if (this.isDown && this.currentIndex !== undefined) { this.currentDiff.offsetX = e.clientX - this.initPoint.pageX; this.$nextTick(() => { // changeLen 表示当前列右边还剩下几个可以改变宽度的列 let changeLen = this.itemColumns.length - this.currentIndex - 1; this.direction = this.currentDiff.offsetX < 0 ? 'left' : 'right'; if (this.direction === 'left') { if (this.itemColumns[this.currentIndex].digitWidth <= this.itemColumns[this.currentIndex].currentMinWidth) return false; } else { for (let i = this.currentIndex + 1; i < this.itemColumns.length; i++) { if (this.minIndexs.indexOf(i) !== -1) changeLen += -1; } } if (changeLen === 0) return false; // 为了防止抖动的出现,需要用 Math.floor 函数特殊处理下 满足最小宽度限制 // 向左右移动会出现距离不能完全被列整除的情况 为保证均匀需要找出列最大最小加上多余的宽度 // 向左移动需要特殊处理下移动距离导致currentIndex列的宽度小于最小宽度 // 向左移动 距离增加的时候 有最大宽度限制 let averageOffset = 0; let remainder = 0; let amendRight = 0; // 会出现向右移动 let amendLeft = 0; // 会出现向左移动导致当前列最小宽度小于46, 需要特殊处理 if (this.direction === 'left' && (this.itemColumns[this.currentIndex].digitWidth + this.currentDiff.offsetX < this.itemColumns[this.currentIndex].currentMinWidth)) { amendLeft = this.itemColumns[this.currentIndex].digitWidth - this.itemColumns[this.currentIndex].currentMinWidth; averageOffset = Math.floor(amendLeft / changeLen); remainder = Math.abs(amendLeft - averageOffset * changeLen); } else { averageOffset = Math.floor(Math.abs(this.currentDiff.offsetX) / changeLen); remainder = Math.abs(Math.abs(this.currentDiff.offsetX) - averageOffset * changeLen); } let changColumnIndex; // let maxOffset; if (this.direction === 'left') { changColumnIndex = this.getColumnIndex(this.currentIndex + 1, 'min'); } else { changColumnIndex = this.getColumnIndex(this.currentIndex + 1, 'max'); } this.itemColumns.forEach((item, index) => { if (index > this.currentIndex) { if (this.direction === 'left') { item.digitWidth += averageOffset; if (index === changColumnIndex) item.digitWidth += remainder; item.copyWidth = item.currentWidth = item.digitWidth; } else if (this.direction === 'right' && item.digitWidth > item.currentMinWidth) { // 向右移动会导致列宽度小于最小宽度 需要手动修正误差操作 item.digitWidth -= averageOffset; if (index === changColumnIndex) item.digitWidth -= remainder; if (item.digitWidth < item.currentMinWidth) amendRight = amendRight + parseFloat(item.currentMinWidth) - item.digitWidth; item.currentWidth = item.digitWidth = item.digitWidth < item.currentMinWidth ? parseFloat(item.currentMinWidth) : item.digitWidth; } // 有滚动条的情况下 最后一列需要特殊处理 if (index === this.itemColumns.length - 1 && this.isYScroll) item.copyWidth = item.digitWidth - this.scrollWidth; else item.copyWidth = item.digitWidth; } }); console.log(amendRight); this.sumOffset += this.currentDiff.offsetX; this.minIndexs = []; this.itemColumns.forEach((item, index) => { if (item.digitWidth <= item.currentMinWidth) this.minIndexs.push(index); }); if (this.direction === 'right') { // 必须确保减去和增加的长度一样 向右移动 满足条件是右边的列不能全部为最小值 this.itemColumns[this.currentIndex].digitWidth = this.itemColumns[this.currentIndex].digitWidth + this.currentDiff.offsetX - amendRight; this.itemColumns[this.currentIndex].copyWidth = this.itemColumns[this.currentIndex].currentWidth = this.itemColumns[this.currentIndex].digitWidth; } else if (this.itemColumns[this.currentIndex].digitWidth > this.itemColumns[this.currentIndex].currentMinWidth && this.direction === 'left') { this.itemColumns[this.currentIndex].digitWidth += this.currentDiff.offsetX; // 需要特殊处理小于 this.itemColumns[this.currentIndex].currentMinWidth的情况 if (this.itemColumns[this.currentIndex].digitWidth < this.itemColumns[this.currentIndex].currentMinWidth) this.itemColumns[this.currentIndex].digitWidth = parseFloat(this.itemColumns[this.currentIndex].currentMinWidth); this.itemColumns[this.currentIndex].copyWidth = this.itemColumns[this.currentIndex].currentWidth = this.itemColumns[this.currentIndex].digitWidth; } this.initPoint.pageX = e.clientX; }); } }, onMouseUp(e) { // 释放 this.pauseEvent(e); this.isDown = false; this.currentIndex = undefined; this.initPoint.pageX = undefined; this.currentDiff.offsetX = undefined; }, pauseEvent(e) { if (e.stopPropagation) e.stopPropagation(); if (e.preventDefault) e.preventDefault(); e.cancelBubble = true; e.returnValue = false; return false; }, // 获取列宽度最大值和最小值 满足缓慢移动过程中没有平均分配移动距离 // 加入自定义最小值,会存在自定义最小值大于46,反而成为最大的值 getColumnIndex(index, type) { let columnIndex = 0; let m = this.itemColumns.slice(index)[0].digitWidth; this.itemColumns.slice(index).forEach((item, kindex) => { if (type === 'max') { if (item.digitWidth > m && item.digitWidth > item.currentMinWidth) { m = item.digitWidth; columnIndex = kindex; } } else { if (item.digitWidth < m) { m = item.digitWidth; columnIndex = kindex; } } }); return columnIndex + index; }, handleSize() { this.$nextTick(() => { const headHeight = parseFloat(getStyle(this.$refs.tableHead, 'height')) || 0; const tableHeight = this.$refs.tableBody.offsetHeight; this.bodyTableHeight = parseFloat(this.height) - headHeight; if (this.height) { if (tableHeight > this.bodyTableHeight) { this.isYScroll = true; this.scrollWidth = getScrollSize(); // this.itemColumns[this.itemColumns.length - 1].copyWidth = this.itemColumns[this.itemColumns.length - 1].digitWidth - this.scrollWidth; } else this.isYScroll = false; } else if (this.maxHeight && !this.loading && this.data.length) { // this.bodyWidth = parseFloat(this.tableWidth) - this.scrollWidth; this.maxBodyHeight = parseFloat(this.maxHeight) - headHeight; this.isYScroll = tableHeight > this.maxBodyHeight; } else if (this.minHeight && !this.loading && this.data.length) { // this.bodyWidth = parseFloat(this.tableWidth) - this.scrollWidth; this.minBodyHeight = parseFloat(this.minHeight) - headHeight; } if (this.isYScroll) { // 有滚动条 特殊处理 this.itemColumns[this.itemColumns.length - 1].copyWidth = this.itemColumns[this.itemColumns.length - 1].digitWidth - this.scrollWidth; } }); }, select(option, column, index) { this.$refs.popper && this.$refs.popper[0] && this.$refs.popper[0].toggle(false); column.selectValue = option.value; this.currentFilter.title = column.title; this.currentFilter.value = option.value; this.currentFilter.column = column; // 需要考虑数据为空 进行过滤 异步加载数据的情况 this.filterTdata = this.tdata = this.copyTdata.filter((item) => { if (this.filterMethod || column.filterMethod) { const filterMethod = this.filterMethod || column.filterMethod; return filterMethod(option.value, item[column.label], item, column); } else return !option.value ? true : item[column.label] === option.value; }); if (this.pattern === 'limit') this.tdata = this.tdata.slice(0, this.limit); this.$emit('filter-change', { value: option.value, column, index, }); }, initTableData(value) { let tdata = []; const copyData = cloneDeep(this.data); this.copyTdata = copyData; copyData.forEach((item, index) => { /* eslint-disable */ item.original_data = this.data[index]; item.original_index = index; }); const selection = this.itemColumns && this.itemColumns.some((item) => item.type && item.type === 'selection'); const expand = this.itemColumns && this.itemColumns.some((item) => item.type && item.type === 'expand'); if (selection && expand) { copyData.forEach((item) => { if (item.selected === undefined) item.selected = false; if (item.expanded === undefined) item.expanded = false; if (item.iconName === undefined) item.iconName = 'right'; if (item.disabled === undefined) item.disabled = false; tdata.push(item); }); } else if (selection) { copyData.forEach((item) => { if (item.selected === undefined) item.selected = false; if (item.disabled === undefined) item.disabled = false; tdata.push(item); }); } else if (expand) { copyData.forEach((item) => { if (item.expanded === undefined) item.expanded = false; if (item.iconName === undefined) item.iconName = 'right'; tdata.push(item); }); } else { copyData.forEach((item) => { tdata.push(item); }); } if (!copyData.length) this.allSel = false; // // 固定左右列同步阴影实现方案 // if (this.fixedLeftColumns.length > 0 || this.fixedRightColumns.length > 0) // tdata.forEach((item) => item.hover = false); if (value) tdata = tdata.slice(0, value); const selectionArr = this.getSelection(this.data); this.$emit('selection-change', selectionArr); return tdata; }, getSelection(value) { const data = value || this.tdata; const selectionIndexes = []; let noDisabledCount = 0; data.forEach((row, index) => { if (row.selected) selectionIndexes.push(index); if (!row.disabled) noDisabledCount++; }); // 这里有坑 行数据的checkbox有disabled状态 点击全选 可以是全选的 if (selectionIndexes.length === noDisabledCount && selectionIndexes.length !== 0) this.allSel = true; else this.allSel = false; this.$emit('update:allChecked', this.allSel); return this.data.filter((data, index) => selectionIndexes.indexOf(index) > -1); }, onResize() { this.handleSize(); }, changeSelect(row) { const selection = this.getSelection(); if (row.selected) this.$emit('select', selection); else this.$emit('select-cancel', selection); this.$emit('selection-change', selection); }, allSelected() { this.$nextTick(() => { const flag = this.allSel; const copydata = this.tdata.concat(); copydata.forEach((item) => { if (!item.disabled) item.selected = flag; }); this.tdata = copydata; const selection = this.getSelection(); if (flag) this.$emit('select-all', selection); this.$emit('selection-change', selection); }); }, handleSort(e, index, column) { // 点击mousedown事件会触发此事件 需要特殊处理 if (e.target === this.$refs.carve[index]) return false; if (column.type === 'sortable') { if (column.title === this.currentSort.title) this.currentSort.order = this.currentSort.order === 'asc' ? 'desc' : 'asc'; else { this.currentSort.title = column.title; this.currentSort.order = 'desc'; } this.currentSortColumn = column; const order = this.currentSort.order === 'asc' ? -1 : 1; this.sortData(column, order); this.$emit('update:defaultSort', this.currentSort); } }, sortData(column, order, type) { // type 字段在data发生变化时传入,此时不能抛sort-change方法,防止死循环 const label = column.label; if (this.sortRemoteMethod || column.sortRemoteMethod) { // 异步执行排序方法 const sortRemoteMethod = this.sortRemoteMethod || column.sortRemoteMethod; sortRemoteMethod(label, this.currentSort.order, column); } else { if (this.sortMethod || column.sortMethod) { const sortMethod = this.sortMethod || column.sortMethod; this.copyTdata.sort((value1, value2) => sortMethod(value1[label], value2[label]) ? order : -order); } else { this.copyTdata.sort((value1, value2) => { if (value1[label] === value2[label]) return 0; return value1[label] < value2[label] ? order : -order; }); } if (this.pattern === 'limit') this.tdata = this.copyTdata.slice(0, this.limit); else this.tdata = this.copyTdata; } if (!type) { this.$emit('sort-change', { order: this.currentSort.order, label, column, }); } }, translateTime(value, format) { if (!value) return this.defaultText; const maps = { YYYY(date) { return date.getFullYear(); }, MM(date) { return this.fixDate(date.getMonth() + 1); }, DD(date) { return this.fixDate(date.getDate()); }, HH(date) { return this.fixDate(date.getHours()); }, mm(date) { return this.fixDate(date.getMinutes()); }, ss(date) { return this.fixDate(date.getSeconds()); }, }; const date = new Date(value); const pattern = new RegExp(Object.keys(maps).join('|'), 'g'); return format.replace(pattern, (capture) => maps[capture] ? maps[capture].apply(this, [date]) : ''); }, fixDate(value) { value = '' + value; return value.length <= 1 ? '0' + value : value; }, showExpandIcon(column, value) { if (column.expandStrict) { if (!value) return false; if (Array.isArray(value)) return value.length; if (typeof value === 'object') return Object.keys(value).length; return value; } return true; }, // 展示表格单元格具体内容函数 现在规则是row[column.label]是对象,数组全部不展示内容,只展示基本类型 showContent(column, value) { if (value === 0) return value; else if (!value && column.defaultText === '') return ''; else { if (Array.isArray(value) || typeof value === 'object' && column.defaultText === '') return column.defaultText; if (Array.isArray(value) || typeof value === 'object') return column.defaultText || this.defaultText; return value || column.defaultText || this.defaultText; } }, toggleExpand(index) { if (this.expandPattern === 'toggle') { this.tdata.forEach((item, kindex) => { if (kindex !== index) { item.expanded = false; item.iconName = 'right'; } }); } const copyRowData = this.tdata[index]; copyRowData.expanded = !copyRowData.expanded; if (!copyRowData.expanded) copyRowData.iconName = 'right'; else copyRowData.iconName = 'down'; this.tdata.splice(index, 1, copyRowData); // 由于点击会导致页面出现滚动条 表格fixed布局不会有变化导致表格显示有问题 需要重新计算下布局 this.handleSize(); this.$emit('toggle-expand', { index, direction: copyRowData.iconName, row: copyRowData.iconName, }); }, showAll() { const filterValue = this.currentFilter.value; if (this.currentFilter.value) { this.tdata = this.copyTdata.filter((item) => { const filterColumn = this.currentFilter.column; if (this.filterMethod || filterColumn.filterMethod) { const filterMethod = this.filterMethod || filterColumn.filterMethod; return filterMethod(filterValue, item[filterColumn.label], item, filterColumn); } else return item[filterColumn.label] === filterValue; }); } else this.tdata = this.copyTdata; }, showLimit() { this.tdata = this.tdata.slice(0, this.limit); }, }, };