UNPKG

ivue-material-plus

Version:

A high quality UI components Library with Vue.js

775 lines (647 loc) • 19.4 kB
import { ref, getCurrentInstance, unref } from 'vue'; import { hasOwn } from '@vue/shared'; import { throwError } from '../../../utils/error'; // 展开行 import useExpand from './expand'; // 嵌套数据 import useTree from './tree'; // 当前数据 import useCurrent from './current'; import { getKeysMap, getRowIdentity, toggleRowStatus, orderBy, getColumnById, getColumnByKey, } from '../utils'; // ts import type { Ref } from 'vue'; import type { TableColumnCtx } from '../table-column/defaults'; import type { Table } from '../table/defaults'; import type { StoreFilter } from './index'; import type { SortOrder } from '../table-header/types/index'; // 扁平化数组 const flattenColumns = (columns: TableColumnCtx[]) => { const result = []; columns.forEach((column: TableColumnCtx) => { // 有子项 if (column.children) { // eslint-disable-next-line prefer-spread result.push.apply(result, flattenColumns(column.children)); } // 没有子项 else { result.push(column); } }); return result; }; // 排序数据 const sortData = (data, states) => { // 排序列 const sortingColumn = states.sortingColumn; // 没有排序 if (!sortingColumn || typeof sortingColumn.sortable === 'string') { return data; } // 对数据进行排序 return orderBy( data, // 排序的key 对应列内容的字段名 states.sortProp, // 数据在排序时所使用排序策略的轮转顺序 states.sortOrder, // 自定义排序方法 sortingColumn.sortMethod, // 指定数据按照哪个属性进行排序 sortingColumn.sortBy ); }; function useWatcher() { // vm const vm = getCurrentInstance() as Table; // data // 显示的数据 const data = ref<TableColumnCtx[]>([]); const _data = ref<TableColumnCtx[]>([]); // 行数据的 Key const rowKey = ref<string | null>(null); // 选择或取消选择所有行的值 const selectOnIndeterminate = ref<boolean>(false); // 选择全部 const isAllSelected = ref<boolean>(false); // 选择列表 const selection = ref<TableColumnCtx[]>([]); // 是否有固定列 const isFixedColumns = ref<boolean>(false); // hover行 const hoverRow = ref<TableColumnCtx>(null); // 列数据 const originColumns = ref<TableColumnCtx[]>([]); // 列 const columns = ref<TableColumnCtx[]>([]); // 备份列 const _columns = ref<TableColumnCtx[]>([]); // 固定列 const fixedColumns = ref<TableColumnCtx[]>([]); // 右边固定列 const rightFixedColumns = ref<TableColumnCtx[]>([]); // 固定列长度 const leafColumnsLength = ref<number>(0); // 固定列长度 const fixedLeafColumnsLength = ref<number>(0); // 右边固定列长度 const rightFixedLeafColumnsLength = ref<number>(0); // 仅对 type=selection 的列有效, // 类型为 Function,Function 的返回值用来决定这一行的 CheckBox 是否可以勾选 const selectable: Ref<(row: TableColumnCtx, index: number) => boolean> | any = ref(null); // 保存数据更新前选中的值 const reserveSelection = ref<boolean>(false); // 排序的key 对应列内容的字段名 const sortProp = ref<string | null>(null); // 需要排序的列 const sortingColumn = ref<TableColumnCtx | null>(null); // 排序 const sortOrder = ref<SortOrder>(null); // 过滤的数据 const filteredData = ref<any[]>([]); // 选择过滤的列 // 例子 // 列id: [ // 选择过滤的数据 // ] const filters: Ref<StoreFilter> = ref({}); // methods // 用于多选表格,切换全选和全不选 const _toggleAllSelection = () => { // 当只选择了一些行(但不是全部)时,选择或取消选择所有行 // 取决于 selectOnIndeterminate 的值 // 是否选择所有行 const value = selectOnIndeterminate.value ? !isAllSelected.value : !(isAllSelected.value || selection.value.length); // 是否选择所有行 isAllSelected.value = value; let selectionChanged = false; let childrenCount = 0; // 行数据的 Key const rowKey = vm?.store?.states?.rowKey.value; // 遍历数据 data.value.forEach((row, index) => { // 行下标 const rowIndex = index + childrenCount; // 这一行的 CheckBox 是否可以勾选 if (selectable.value) { // 勾选 if ( selectable.value.call(null, row, rowIndex) && toggleRowStatus(selection.value, row, value) ) { selectionChanged = true; } } // 修改当前行的状态 else { if (toggleRowStatus(selection.value, row, value)) { selectionChanged = true; } } // 通过rowKey获取所有子节点的个数 childrenCount += getChildrenCount(getRowIdentity(row, rowKey)); }); // 勾选了 if (selectionChanged) { vm.emit('on-selection-change', selection.value || []); } // 选择全部 vm.emit('on-select-all', selection.value); }; // 展开行数据 const { toggleRowExpansion, updateExpandRows, setExpandRowKeys, isRowExpanded, states: expandStates, } = useExpand({ data, rowKey, }); // 嵌套数据 const { loadOrToggle, updateTreeData, updateTreeExpandKeys, toggleTreeExpansion, states: treeStates, }: any = useTree({ data, rowKey, }); // 检查 rowKey 是否存在 const isRowKey = () => { if (!rowKey.value) { throwError('ivue-table', 'prop row-key is required'); } }; // 更新多选框key const updateSelectionByRowKey = () => { // 获取多选框标志 const selectedMap = getKeysMap(selection.value, rowKey.value); data.value.forEach((row) => { // 获取rowKey对应的数据 const rowId = getRowIdentity(row, rowKey.value); const rowInfo = selectedMap[rowId]; if (rowInfo) { selection.value[rowInfo.index] = row; } }); }; // 更新列数据 const updateColumns = () => { // 不是固定列 const notFixedColumns = _columns.value.filter((column) => !column.fixed); // 固定列 fixedColumns.value = _columns.value.filter( (column) => column.fixed === true || column.fixed === 'left' ) || []; // 右边固定列 rightFixedColumns.value = _columns.value.filter( (column) => column.fixed === 'right' ); // 未扁平化列数据 originColumns.value = []; originColumns.value = originColumns.value .concat(fixedColumns.value) .concat(notFixedColumns) .concat(rightFixedColumns.value); // 扁平化不是固定列 const leafColumns = flattenColumns(notFixedColumns); // 扁平化固定列 const fixedLeafColumns: any[] = flattenColumns(fixedColumns.value); // 扁平化右边固定列 const rightFixedLeafColumns = flattenColumns(rightFixedColumns.value); leafColumnsLength.value = leafColumns.length; fixedLeafColumnsLength.value = fixedLeafColumns.length; rightFixedLeafColumnsLength.value = rightFixedLeafColumns.length; // 扁平化后的列数据 columns.value = []; columns.value = columns.value .concat(fixedLeafColumns) .concat(leafColumns) .concat(rightFixedLeafColumns); // 是否有固定列 isFixedColumns.value = fixedColumns.value.length > 0 || rightFixedColumns.value.length > 0; }; // 更新当前数据 const { updateCurrentRowData, updateCurrentRow, setCurrentRowKey, states: currentData, } = useCurrent({ data, rowKey, }); // 更新 DOM 更新列数据 const scheduleLayout = (needUpdateColumns?: boolean, immediate = false) => { // 更新列数据 未扁平化列数据 | 扁平化后的列数据 | 是否有固定列 if (needUpdateColumns) { updateColumns(); } // 立即执行更新布局 if (immediate) { vm.state.updateLayout(); } // 延迟50毫秒更新布局 else { vm.state.debouncedUpdateLayout(); } }; // 选择多选 const isSelected = (row: TableColumnCtx) => { return selection.value.includes(row); }; // 多选返回当前选中的行 const getSelectionRows = () => { return (selection.value || []).slice(); }; // 多选选择的行 const toggleRowSelection = ( row: any, selected: any = undefined, emitChange = true ) => { const changed = toggleRowStatus(selection.value, row, selected); if (changed) { const newSelection = (selection.value || []).slice(); // 通过api调用时触发 if (emitChange) { vm.emit('on-select', newSelection, row); } vm.emit('on-selection-change', newSelection); } }; // 更新是否选择全部行 const updateAllSelected = () => { // 没有数据 if (data.value?.length === 0) { isAllSelected.value = false; return; } let selectedMap; // 行数据的Key if (rowKey.value) { selectedMap = getKeysMap(selection.value, rowKey.value); } // 是否选择了 const isSelected = (row) => { // 有行数据的Key if (selectedMap) { return !!selectedMap[getRowIdentity(row, rowKey.value)]; } // 没有key else { return selection.value.includes(row); } }; let _isAllSelected = true; let selectedCount = 0; let childrenCount = 0; for (let i = 0, j = (data.value || []).length; i < j; i++) { // 行数据的Key const keyProp = vm?.store?.states?.rowKey.value; // 行index const rowIndex = i + childrenCount; // 当前数据 const item = data.value[i]; // 是否有可选行 const isRowSelectable = selectable.value && selectable.value.call(null, item, rowIndex); // 当前行没有选中 if (!isSelected(item)) { if (!selectable.value || isRowSelectable) { _isAllSelected = false; break; } } // 选中了 else { selectedCount++; } // 子节点数量 childrenCount += getChildrenCount(getRowIdentity(item, keyProp)); } // 不是全选 if (selectedCount === 0) { _isAllSelected = false; } // 设置全选状态 isAllSelected.value = _isAllSelected; }; // 通过rowKey获取所有子节点的个数 const getChildrenCount = (rowKey: string) => { if (!vm || !vm.store) { return 0; } // 树形 const { treeData } = vm.store.states; // 子节点数量 let count = 0; // 有子节点 const children = treeData.value[rowKey]?.children; // 有子节点 if (children) { count += children.length; children.forEach((childKey) => { count += getChildrenCount(childKey); }); } return count; }; // 清除多选选择行 const clearSelection = () => { isAllSelected.value = false; const oldSelection = selection.value; // 有选择行 if (oldSelection.length) { selection.value = []; vm.emit('on-selection-change', []); } }; // 清洁选择 const cleanRedundantSelection = () => { // 需要删除的数据 let deleted; // 有rowKey if (rowKey.value) { deleted = []; // 行数据的Key const selectedMap = getKeysMap(selection.value, rowKey.value); // 获取列数据 {index, row} const dataMap = getKeysMap(data.value, rowKey.value); for (const key in selectedMap) { // 是否存在数据 if (hasOwn(selectedMap, key) && !dataMap[key]) { // 获取需要删除的数据(当前数据列表中不存在的数据) deleted.push(selectedMap[key].row); } } } // 没有设置rowKey else { // 获取需要删除的数据(当前数据列表中不存在的数据) deleted = selection.value.filter((item) => !data.value.includes(item)); } // 有需要删除的数组 if (deleted.length) { // 过滤数据获取最新的选择数据 const newSelection = selection.value.filter( (item) => !deleted.includes(item) ); selection.value = newSelection; // 选择的数据 vm.emit('on-selection-change', newSelection); } }; // 根据 filters 与 sort 去过滤 data const handleExecQueryData = (filter = true) => { // 过滤数据 if (filter) { initFilterData(); } // 排序数据 initSortData(); }; // 排序数据 const initSortData = () => { data.value = sortData(filteredData.value, { // 需要排序的列 sortingColumn: sortingColumn.value, // 排序的key 对应列内容的字段名 sortProp: sortProp.value, // 排序顺序 sortOrder: sortOrder.value, }); }; // 过滤数据 const initFilterData = () => { let sourceData = unref(_data); // 循环列 Object.keys(filters.value).forEach((columnId) => { // 列的值->选择过滤的数据 const values = filters.value[columnId]; // 没有选择数据 if (!values || values.length === 0) { return; } // 根据当前选择需要筛选的列id -> 获取表格中的当前选择列数据 const column = getColumnById( { columns: columns.value, }, columnId ); // 是否有自定义过滤方法 if (column && column.filterMethod) { sourceData = sourceData.filter((row) => { return values.some((value) => column.filterMethod.call(null, value, row, column) ); }); } }); filteredData.value = sourceData; }; // 更新排序属性 const updateSort = (column, prop, order) => { // 重置排序排序顺序 if (sortingColumn.value && sortingColumn.value !== column) { sortingColumn.value.order = null; } // 重新设置排序顺序 sortingColumn.value = column; // 排序的key 对应列内容的字段名 sortProp.value = prop; // 排序方向 sortOrder.value = order; }; // 检查 rowKey 是否存在 const assertRowKey = () => { if (!rowKey.value) { throwError('ivue-table', 'prop row-key is required'); } }; // 更新过滤的数据 const updateFilters = (columns, values) => { if (!Array.isArray(columns)) { columns = [columns]; } const obj: any = {}; // 获取列表数据 columns.forEach((item) => { filters.value[`${item.id}`] = values; obj[item.columnKey || item.id] = values; }); return obj; }; // 用于清空排序条件,数据会恢复成未排序的状态 const clearSort = () => { // 没有排序 if (!sortingColumn.value) { return; } // 更新排序属性 updateSort(null, null, null); vm.store.commit('changeSortCondition', { silent: true, }); }; // 传入由columnKey 组成的数组以清除指定列的过滤条件 // 如果没有参数,清除所有过滤器 const clearFilter = (columnKeys) => { const { tableHeaderContent } = vm.refs; // 没有头部 if (!tableHeaderContent) { return; } // 当前有哪些过滤列表 const panels = Object.assign({}, tableHeaderContent.filterPanels); const keys = Object.keys(panels); // 没有过滤列表 if (!keys.length) { return; } // 清除指定列的过滤条件 if (typeof columnKeys === 'string') { columnKeys = [columnKeys]; } // 有过滤条件 if (Array.isArray(columnKeys)) { const _columns = columnKeys.map((key) => getColumnByKey( { columns: columns.value, }, key ) ); // 清空过滤的值 keys.forEach((key) => { const column = _columns.find((col) => col.id === key); // 清空过滤的值 if (column) { column.filteredValue = []; } }); // 发送事件 过滤改变 vm.store.commit('filterChange', { column: _columns, values: [], silent: true, multi: true, }); } // 没有过滤条件清空全部过滤列表 else { // 清空全部过滤列表 keys.forEach((key) => { const column = columns.value.find((col) => col.id === key); if (column) { column.filteredValue = []; } }); filters.value = {}; // 发送事件 过滤改变 vm.store.commit('filterChange', { column: {}, values: [], silent: true, }); } }; // 展开行与 TreeTable 都要使用 const toggleRowExpansionAdapter = ( row: TableColumnCtx, expanded: boolean ) => { // 是否是展开列 const hasExpandColumn = columns.value.some(({ type }) => type === 'expand'); // 展开列 if (hasExpandColumn) { toggleRowExpansion(row, expanded); } // 切换树节点展开 else { toggleTreeExpansion(row, expanded); } }; // 适配层,expand-row-keys 在 Expand 与 TreeTable 中都有使用 const setExpandRowKeysAdapter = (val: string[]) => { // 这里会触发额外的计算,但为了兼容性,暂时这么做 setExpandRowKeys(val); // 更新树节点展开 updateTreeExpandKeys(val); }; return { _toggleAllSelection, toggleAllSelection: () => {}, isRowKey, updateSelectionByRowKey, updateColumns, scheduleLayout, updateCurrentRowData, updateCurrentRow, isSelected, toggleRowSelection, updateAllSelected, clearSelection, cleanRedundantSelection, handleExecQueryData, updateSort, assertRowKey, updateFilters, clearSort, clearFilter, getSelectionRows, toggleRowExpansion, toggleRowExpansionAdapter, updateExpandRows, setExpandRowKeysAdapter, setCurrentRowKey, isRowExpanded, updateTreeData, loadOrToggle, // 状态 states: { data, _data, rowKey, selectOnIndeterminate, isFixedColumns, hoverRow, columns, _columns, originColumns, rightFixedLeafColumnsLength, fixedLeafColumnsLength, fixedColumns, rightFixedColumns, isAllSelected, selectable, reserveSelection, selection, sortProp, sortingColumn, sortOrder, ...expandStates, ...treeStates, ...currentData, }, }; } export default useWatcher;