20260812172705

This commit is contained in:
oneao committed 2026-08-12 17:27:06 +08:00
1 parent 1d7f7c2bba
commit 87b2d3c482
1 file changed
+60 -18
@@ -66,6 +66,8 @@ const rowEditingStates = new WeakMap();
let activeEditingRow = null; let activeEditingRow = null;
let pendingEditingTarget = null; let pendingEditingTarget = null;
const pressedNavigationKeys = new Set(); const pressedNavigationKeys = new Set();
let editRequestId = 0;
let currentTableRow = null;
function getRowKey(row) { function getRowKey(row) {
return typeof props.rowKey === "function" ? props.rowKey(row) : row?.[props.rowKey]; return typeof props.rowKey === "function" ? props.rowKey(row) : row?.[props.rowKey];
@@ -81,6 +83,18 @@ function getRowEditingState(row) {
return state; return state;
} }
// StkTable 的当前行更新会触发可见行重算。保持幂等,避免
// activeRowKey watcher 与 editRow() 同时设置同一个行时重复刷新。
function setCurrentRow(row) {
if (currentTableRow === row) return;
currentTableRow = row || null;
tableRef.value?.setCurrentRow?.(row, { silent: true });
}
function handleCurrentChange(_event, row, { select } = {}) {
currentTableRow = select ? row : null;
}
function captureEditableValues(row) { function captureEditableValues(row) {
return new Map( return new Map(
props.columns.filter((col) => col.editor).map((col) => [col.dataIndex, row[col.dataIndex]]), props.columns.filter((col) => col.editor).map((col) => [col.dataIndex, row[col.dataIndex]]),
@@ -248,10 +262,7 @@ function onColumnsUpdate(columns) {
const real = (columns || []) const real = (columns || [])
.filter( .filter(
(c) => (c) =>
!isSelectionColumn(c) && !isSelectionColumn(c) && c.type !== "seq" && c.type !== "dragRow" && c.dataIndex !== "",
c.type !== "seq" &&
c.type !== "dragRow" &&
c.dataIndex !== "",
) )
.map((col) => { .map((col) => {
const original = originalByKey.get(String(col.key ?? col.dataIndex ?? "")); const original = originalByKey.get(String(col.key ?? col.dataIndex ?? ""));
@@ -301,18 +312,32 @@ async function scrollToRow(rowKey) {
// 程序化激活行编辑。新增行等场景默认先滚动到目标行;行内操作可传 // 程序化激活行编辑。新增行等场景默认先滚动到目标行;行内操作可传
// { scroll: false } 保持当前滚动位置。 // { scroll: false } 保持当前滚动位置。
async function editRow(rowKey, focusCol = null, options = {}) { async function editRow(rowKey, focusCol = null, options = {}) {
const requestId = ++editRequestId;
if (options.scroll === false) await nextTick(); if (options.scroll === false) await nextTick();
else await scrollToRow(rowKey); else await scrollToRow(rowKey);
if (requestId !== editRequestId) return;
await nextTick(); await nextTick();
if (requestId !== editRequestId) return;
const row = props.dataSource.find((item) => String(getRowKey(item)) === String(rowKey)); const row = props.dataSource.find((item) => String(getRowKey(item)) === String(rowKey));
if (!row) return; if (!row) return;
const table = tableRef.value;
table?.setCurrentRow?.(row, { silent: true }); const now = performance.now();
setCurrentRow(row);
if (options.scroll === false && now - lastEditActivateAt < 150) {
pendingEditingTarget = { row, focusCol, setAreaSelection: true };
clearActiveEditing();
scheduleEditActivation();
return;
}
pendingEditingTarget = null;
clearEditActivateTimer();
activateEditing(row, focusCol); activateEditing(row, focusCol);
lastEditActivateAt = now;
const focusColumn = displayColumns.value.find((column) => column.dataIndex === focusCol); const focusColumn = displayColumns.value.find((column) => column.dataIndex === focusCol);
if (focusColumn) { if (focusColumn) {
table?.setAreaSelection?.( tableRef.value?.setAreaSelection?.(
{ begin: { row, col: focusColumn } }, { begin: { row, col: focusColumn } },
{ silent: true, scrollToView: false }, { silent: true, scrollToView: false },
); );
@@ -352,12 +377,12 @@ function clearEditActivateTimer() {
// 键盘导航停止(松开按键或停顿)后再激活编辑器,合并连续/按住按键, // 键盘导航停止(松开按键或停顿)后再激活编辑器,合并连续/按住按键,
// 避免滚动过程中每帧创建/销毁编辑器组件导致掉帧。 // 避免滚动过程中每帧创建/销毁编辑器组件导致掉帧。
function scheduleEditActivation() { function scheduleEditActivation(delay = 100) {
clearEditActivateTimer(); clearEditActivateTimer();
editActivateTimer = setTimeout(() => { editActivateTimer = setTimeout(() => {
editActivateTimer = null; editActivateTimer = null;
commitPendingEditing(); commitPendingEditing();
}, 100); }, delay);
} }
function commitPendingEditing() { function commitPendingEditing() {
@@ -368,8 +393,19 @@ function commitPendingEditing() {
(item) => item === target.row || String(getRowKey(item)) === String(getRowKey(target.row)), (item) => item === target.row || String(getRowKey(item)) === String(getRowKey(target.row)),
); );
if (row) { if (row) {
tableRef.value?.setCurrentRow?.(row, { silent: true }); setCurrentRow(row);
activateEditing(row, target.focusCol); activateEditing(row, target.focusCol);
if (target.setAreaSelection) {
const focusColumn = displayColumns.value.find(
(column) => column.dataIndex === target.focusCol,
);
if (focusColumn) {
tableRef.value?.setAreaSelection?.(
{ begin: { row, col: focusColumn } },
{ silent: true, scrollToView: false },
);
}
}
lastEditActivateAt = performance.now(); lastEditActivateAt = performance.now();
} }
} }
@@ -447,7 +483,7 @@ function handleAreaSelectionChange(ranges) {
pendingEditingTarget = null; pendingEditingTarget = null;
clearEditActivateTimer(); clearEditActivateTimer();
activateEditing(row, focusCol); activateEditing(row, focusCol);
tableRef.value?.setCurrentRow?.(row, { silent: true }); setCurrentRow(row);
lastEditActivateAt = now; lastEditActivateAt = now;
} else { } else {
pendingEditingTarget = { row, focusCol }; pendingEditingTarget = { row, focusCol };
@@ -457,9 +493,8 @@ function handleAreaSelectionChange(ranges) {
return; return;
} }
// 非键盘导航(鼠标点击等):立即激活。 // 鼠标点击为离散意图操作,期望即时聚焦,不做连点合并。
// 同一行已编辑时仍更新 focusCol(保证焦点切换到新列), // 同一行内切换列仍立即更新 focusCol,并暂时禁用 select 全选。
// 但暂时禁用 select 全选(避免鼠标点击触发全选文本)。
pendingEditingTarget = null; pendingEditingTarget = null;
clearEditActivateTimer(); clearEditActivateTimer();
if (activeEditingRow === row) { if (activeEditingRow === row) {
@@ -480,11 +515,11 @@ watch(
rebindActiveEditingRow(props.dataSource); rebindActiveEditingRow(props.dataSource);
await nextTick(); await nextTick();
if (rowKey == null) { if (rowKey == null) {
tableRef.value?.setCurrentRow?.(undefined, { silent: true }); setCurrentRow(undefined);
return; return;
} }
const row = props.dataSource.find((item) => String(getRowKey(item)) === String(rowKey)); const row = props.dataSource.find((item) => String(getRowKey(item)) === String(rowKey));
tableRef.value?.setCurrentRow?.(row, { silent: true }); setCurrentRow(row);
}, },
{ immediate: true }, { immediate: true },
); );
@@ -493,6 +528,7 @@ watch(
() => props.editable, () => props.editable,
(editable) => { (editable) => {
if (editable) return; if (editable) return;
editRequestId += 1;
pendingEditingTarget = null; pendingEditingTarget = null;
removeNavigationListeners(); removeNavigationListeners();
pressedNavigationKeys.clear(); pressedNavigationKeys.clear();
@@ -504,6 +540,7 @@ watch(
); );
onBeforeUnmount(() => { onBeforeUnmount(() => {
editRequestId += 1;
removeNavigationListeners(); removeNavigationListeners();
clearEditActivateTimer(); clearEditActivateTimer();
pressedNavigationKeys.clear(); pressedNavigationKeys.clear();
@@ -545,6 +582,7 @@ defineExpose({
:show-overflow="showOverflow" :show-overflow="showOverflow"
:area-selection="editable" :area-selection="editable"
:bordered="bordered" :bordered="bordered"
@current-change="handleCurrentChange"
@area-selection-change="handleAreaSelectionChange" @area-selection-change="handleAreaSelectionChange"
@row-order-change="handleRowOrderChange" @row-order-change="handleRowOrderChange"
> >
@@ -591,11 +629,16 @@ defineExpose({
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.stk-table tbody tr {
transform: translateZ(0);
}
</style> </style>
<!-- 非 scoped 全局样式:原生 checkbox 保持低组件开销,外观使用 shadcn 语义变量。 --> <!-- 非 scoped 全局样式:原生 checkbox 保持低组件开销,外观使用 shadcn 语义变量。 -->
<style lang="scss"> <style lang="scss">
.stk-table tbody tr {
transform: translateZ(0);
}
/* 双类选择器 .fms-table__inner.stk-table 权重 (0,2,1) > 库默认 .stk-table (0,1,1) */ /* 双类选择器 .fms-table__inner.stk-table 权重 (0,2,1) > 库默认 .stk-table (0,1,1) */
.fms-table__inner.stk-table { .fms-table__inner.stk-table {
th, th,
@@ -767,7 +810,6 @@ defineExpose({
white-space: nowrap; white-space: nowrap;
} }
// actions 列按钮容器 // actions 列按钮容器
.fms-actions-cell { .fms-actions-cell {
display: inline-flex; display: inline-flex;