Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hook): table 组件新增 useTrackTableRow 钩子,实现自动收集行信息 #3586

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/Table/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export * from './src/types/table';
export * from './src/types/pagination';
export * from './src/types/tableAction';
export { useTable } from './src/hooks/useTable';
export { useTrackTableRow, useTrackTableRowContext } from './src/hooks/useTrackTableRow';
export type { FormSchema, FormProps } from '@/components/Form/src/types/form';
export type { EditRecordRow } from './src/components/editable';
3 changes: 3 additions & 0 deletions src/components/Table/src/BasicTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
reload,
getAutoCreateKey,
updateTableData,
rowKeyToRowMap,
} = useDataSource(
getProps,
{
Expand Down Expand Up @@ -319,6 +320,8 @@
return unref(getBindValues).size as SizeType;
},
setCacheColumns,
getWrapperElement: () => unref(wrapRef),
getRowKeyToRowMap: () => unref(rowKeyToRowMap),
};
createTableContext({ ...tableAction, wrapRef, getBindValues });

Expand Down
44 changes: 43 additions & 1 deletion src/components/Table/src/hooks/useDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import {
reactive,
Ref,
watchEffect,
toRaw,
} from 'vue';
import { useTimeoutFn } from '@vben/hooks';
import { buildUUID } from '@/utils/uuid';
import { isFunction, isBoolean, isObject } from '@/utils/is';
import { get, cloneDeep, merge } from 'lodash-es';
import { get, cloneDeep, merge, isString, isNull, toString } from 'lodash-es';
import { FETCH_SETTING, ROW_KEY, PAGE_SIZE } from '../const';
import { parseRowKeyValue } from '../helper';
import type { Key } from 'ant-design-vue/lib/table/interface';
Expand Down Expand Up @@ -113,6 +114,46 @@ export function useDataSource(
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
});

const rowKeyToRowMap = computed(() => {
const { rowKey, childrenColumnName = 'children' } = unref(propsRef);
const map = new Map<string, Recordable>();

const traverse = <T extends Recordable = any>(
data: T[],
callback: (item: T, index: number) => void,
) => {
for (let i = 0; i < data.length; i++) {
const item = data[i];
const hasChildren = item && item[childrenColumnName] && item[childrenColumnName].length > 0;
callback && callback(item, i);
if (hasChildren) {
traverse(item[childrenColumnName], callback);
}
}
};

traverse(unref(dataSourceRef), (item, index) => {
if (unref(getAutoCreateKey)) {
const rowKey = item[ROW_KEY];
map.set(rowKey, toRaw(item));
return;
}

const key = isString(rowKey)
? get(item, rowKey)
: isFunction(rowKey)
? rowKey(item, index)
: null;

if (!isNull(key)) {
map.set(toString(key), toRaw(item));
return;
}
});

return map;
});

const getDataSourceRef = computed(() => {
const dataSource = unref(dataSourceRef);
if (!dataSource || dataSource.length === 0) {
Expand Down Expand Up @@ -360,5 +401,6 @@ export function useDataSource(
insertTableDataRecord,
findTableDataRecord,
handleTableChange,
rowKeyToRowMap,
};
}
6 changes: 6 additions & 0 deletions src/components/Table/src/hooks/useTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ export function useTable(tableProps?: Props): [
scrollTo: (pos: string) => {
getTableInstance().scrollTo(pos);
},
getRowKeyToRowMap: () => {
return getTableInstance().getRowKeyToRowMap();
},
getWrapperElement: () => {
return getTableInstance().getWrapperElement();
},
};

return [register, methods];
Expand Down
2 changes: 1 addition & 1 deletion src/components/Table/src/hooks/useTableContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ export function createTableContext(instance: Instance) {
}

export function useTableContext(): RetInstance {
return inject(key) as RetInstance;
return inject(key, null as any) as RetInstance;
}
180 changes: 180 additions & 0 deletions src/components/Table/src/hooks/useTrackTableRow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { useEventListener } from '@vueuse/core';
import type { TableActionType } from '../types/table';
import {
ref,
type ComponentPublicInstance,
type ComputedRef,
type MaybeRef,
unref,
computed,
toRaw,
InjectionKey,
provide,
inject,
} from 'vue';
import { useDesign } from '@/hooks/web/useDesign';
import { useTableContext } from './useTableContext';
import { noop } from 'lodash-es';

const rowAttributeKey = 'data-row-key'; // AntDesignVue Table 挂载到 tr 上的属性
/**
* @description 跟踪表格行信息
*/
interface UseTrackTableRowOptions {
/**
* @description 跟踪行信息的触发时机
* @default 'click'
*/
trigger?: 'click' | 'hover';
/**
* @description 跟踪行信息的拦截器,可以返回 false 阻止跟踪,在遇到一些特殊的场景时可能会有用
*/
guard?: (ev: MouseEvent) => boolean | void;
/**
* @description 是否向下注入行信息,子组件可以使用 useTrackTableRowContext() 直接获取行信息
*/
provide?: boolean;
}

const triggerToEventNameMap: Record<string, string> = {
click: 'click',
hover: 'mousemove',
};

export interface UseTrackTableRowReturn<Row = any> {
row: ComputedRef<Row | null>;
extend: (data: Partial<Row>) => void;
}

export function useTrackTableRow<Row extends Record<string, any>>(
tableActionRef: MaybeRef<TableActionType | undefined>,
options: UseTrackTableRowOptions = {},
) {
const { guard, provide = false, trigger = 'click' } = options;

const tableContext = useTableContext();
const { prefixCls } = useDesign('basic-table');

const row = ref<Row | null>(null);
const extendInfo = ref<Partial<Row>>({});

function trackRowInfo(ev: MouseEvent) {
const shouldTrack = guard?.(ev) ?? true;
if (!shouldTrack) return;

const rowKey = findRowKeyByElement(ev.target);
const rowInfo = tryGetRow(rowKey as string);
(row as any).value = rowInfo;
}

function getRowKey(target: Element) {
return target.getAttribute(rowAttributeKey);
}

function hasRowKey(target: Element) {
return !!getRowKey(target);
}

function isRootElement(target: Element) {
return target.classList.contains(prefixCls);
}

function findRowKeyByElement(target: EventTarget | null) {
const rowKey = null;
let current = target as Element;
while (current) {
if (isRootElement(current)) return null;

if (hasRowKey(current)) return getRowKey(current);

current = current.parentElement as Element;
}
return rowKey;
}

function tryGetRow(rowKey: string) {
try {
const tableAction = unref(tableActionRef);
/**
* support useTableContext
*/
const row1 = tableContext?.getRowKeyToRowMap?.().get?.(rowKey as string);
/**
* support useTable and ref
*/
const row2 = tableAction?.getRowKeyToRowMap?.().get?.(rowKey as string);
/**
* try guess row1 or row2 not empty
*/
return row1 ?? row2 ?? null;
} catch (error) {
return null;
}
}

function tryGetTableWrapperElement() {
try {
const tableAction = unref(tableActionRef);
/**
* support useTableContext
*/
const el1 = tableContext?.getWrapperElement?.();
/**
* support useTable
*/
const el2 = tableAction?.getWrapperElement?.();
/**
* support ref
*/
const el3 = (tableAction as any as ComponentPublicInstance)?.$el;
/**
* try guess el1、el2、el3 not empty
*/
return el1 ?? el2 ?? el3 ?? null;
} catch (error) {
return null;
}
}

useEventListener(
computed(tryGetTableWrapperElement),
triggerToEventNameMap[trigger],
trackRowInfo,
{ capture: true },
);

function extend(data: Partial<Row>) {
extendInfo.value = data as any;
}

const returned: UseTrackTableRowReturn = {
row: computed(() => {
const rawRow = toRaw(unref(row));
if (!rawRow) return null;
const rawExtendInfo = toRaw(unref(extendInfo)) as Partial<Row>;
return { ...rawRow, ...rawExtendInfo };
}) as ComputedRef<Row | null>,
extend,
};

if (provide) {
createTrackTableRowContext(returned);
}

return returned;
}

const useTrackTableRowInjectionKey = Symbol(
'track-table-row',
) as InjectionKey<UseTrackTableRowReturn>;

function createTrackTableRowContext(context: UseTrackTableRowReturn) {
return provide(useTrackTableRowInjectionKey, context);
}

export function useTrackTableRowContext<Row extends Record<string, any>>() {
return inject(useTrackTableRowInjectionKey, {
row: computed(() => null),
extend: noop,
}) as UseTrackTableRowReturn<Row>;
}
2 changes: 2 additions & 0 deletions src/components/Table/src/types/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ export interface TableActionType {
getShowPagination: () => boolean;
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void;
setCacheColumns?: (columns: BasicColumn[]) => void;
getRowKeyToRowMap: () => Map<string, Recordable>;
getWrapperElement: () => Nullable<HTMLElement>;
}

export interface FetchSetting {
Expand Down
4 changes: 3 additions & 1 deletion src/locales/lang/en/routes/demo.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@
"editRowTable": "Editable row",
"authColumn": "Auth column",
"resizeParentHeightTable": "resizeParentHeightTable",
"vxeTable": "VxeTable"
"vxeTable": "VxeTable",
"trackTableRow":"autoTrackTableRowInfo",
"trackTableRowBindingModal":"trackTableRowBindingModal"
}
}
4 changes: 3 additions & 1 deletion src/locales/lang/zh-CN/routes/demo.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@
"editRowTable": "可编辑行",
"authColumn": "权限列",
"resizeParentHeightTable": "继承父元素高度",
"vxeTable": "VxeTable"
"vxeTable": "VxeTable",
"trackTableRow":"自动收集行信息",
"trackTableRowBindingModal":"自动收集结合弹窗回填"
}
}
16 changes: 16 additions & 0 deletions src/router/routes/modules/demo/comp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,22 @@ const comp: AppRouteModule = {
title: t('routes.demo.table.vxeTable'),
},
},
{
path: 'trackTableRow',
name: 'TrackTableRowDemo',
component: () => import('@/views/demo/table/TrackTableRow.vue'),
meta: {
title: t('routes.demo.table.trackTableRow'),
},
},
{
path: 'trackTableRowBindingModal',
name: 'TrackTableRowBindingModalDemo',
component: () => import('@/views/demo/table/TrackTableRowBindingModal.vue'),
meta: {
title: t('routes.demo.table.trackTableRowBindingModal'),
},
},
],
},
{
Expand Down
45 changes: 45 additions & 0 deletions src/views/demo/table/AccountModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';
import { accountFormSchema } from '@/views/demo/system/account/account.data';
import { useTrackTableRowContext } from '@/components/Table';
import { computed, unref } from 'vue';

defineOptions({ name: 'AccountModal' });

const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
labelWidth: 100,
baseColProps: { span: 24 },
schemas: accountFormSchema,
showActionButtonGroup: false,
actionColOptions: {
span: 23,
},
});

const { row } = useTrackTableRowContext()
const getTitle = computed(() => `${unref(row) ? '编辑' : '新增'}账号`)

const [registerModal, { setModalProps, closeModal }] = useModalInner(() => {
resetFields();
setModalProps({ confirmLoading: false });
setFieldsValue(unref(row) ?? {})
});

async function handleSubmit() {
try {
const values = await validate();
setModalProps({ confirmLoading: true });
// TODO custom api
console.log(values);
closeModal();
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
Loading