说明
这一部分是为 table
相关组件实现的对应的状态信息的管理。
源码解读
import Vue from 'vue';
import debounce from 'throttle-debounce/debounce';
import { orderBy, getColumnById, getRowIdentity } from './util';
/**
* 对数据进行排序
* @param data 要排序的数据
* @param states 状态信息
*/
const sortData = (data, states) => {
const sortingColumn = states.sortingColumn; // 要排序的列
// 如果不存在要排序的列或者 sortable 不是 true
if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
return data;
}
// 排序
return orderBy(data, states.sortProp, states.sortOrder, sortingColumn.sortMethod);
};
/**
* 获取值和 行、索引 的映射
* @param array 要处理的数列
* @param rowKey 对应的键名或者搜索函数
* @returns {{}}
*/
const getKeysMap = function(array, rowKey) {
const arrayMap = {};
(array || []).forEach((row, index) => {
arrayMap[getRowIdentity(row, rowKey)] = { row, index };
});
return arrayMap;
};
/**
* 切换被选中的行
* @param states 状态信息
* @param row 要处理的行
* @param selected 是否选中
* @returns {boolean} 是否改变
*/
const toggleRowSelection = function(states, row, selected) {
let changed = false;
const selection = states.selection; // 选中的数组
const index = selection.indexOf(row); // 查找相应的行
if (typeof selected === 'undefined') { // 如果没有规定是否选中
if (index === -1) { // 如果之前没有选中过
selection.push(row); // 加入到选择列表中
changed = true;
} else { // 否则移除
selection.splice(index, 1);
changed = true;
}
} else { // 如果指定了是否选中
if (selected && index === -1) { // 选中,并且之前没选中
selection.push(row);
changed = true;
} else if (!selected && index > -1) { // 没选中,并且之前选中了
selection.splice(index, 1);
changed = true;
}
}
return changed;
};
const TableStore = function(table, initialState = {}) {
if (!table) { // 必须传入 vue 实例
throw new Error('Table is required.');
}
this.table = table;
this.states = {
rowKey: null,
_columns: [],
originColumns: [],
columns: [],
fixedColumns: [],
rightFixedColumns: [],
isComplex: false,
_data: null,
filteredData: null,
data: null,
sortingColumn: null,
sortProp: null,
sortOrder: null,
isAllSelected: false,
selection: [],
reserveSelection: false,
selectable: null,
currentRow: null,
hoverRow: null,
filters: {},
expandRows: [],
defaultExpandAll: false
};
// 初始化相应的状态信息
for (let prop in initialState) {
if (initialState.hasOwnProperty(prop) && this.states.hasOwnProperty(prop)) {
this.states[prop] = initialState[prop];
}
}
};
/**
* mutations
*/
TableStore.prototype.mutations = {
/**
* 设置数据信息
* @param states 状态信息
* @param data 数据
*/
setData(states, data) { // 设置数据
const dataInstanceChanged = states._data !== data; // 数据发生改变
states._data = data; // 保存原始值
states.data = sortData((data || []), states); // 排序
// states.data.forEach((item) => {
// if (!item.$extra) {
// Object.defineProperty(item, '$extra', {
// value: {},
// enumerable: false
// });
// }
// });
this.updateCurrentRow(); // 更新当前行的信息
if (!states.reserveSelection) { // 如果有保留选择的项
if (dataInstanceChanged) { // 如果数据实例发生了改变
this.clearSelection(); // 清空选择,包括取消全选效果
} else {
this.cleanSelection(); // 清理选择
}
this.updateAllSelected(); // 更新全选的信息
} else {
const rowKey = states.rowKey;
if (rowKey) {
const selection = states.selection;
const selectedMap = getKeysMap(selection, rowKey);
// 更新选中行的信息
states.data.forEach((row) => {
// 行 id
const rowId = getRowIdentity(row, rowKey);
const rowInfo = selectedMap[rowId];
if (rowInfo) {
selection[rowInfo.index] = row;
}
});
// 更新全选信息
this.updateAllSelected();
} else { // 报错,因为保留选择模式必须有主键
console.warn('WARN: rowKey is required when reserve-selection is enabled.');
}
}
const defaultExpandAll = states.defaultExpandAll;
if (defaultExpandAll) { // 如果默认全部展开
this.states.expandRows = (states.data || []).slice(0);
}
// 在下次 DOM 更新结束后,更新垂直滚动的信息
Vue.nextTick(() => this.table.updateScrollY());
},
/**
* 更改排序条件
* @param states
*/
changeSortCondition(states) {
states.data = sortData((states.filteredData || states._data || []), states);
// 触发 sort-change 事件
this.table.$emit('sort-change', {
column: this.states.sortingColumn,
prop: this.states.sortProp,
order: this.states.sortOrder
});
Vue.nextTick(() => this.table.updateScrollY());
},
/**
* 更改筛选器信息
* @param states
* @param options
*/
filterChange(states, options) {
let { column, values } = options;
if (values && !Array.isArray(values)) {
values = [values];
}
const prop = column.property;
const filters = [];
if (prop) {
states.filters[column.id] = values;
filters[column.columnKey || column.id] = values;
}
let data = states._data;
// 对全部有筛选器器的列进行处理
Object.keys(states.filters).forEach((columnId) => {
const values = states.filters[columnId];
if (!values || values.length === 0) return;
const column = getColumnById(this.states, columnId);
// 如果存在列,并且该列上有过滤的方法
if (column && column.filterMethod) {
data = data.filter((row) => {
// 至少符合一条筛选条件
return values.some(value => column.filterMethod.call(null, value, row));
});
}
});
// 筛选后的信息
states.filteredData = data;
// 重新排列数据
states.data = sortData(data, states);
this.table.$emit('filter-change', filters);
Vue.nextTick(() => this.table.updateScrollY());
},
/**
* 插入列
* @param states 状态信息
* @param column 列
* @param index 索引
* @param parent 父级
*/
insertColumn(states, column, index, parent) {
let array = states._columns; // 全部列
if (parent) { // 如果有父级
array = parent.children;
if (!array) array = parent.children = []; // 如果还是不存在,则表明目前该父级还没有孩子
}
if (typeof index !== 'undefined') { // 如果指定了插入的位置
array.splice(index, 0, column);
} else { // 否则加入到最后
array.push(column);
}
if (column.type === 'selection') { // 如果当前列是多选框的列
states.selectable = column.selectable;
states.reserveSelection = column.reserveSelection;
}
// 重新规划布局
this.scheduleLayout();
},
/**
* 移除列
* @param states 状态信息
* @param column 要移除的列
*/
removeColumn(states, column) {
let _columns = states._columns;
if (_columns) { // 如果存在列的数组,移除对应列
_columns.splice(_columns.indexOf(column), 1);
}
this.scheduleLayout();
},
/**
* 设置鼠标悬浮的行
* @param states 状态信息
* @param row 鼠标悬浮的行
*/
setHoverRow(states, row) {
states.hoverRow = row;
},
/**
* 设置当前行
* @param states 状态信息
* @param row 要更新的行
*/
setCurrentRow(states, row) {
const oldCurrentRow = states.currentRow;
states.currentRow = row;
// 如果发生了改变,则触发 current-change 事件
if (oldCurrentRow !== row) {
this.table.$emit('current-change', row, oldCurrentRow);
}
},
/**
* 改变选中行
* @param states 状态信息
* @param row 要改变的行
*/
rowSelectedChanged(states, row) {
const changed = toggleRowSelection(states, row);
const selection = states.selection;
if (changed) { // 如果发生了改变
const table = this.table;
table.$emit('selection-change', selection);
table.$emit('select', selection, row);
}
// 更新全选信息
this.updateAllSelected();
},
/**
* 切换展开行
* @param states 状态信息
* @param row 行
* @param expanded 是否展开
*/
toggleRowExpanded: function(states, row, expanded) {
const expandRows = states.expandRows;
if (typeof expanded !== 'undefined') { // 如果指定了是否展开
const index = expandRows.indexOf(row); // 看看之前是否处理过
if (expanded) { // 展开
if (index === -1) expandRows.push(row); // 不存在就添加当前航
} else { // 不展开
if (index !== -1) expandRows.splice(index, 1); // 存在就移除当前行
}
} else { // 如果没有指定是否展开
const index = expandRows.indexOf(row);
if (index === -1) { // 如果之前不存在,就加入
expandRows.push(row);
} else { // 之前存在就移除
expandRows.splice(index, 1);
}
}
// 触发 expand 事件
this.table.$emit('expand', row, expandRows.indexOf(row) !== -1);
},
/**
* 切换全选,有 10ms 的限制
*/
toggleAllSelection: debounce(10, function(states) {
const data = states.data || [];
const value = !states.isAllSelected;
const selection = this.states.selection;
let selectionChanged = false;
data.forEach((item, index) => {
if (states.selectable) { // 如果可选
// 如果当前行可选,就切换当前行的选项
if (states.selectable.call(null, item, index) && toggleRowSelection(states, item, value)) {
selectionChanged = true;
}
} else { // 如果不可选
if (toggleRowSelection(states, item, value)) { // 切换该行的选项
selectionChanged = true;
}
}
});
const table = this.table;
if (selectionChanged) { // 如果所选项发生了改变
table.$emit('selection-change', selection);
}
// 触发全选事件
table.$emit('select-all', selection);
states.isAllSelected = value;
})
};
/**
* 平整化列,即降嵌套的列变成平级的
*/
const doFlattenColumns = (columns) => {
const result = [];
columns.forEach((column) => {
if (column.children) {
result.push.apply(result, doFlattenColumns(column.children));
} else {
result.push(column);
}
});
return result;
};
/**
* 更新列
*/
TableStore.prototype.updateColumns = function() {
const states = this.states;
const _columns = states._columns || [];
// 固定在左侧的列
states.fixedColumns = _columns.filter((column) => column.fixed === true || column.fixed === 'left');
// 固定在右侧的列
states.rightFixedColumns = _columns.filter((column) => column.fixed === 'right');
// 如果有左侧的固定列,并且第一列为未指定固定的勾选列,则让其固定在左侧
if (states.fixedColumns.length > 0 && _columns[0] && _columns[0].type === 'selection' && !_columns[0].fixed) {
_columns[0].fixed = true;
states.fixedColumns.unshift(_columns[0]);
}
// 原始列
states.originColumns = [].concat(states.fixedColumns).concat(_columns.filter((column) => !column.fixed)).concat(states.rightFixedColumns);
// 将原始列平整化
states.columns = doFlattenColumns(states.originColumns);
// 如果存在固定列就是复杂的
states.isComplex = states.fixedColumns.length > 0 || states.rightFixedColumns.length > 0;
};
/**
* 判断某行是否选中
* @param row 要判断的行
* @returns {boolean}
*/
TableStore.prototype.isSelected = function(row) {
return (this.states.selection || []).indexOf(row) > -1;
};
/**
* 清空选择
*/
TableStore.prototype.clearSelection = function() {
const states = this.states;
states.isAllSelected = false; // 取消全选
const oldSelection = states.selection;
states.selection = [];
if (oldSelection.length > 0) { // 如果之前有选择过,触发 selection-change
this.table.$emit('selection-change', states.selection);
}
};
/**
* 设置要展开的行
* @param rowKeys 要展开行的主键
*/
TableStore.prototype.setExpandRowKeys = function(rowKeys) {
const expandRows = [];
const data = this.states.data;
const rowKey = this.states.rowKey;
if (!rowKey) throw new Error('[Table] prop row-key should not be empty.');
const keysMap = getKeysMap(data, rowKey);
// 一次对每一行进行处理
rowKeys.forEach((key) => {
const info = keysMap[key];
if (info) {
expandRows.push(info.row);
}
});
this.states.expandRows = expandRows;
};
/**
* 切换行选择
* @param row 要切换的行
* @param selected 是否选择
*/
TableStore.prototype.toggleRowSelection = function(row, selected) {
const changed = toggleRowSelection(this.states, row, selected);
if (changed) { // 如果改变了,就触发相应事件
this.table.$emit('selection-change', this.states.selection);
}
};
/**
* 清理选择
*/
TableStore.prototype.cleanSelection = function() {
const selection = this.states.selection || [];
const data = this.states.data;
const rowKey = this.states.rowKey;
let deleted;
if (rowKey) { // 如果有 rowKey
deleted = [];
const selectedMap = getKeysMap(selection, rowKey);
const dataMap = getKeysMap(data, rowKey);
for (let key in selectedMap) {
// 如果选择的项不再存在于数据中
if (selectedMap.hasOwnProperty(key) && !dataMap[key]) {
deleted.push(selectedMap[key].row);
}
}
} else { 否则
// 搜索不再存在的
deleted = selection.filter((item) => {
return data.indexOf(item) === -1;
});
}
// 取消选择
deleted.forEach((deletedItem) => {
selection.splice(selection.indexOf(deletedItem), 1);
});
// 如果有需要被删的项,触发 selection-change 事件
if (deleted.length) {
this.table.$emit('selection-change', selection);
}
};
/**
* 更新是否全部被选中
*/
TableStore.prototype.updateAllSelected = function() {
const states = this.states;
const { selection, rowKey, selectable, data } = states;
// 如果不存在数据,或者数据长度为 0,肯定不可能有被选中的,因此直接设置 false
if (!data || data.length === 0) {
states.isAllSelected = false;
return;
}
let selectedMap;
// 如果存在 rowKey,则获取对应的映射
if (rowKey) {
selectedMap = getKeysMap(states.selection, rowKey);
}
// 判断是否被选中
const isSelected = function(row) {
if (selectedMap) { // 判断映射中是否存在这一行
return !!selectedMap[getRowIdentity(row, rowKey)];
} else { // 判断选择中是否有这一行
return selection.indexOf(row) !== -1;
}
};
let isAllSelected = true;
let selectedCount = 0;
for (let i = 0, j = data.length; i < j; i++) {
const item = data[i];
if (selectable) { // 如果可选
const isRowSelectable = selectable.call(null, item, i); // 判断是否可选
if (isRowSelectable) { // 如果可选
if (!isSelected(item)) { // 如果当前行没被选中
isAllSelected = false;
break;
} else { // 当前行被选中
selectedCount++;
}
}
} else { // 如果不可选
if (!isSelected(item)) { // 如果当前行没被选中了
isAllSelected = false;
break;
} else { // 当前行被选中
selectedCount++;
}
}
}
// 如果被选择的数量仍是 0
if (selectedCount === 0) isAllSelected = false;
// 更改状态信息
states.isAllSelected = isAllSelected;
};
/**
* 重新规划布局
*/
TableStore.prototype.scheduleLayout = function() {
this.table.debouncedLayout();
};
/**
* 设置当前的行 rowKey
* @param key
*/
TableStore.prototype.setCurrentRowKey = function(key) {
const states = this.states;
const rowKey = states.rowKey;
if (!rowKey) throw new Error('[Table] row-key should not be empty.');
const data = states.data || [];
const keysMap = getKeysMap(data, rowKey);
const info = keysMap[key]; // 找到赌赢的列
if (info) {
states.currentRow = info.row;
}
};
/**
* 更新当前行的信息
*/
TableStore.prototype.updateCurrentRow = function() {
const states = this.states;
const table = this.table;
const data = states.data || [];
const oldCurrentRow = states.currentRow;
// 如果旧的当前行不存在了
if (data.indexOf(oldCurrentRow) === -1) {
states.currentRow = null;
// 如果当前行发生了改变
if (states.currentRow !== oldCurrentRow) {
table.$emit('current-change', null, oldCurrentRow);
}
}
};
/**
* commit 触发相应的 mutation
*/
TableStore.prototype.commit = function(name, ...args) {
const mutations = this.mutations;
if (mutations[name]) {
mutations[name].apply(this, [this.states].concat(args));
} else {
throw new Error(`Action not found: ${name}`);
}
};
export default TableStore;