diff --git a/packages/effects/plugins/src/vxe-table/api.ts b/packages/effects/plugins/src/vxe-table/api.ts index 779a5964b..076686ff3 100644 --- a/packages/effects/plugins/src/vxe-table/api.ts +++ b/packages/effects/plugins/src/vxe-table/api.ts @@ -6,6 +6,7 @@ import type { } from '@vben-core/form-ui'; import type { VxeGridProps } from './types'; +import type {ViewedRowHelper} from './use-viewed-row'; import { toRaw } from 'vue'; @@ -42,20 +43,15 @@ export class VxeGridApi< public store: Store>; + /** + * 已读行 helper(在 mount 中初始化,业务能力全部封装在 useViewedRow 中) + */ + public viewedRowHelper: null | ViewedRowHelper = null; + private isMounted = false; private stateHandler: StateHandler; - // 已读行相关方法(由 use-vxe-grid.vue 注入) - private viewedRowHelper: null | { - clearViewed: () => void; - isViewed: (record: T) => boolean; - markAsViewed: (record: T) => void; - markKeysAsViewed: (keys: Array) => void; - removeKeys: (keys: Array) => void; - viewedSet: { value: Set }; - } = null; - constructor(options: VxeGridProps = {} as VxeGridProps) { const storeState = { ...options }; @@ -82,7 +78,7 @@ export class VxeGridApi< } /** - * 获取所有已读的 key 集合 + * 获取所有已读的 key 集合(返回副本,避免外部修改内部状态) */ getViewedKeys(): Set { const raw = this.viewedRowHelper?.viewedSet.value; @@ -170,14 +166,6 @@ export class VxeGridApi< } } - /** - * 设置已读行 helper(由组件内部调用) - * @internal - */ - setViewedRowHelper(helper: VxeGridApi['viewedRowHelper']) { - this.viewedRowHelper = helper; - } - toggleSearchForm(show?: boolean) { this.setState({ showSearchForm: isBoolean(show) ? show : !this.state?.showSearchForm, @@ -191,5 +179,6 @@ export class VxeGridApi< unmount() { this.isMounted = false; this.stateHandler.reset(); + this.viewedRowHelper = null; } } diff --git a/packages/effects/plugins/src/vxe-table/types.ts b/packages/effects/plugins/src/vxe-table/types.ts index d4f138986..ed52eb417 100644 --- a/packages/effects/plugins/src/vxe-table/types.ts +++ b/packages/effects/plugins/src/vxe-table/types.ts @@ -168,7 +168,7 @@ export interface VxeGridProps< /** * 已读行功能 */ - viewedRow?: boolean | ViewedRowOptions; + viewedRowOptions?: boolean | ViewedRowOptions; } export type ExtendedVxeGridApi< diff --git a/packages/effects/plugins/src/vxe-table/use-viewed-row.ts b/packages/effects/plugins/src/vxe-table/use-viewed-row.ts index 6deba1e59..1c0ee0a50 100644 --- a/packages/effects/plugins/src/vxe-table/use-viewed-row.ts +++ b/packages/effects/plugins/src/vxe-table/use-viewed-row.ts @@ -373,6 +373,8 @@ export function useViewedRow( }; } +export type ViewedRowHelper = ReturnType>; + // ========== 工具函数 ========== function normalizeClassName(value: any): string { @@ -445,24 +447,64 @@ export function applyViewedRowOptions( viewedRowConfig: boolean | ViewedRowOptions, helper: ReturnType, ) { + // 从最新的配置中读取 rowClassName 和 rowStyle(支持运行时修改) + const viewedRowClassName = isBoolean(viewedRowConfig) + ? undefined + : viewedRowConfig.rowClassName; + const viewedRowStyle = isBoolean(viewedRowConfig) + ? undefined + : viewedRowConfig.rowStyle; + // 注入 rowClassName const originalRowClassName = mergedOptions.rowClassName; mergedOptions.rowClassName = (params: any) => { + if (!helper.isViewed(params.row)) { + return normalizeClassName( + isFunction(originalRowClassName) + ? originalRowClassName(params) + : originalRowClassName, + ); + } + + let viewedClass: string; + if (viewedRowClassName === undefined || viewedRowClassName === null) { + viewedClass = DEFAULT_VIEWED_CLASS; + } else if (typeof viewedRowClassName === 'string') { + viewedClass = viewedRowClassName; + } else if (isFunction(viewedRowClassName)) { + viewedClass = normalizeClassName(viewedRowClassName(params)); + } else { + viewedClass = DEFAULT_VIEWED_CLASS; + } + return mergeClassNames( isFunction(originalRowClassName) ? originalRowClassName(params) : originalRowClassName, - helper.getRowClassName(params), + viewedClass, ); }; // 注入 rowStyle const originalRowStyle = mergedOptions.rowStyle; mergedOptions.rowStyle = (params: any) => { - const viewedStyle = helper.getRowStyle(params); const originalStyle = isFunction(originalRowStyle) ? originalRowStyle(params) : originalRowStyle; + + if (!helper.isViewed(params.row)) { + return originalStyle || undefined; + } + + let viewedStyle: any; + if (viewedRowStyle === undefined || viewedRowStyle === null) { + viewedStyle = undefined; + } else if (isFunction(viewedRowStyle)) { + viewedStyle = viewedRowStyle(params); + } else { + viewedStyle = viewedRowStyle; + } + if (!viewedStyle && !originalStyle) return undefined; if (!originalStyle) return viewedStyle; if (!viewedStyle) return originalStyle; diff --git a/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue b/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue index c74c7d595..173c1bfab 100644 --- a/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue +++ b/packages/effects/plugins/src/vxe-table/use-vxe-grid.vue @@ -19,12 +19,10 @@ import { nextTick, onMounted, onUnmounted, - shallowRef, toRaw, useSlots, useTemplateRef, watch, - watchEffect, } from 'vue'; import { usePriorityValues } from '@vben/hooks'; @@ -79,45 +77,31 @@ const { tableTitleHelp, showSearchForm, separator, - viewedRow, + viewedRowOptions, } = usePriorityValues(props, state); -// ========== 已读行:响应 viewedRow 配置变化 ========== -const defaultKeyField = (gridOptions.value?.rowConfig as any)?.keyField || 'id'; +// viewedRowOptions:helper 只创建一次(persist/keyField 不支持运行时切换) +// actionCodes、rowClassName、rowStyle、viewedKeys 的变化通过 options computed 自然响应 +const gridApi = props.api; -const viewedRowHelper = shallowRef>( - null, -); - -// 初始化 + 监听配置变化时重建 helper watch( - viewedRow, + viewedRowOptions, (cfg) => { - if (!cfg) { - viewedRowHelper.value = null; - props.api?.setViewedRowHelper?.(null); - return; - } - const resolvedOptions = isBoolean(cfg) - ? {keyField: defaultKeyField} - : {keyField: defaultKeyField, ...cfg}; - viewedRowHelper.value = useViewedRow(resolvedOptions); - // 同步更新 API 中的 helper 引用 - if (props.api?.setViewedRowHelper) { - props.api.setViewedRowHelper(viewedRowHelper.value); - } + // helper 已存在则不重建 + if (gridApi.viewedRowHelper) return; + + if (!cfg) return; + + const keyField = + (gridOptions.value?.rowConfig as any)?.keyField || 'id'; + const resolved = isBoolean(cfg) + ? {keyField} + : {keyField, ...cfg}; + gridApi.viewedRowHelper = useViewedRow(resolved); }, {immediate: true}, ); -// viewedSet 变化时,主动刷新 grid 行样式 -watchEffect(() => { - const helper = viewedRowHelper.value; - if (!helper) return; - // 访问 viewedSet.value 建立依赖追踪 - void helper.viewedSet.value; -}); - const { isMobile } = usePreferences(); const isSeparator = computed(() => { if ( @@ -276,11 +260,11 @@ const options = computed(() => { } // 注入已读行功能(rowClassName、rowStyle、columns 拦截) - if (viewedRow.value && viewedRowHelper.value) { + if (viewedRowOptions.value && gridApi.viewedRowHelper) { applyViewedRowOptions( mergedOptions, - viewedRow.value, - viewedRowHelper.value, + viewedRowOptions.value, + gridApi.viewedRowHelper, ); } diff --git a/playground/src/views/examples/vxe-table/viewed.vue b/playground/src/views/examples/vxe-table/viewed.vue index 5df0e9047..b849ab70e 100644 --- a/playground/src/views/examples/vxe-table/viewed.vue +++ b/playground/src/views/examples/vxe-table/viewed.vue @@ -87,7 +87,7 @@ const gridOptions: VxeGridProps = { const [Grid, gridApi] = useVbenVxeGrid({ gridOptions, - viewedRow: { + viewedRowOptions: { // 触发已读的操作码 actionCodes: ['view'], // 行数据中的唯一标识字段 @@ -144,6 +144,33 @@ function onView(row: RowType) { }); } +const isStyle = ref(false); + +function onStyleSet() { + isStyle.value = !isStyle.value; + gridApi.setState({ + viewedRowOptions: { + rowStyle: () => { + return isStyle.value ? {backgroundColor: 'gray'} : ''; + }, + }, + }); +} + +const isClassName = ref(false); + +function onClassNameSet() { + isClassName.value = !isClassName.value; + gridApi.setState({ + viewedRowOptions: { + rowClassName: () => { + + return isClassName.value ? 'bg-red-100 vxe-row--viewed' : 'vxe-row--viewed'; + }, + }, + }); +} + function onCustomSet() { const tableData = gridApi.grid.getData(); const keys = tableData.slice(0, 2).map((row) => row.id); @@ -168,7 +195,13 @@ function onClearViewed() {