fastlion-amis
Version:
一种MIS页面生成工具
1,733 lines (1,523 loc) • 57.3 kB
text/typescript
import {
types,
getParent,
SnapshotIn,
flow,
getEnv,
getRoot,
IAnyModelType,
isAlive,
Instance
} from 'mobx-state-tree';
import { iRendererStore } from './iRenderer';
import { resolveVariable } from '../utils/tpl-builtin';
import isEqual from 'lodash/isEqual';
import find from 'lodash/find';
import {
isBreakpoint,
createObject,
isObject,
isVisible,
guid,
findTree,
flattenTree,
eachTree,
difference,
immutableExtends,
extendObject,
hasVisibleExpression,
filterTree,
findTreeIndex,
spliceTree,
isMobile,
getTextWidth,
standardValueText,
numberFormatter,
} from '../utils/helper';
import { evalExpression } from '../utils/tpl';
import { IFormStore } from './form';
import { getStoreById } from './manager';
import { EventEnum, EventSub } from '../utils/sub';
import { DATAKEYID } from './crud';
import { getCellValue, getHeadRows, handleFilter, handleSort } from './utils/commonTableFunction';
import { cloneDeep, isNil, last } from 'lodash';
export const Column = types
.model('Column', {
label: types.optional(types.frozen(), undefined),
type: types.optional(types.string, 'plain'),
name: types.maybe(types.string),
value: types.frozen(),
groupName: '',
toggled: false,
toggable: true,
expandable: false,
checkdisable: false,
isPrimary: false,
searchable: types.maybe(types.frozen()),
enableSearch: true,
sortable: false,
filterable: types.optional(types.frozen(), undefined),
fixed: '',
align: '',
index: 0,
rawIndex: 0,
breakpoint: types.optional(types.frozen(), undefined),
pristine: types.optional(types.frozen(), undefined),
remark: types.optional(types.frozen(), undefined),
className: '',
map: types.frozen(),
})
.actions(self => ({
toggleToggle() {
self.toggled = !self.toggled;
const table = getParent(self, 2) as ITableStore;
if (!table.activeToggaleColumns.length) {
self.toggled = true;
}
table.persistSaveToggledColumns();
},
setToggled(value: boolean) {
self.toggled = value;
},
setEnableSearch(value: boolean) {
self.enableSearch = value;
},
setMapValue(obj: object) {
self.map = { ...self.map, ...obj }
// self.map = Object.assign(obj, self.map)
}
}));
export type IColumn = Instance<typeof Column>;
export type SColumn = SnapshotIn<typeof Column>;
export const Row = types
.model('Row', {
storeType: 'Row',
id: types.identifier,
parentId: '',
key: types.string,
pristine: types.frozen({} as any),
data: types.frozen({} as any),
rowSpans: types.frozen({} as any),
index: types.number,
newIndex: types.number,
path: '', // 行数据的位置
expandable: false,
checkdisable: false,
checked: false, // 是否选中
isHover: false,
children: types.optional(
types.array(types.late((): IAnyModelType => Row)),
[]
),
depth: types.number, // 当前children位于第几层,便于使用getParent获取最顶层TableStore
})
.views(self => ({
get modified() {
if (!self.data) {
return false;
}
return Object.keys(self.data).some(
key => !isEqual(self.data[key], self.pristine[key])
);
},
getDataWithModifiedChilden() {
let data = {
...self.data
};
if (data.children && self.children) {
data.children = self.children.map(item =>
item.getDataWithModifiedChilden()
);
}
return data;
},
get collapsed(): boolean {
const table = getParent(self, self.depth * 2) as ITableStore;
if (table.dragging) {
return true;
}
let from: IRow = self as any;
while (from && (from as any) !== table) {
if (!table.isExpanded(from)) {
return true;
}
from = getParent(from, 2);
}
return false;
},
get expanded(): boolean {
return !this.collapsed;
},
get moved() {
return self.index !== self.newIndex;
},
get locals(): any {
let children: Array<any> | null = null;
if (self.children.length) {
children = self.children.map(item => item.locals);
}
const parent = getParent(self, 2) as ITableStore;
return createObject(
extendObject((getParent(self, self.depth * 2) as ITableStore).data, {
index: self.index,
// todo 以后再支持多层,目前先一层
parent: parent.storeType === Row.name ? parent.data : undefined
}),
children
? {
...self.data,
children
}
: self.data
);
},
get checkable(): boolean {
const table = getParent(self, self.depth * 2) as ITableStore;
return table && table.itemCheckableOn
? evalExpression(table.itemCheckableOn, (self as IRow).locals)
: true;
},
get draggable(): boolean {
const table = getParent(self, self.depth * 2) as ITableStore;
return table && table.itemDraggableOn
? evalExpression(table.itemDraggableOn, (self as IRow).locals)
: true;
}
}))
.actions(self => ({
toggle() {
(getParent(self, self.depth * 2) as ITableStore).toggle(self as IRow);
},
toggleExpanded() {
(getParent(self, self.depth * 2) as ITableStore).toggleExpanded(
self as IRow
);
},
change(values: object, savePristine?: boolean) {
self.data = immutableExtends(self.data, values);
savePristine && (self.pristine = self.data);
},
reset() {
self.newIndex = self.index;
self.data = self.pristine;
},
setCheckdisable(bool: boolean) {
self.checkdisable = bool;
},
setCheckded(bool: boolean) {
self.checked = bool;
},
setIsHover(value: boolean) {
self.isHover = value;
},
replaceWith(data: any) {
Object.keys(data).forEach(key => {
if (key !== 'id') {
(self as any)[key] = data[key];
}
});
if (Array.isArray(data.children)) {
const arr = data.children;
const pool = arr.concat();
// 把多的删了先
if (self.children.length > arr.length) {
self.children.splice(arr.length, self.children.length - arr.length);
}
let index = 0;
const len = self.children.length;
while (pool.length) {
// 因为父级id未更新,所以需要将子级的parentId正确指向父级id
const item = {
...pool.shift(),
parentId: self.id
}!;
if (index < len) {
self.children[index].replaceWith(item);
} else {
const row = Row.create(item);
self.children.push(row);
}
index++;
}
}
}
}));
export type IRow = Instance<typeof Row>;
export type SRow = SnapshotIn<typeof Row>;
export const TableStore = iRendererStore
.named('TableStore')
.props({
visitMap: types.optional(types.frozen(), {}),
rowSpanIndexsInterval: types.optional(types.frozen(), {}),// rowSpan区间记录
textWidhMap: types.optional(types.frozen(), {}),
headTextWidhMap: types.optional(types.frozen(), {}),
modefiedMap: types.optional(types.frozen(), { dataSetId: '', modifiedDataSet: {} }),
// ui属性表格项目可见性
columns: types.array(Column),
rawColumns: types.array(Column), // Jay
rows: types.array(Row),
expandedRows: types.array(types.string),
primaryField: 'id',
orderBy: '',
orderDir: types.optional(
types.union(types.literal('asc'), types.literal('desc')),
'asc'
),
draggable: false,
dragging: false,
selectable: false,
multiple: true,
showIndex: false,
footable: types.frozen(),
expandConfig: types.frozen(),
isNested: false,
columnsTogglable: types.optional(
types.union(types.boolean, types.literal('auto')),
'auto'
),
itemCheckableOn: '',
itemDraggableOn: '',
hideCheckToggler: false,
combineNum: 0,
sortItemsRaw: types.optional(types.array(types.frozen()), []),
combineFromIndex: 0,
formsRef: types.optional(types.array(types.frozen()), []),
maxKeepItemSelectionLength: 0,
keepItemSelectionOnPageChange: false,
// Jay
stickyWidths: types.optional(types.frozen(), {}),
mobileUI: false, //Aug,
orderColumns: types.optional(types.frozen(), new Map<string, { order: string, map?: object }>()),
filterColumns: types.optional(types.frozen(), new Map()),
originColumns: types.optional(types.array(types.frozen()), []),
tableLayout: '',
tableExpandLength: 0,
columnsInfo: types.optional(types.frozen(), {})
})
.views(self => {
function getColumnsExceptBuiltinTypes() {
return self.columns.filter(item => !/^__/.test(item.type));
}
// Jay
function getRawColumnsExceptBuiltinTypes() {
return self.rawColumns.filter(item => !/^__/.test(item.type));
}
function getForms() {
return self.formsRef.map(item => ({
store: getStoreById(item.id) as IFormStore,
rowIndex: item.rowIndex
}));
}
function getFilteredColumns() {
let filterCols = self.columns.filter(
item => {
// 提取公共条件
const commonConditions = self.selectable && !self.dragging && self.rows.length;
const isMobileCheck = isMobile() ? true : !self.hideCheckToggler;
const hasFootableColumnsOrNested = getFootableColumns().length || self.isNested;
// return item &&
// isVisible(
// item.pristine,
// hasVisibleExpression(item.pristine) ? self.data : {}
// ) &&
// (item.type === '__checkme'
// ? self.selectable &&
// !self.dragging &&
// (isMobile() ? true : !self.hideCheckToggler) &&
// self.rows.length
// : item.type === '__dragme'
// ? self.dragging
// : item.type === '__expandme'
// ? (getFootableColumns().length || self.isNested) && !self.dragging
// : (item.toggled || !item.toggable) &&
// (!self.footable ||
// !item.breakpoint ||
// !isBreakpoint(item.breakpoint)))
if (!item ||
!isVisible(
item.pristine,
hasVisibleExpression(item.pristine) ? self.data : {}
))
return false
// 这几个是特殊展示的
switch (item.type) {
case '__checkme':
return commonConditions && isMobileCheck
case '__dragme':
return self.dragging
case '__expandme':
return hasFootableColumnsOrNested && !self.dragging
case '__pseudoColumn':
return self.showIndex
default:
return (item.toggled || !item.toggable)
// && (!self.footable ||
// !item.breakpoint ||
// !isBreakpoint(item.breakpoint))
}
}
);
if (self.footable && isMobile() && self.tableLayout === 'vertical') {
const expandedCol = filterCols.find(item => item.type === '__expandme');
filterCols = filterCols.filter(item => item.type !== '__expandme');
expandedCol && filterCols.push(expandedCol);
}
return filterCols;
}
function getFootableColumns() {
return self.columns.filter(item =>
item.type === '__checkme' ||
item.type === '__dragme' ||
item.type === '__expandme' ||
item.type === '__pseudoColumn'
? false
: (item.toggled || !item.toggable)
&&
self.footable &&
item.breakpoint &&
isBreakpoint(item.breakpoint)
);
}
function getLeftFixedColumns() {
if (self.dragging) {
return [];
}
let columns = getFilteredColumns().filter(item => item.fixed === 'left');
// 有才带过去,没有就不带了
if (columns.length) {
columns = getFilteredColumns().filter(
item => item.fixed === 'left' || /^__/.test(item.type)
);
}
return columns;
}
function getRightFixedColumns() {
if (self.dragging) {
return [];
}
return getFilteredColumns().filter(item => item.fixed === 'right');
}
function isSelected(row: IRow): boolean {
return !!~getSelectedRows().indexOf(row);
}
function isExpanded(row: IRow): boolean {
return self.expandedRows.includes(row.id);
}
function getTogglable() {
if (self.columnsTogglable === 'auto') {
return self.columns.filter(item => !/^__/.test(item.type)).length > 5;
}
return self.columnsTogglable;
}
function getToggableColumns() {
return self.columns.filter(
item => isVisible(item.pristine, self.data) && item.toggable !== false
);
}
function getActiveToggableColumns() {
return getToggableColumns().filter(item => item.toggled);
}
function getModifiedRows(rows: IRow[] = [], modifiedRows: IRow[] = []): any[] {
rows = rows && rows.length ? rows : self.rows;
// 每次初始化的时候初始化的时候初始化修改map
for (const item of rows) {
if (item.children && item.children.length) {
getModifiedRows(item.children, modifiedRows);
}
}
// 如果没有修改的数据-有可能是前端分页导致的数据-尝试检查是否要更新编辑数据缓存
return Object.values(self.modefiedMap.modifiedDataSet);
}
function getFlatRows() {
const ret: IRow[] = []
const fn = (rows: IRow[]) => {
rows.forEach(row => {
ret.push(row)
row.expanded && row.children.length > 0 && fn(row.children)
})
}
fn(self.rows)
return ret
// return flattenTree<IRow>(self.rows)
}
function getModified() {
return getModifiedRows().length;
}
function getMovedRows() {
return flattenTree(self.rows).filter((item: IRow) => item.moved);
}
function getMoved() {
return getMovedRows().length;
}
function getHovedRow(): IRow | undefined {
return flattenTree<IRow>(self.rows).find((item: IRow) => item.isHover);
}
function getUnSelectedRows() {
return flattenTree<IRow>(self.rows).filter((item: IRow) => !item.checked);
}
function getSelectedRows() {
return flattenTree<IRow>(self.rows).filter((item: IRow) => item.checked);
}
function getData(superData: any): any {
return createObject(superData, {
items: self.rows.map(item => item.data),
selectedItems: getSelectedRows().map(item => item.data),
unSelectedItems: getUnSelectedRows().map(item => item.data)
});
}
function hasColumnHidden() {
return self.columns.findIndex(column => !column.toggled) !== -1;
}
function getTableHeadRows() {
const filtedColumns = getFilteredColumns()
return getHeadRows(filtedColumns)
}
function getColumnGroup() {
const columns = getFilteredColumns();
const len = columns.length;
if (!len) {
return [];
}
const groups: Array<{
label: string;
index: number;
colSpan: number;
rowSpan: number;
has: Array<any>;
}> = [
{
label: columns[0].groupName,
colSpan: 1,
rowSpan: 1,
index: columns[0].index,
has: [columns[0]]
}
];
const needGroup = columns.find(item => !!item.groupName)
if (needGroup) {
for (let i = 1; i < len; i++) {
let prev = groups[groups.length - 1];
const current = columns[i];
if (current.groupName && current.groupName === prev.label) {
prev.colSpan++;
prev.has.push(current);
} else {
groups.push({
label: current.groupName,
colSpan: 1,
rowSpan: 1,
index: current.index,
has: [current]
});
}
}
}
if (groups.length === 1 && !groups[0].label) {
groups.pop();
}
return groups.map(item => {
const rowSpan =
!item.label ||
(item.has.length === 1 && item.label === item.has[0].label)
? 2
: 1;
return {
...item,
rowSpan,
label: rowSpan === 2 ? item.label || item.has[0].label : item.label
};
});
}
function getFirstToggledColumnIndex() {
const column = self.columns.find(
column => !/^__/.test(column.type) && column.toggled
);
return column == null ? null : column.index;
}
function getSearchableColumns() {
return self.columns.filter(
column => column.searchable && isObject(column.searchable)
);
}
function getActivedSearchableColumns() {
return self.columns.filter(
column =>
column.searchable &&
isObject(column.searchable) &&
column.enableSearch
);
}
return {
get columnsData() {
return getColumnsExceptBuiltinTypes();
},
get rawColumnsData() {
return getRawColumnsExceptBuiltinTypes();
},
get forms() {
return getForms();
},
get searchableColumns() {
return getSearchableColumns();
},
get activedSearchableColumns() {
return getSearchableColumns().filter(column => column.enableSearch);
},
get filteredColumns() {
return getFilteredColumns();
},
get footableColumns() {
return getFootableColumns();
},
get leftFixedColumns() {
return getLeftFixedColumns();
},
get rightFixedColumns() {
return getRightFixedColumns();
},
get toggableColumns() {
return getToggableColumns();
},
get activeToggaleColumns() {
return getActiveToggableColumns();
},
get someChecked() {
return !!getSelectedRows().length;
},
get allChecked(): boolean {
return !!(
getSelectedRows().length ===
(self as ITableStore).checkableRows.length &&
(self as ITableStore).checkableRows.length
);
},
isSelected,
get allExpanded() {
return !!(
self.expandedRows.length === self.tableExpandLength &&
self.tableExpandLength
);
},
isExpanded,
get toggable() {
return getTogglable();
},
get modified() {
return getModified();
},
get modifiedRows() {
return getModifiedRows();
},
get unSelectedRows() {
return getUnSelectedRows();
},
get selectedRows() {
return getSelectedRows();
},
get checkableRows() {
return flattenTree<IRow>(self.rows).filter(
(item: IRow) => item.checkable
);
},
get expandableRows() {
return self.rows.filter(item => item.expandable);
},
get moved() {
return getMoved();
},
get movedRows() {
return getMovedRows();
},
get hoverRow() {
return getHovedRow();
},
get disabledHeadCheckbox() {
const selectedLength = self.data?.selectedItems?.length;
const maxLength = self.maxKeepItemSelectionLength;
if (!self.data || !self.keepItemSelectionOnPageChange || !maxLength) {
return false;
}
return maxLength === selectedLength;
},
get firstToggledColumnIndex() {
return getFirstToggledColumnIndex();
},
getData,
get columnGroup() {
return getColumnGroup();
},
get tableHeadRows() {
return getTableHeadRows()
},
get flatRows() {
return getFlatRows()
},
getRowById(id: string) {
return findTree(self.rows, item => item.id === id);
},
getItemsByName(name: string): any {
return this.forms
.filter(form => form.rowIndex === parseInt(name, 10))
.map(item => item.store);
},
// 是否隐藏了某列
hasColumnHidden() {
return hasColumnHidden();
},
getExpandedRows() {
const list: Array<IRow> = [];
eachTree(self.rows, i => {
if (self.expandedRows.includes(i.id)) {
list.push(i as any);
}
});
return list;
}
};
})
.actions(self => {
function update(config: Partial<STableStore>) {
config.primaryField !== void 0 &&
(self.primaryField = config.primaryField);
config.selectable !== void 0 && (self.selectable = config.selectable);
config.columnsTogglable !== void 0 &&
(self.columnsTogglable = config.columnsTogglable);
config.draggable !== void 0 && (self.draggable = config.draggable);
config.hideCheckToggler !== void 0 &&
(self.hideCheckToggler = !!config.hideCheckToggler);
if (typeof config.orderBy === 'string') {
setOrderByInfo(
config.orderBy,
config.orderDir === 'desc' ? 'desc' : 'asc'
);
}
config.orderColumns !== void 0 && (self.orderColumns = config.orderColumns)
config.filterColumns !== void 0 && (self.filterColumns = config.filterColumns)
config.multiple !== void 0 && (self.multiple = config.multiple);
config.showIndex !== void 0 && (self.showIndex = config.showIndex);
config.footable !== void 0 && (self.footable = config.footable);
config.expandConfig !== void 0 &&
(self.expandConfig = config.expandConfig);
config.itemCheckableOn !== void 0 &&
(self.itemCheckableOn = config.itemCheckableOn);
config.itemDraggableOn !== void 0 &&
(self.itemDraggableOn = config.itemDraggableOn);
config.hideCheckToggler !== void 0 &&
(self.hideCheckToggler = !!config.hideCheckToggler);
config.combineNum !== void 0 &&
(self.combineNum = parseInt(config.combineNum as any, 10) || 0);
config.combineFromIndex !== void 0 &&
(self.combineFromIndex =
parseInt(config.combineFromIndex as any, 10) || 0);
config.maxKeepItemSelectionLength !== void 0 &&
(self.maxKeepItemSelectionLength = config.maxKeepItemSelectionLength);
config.keepItemSelectionOnPageChange !== void 0 &&
(self.keepItemSelectionOnPageChange =
config.keepItemSelectionOnPageChange);
// Aug
config.mobileUI !== void 0 &&
(self.mobileUI = !!config.mobileUI);
config.tableLayout && (self.tableLayout = config.tableLayout);
config.columnsInfo !== void 0 && (self.columnsInfo = config.columnsInfo);
if (config.columns && Array.isArray(config.columns)) {
let columns: Array<SColumn> = config.columns
.filter(column => column)
.concat();
if (!columns.length) {
columns.push({
type: 'text',
label: '空'
});
}
//Aug
// columns['unshift']({
// type: '__expandme',
// toggable: false,
// fixed: 'left',
// className: 'Table-expandCell',
// label: self.mobileUI ? false : undefined,
// });
if (config.showIndex) {
columns.unshift({
type: '__pseudoColumn',
toggable: false,
// Jay
fixed: 'left',
className: 'Table-pseudoColumn'
});
}
columns.unshift({
type: '__checkme',
// fixed: 'left',
fixed: self.mobileUI ? (config.tableLayout == 'horizontal' ? 'left' : undefined) : 'left',//Aug
toggable: false,
className: 'Table-checkCell',
label: self.mobileUI ? false : undefined,//Aug
});
columns.unshift({
type: '__dragme',
toggable: false,
className: 'Table-dragCell'
});
columns = columns.map((item, index) => ({
...item,
index,
rawIndex: index - (config.showIndex ? 4 : 3),
type: item.type || 'plain',
pristine: item,
toggled: item.toggled !== false,
breakpoint: item.breakpoint,
isPrimary: index === (config.showIndex ? 4 : 3),
className: item.className || ''
}));
self.columns.replace(columns as any);
}
// Jay
if (config.rawColumns && Array.isArray(config.rawColumns)) {
let columns: Array<SColumn> = config.rawColumns
.filter(column => column)
.concat();
if (!columns.length) {
columns.push({
type: 'text',
label: '空'
});
}
//Aug
// columns['unshift']({
// type: '__expandme',
// toggable: false,
// fixed: 'left',
// className: 'Table-expandCell',
// label: self.mobileUI ? false : undefined,
// });
if (config.showIndex) {
columns.unshift({
type: '__pseudoColumn',
toggable: false,
// Jay
fixed: 'left',
className: 'Table-pseudoColumn'
});
}
columns.unshift({
type: '__checkme',
// fixed: 'left',
fixed: self.mobileUI ? (config.tableLayout == 'horizontal' ? 'left' : undefined) : 'left',//Aug
toggable: false,
className: 'Table-checkCell',
label: self.mobileUI ? false : undefined,//Aug
});
columns.unshift({
type: '__dragme',
toggable: false,
className: 'Table-dragCell'
});
columns = columns.map((item, index) => ({
...item,
index,
rawIndex: index - (config.showIndex ? 4 : 3),
type: item.type || 'plain',
pristine: item,
toggled: item.toggled !== false,
breakpoint: item.breakpoint,
isPrimary: index === (config.showIndex ? 4 : 3),
className: item.className || ''
}));
self.rawColumns.replace(columns as any);
}
}
// 初始化 forced 是否强制更新
function initmodifiedMap(forced: boolean = false) {
const modefiedMap = cloneDeep(self.modefiedMap)
// 如果强制更新 或者 datasetid发生变化 即数据源发生变化 就进行初始化
if (forced || (self.data.dataSetId !== modefiedMap.dataSetId)) {
if (self.data.dataSetId !== modefiedMap.dataSetId)
modefiedMap.dataSetId = self.data.dataSetId // 即数据源发生变化更新数据源id
// 发现数据集更新了就进行修改数据的更新
modefiedMap.modifiedDataSet = {}
// 强制更新一下视图
}
self.modefiedMap = modefiedMap
}
function updateColumns(columns: Array<SColumn>) {
if (columns && Array.isArray(columns)) {
columns = columns.filter(column => column).concat();
if (!columns.length) {
columns.push({
type: 'text',
label: '空'
});
}
// columns.unshift({
// type: '__expandme',
// toggable: false,
// // Jay
// fixed: 'left',
// className: 'Table-expandCell'
// });
if (self.showIndex) {
columns.unshift({
type: '__pseudoColumn',
toggable: false,
// Jay
fixed: 'left',
className: 'Table-pseudoColumn'
});
}
columns.unshift({
type: '__checkme',
fixed: self.mobileUI ? (self.tableLayout == 'horizontal' ? 'left' : undefined) : 'left',
toggable: false,
className: 'Table-checkCell'
});
columns.unshift({
type: '__dragme',
toggable: false,
// Jay
fixed: 'left',
className: 'Table-dragCell'
});
columns = columns.map((item, index) => ({
...item,
index,
rawIndex: index - (self.showIndex ? 4 : 3),
type: item.type || 'plain',
pristine: item.pristine || item,
toggled: item.toggled !== false,
breakpoint: item.breakpoint,
isPrimary: index === (self.showIndex ? 4 : 3)
}));
self.columns.replace(columns as any);
}
}
// 更新折叠操作列
function updateOperation(foldColumns: string[]) {
const temp = self.columns.map((col) => {
const item = col.pristine
if (item?.type === 'operation') {
const props = {
style: { overFlow: 'hidden' },
label: [
{
type: 'button',
label: '',
size: 'xs',
level: 'link',
icon: foldColumns?.includes('scale' + item.name) ? 'fa fa-step-backward' : 'fa fa-step-forward',
actionType: 'scale' + item.name
},
item.label[1]
]
}
return {
...col,
...props,
pristine: {
...item,
...props,
}
}
}
return col
})
self.columns.replace(temp);
}
function combineCell(arr: Array<SRow>, keys: Array<string>): Array<SRow> {
if (!keys.length || !arr.length) {
return arr;
}
const key: string = keys.shift() as string;
let rowIndex = 0;
let row = arr[rowIndex];
row.rowSpans[key] = 1;
// 区间数组
const currentInterval = []
let value = resolveVariable(key, row.data);
for (let i = 1, len = arr.length; i < len; i++) {
const current = arr[i];
// 合并部分
if (isEqual(resolveVariable(key, current.data), value)) {
row.rowSpans[key] += 1;
current.rowSpans[key] = 0;
} else {
if (row.rowSpans[key] > 1) {
// 先行记录一次
combineCell(arr.slice(rowIndex, i), keys.concat());
}
rowIndex = i;
row = current;
row.rowSpans[key] = 1;
value = resolveVariable(key, row.data);
}
}
// 区间对象
//走完数组
if (row.rowSpans[key] > 1 && keys.length) {
combineCell(arr.slice(rowIndex, arr.length), keys.concat());
}
return arr;
}
function autoCombineCell(
arr: Array<SRow>,
columns: Array<IColumn>,
maxCount: number,
fromIndex = 0
): Array<SRow> {
if (!columns.length || !maxCount || !arr.length) {
return arr;
}
// 如果是嵌套模式,通常第一列都是存在差异的,所以从第二列开始。
fromIndex =
fromIndex ||
(arr.some(item => Array.isArray(item.children) && item.children.length)
? 1
: 0);
const keys: Array<string> = [];
const len = columns.length;
for (let i = 0; i < len; i++) {
const column = columns[i];
// maxCount 可能比实际配置的 columns 还有多。
if (!column) {
break;
}
if ('__' === column.type.substring(0, 2)) {
maxCount++;
continue;
}
const key = column.name;
if (!key) {
break;
}
keys.push(key);
}
while (fromIndex--) {
keys.shift();
}
while (keys.length > maxCount) {
keys.pop();
}
return combineCell(arr, keys);
}
function initChildren(
children: Array<any>,
depth: number,
pindex: number,
parentId: string,
path: string = ''
): any {
depth += 1;
return children.map((item, index) => {
item = isObject(item)
? item
: {
item
};
const id = guid();
return {
// id: String(item && (item as any)[self.primaryField] || `${pindex}-${depth}-${key}`),
id: id,
parentId,
key: String(`${pindex}-${depth}-${index}`),
path: `${path}${index}`,
depth: depth,
index: index,
newIndex: index,
pristine: item,
data: item,
rowSpans: {},
children:
item && Array.isArray(item.children)
? initChildren(
item.children,
depth,
index,
id,
`${path}${index}.`
)
: [],
expandable: !!(
(item && Array.isArray(item.children) && item.children.length) ||
(self.footable && self.footableColumns.length)
)
};
});
}
function setTextWidth(textWidhMap: any) {
self.textWidhMap = textWidhMap
}
function initRows(
rows: Array<any>,
getEntryId?: (entry: any, index: number) => string,
reUseRow?: boolean,
option?: { caculateWidth: boolean, autoUnfold: number },
) {
// clearSelectItem()
window['getTableRows'] = () => {
// 生产调试用的方法
if (window['onDebugMode']) {
return self
}
}
// self.expandedRows.clear();
let arr: Array<SRow> = []
// 更新了数据源也进行重新计算
// 移动端每次都算 移动端不会更新数据源
if (isMobile() || !isNil(option?.caculateWidth)) {
self.textWidhMap = {}
}
for (const index in rows) {
const item = rows[index]
let id = getEntryId ? getEntryId(item, +index) : guid();
arr.push({
// id: getEntryId ? getEntryId(item, key) : String(item && (item as any)[self.primaryField] || `${key}-1-${key}`),
id: id,
key: String(`${index}-1-${index}`),
depth: 1, // 最大父节点默认为第一层,逐层叠加
index: +index,
newIndex: +index,
pristine: item,
path: `${index}`,
data: item,
checked: selectedIds.includes(item[DATAKEYID]),
rowSpans: {},
children:
item && Array.isArray(item.children)
? initChildren(item.children, 1, +index, id, `${index}.`)
: [],
expandable: !!(
(item && Array.isArray(item.children) && item.children.length) ||
(self.footable && self.footableColumns.length)
)
})
}
if (self.combineNum) {
arr = autoCombineCell(
arr,
self.columns,
self.combineNum,
self.combineFromIndex
);
const rowSpanIndexsInterval = {}
// 遍历计算-rowspan区间
arr.map(_ => {
Object.keys(_.rowSpans).map(key => {
// 如果有rowSpan
if (_.rowSpans[key]) {
rowSpanIndexsInterval[key] = rowSpanIndexsInterval[key] || [0]
rowSpanIndexsInterval[key].push(_.rowSpans[key] + (last(rowSpanIndexsInterval[key]) || 0))
}
})
})
self.rowSpanIndexsInterval = { ...rowSpanIndexsInterval }
}
// 有更新数据才走这里
if (self.modifiedRows.length)
// 合并修改信息到新数组
for (const item of arr) {
const dataId = item.data[DATAKEYID]
const modifiedData = !!dataId && self.modefiedMap.modifiedDataSet[dataId]
if (modifiedData) {
item.data = { ...modifiedData }
}
}
replaceRow(arr, reUseRow);
self.isNested = self.rows.some(item => item.children.length);
const expand = self.footable && self.footable.expand;
if (
expand === 'first' ||
(self.expandConfig && self.expandConfig.expand === 'first')
) {
self.rows.length && self.expandedRows.push(self.rows[0].id);
} else if (
(expand === 'all' && !self.footable.accordion) ||
(self.expandConfig &&
self.expandConfig.expand === 'all' &&
!self.expandConfig.accordion)
) {
self.expandedRows.replace(self.rows.map(item => item.id));
}
// 如果有 autoUnfold 字段
if (!isNil(option?.autoUnfold)) {
self.expandedRows.replace(getToggleExpandLevelIds(option?.autoUnfold || 0))
}
// 获取展开后总长度
self.tableExpandLength = getToggleExpandLevelIds(Infinity).length
// console.log(self, option?.autoUnfold, getToggleExpandLevelIds(option?.autoUnfold || 0), self.rows)
self.dragging = false;
}
// 尽可能的复用 row
function replaceRow(arr: Array<SRow>, reUseRow?: boolean) {
if (reUseRow === false) {
self.rows.replace(arr.map(item => Row.create(item)));
return;
}
const pool = arr.concat();
// 把多的删了先
if (self.rows.length > arr.length) {
self.rows.splice(arr.length, self.rows.length - arr.length);
}
let index = 0;
const len = self.rows.length;
while (pool.length) {
const item = pool.shift()!;
if (index < len) {
self.rows[index].replaceWith(item);
} else {
const row = Row.create(item);
self.rows.push(row);
}
index++;
}
}
function updateSelected(selected: Array<any>, valueField?: string) {
clearSelectItem()
self.rows.forEach(item => {
if (~selected.indexOf(item.pristine)) {
item.setCheckded(true)
selectedIds.push(item.data[DATAKEYID])
} else if (
find(
selected,
a =>
a[valueField || 'value'] &&
a[valueField || 'value'] == item.pristine[valueField || 'value']
)
) {
item.setCheckded(true)
selectedIds.push(item.data[DATAKEYID])
}
});
updateCheckDisable();
}
function toggleAll() {
const maxLength = self.maxKeepItemSelectionLength;
const keep = self.keepItemSelectionOnPageChange;
if (self.allChecked) {
clearSelectItem()
} else {
const selectedItems = self.data?.selectedItems;
if (
keep &&
maxLength &&
selectedItems &&
maxLength >= selectedItems.length
) {
// 剩下的没有被选中的项
const restCheckableRows = self.checkableRows.filter(
item => !item.checked
);
// 没被选中的项中,下标小于最大允许选中项数量
const checkableRows = restCheckableRows.filter(
(item, i) => i < maxLength - selectedItems.length
);
self.checkableRows.map((item, index) => {
// 如果存在可以选中的项
if (checkableRows[index]) {
item.setCheckded(true)
selectedIds.push(item.data[DATAKEYID])
}
});
// self.selectedRows.replace([...self.selectedRows, ...checkableRows]);
} else {
// 全选可选中的项
self.checkableRows.map(item => {
item.setCheckded(true)
selectedIds.push(item.data[DATAKEYID])
});
}
}
}
// 全部反选
function checkReverse() {
self.rows.forEach(item => {
if (item.checkable) {
if (item.checked) {
const idx = selectedIds.indexOf(item.data[DATAKEYID])
selectedIds.splice(idx, 1)
} else {
selectedIds.push(item.data[DATAKEYID])
}
item.setCheckded(!item.checked)
}
});
}
function checkAll() {
if (!self.allChecked) {
const maxLength = self.maxKeepItemSelectionLength;
const keep = self.keepItemSelectionOnPageChange;
const selectedItems = self.data?.selectedItems;
if (
keep &&
maxLength &&
selectedItems &&
maxLength >= selectedItems.length
) {
// 剩下的没有被选中的项
const restCheckableRows = self.checkableRows.filter(
item => !item.checked
);
// 没被选中的项中,下标小于最大允许选中项数量
const checkableRows = restCheckableRows.filter(
(item, i) => i < maxLength - selectedItems.length
);
//可选中的,下标小于最大允许选中项数量未选中全选
self.checkableRows.map((item, index) => {
// 如果存在可以选中的项
if (checkableRows[index]) {
item.setCheckded(true)
selectedIds.push(item.data[DATAKEYID])
}
});
} else {
// 可选中的全选
self.checkableRows.map(item => {
item.setCheckded(true)
selectedIds.push(item.data[DATAKEYID])
});
}
}
}
function dealHeaderValue() {
return self.columnsData.map(column => {
if (!column.pristine?.hidden) {
if (!(column.label instanceof Array)) {
const groupName = column.groupName ?? ''
return `${groupName ? (groupName + '·') : ''}${column.label.replaceAll('\n', '').replaceAll('\r', '')}`
}
}
return undefined
}).filter(v => v !== undefined).join('\t')
}
function dealCellValue(originValue: any, columnName: string, clearFormat: boolean = false, originColumn?: any, isCopyCell?: boolean) {
const column = originColumn || self.columnsData.find(column => column.name === columnName)
return getCellValue(originValue, column, clearFormat, isCopyCell)
}
function getCopyCellValue(rowIndex: number, columnName: string, clearFormat: boolean = false) {
const items = self.rows.map(row => row.data)
const value = items[rowIndex][columnName]
return dealCellValue(value, columnName, clearFormat, undefined, true)
}
function getCopyRowValue(rowIndex: number, hasHead: boolean = false, clearFormat: boolean = false) {
const columnFields = self.columnsData.filter(column => !column.pristine?.hidden).map(column => column.name) as string[]
if (self.selectedRows.length > 0) {
const items = self.selectedRows.map(rows => rows.data)
const rowValues = items.map((item: any) => columnFields.map(field => dealCellValue(item[field], field, clearFormat)).join('\t')).join('\n')
return hasHead ? dealHeaderValue() + '\n' + rowValues : rowValues
} else {
const items = self.rows.map(row => row.data)
const rowValue = columnFields.map(field => dealCellValue(items[rowIndex][field], field, clearFormat)).join('\t')
return hasHead ? dealHeaderValue() + '\n' + rowValue : rowValue
}
}
function getCopyColumnValue(columnNames: string | string[], clearFormat: boolean = false): string {
const items = self.selectedRows.length > 0 ? self.selectedRows.map(row => row.data) : self.rows.map(row => row.data)
if (Array.isArray(columnNames)) {
return items.map((item: any) => columnNames.map((name) => dealCellValue(item[name], name, clearFormat)).join('\t')).join('\n')
} else {
return items.map((item: any) => dealCellValue(item[columnNames], columnNames, clearFormat)).join('\n')
}
}
// allpage: 前端分页下复制全部数据
function getCopyPageValue(allPage = false, clearFormat: boolean = false) {
const items = allPage ? self.data.itemsRaw.slice() : self.rows.map(row => row.data)
//复制全部数据时先排序
const orderMap = self.orderColumns
const temp = allPage ? handleSort(items.slice(), Array.from(orderMap.keys()), orderMap) : items.slice()
const columnFields = self.columnsData.filter(column => !column.pristine?.hidden)
// 复制所有数据的时候移出每一行的引号 防止被excel误认
const value = temp.map((item: obj) => columnFields.map((column) => dealCellValue(item[column.name || ''], column.name || '', clearFormat, column).replace(/'|"/, '')).join('\t'))
return dealHeaderValue() + '\n' + value.join('\n');
}
// 记录最近一次点击的多选框,主要用于 shift 多选时判断上一个选的是什么
let lastCheckedRow: any = null;
const selectedIds: string[] = []
function toggle(row: IRow) {
if (!row.checkable) {
return;
}
lastCheckedRow = row;
// 如果是多选 进行反选
if (self.multiple) {
row.setCheckded(!row.checked)
if (row.checked) {
selectedIds.push(row.data[DATAKEYID])
} else {
const idx = selectedIds.indexOf(row.data[DATAKEYID])
selectedIds.splice(idx, 1)
}
} else {
// 如果是单选 移除其他的再选择一个
// 选中说明已经有一个选中
if (row.checked) {
row.setCheckded(false)
const idx = selectedIds.indexOf(row.data[DATAKEYID])
selectedIds.splice(idx, 1)
}
else {
// 清除其他选中的
self.selectedRows.map(item => {
item.setCheckded(false)
const idx = selectedIds.indexOf(item.data[DATAKEYID])
selectedIds.splice(idx, 1)
})
row.setCheckded(true)
selectedIds.push(row.data[DATAKEYID])
}
}
}
// 按住 shift 的时候点击选项
function toggleShift(row: IRow) {
// 如果是同一个或非 multiple 模式下就和不用 shift 一样
if (!lastCheckedRow || row === lastCheckedRow || !self.multiple) {
toggle(row);
return;
}
const maxLength = self.maxKeepItemSelectionLength;
const checkableRows = self.checkableRows;
const lastCheckedRowIndex = checkableRows.findIndex(
row => row === lastCheckedRow
);
const rowIndex = checkableRows.findIndex(rowItem => row === rowItem);
const minIndex =
lastCheckedRowIndex > rowIndex ? rowIndex : lastCheckedRowIndex;
const maxIndex =
lastCheckedRowIndex > rowIndex ? lastCheckedRowIndex : rowIndex;
const rows = checkableRows.slice(minIndex, maxIndex);
rows.push(row); // 将当前行也加入进行判断
for (const rowItem of rows) {
const idx = self.selectedRows.indexOf(rowItem);
if (idx === -1) {
// 如果上一个是选中状态,则将之间的所有 check 都变成可选
if (lastCheckedRow.checked) {
if (maxLength) {
if (self.selectedRows.length < maxLength) {
rowItem.setCheckded(true)
selectedIds.push(rowItem.data[DATAKEYID])
}
} else {
rowItem.setCheckded(true)
selectedIds.push(rowItem.data[DATAKEYID])
}
}
} else {
if (!lastCheckedRow.checked) {
rowItem.setCheckded(false)
const idx = selectedIds.indexOf(rowItem.data[DATAKEYID])
selectedIds.splice(idx, 1)
}
}
}
lastCheckedRow = row;
}
/**
*@description 清空已选项
*@date 2023-08-16
*/
function clearSelectItem() {
self.selectedRows.map(item => item.setCheckded(false));
selectedIds.splice(0, selectedIds.length)
}
function updateCheckDisable() {
if (!self.data) {
return;
}
const maxLength = self.maxKeepItemSelectionLength;
const selectedItems = self.data.selectedItems;
self.selectedRows.map(item => item.setCheckdisable(false));
if (maxLength && maxLength <= selectedItems.length) {
self.unSelectedRows.map(
item => !item.checked && item.setCheckdisable(true)
);
} else {
self.unSelectedRows.map(
item => item.checkdisable && item.setCheckdisable(false)
);
}
}
function clear() {
clearSelectItem()
}
// 获取指定层级下需要打开的按钮
function getToggleExpandLevelIds(level: number) {
const allRowStack: string[] = []
const getAllIds = (arr: any[]) => {
for (const item of arr) {
allRowStack.push(item.id)
// 如果有子节点的情况并且需要打开
if (level - item.depth > 1 && item.children) {
// 递归展开所有id
getAllIds(item.children)
}
}
}
getAllIds(self.rows)
return allRowStack
}
function toggleExpandAll() {
// row 堆栈方便处理所有row及其子row
if (self.allExpanded) {
self.expandedRows.clear();
} else {
const expandIdArr = getToggleExpandLevelIds(Infinity)
// 设置一下总
self.expandedRows.replace(
expandIdArr
);
}
}
function toggleExpanded(row: IRow) {
const idx = self.expandedRows.indexOf(row.id);
if (~idx) {
self.expandedRows.splice(idx, 1);
} else if (self.footable && self.footable.accordion) {
self.expandedRows.replace([row.id]);
} else if (self.expandConfig && self.expandConfig.accordion) {
let rows = self
.getExpandedRows()
.filter(item => item.depth !== row.depth);
rows.push(row);
self.expandedRows.replace(rows.map(item => item.id));
} else {
self.expandedRows.push(row.id);
}
}
function collapseAllAtDepth(depth: number) {
let rows = self.getExpandedRows().filter(item => item.depth !== depth);
self.expandedRows.replace(rows.map(item => item.id));
}
function setOrderByInfo(key: string, direction: 'asc' | 'desc') {
self.orderBy = key;
self.orderDir = direction;
}
// 强制赋予数据源id
function updateDataSetId(dataSetId: string) {
if (self.modefiedMap.dataSetId !== dataSetId) {
initmodifiedMap()
}
}
function handleMutilSort(loadDataOnce: boolean = false, caseSensitive = false) {
const orderMap = self.orderColumns
const filterMap = self.filterColumns
const { page = 1, perPage = self.data.itemRaws.length } = self.data
let items = loadDataOnce ? self.data.itemsRaw.slice() : self.data.items.slice()
if (orderMap.size > 0) {
items = handleSort(items, Array.from(orderMap.keys()), orderMap, self.modefiedMap.modifiedDataSet)
}
if (filterMap.size > 0) {
items = handleFilter(items, Array.from(filterMap.keys()), filterMap, caseSensitive)
}
const result = loadDataOnce ? items.slice((page - 1) * perPage, page * perPage) : items.slice()
initRows(result)
return items
}
function bulkQuickChange(changedRows: typeof self.rows, changeList: any[]) {
changedRows.forEach((item, index) => {
item.data = changeList[index].rowData
});
}
function reset() {
initmodifiedMap(true)
self.rows.forEach(item => item.reset());
EventSub.emit(EventEnum.ClearVistiMap)
let rows = self.rows.conca