20260724173425
This commit is contained in:
1 parent
4f40ad9f11
commit
b33c743236
27 files changed
+1310
-437
No files matched your search
@@ -6,16 +6,67 @@ function resolveOption(option, row) {
|
||||
}
|
||||
|
||||
export function createEditableCellRenderer({ getRowKey, getActiveRowKey, getActiveColKey }) {
|
||||
// 每个单元格独立的 debounce timer,避免每次击键都触发
|
||||
// onChange → emitUpdate → emit → parent drafts → watch → localData 新引用
|
||||
// → FmsTable dataSource 新引用 → StkTable 全量虚拟滚动重算 → 卡顿。
|
||||
// 150ms debounce 让连续打字期间只触发一次同步。
|
||||
const changeTimers = new Map();
|
||||
|
||||
// 追踪已聚焦过的单元格 key,避免在同一个编辑会话内重复 focus+select
|
||||
//(例如父组件数据变化导致行重渲染时不应打断正在输入的用户)。
|
||||
// 与旧实现不同:不依赖 DOM dataset,因为优化后编辑组件始终驻留 DOM。
|
||||
const focusedCells = new Map();
|
||||
|
||||
// 缓存每个 cellKey 的 focusRef 闭包引用,避免每次渲染创建新引用导致
|
||||
// Vue ref 回调反复触发(ref 变化 → ref(null) → ref(instance) → focus+select)。
|
||||
const focusRefCache = new Map();
|
||||
|
||||
// 稳定引用:Vue VNode diff 按引用比较 props,每次 render 创建新函数/对象
|
||||
// 会触发事件监听器重绑定和样式对比。提到外面让引用恒定,消除击键时的无意义 diff。
|
||||
const stopPropagation = (e) => e.stopPropagation();
|
||||
const fullWidth = { width: "100%" };
|
||||
|
||||
function getFocusRef(cellKey, enabled) {
|
||||
if (!enabled) return undefined;
|
||||
let fn = focusRefCache.get(cellKey);
|
||||
if (!fn) {
|
||||
fn = (instance) => {
|
||||
if (!instance) return;
|
||||
if (focusedCells.get(cellKey)) return;
|
||||
focusedCells.set(cellKey, true);
|
||||
const el = instance.$el;
|
||||
const target = instance.focus ? instance : el;
|
||||
target?.focus?.({ preventScroll: true });
|
||||
const inputEl = el?.matches?.("input") ? el : el?.querySelector?.("input");
|
||||
inputEl?.select?.();
|
||||
};
|
||||
focusRefCache.set(cellKey, fn);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
return function EditableCell({ row, col, cellValue }) {
|
||||
const editor = typeof col.editor === "string" ? { type: col.editor } : col.editor || {};
|
||||
const rowKey = String(getRowKey(row));
|
||||
const activeRowKey = getActiveRowKey();
|
||||
const isEditing = activeRowKey != null && String(getRowKey(row)) === String(activeRowKey);
|
||||
const isEditing = activeRowKey != null && String(activeRowKey) === rowKey;
|
||||
const activeColKey = getActiveColKey?.();
|
||||
const shouldFocus = isEditing && (activeColKey == null || col.dataIndex === activeColKey);
|
||||
const cellKey = `${rowKey}_${col.dataIndex}`;
|
||||
|
||||
const update = (value) => {
|
||||
row[col.dataIndex] = value;
|
||||
editor.onChange?.(value, row, col);
|
||||
if (editor.onChange) {
|
||||
const timerKey = `${rowKey}_${col.dataIndex}`;
|
||||
clearTimeout(changeTimers.get(timerKey));
|
||||
changeTimers.set(
|
||||
timerKey,
|
||||
setTimeout(() => {
|
||||
changeTimers.delete(timerKey);
|
||||
editor.onChange?.(value, row, col);
|
||||
}, 150),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// checkbox 始终可交互(显示态与编辑态一致)
|
||||
@@ -30,32 +81,40 @@ export function createEditableCellRenderer({ getRowKey, getActiveRowKey, getActi
|
||||
});
|
||||
}
|
||||
|
||||
if (!isEditing) {
|
||||
if (editor.type === "select") {
|
||||
const options = resolveOption(editor.options, row) || [];
|
||||
const option = options.find((item) => item.value === cellValue);
|
||||
return h("span", { class: "fms-cell-text" }, option?.label ?? cellValue ?? "");
|
||||
}
|
||||
return h("span", { class: "fms-cell-text" }, cellValue ?? "");
|
||||
}
|
||||
// 文本态内容
|
||||
const textContent =
|
||||
editor.type === "select"
|
||||
? ((resolveOption(editor.options, row) || []).find((item) => item.value === cellValue)
|
||||
?.label ??
|
||||
cellValue ??
|
||||
"")
|
||||
: (cellValue ?? "");
|
||||
|
||||
// 始终同时渲染文本 span 和编辑组件,仅通过 CSS display 切换可见性。
|
||||
// 这样 Vue diff 看到的是同一个 div 容器 + 同一对子 VNode 类型,
|
||||
// 跨行切换时只更新 props/style,不触发组件卸载/挂载。
|
||||
// (之前 if(!isEditing) return span else return Input → 不同类型 VNode
|
||||
// → 强制 mount/unmount Ant Design 组件,是键盘导航时 FPS 暴跌的另一主因。)
|
||||
|
||||
const textSpan = h(
|
||||
"span",
|
||||
{
|
||||
class: "fms-cell-text",
|
||||
style: { display: isEditing ? "none" : "inline-block" },
|
||||
},
|
||||
textContent,
|
||||
);
|
||||
|
||||
const common = {
|
||||
size: "small",
|
||||
style: { width: "100%" },
|
||||
onClick: (event) => event.stopPropagation(),
|
||||
// 仅被点击的列首次渲染时自动聚焦
|
||||
ref: shouldFocus
|
||||
? (instance) => {
|
||||
if (instance) {
|
||||
const target = instance.focus ? instance : instance.$el;
|
||||
target?.focus?.();
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
style: { ...fullWidth, display: isEditing ? "inline-block" : "none" },
|
||||
onClick: stopPropagation,
|
||||
ref: getFocusRef(cellKey, shouldFocus),
|
||||
};
|
||||
|
||||
let editComponent;
|
||||
if (editor.type === "number") {
|
||||
return h(InputNumber, {
|
||||
editComponent = h(InputNumber, {
|
||||
...common,
|
||||
value: cellValue,
|
||||
min: resolveOption(editor.min, row),
|
||||
@@ -64,25 +123,27 @@ export function createEditableCellRenderer({ getRowKey, getActiveRowKey, getActi
|
||||
disabled: resolveOption(editor.disabled, row),
|
||||
"onUpdate:value": update,
|
||||
});
|
||||
}
|
||||
if (editor.type === "select") {
|
||||
return h(Select, {
|
||||
} else if (editor.type === "select") {
|
||||
editComponent = h(Select, {
|
||||
...common,
|
||||
value: cellValue,
|
||||
options: resolveOption(editor.options, row) || [],
|
||||
disabled: resolveOption(editor.disabled, row),
|
||||
"onUpdate:value": update,
|
||||
});
|
||||
} else {
|
||||
editComponent = h(Input, {
|
||||
...common,
|
||||
value: cellValue,
|
||||
disabled: resolveOption(editor.disabled, row),
|
||||
maxlength: resolveOption(editor.maxlength, row),
|
||||
placeholder: resolveOption(editor.placeholder, row),
|
||||
status: resolveOption(editor.status, row),
|
||||
allowClear: editor.allowClear ?? false,
|
||||
"onUpdate:value": update,
|
||||
});
|
||||
}
|
||||
return h(Input, {
|
||||
...common,
|
||||
value: cellValue,
|
||||
disabled: resolveOption(editor.disabled, row),
|
||||
maxlength: resolveOption(editor.maxlength, row),
|
||||
placeholder: resolveOption(editor.placeholder, row),
|
||||
status: resolveOption(editor.status, row),
|
||||
allowClear: editor.allowClear ?? true,
|
||||
"onUpdate:value": update,
|
||||
});
|
||||
|
||||
return h("div", { class: "fms-editable-cell-wrap" }, [textSpan, editComponent]);
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user