Merge branch 'fork/LayHuts/feature/vxe-table-viewed'
This commit is contained in:
@@ -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,6 +43,11 @@ export class VxeGridApi<
|
||||
|
||||
public store: Store<VxeGridProps<T, D, P>>;
|
||||
|
||||
/**
|
||||
* 已读行 helper(在 mount 中初始化,业务能力全部封装在 useViewedRow 中)
|
||||
*/
|
||||
public viewedRowHelper: null | ViewedRowHelper<T> = null;
|
||||
|
||||
private isMounted = false;
|
||||
|
||||
private stateHandler: StateHandler;
|
||||
@@ -64,6 +70,42 @@ export class VxeGridApi<
|
||||
bindMethods(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有已读状态
|
||||
*/
|
||||
clearViewedRows() {
|
||||
this.viewedRowHelper?.clearViewed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已读的 key 集合(返回副本,避免外部修改内部状态)
|
||||
*/
|
||||
getViewedKeys(): Set<number | string> {
|
||||
const raw = this.viewedRowHelper?.viewedSet.value;
|
||||
return raw ? new Set(raw) : new Set();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断某行是否已读
|
||||
*/
|
||||
isRowViewed(record: T): boolean {
|
||||
return this.viewedRowHelper?.isViewed(record) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量标记行为已读
|
||||
*/
|
||||
markKeysAsViewed(keys: Array<number | string>) {
|
||||
this.viewedRowHelper?.markKeysAsViewed(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记某行为已读
|
||||
*/
|
||||
markRowAsViewed(record: T) {
|
||||
this.viewedRowHelper?.markAsViewed(record);
|
||||
}
|
||||
|
||||
mount(instance: null | VxeGridInstance, formApi: ExtendedFormApi) {
|
||||
if (!this.isMounted && instance) {
|
||||
this.grid = instance;
|
||||
@@ -89,6 +131,13 @@ export class VxeGridApi<
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定 key 的已读状态
|
||||
*/
|
||||
removeViewedKeys(keys: Array<number | string>) {
|
||||
this.viewedRowHelper?.removeKeys(keys);
|
||||
}
|
||||
|
||||
setGridOptions(options: Partial<VxeGridProps<T, D, P>['gridOptions']>) {
|
||||
this.setState({
|
||||
gridOptions: options,
|
||||
@@ -130,5 +179,6 @@ export class VxeGridApi<
|
||||
unmount() {
|
||||
this.isMounted = false;
|
||||
this.stateHandler.reset();
|
||||
this.viewedRowHelper = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,3 +117,12 @@
|
||||
.vxe-grid--layout-body-content-wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 已读行默认样式 */
|
||||
.vxe-row--viewed {
|
||||
color: hsl(var(--foreground) / 50%);
|
||||
|
||||
.vxe-body--column {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
VxeGridListeners,
|
||||
VxeGridPropTypes,
|
||||
VxeGridProps as VxeTableGridProps,
|
||||
VxeTablePropTypes,
|
||||
VxeUIExport,
|
||||
} from 'vxe-table';
|
||||
|
||||
@@ -38,6 +39,87 @@ export interface SeparatorOptions {
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义存储适配器接口
|
||||
* 用户可接入任意后端(API、IndexedDB wrapper、第三方库等)
|
||||
*/
|
||||
export interface ViewedRowStorageAdapter {
|
||||
/** 读取所有已查看的 key 列表 */
|
||||
getKeys(): Promise<Array<number | string>>;
|
||||
|
||||
/** 移除所有已查看数据 */
|
||||
removeKeys(): Promise<void>;
|
||||
|
||||
/** 持久化已查看的 key 列表 */
|
||||
setKeys(keys: Array<number | string>): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 已读行持久化 — 公共基础字段
|
||||
*/
|
||||
interface ViewedRowPersistBase {
|
||||
/** 持久化数据的存活时间(毫秒) */
|
||||
ttl?: number;
|
||||
/** 最大缓存数量,超出时淘汰最早标记的 key(FIFO),默认 100 */
|
||||
maxSize?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 已读行持久化配置(按 type 区分的联合类型)
|
||||
*
|
||||
* - 'memory' → 仅内存,不持久化
|
||||
* - 'localStorage' → 使用 localStorage 整体存储,key 必传
|
||||
* - 'sessionStorage' → 使用 sessionStorage 整体存储,key 必传
|
||||
* - 'indexedDB' → 使用 IndexedDB 单条存储,key 必传
|
||||
* - 'custom' → 用户自定义存储适配器,storage 必传
|
||||
*/
|
||||
export type ViewedRowPersistOptions =
|
||||
| ({
|
||||
/** IndexedDB 数据库名称,默认 'viewed-table-db' */
|
||||
dbName?: string;
|
||||
/** IndexedDB 数据库版本,默认 1 */
|
||||
dbVersion?: number;
|
||||
/** 存储 key / prefix(必传) */
|
||||
key: string;
|
||||
/** IndexedDB 对象存储名称,默认 'viewed-table-row' */
|
||||
storeName?: string;
|
||||
type: 'indexedDB';
|
||||
} & ViewedRowPersistBase)
|
||||
| ({
|
||||
/** 存储 key(必传) */
|
||||
key: string;
|
||||
type: 'localStorage' | 'sessionStorage';
|
||||
} & ViewedRowPersistBase)
|
||||
| ({
|
||||
/** 自定义存储适配器(必传) */
|
||||
storage: ViewedRowStorageAdapter;
|
||||
type: 'custom';
|
||||
} & ViewedRowPersistBase)
|
||||
| (ViewedRowPersistBase & {
|
||||
type: 'memory';
|
||||
});
|
||||
|
||||
/**
|
||||
* 已查看row设置
|
||||
*/
|
||||
export interface ViewedRowOptions<T = any> {
|
||||
/** 点击 CellOperation 中匹配的 code 时,自动将该行标记为已读 */
|
||||
actionCodes?: string | string[];
|
||||
/** 行唯一标识字段,默认取 gridOptions.rowConfig.keyField,最终兜底 'id' */
|
||||
keyField?: string;
|
||||
/** 已查看的行key列表 */
|
||||
viewedKeys?: Array<number | string> | Ref<Array<number | string>>;
|
||||
/**
|
||||
* 持久化配置
|
||||
* - 传 string:使用内置 localStorage,值为 storage key(向后兼容)
|
||||
* - 传 object:高级配置
|
||||
* - 不传:不持久化(等同于 memory)
|
||||
*/
|
||||
persist?: string | ViewedRowPersistOptions;
|
||||
rowClassName?: VxeTablePropTypes.RowClassName<T>;
|
||||
rowStyle?: VxeTablePropTypes.RowStyle<T>;
|
||||
}
|
||||
|
||||
export interface VxeGridProps<
|
||||
T extends Record<string, any> = any,
|
||||
D extends BaseFormComponentType = BaseFormComponentType,
|
||||
@@ -83,6 +165,10 @@ export interface VxeGridProps<
|
||||
* 搜索表单与表格主体之间的分隔条
|
||||
*/
|
||||
separator?: boolean | SeparatorOptions;
|
||||
/**
|
||||
* 已读行功能
|
||||
*/
|
||||
viewedRowOptions?: boolean | ViewedRowOptions<T>;
|
||||
}
|
||||
|
||||
export type ExtendedVxeGridApi<
|
||||
|
||||
536
packages/effects/plugins/src/vxe-table/use-viewed-row.ts
Normal file
536
packages/effects/plugins/src/vxe-table/use-viewed-row.ts
Normal file
@@ -0,0 +1,536 @@
|
||||
import type {VxeGridProps as VxeTableGridProps} from 'vxe-table';
|
||||
|
||||
import type {
|
||||
ViewedRowOptions,
|
||||
ViewedRowPersistOptions,
|
||||
ViewedRowStorageAdapter,
|
||||
} from './types';
|
||||
|
||||
import {isRef, shallowRef, toRaw, triggerRef, watch} from 'vue';
|
||||
|
||||
import {isBoolean, isFunction} from '@vben/utils';
|
||||
|
||||
import {
|
||||
IndexedDBDriver,
|
||||
LocalStorageDriver,
|
||||
StorageManager,
|
||||
} from '@vben-core/shared/cache';
|
||||
|
||||
import {useDebounceFn} from '@vueuse/core';
|
||||
|
||||
const DEFAULT_VIEWED_CLASS = 'vxe-row--viewed';
|
||||
|
||||
// ========== 持久化策略 ==========
|
||||
|
||||
/**
|
||||
* localStorage / sessionStorage 适配器
|
||||
* 整体存储:key → [1, 2, 3]
|
||||
*/
|
||||
function createWebStorageAdapter(
|
||||
storageType: 'localStorage' | 'sessionStorage',
|
||||
key: string,
|
||||
ttl?: number,
|
||||
): ViewedRowStorageAdapter {
|
||||
const manager = new StorageManager({
|
||||
driver: new LocalStorageDriver({storageType}),
|
||||
});
|
||||
|
||||
return {
|
||||
async getKeys() {
|
||||
const stored = await manager.getItem<Array<number | string>>(key);
|
||||
return stored ?? [];
|
||||
},
|
||||
async removeKeys() {
|
||||
await manager.removeItem(key);
|
||||
},
|
||||
async setKeys(keys) {
|
||||
await manager.setItem(key, keys, ttl);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* IndexedDB 适配器
|
||||
* 单条存储:prefix:1 → { expiry, value: 1 }
|
||||
*/
|
||||
function createIndexedDBAdapter(
|
||||
opts: Extract<ViewedRowPersistOptions, { type: 'indexedDB' }>,
|
||||
): ViewedRowStorageAdapter {
|
||||
const prefix = opts.key;
|
||||
const manager = new StorageManager({
|
||||
driver: new IndexedDBDriver({
|
||||
dbName: opts.dbName || 'viewed-table-db',
|
||||
dbVersion: opts.dbVersion || 1,
|
||||
storeName: opts.storeName || 'viewed-table-row',
|
||||
}),
|
||||
prefix,
|
||||
});
|
||||
|
||||
return {
|
||||
async getKeys() {
|
||||
try {
|
||||
// 通过 StorageManager 获取当前前缀下所有 key,再逐条读取(自动过滤过期)
|
||||
const shortKeys = await manager.keys();
|
||||
|
||||
const results: Array<number | string> = [];
|
||||
for (const shortKey of shortKeys) {
|
||||
const value = await manager.getItem<number | string>(shortKey);
|
||||
if (value !== null) {
|
||||
results.push(value);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error('[viewedRow] indexedDB restore failed:', error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
async removeKeys() {
|
||||
try {
|
||||
await manager.clear();
|
||||
} catch (error) {
|
||||
console.error('[viewedRow] indexedDB clear failed:', error);
|
||||
}
|
||||
},
|
||||
async setKeys(keys) {
|
||||
try {
|
||||
const newKeySet = new Set(keys.map(String));
|
||||
// 获取已存在的 key,避免重复写入刷新过期时间
|
||||
const existingKeys = await manager.keys();
|
||||
const existingKeySet = new Set(existingKeys);
|
||||
|
||||
// 只写入新增的 key,不覆盖已有记录的过期时间
|
||||
const toAdd = keys.filter((key) => !existingKeySet.has(String(key)));
|
||||
if (toAdd.length > 0) {
|
||||
await Promise.all(
|
||||
toAdd.map((key) => manager.setItem(String(key), key, opts.ttl)),
|
||||
);
|
||||
}
|
||||
|
||||
// 清理不在新集合中的旧 key
|
||||
const toRemove = existingKeys.filter((k) => !newKeySet.has(k));
|
||||
if (toRemove.length > 0) {
|
||||
await Promise.all(toRemove.map((k) => manager.removeItem(k)));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[viewedRow] indexedDB persist failed:', error);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 persist 配置创建存储适配器
|
||||
*/
|
||||
function createStorageAdapter(
|
||||
persist?: string | ViewedRowPersistOptions,
|
||||
): null | ViewedRowStorageAdapter {
|
||||
if (!persist) return null;
|
||||
|
||||
// 简写模式:string → localStorage
|
||||
if (typeof persist === 'string') {
|
||||
return createWebStorageAdapter('localStorage', persist);
|
||||
}
|
||||
|
||||
switch (persist.type) {
|
||||
case 'custom': {
|
||||
// 用户自定义适配器,解除 Vue 响应式代理
|
||||
return toRaw(persist.storage);
|
||||
}
|
||||
case 'indexedDB': {
|
||||
return createIndexedDBAdapter(persist);
|
||||
}
|
||||
case 'localStorage': {
|
||||
return createWebStorageAdapter('localStorage', persist.key, persist.ttl);
|
||||
}
|
||||
case 'memory': {
|
||||
return null;
|
||||
}
|
||||
case 'sessionStorage': {
|
||||
return createWebStorageAdapter(
|
||||
'sessionStorage',
|
||||
persist.key,
|
||||
persist.ttl,
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== maxSize 淘汰 ==========
|
||||
|
||||
/**
|
||||
* 强制执行 maxSize 限制,超出时淘汰最早插入的 key(FIFO)
|
||||
*/
|
||||
function enforceMaxSize(set: Set<number | string>, maxSize: number): void {
|
||||
if (maxSize > 0 && set.size > maxSize) {
|
||||
const iterator = set.values();
|
||||
while (set.size > maxSize) {
|
||||
const oldest = iterator.next().value;
|
||||
if (oldest !== undefined) {
|
||||
set.delete(oldest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 核心 composable ==========
|
||||
|
||||
export function useViewedRow<T = any>(
|
||||
options: ViewedRowOptions<T> & { keyField: string },
|
||||
) {
|
||||
// ========== 解析持久化配置 ==========
|
||||
const persistOpts: null | ViewedRowPersistOptions = options.persist
|
||||
? (typeof options.persist === 'string'
|
||||
? {key: options.persist, type: 'localStorage'}
|
||||
: options.persist)
|
||||
: null;
|
||||
|
||||
const adapter = createStorageAdapter(options.persist);
|
||||
const maxSize = persistOpts?.maxSize ?? 100;
|
||||
|
||||
// ========== 初始化已读集合 ==========
|
||||
const viewedSet = shallowRef<Set<number | string>>(new Set());
|
||||
|
||||
// ========== 持久化(防抖) ==========
|
||||
function persistImmediate() {
|
||||
if (!adapter) return;
|
||||
adapter.setKeys([...viewedSet.value]).catch((error) => {
|
||||
console.error('[viewedRow] persist failed:', error);
|
||||
});
|
||||
}
|
||||
|
||||
const persist = useDebounceFn(persistImmediate, 300);
|
||||
|
||||
// ========== 从存储恢复 ==========
|
||||
async function restoreFromStorage(): Promise<void> {
|
||||
if (!adapter) return;
|
||||
|
||||
try {
|
||||
const stored = await adapter.getKeys();
|
||||
if (stored && stored.length > 0) {
|
||||
for (const key of stored) {
|
||||
viewedSet.value.add(key);
|
||||
}
|
||||
if (maxSize > 0) {
|
||||
enforceMaxSize(viewedSet.value, maxSize);
|
||||
}
|
||||
triggerRef(viewedSet);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[viewedRow] restore failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 先恢复存储,再合并外部 viewedKeys,确保 viewedKeys 是最新插入的(最后被淘汰)
|
||||
restoreFromStorage().then(() => {
|
||||
if (options.viewedKeys) {
|
||||
const keys = isRef(options.viewedKeys)
|
||||
? options.viewedKeys.value
|
||||
: options.viewedKeys;
|
||||
updateViewedSet((set) => {
|
||||
let changed = false;
|
||||
for (const key of keys) {
|
||||
if (!set.has(key)) {
|
||||
set.add(key);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ========== 更新 viewedSet 的统一入口 ==========
|
||||
function updateViewedSet(updater: (set: Set<number | string>) => boolean) {
|
||||
const changed = updater(viewedSet.value);
|
||||
|
||||
if (changed) {
|
||||
if (maxSize > 0) {
|
||||
enforceMaxSize(viewedSet.value, maxSize);
|
||||
}
|
||||
triggerRef(viewedSet);
|
||||
persist();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 监听外部 viewedKeys 变化(如果是 Ref) ==========
|
||||
if (isRef(options.viewedKeys)) {
|
||||
watch(options.viewedKeys, (newKeys) => {
|
||||
updateViewedSet((set) => {
|
||||
let changed = false;
|
||||
for (const key of newKeys) {
|
||||
if (!set.has(key)) {
|
||||
set.add(key);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ========== 标记已读 ==========
|
||||
function markAsViewed(record: T) {
|
||||
const key = (record as Record<string, any>)[options.keyField] as
|
||||
| number
|
||||
| string;
|
||||
if (key === null || key === undefined) return;
|
||||
|
||||
updateViewedSet((set) => {
|
||||
if (set.has(key)) return false;
|
||||
set.add(key);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function markKeysAsViewed(keys: Array<number | string>) {
|
||||
updateViewedSet((set) => {
|
||||
let changed = false;
|
||||
for (const key of keys) {
|
||||
if (!set.has(key)) {
|
||||
set.add(key);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
});
|
||||
}
|
||||
|
||||
// ========== 查询 ==========
|
||||
function isViewed(record: T): boolean {
|
||||
const key = (record as Record<string, any>)[options.keyField] as
|
||||
| number
|
||||
| string;
|
||||
return viewedSet.value.has(key);
|
||||
}
|
||||
|
||||
// ========== 清除 ==========
|
||||
function clearViewed() {
|
||||
const hadData = viewedSet.value.size > 0;
|
||||
viewedSet.value.clear();
|
||||
|
||||
if (hadData) {
|
||||
triggerRef(viewedSet);
|
||||
}
|
||||
|
||||
if (adapter) {
|
||||
adapter.removeKeys().catch((error) => {
|
||||
console.error('[viewedRow] clear persist failed:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 移除指定 keys ==========
|
||||
function removeKeys(keys: Array<number | string>) {
|
||||
updateViewedSet((set) => {
|
||||
let changed = false;
|
||||
for (const key of keys) {
|
||||
if (set.has(key)) {
|
||||
set.delete(key);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
});
|
||||
}
|
||||
|
||||
// ========== rowClassName 函数 ==========
|
||||
function getRowClassName(params: any): string {
|
||||
if (!isViewed(params.row)) return '';
|
||||
|
||||
const {rowClassName} = options;
|
||||
if (rowClassName === undefined || rowClassName === null) {
|
||||
return DEFAULT_VIEWED_CLASS;
|
||||
}
|
||||
if (typeof rowClassName === 'string') {
|
||||
return rowClassName;
|
||||
}
|
||||
if (isFunction(rowClassName)) {
|
||||
return normalizeClassName(rowClassName(params));
|
||||
}
|
||||
return DEFAULT_VIEWED_CLASS;
|
||||
}
|
||||
|
||||
// ========== rowStyle 函数 ==========
|
||||
function getRowStyle(params: any): any {
|
||||
if (!isViewed(params.row)) return undefined;
|
||||
|
||||
const {rowStyle} = options;
|
||||
if (rowStyle === undefined || rowStyle === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (isFunction(rowStyle)) {
|
||||
return rowStyle(params);
|
||||
}
|
||||
return rowStyle;
|
||||
}
|
||||
|
||||
return {
|
||||
clearViewed,
|
||||
getRowClassName,
|
||||
getRowStyle,
|
||||
isViewed,
|
||||
markAsViewed,
|
||||
markKeysAsViewed,
|
||||
removeKeys,
|
||||
viewedSet,
|
||||
};
|
||||
}
|
||||
|
||||
export type ViewedRowHelper<T = any> = ReturnType<typeof useViewedRow<T>>;
|
||||
|
||||
// ========== 工具函数 ==========
|
||||
|
||||
function normalizeClassName(value: any): string {
|
||||
if (!value) return '';
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'object') {
|
||||
return Object.entries(value)
|
||||
.filter(([, v]) => v)
|
||||
.map(([k]) => k)
|
||||
.join(' ');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function mergeClassNames(...classNames: any[]): string {
|
||||
return classNames
|
||||
.map((c) => normalizeClassName(c))
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装 columns,拦截 CellOperation 的 onClick,根据 actionCodes 自动标记已读
|
||||
* 注意:columns 每次都是 cloneDeep 后的新对象,不存在重复包装问题
|
||||
*/
|
||||
function wrapColumnsForViewedRow(
|
||||
columns: any[],
|
||||
actionCodes: string[],
|
||||
markAsViewed: (record: any) => void,
|
||||
): any[] {
|
||||
return columns.map((column) => {
|
||||
if (!column || typeof column !== 'object') return column;
|
||||
|
||||
const nextColumn = {...column};
|
||||
|
||||
if (nextColumn.cellRender?.name === 'CellOperation') {
|
||||
const cellRender = {...nextColumn.cellRender};
|
||||
const attrs = {...cellRender.attrs};
|
||||
const originalOnClick = attrs.onClick;
|
||||
|
||||
attrs.onClick = (params: { code: string; row: any }) => {
|
||||
originalOnClick?.(params);
|
||||
if (actionCodes.includes(params.code)) {
|
||||
markAsViewed(params.row);
|
||||
}
|
||||
};
|
||||
|
||||
cellRender.attrs = attrs;
|
||||
nextColumn.cellRender = cellRender;
|
||||
}
|
||||
|
||||
if (Array.isArray(nextColumn.children)) {
|
||||
nextColumn.children = wrapColumnsForViewedRow(
|
||||
nextColumn.children,
|
||||
actionCodes,
|
||||
markAsViewed,
|
||||
);
|
||||
}
|
||||
|
||||
return nextColumn;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 viewedRow 配置应用到 mergedOptions 上
|
||||
* 注入 rowClassName、rowStyle、columns 拦截
|
||||
*/
|
||||
export function applyViewedRowOptions(
|
||||
mergedOptions: VxeTableGridProps,
|
||||
viewedRowConfig: boolean | ViewedRowOptions,
|
||||
helper: ReturnType<typeof useViewedRow>,
|
||||
) {
|
||||
// 从最新的配置中读取 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,
|
||||
viewedClass,
|
||||
);
|
||||
};
|
||||
|
||||
// 注入 rowStyle
|
||||
const originalRowStyle = mergedOptions.rowStyle;
|
||||
mergedOptions.rowStyle = (params: any) => {
|
||||
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;
|
||||
return {...originalStyle, ...viewedStyle};
|
||||
};
|
||||
|
||||
// 拦截 CellOperation columns
|
||||
const actionCodes =
|
||||
!isBoolean(viewedRowConfig) && viewedRowConfig.actionCodes
|
||||
? (Array.isArray(viewedRowConfig.actionCodes)
|
||||
? viewedRowConfig.actionCodes
|
||||
: [viewedRowConfig.actionCodes])
|
||||
: [];
|
||||
|
||||
if (actionCodes.length > 0 && Array.isArray(mergedOptions.columns)) {
|
||||
mergedOptions.columns = wrapColumnsForViewedRow(
|
||||
mergedOptions.columns,
|
||||
actionCodes,
|
||||
helper.markAsViewed,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import { VxeGrid, VxeUI } from 'vxe-table';
|
||||
|
||||
import { extendProxyOptions } from './extends';
|
||||
import { useTableForm } from './init';
|
||||
import {applyViewedRowOptions, useViewedRow} from './use-viewed-row';
|
||||
|
||||
import 'vxe-table/styles/cssvar.scss';
|
||||
import 'vxe-pc-ui/styles/cssvar.scss';
|
||||
@@ -76,8 +77,28 @@ const {
|
||||
tableTitleHelp,
|
||||
showSearchForm,
|
||||
separator,
|
||||
viewedRowOptions,
|
||||
} = usePriorityValues(props, state);
|
||||
|
||||
// viewedRowOptions:helper 只创建一次(persist/keyField 不支持运行时切换)
|
||||
// actionCodes、rowClassName、rowStyle、viewedKeys 的变化通过 options computed 自然响应
|
||||
const gridApi = props.api;
|
||||
|
||||
watch(
|
||||
viewedRowOptions,
|
||||
(cfg) => {
|
||||
// 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},
|
||||
);
|
||||
|
||||
const { isMobile } = usePreferences();
|
||||
const isSeparator = computed(() => {
|
||||
if (
|
||||
@@ -234,6 +255,16 @@ const options = computed(() => {
|
||||
mergedOptions.data = tableData.value;
|
||||
}
|
||||
}
|
||||
|
||||
// 注入已读行功能(rowClassName、rowStyle、columns 拦截)
|
||||
if (viewedRowOptions.value && gridApi.viewedRowHelper) {
|
||||
applyViewedRowOptions(
|
||||
mergedOptions,
|
||||
viewedRowOptions.value,
|
||||
gridApi.viewedRowHelper,
|
||||
);
|
||||
}
|
||||
|
||||
return mergedOptions;
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user