|
1 | 1 | import type { ListTable, PivotTable } from '@visactor/vtable'; |
2 | | -import type { FilterState, FilterAction, FilterConfig, FilterListener } from './types'; |
| 2 | +import type { FilterState, FilterAction, FilterConfig, FilterListener, FilterStateSnapshot } from './types'; |
3 | 3 | import { FilterActionType } from './types'; |
4 | 4 | import type { FilterEngine } from './filter-engine'; |
5 | 5 |
|
@@ -45,6 +45,145 @@ export class FilterStateManager { |
45 | 45 | return this.state; |
46 | 46 | } |
47 | 47 |
|
| 48 | + /** |
| 49 | + * 生成可序列化的筛选快照,用于撤销/恢复或跨实例回放。 |
| 50 | + * - 只保留声明式配置(byValue 的 values、byCondition 的 operator/condition 等),不包含运行时函数。 |
| 51 | + * - 对 byValue 的 values 做排序,避免因为用户勾选顺序不同导致快照 diff 抖动。 |
| 52 | + * - 对 filters 按 field 排序,保证快照稳定。 |
| 53 | + */ |
| 54 | + getSnapshot(): FilterStateSnapshot { |
| 55 | + const filters: FilterConfig[] = []; |
| 56 | + this.state.filters.forEach(v => { |
| 57 | + const next: any = { ...v }; |
| 58 | + if (next && next.type === 'byValue' && Array.isArray(next.values)) { |
| 59 | + next.values = next.values.slice().sort((a: any, b: any) => String(a).localeCompare(String(b))); |
| 60 | + } |
| 61 | + if (next && Array.isArray(next.condition) && next.condition.length > 2) { |
| 62 | + next.condition = next.condition.slice(); |
| 63 | + } |
| 64 | + filters.push(next); |
| 65 | + }); |
| 66 | + filters.sort((a, b) => String(a.field).localeCompare(String(b.field))); |
| 67 | + return { filters }; |
| 68 | + } |
| 69 | + |
| 70 | + /** |
| 71 | + * 从快照恢复筛选状态并立即重新应用筛选。 |
| 72 | + * 典型用途:HistoryPlugin 回放(undo/redo)时恢复筛选配置。 |
| 73 | + */ |
| 74 | + applySnapshot(snapshot: FilterStateSnapshot, actionType: FilterActionType = FilterActionType.APPLY_FILTERS): void { |
| 75 | + const next = new Map<string | number, FilterConfig>(); |
| 76 | + (snapshot?.filters ?? []).forEach(cfg => { |
| 77 | + if (!cfg) { |
| 78 | + return; |
| 79 | + } |
| 80 | + const cloned: any = { ...cfg }; |
| 81 | + if (cloned && cloned.type === 'byValue' && Array.isArray(cloned.values)) { |
| 82 | + cloned.values = cloned.values.slice(); |
| 83 | + } |
| 84 | + if (cloned && Array.isArray(cloned.condition)) { |
| 85 | + cloned.condition = cloned.condition.slice(); |
| 86 | + } |
| 87 | + next.set(cloned.field, cloned); |
| 88 | + }); |
| 89 | + this.state = { ...this.state, filters: next }; |
| 90 | + this.applyFilters(); |
| 91 | + this.notifyListeners({ type: actionType, payload: { fromSnapshot: true } } as any); |
| 92 | + } |
| 93 | + |
| 94 | + /** |
| 95 | + * 列插入后修正筛选配置中的 field。 |
| 96 | + * 背景:ListTable.addColumns(..., isMaintainArrayData=true) 会重排 columns[i].field=i, |
| 97 | + * 此时 FilterStateManager 里如果存的是数字 field(数组 records 场景),需要同步 +columnCount, |
| 98 | + * 否则旧筛选会错位应用到新列,表现为“全部被过滤掉/筛选异常”。 |
| 99 | + */ |
| 100 | + shiftFieldsOnAddColumns(columnIndex: number, columnCount: number): void { |
| 101 | + if (!Number.isFinite(columnIndex) || !Number.isFinite(columnCount) || columnCount <= 0) { |
| 102 | + return; |
| 103 | + } |
| 104 | + const next = new Map<string | number, FilterConfig>(); |
| 105 | + this.state.filters.forEach((cfg, key) => { |
| 106 | + let newKey: string | number = key; |
| 107 | + const cloned: any = { ...cfg }; |
| 108 | + if (typeof newKey === 'number' && newKey >= columnIndex) { |
| 109 | + newKey = newKey + columnCount; |
| 110 | + } |
| 111 | + if (typeof cloned.field === 'number' && cloned.field >= columnIndex) { |
| 112 | + cloned.field = cloned.field + columnCount; |
| 113 | + } |
| 114 | + if (cloned && cloned.type === 'byValue' && Array.isArray(cloned.values)) { |
| 115 | + cloned.values = cloned.values.slice(); |
| 116 | + } |
| 117 | + if (cloned && Array.isArray(cloned.condition)) { |
| 118 | + cloned.condition = cloned.condition.slice(); |
| 119 | + } |
| 120 | + next.set(newKey, cloned); |
| 121 | + }); |
| 122 | + this.state = { ...this.state, filters: next }; |
| 123 | + } |
| 124 | + |
| 125 | + /** |
| 126 | + * 列删除后修正筛选配置中的 field。 |
| 127 | + * - 命中被删除列的筛选项会被移除(否则将引用无效列)。 |
| 128 | + * - 删除多列时按升序依次“前移”,与 ListTable.deleteColumns 的维护逻辑一致。 |
| 129 | + */ |
| 130 | + shiftFieldsOnDeleteColumns(deleteColIndexs: number[]): void { |
| 131 | + if (!Array.isArray(deleteColIndexs) || deleteColIndexs.length === 0) { |
| 132 | + return; |
| 133 | + } |
| 134 | + const sorted = deleteColIndexs |
| 135 | + .slice() |
| 136 | + .filter(n => Number.isFinite(n)) |
| 137 | + .sort((a, b) => a - b); |
| 138 | + const deleteIndexNums = sorted.map((idx, i) => idx - i); |
| 139 | + |
| 140 | + const next = new Map<string | number, FilterConfig>(); |
| 141 | + this.state.filters.forEach((cfg, key) => { |
| 142 | + let newKey: string | number = key; |
| 143 | + const cloned: any = { ...cfg }; |
| 144 | + let removed = false; |
| 145 | + if (typeof newKey === 'number') { |
| 146 | + let k = newKey; |
| 147 | + for (let i = 0; i < deleteIndexNums.length; i++) { |
| 148 | + const d = deleteIndexNums[i]; |
| 149 | + if (k === d) { |
| 150 | + removed = true; |
| 151 | + break; |
| 152 | + } |
| 153 | + if (k > d) { |
| 154 | + k -= 1; |
| 155 | + } |
| 156 | + } |
| 157 | + newKey = k; |
| 158 | + } |
| 159 | + if (typeof cloned.field === 'number') { |
| 160 | + let f = cloned.field; |
| 161 | + for (let i = 0; i < deleteIndexNums.length; i++) { |
| 162 | + const d = deleteIndexNums[i]; |
| 163 | + if (f === d) { |
| 164 | + removed = true; |
| 165 | + break; |
| 166 | + } |
| 167 | + if (f > d) { |
| 168 | + f -= 1; |
| 169 | + } |
| 170 | + } |
| 171 | + cloned.field = f; |
| 172 | + } |
| 173 | + if (removed) { |
| 174 | + return; |
| 175 | + } |
| 176 | + if (cloned && cloned.type === 'byValue' && Array.isArray(cloned.values)) { |
| 177 | + cloned.values = cloned.values.slice(); |
| 178 | + } |
| 179 | + if (cloned && Array.isArray(cloned.condition)) { |
| 180 | + cloned.condition = cloned.condition.slice(); |
| 181 | + } |
| 182 | + next.set(newKey, cloned); |
| 183 | + }); |
| 184 | + this.state = { ...this.state, filters: next }; |
| 185 | + } |
| 186 | + |
48 | 187 | /** |
49 | 188 | * 公共方法:重新应用当前所有激活的筛选状态 |
50 | 189 | * 用于在表格配置更新后恢复筛选显示 |
|
0 commit comments