20260820173056

This commit is contained in:
oneao committed 2026-08-20 17:30:56 +08:00
1 parent 8e75c7989a
commit 68ac5db038
24 files changed
+975 -6035

No files matched your search

@@ -55,22 +55,42 @@ const tableTheme = computed(() => props.theme ?? (appStore.darkMode ? 'dark' : '
// Tab 面板从隐藏恢复时,等到容器拥有有效尺寸再刷新虚拟滚动布局。
// rAF 将同一帧内的多次 ResizeObserver 通知合并,避免重复重排。
let refreshRafId = 0
function refreshLayout() {
function refreshLayout(xOnly = false) {
if (refreshRafId) cancelAnimationFrame(refreshRafId)
refreshRafId = requestAnimationFrame(async () => {
refreshRafId = 0
await nextTick()
tableRef.value?.initVirtualScroll?.()
// 高度未变、仅宽度变化时只重算 X 方向虚拟滚动,省掉 Y 方向重算
if (xOnly) tableRef.value?.initVirtualScrollX?.()
else tableRef.value?.initVirtualScroll?.()
await nextTick()
})
}
/* 尺寸防抖(trailing):折叠动画 / 窗口缩放 / 拖拽调宽等连续尺寸变化期间,
* ResizeObserver 每帧都会通知,但中间尺寸是瞬态的——逐帧 initVirtualScroll
* 会每帧强制 reflow 并重渲染可见行列,造成动画掉帧(如侧栏折叠时 dashboard
* 10 万单元格表格的卡顿)。这里合并通知,尺寸稳定(约 150ms 无变化)后才
* 刷新一次,与 stk-table 库 autoResize 的 200ms 防抖语义对齐(库 L4130-4133)。
* 动画期间表格仅随 CSS 拉伸,结束后一次到位。 */
const RESIZE_DEBOUNCE_MS = 150
let resizeDebounceTimer = 0
// 上次生效尺寸:用于判断是否仅宽度变化(只重算 X 方向)
let lastSize = null
let resizeObserver = null
onMounted(() => {
if (typeof ResizeObserver === 'undefined' || !rootRef.value) return
resizeObserver = new ResizeObserver(([entry]) => {
const { width, height } = entry.contentRect
if (width > 0 && height > 0) refreshLayout()
if (width <= 0 || height <= 0) return
clearTimeout(resizeDebounceTimer)
resizeDebounceTimer = setTimeout(() => {
resizeDebounceTimer = 0
const xOnly = lastSize != null && lastSize.height === height && lastSize.width !== width
lastSize = { width, height }
refreshLayout(xOnly)
}, RESIZE_DEBOUNCE_MS)
})
resizeObserver.observe(rootRef.value)
})
@@ -515,6 +535,7 @@ watch(
onBeforeUnmount(() => {
if (refreshRafId) cancelAnimationFrame(refreshRafId)
clearTimeout(resizeDebounceTimer)
resizeObserver?.disconnect()
resizeObserver = null
removeNavigationListeners()
@@ -589,6 +610,11 @@ defineExpose({
.fms-table__inner {
width: 100%;
height: 100%;
/* transform 虚拟滚动模式下,把 tbody 提升为合成层,translateY 走合成器、避免每帧重绘整表 */
.stk-tbody-main {
will-change: transform;
}
}
</style>
@@ -42,6 +42,8 @@ function actionButton(action, row, triggerAction, className = 'fms-action-btn')
)
}
// 确认弹窗:状态化渲染(替代 antdv 的 Modal.confirm 命令式调用),
// 由内部 Modal 承载;okType=danger(默认)时用 danger 按钮定制 footer。
function createConfirmDialog(state, close) {
const { action, row } = state
const config = typeof action.confirm === 'string' ? { title: action.confirm } : action.confirm || {}
@@ -203,23 +205,24 @@ function createActionsCell(actions) {
}
}
// ===== A/B 变体:模拟优化前的行为 =====
// 所有列(含纯展示列、非编辑态可编辑列)一律注入 customCell 函数式组件,
// 即每格都是一个函数式组件,滚动时每帧全部重建。
function displayCell(col, row) {
const content = row[col.dataIndex]
return h('span', { class: 'fms-cell-display', title: String(content ?? '') }, content ?? '')
}
export function resolveCustomCell(col, editingContext, editingActive = true) {
if (col.customCell) return col
if (col.editor) {
// 可编辑列:仅在存在活动编辑行时注入编辑器渲染(非编辑态走库原生 td,避免滚动时
// 每格函数式组件重渲染)。checkbox 列始终可交互、select 列需解析 label 展示,
// 两者保持注入 customCell。
if (
col.editor &&
(editingActive || col.editor.type === 'checkbox' || col.editor.type === 'select')
) {
return { ...col, customCell: createEditableCell(col, editingContext) }
}
if (col.actions?.length) {
const customCell = createActionsCell(col.actions)
if (customCell) return { ...col, customCell }
}
// 纯展示列:也注入 customCell(旧行为)
return { ...col, customCell: ({ row }) => displayCell(col, row) }
// 纯展示列(无 editor、无 actions)与非编辑态的可编辑列:不再注入 customCell,
// 走 StkTable 原生 td 渲染路径(减少每格函数式组件数量)。mousedown 照常冒泡到
// tbody 以支持点击激活行编辑,文字选中通过 CSS 层 user-select 保障。
return col
}