From 1bf3560c6e92d3e7dbe3a4562bcc9fcb0e02b051 Mon Sep 17 00:00:00 2001 From: zhengchangfu Date: Thu, 1 Feb 2024 12:54:03 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(hook):=20table=20=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20useTrackTableRow=20=E9=92=A9=E5=AD=90,?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=87=AA=E5=8A=A8=E6=94=B6=E9=9B=86=E8=A1=8C?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Table/index.ts | 1 + src/components/Table/src/BasicTable.vue | 3 + .../Table/src/hooks/useDataSource.ts | 44 ++++- src/components/Table/src/hooks/useTable.ts | 6 + .../Table/src/hooks/useTrackTableRow.ts | 180 ++++++++++++++++++ src/components/Table/src/types/table.ts | 2 + 6 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 src/components/Table/src/hooks/useTrackTableRow.ts diff --git a/src/components/Table/index.ts b/src/components/Table/index.ts index 37c9d5e7329..ba3108bba23 100644 --- a/src/components/Table/index.ts +++ b/src/components/Table/index.ts @@ -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'; diff --git a/src/components/Table/src/BasicTable.vue b/src/components/Table/src/BasicTable.vue index 174d6d97135..510aaa1218f 100644 --- a/src/components/Table/src/BasicTable.vue +++ b/src/components/Table/src/BasicTable.vue @@ -154,6 +154,7 @@ reload, getAutoCreateKey, updateTableData, + rowKeyToRowMap, } = useDataSource( getProps, { @@ -319,6 +320,8 @@ return unref(getBindValues).size as SizeType; }, setCacheColumns, + getWrapperElement: () => unref(wrapRef), + getRowKeyToRowMap: () => unref(rowKeyToRowMap), }; createTableContext({ ...tableAction, wrapRef, getBindValues }); diff --git a/src/components/Table/src/hooks/useDataSource.ts b/src/components/Table/src/hooks/useDataSource.ts index 2872b1de831..87dbb8247bc 100644 --- a/src/components/Table/src/hooks/useDataSource.ts +++ b/src/components/Table/src/hooks/useDataSource.ts @@ -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'; @@ -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(); + + const traverse = ( + 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) { @@ -360,5 +401,6 @@ export function useDataSource( insertTableDataRecord, findTableDataRecord, handleTableChange, + rowKeyToRowMap, }; } diff --git a/src/components/Table/src/hooks/useTable.ts b/src/components/Table/src/hooks/useTable.ts index 196eecd94fc..1d16e910429 100644 --- a/src/components/Table/src/hooks/useTable.ts +++ b/src/components/Table/src/hooks/useTable.ts @@ -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]; diff --git a/src/components/Table/src/hooks/useTrackTableRow.ts b/src/components/Table/src/hooks/useTrackTableRow.ts new file mode 100644 index 00000000000..979ce3e9f77 --- /dev/null +++ b/src/components/Table/src/hooks/useTrackTableRow.ts @@ -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 = { + click: 'click', + hover: 'mousemove', +}; + +export interface UseTrackTableRowReturn { + row: ComputedRef; + extend: (data: Partial) => void; +} + +export function useTrackTableRow>( + tableActionRef: MaybeRef, + options: UseTrackTableRowOptions = {}, +) { + const { guard, provide = false, trigger = 'click' } = options; + + const tableContext = useTableContext(); + const { prefixCls } = useDesign('basic-table'); + + const row = ref(null); + const extendInfo = ref>({}); + + 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) { + 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; + return { ...rawRow, ...rawExtendInfo }; + }) as ComputedRef, + extend, + }; + + if (provide) { + createTrackTableRowContext(returned); + } + + return returned; +} + +const useTrackTableRowInjectionKey = Symbol( + 'track-table-row', +) as InjectionKey; + +function createTrackTableRowContext(context: UseTrackTableRowReturn) { + return provide(useTrackTableRowInjectionKey, context); +} + +export function useTrackTableRowContext>() { + return inject(useTrackTableRowInjectionKey, { + row: computed(() => null), + extend: noop, + }) as UseTrackTableRowReturn; +} diff --git a/src/components/Table/src/types/table.ts b/src/components/Table/src/types/table.ts index ed268f1783b..c13e7ad91ef 100644 --- a/src/components/Table/src/types/table.ts +++ b/src/components/Table/src/types/table.ts @@ -128,6 +128,8 @@ export interface TableActionType { getShowPagination: () => boolean; setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void; setCacheColumns?: (columns: BasicColumn[]) => void; + getRowKeyToRowMap: () => Map; + getWrapperElement: () => Nullable; } export interface FetchSetting { From 1bfe9c7c4ea6bae7f1b488caa04fd56f41cde1a0 Mon Sep 17 00:00:00 2001 From: zhengchangfu Date: Thu, 1 Feb 2024 12:55:32 +0800 Subject: [PATCH 2/3] =?UTF-8?q?docs(component):=20=E5=A2=9E=E5=8A=A0=20use?= =?UTF-8?q?TrackTableRow=20=E7=9A=84=E7=A4=BA=E4=BE=8B=20demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/locales/lang/en/routes/demo.json | 4 +- src/locales/lang/zh-CN/routes/demo.json | 4 +- src/router/routes/modules/demo/comp.ts | 16 +++++ src/views/demo/table/AccountModal.vue | 45 +++++++++++++ src/views/demo/table/TrackTableRow.vue | 67 +++++++++++++++++++ .../demo/table/TrackTableRowBindingModal.vue | 48 +++++++++++++ 6 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 src/views/demo/table/AccountModal.vue create mode 100644 src/views/demo/table/TrackTableRow.vue create mode 100644 src/views/demo/table/TrackTableRowBindingModal.vue diff --git a/src/locales/lang/en/routes/demo.json b/src/locales/lang/en/routes/demo.json index 7e71f9eed16..0bc113ef958 100644 --- a/src/locales/lang/en/routes/demo.json +++ b/src/locales/lang/en/routes/demo.json @@ -174,6 +174,8 @@ "editRowTable": "Editable row", "authColumn": "Auth column", "resizeParentHeightTable": "resizeParentHeightTable", - "vxeTable": "VxeTable" + "vxeTable": "VxeTable", + "trackTableRow":"autoTrackTableRowInfo", + "trackTableRowBindingModal":"trackTableRowBindingModal" } } diff --git a/src/locales/lang/zh-CN/routes/demo.json b/src/locales/lang/zh-CN/routes/demo.json index aec43785eb6..a88ed54eafe 100644 --- a/src/locales/lang/zh-CN/routes/demo.json +++ b/src/locales/lang/zh-CN/routes/demo.json @@ -173,6 +173,8 @@ "editRowTable": "可编辑行", "authColumn": "权限列", "resizeParentHeightTable": "继承父元素高度", - "vxeTable": "VxeTable" + "vxeTable": "VxeTable", + "trackTableRow":"自动收集行信息", + "trackTableRowBindingModal":"自动收集结合弹窗回填" } } diff --git a/src/router/routes/modules/demo/comp.ts b/src/router/routes/modules/demo/comp.ts index 26cd9c20e07..74bf2c859fb 100644 --- a/src/router/routes/modules/demo/comp.ts +++ b/src/router/routes/modules/demo/comp.ts @@ -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'), + }, + }, ], }, { diff --git a/src/views/demo/table/AccountModal.vue b/src/views/demo/table/AccountModal.vue new file mode 100644 index 00000000000..580b2715982 --- /dev/null +++ b/src/views/demo/table/AccountModal.vue @@ -0,0 +1,45 @@ + + diff --git a/src/views/demo/table/TrackTableRow.vue b/src/views/demo/table/TrackTableRow.vue new file mode 100644 index 00000000000..b4600eb358a --- /dev/null +++ b/src/views/demo/table/TrackTableRow.vue @@ -0,0 +1,67 @@ + + diff --git a/src/views/demo/table/TrackTableRowBindingModal.vue b/src/views/demo/table/TrackTableRowBindingModal.vue new file mode 100644 index 00000000000..44c920c769f --- /dev/null +++ b/src/views/demo/table/TrackTableRowBindingModal.vue @@ -0,0 +1,48 @@ + + From 4072ece1a468a17e6798fce5bb022c01324ce26b Mon Sep 17 00:00:00 2001 From: zhengchangfu Date: Thu, 1 Feb 2024 12:56:42 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(hook):=20=E4=BF=AE=E5=A4=8D=20useTableC?= =?UTF-8?q?ontext=20=E5=9C=A8=E6=B2=A1=E6=9C=89=E4=BD=BF=E7=94=A8=20create?= =?UTF-8?q?TableContext=20=E6=B3=A8=E5=85=A5=E7=9A=84=E6=83=85=E5=86=B5?= =?UTF-8?q?=E4=B8=8B,=E6=8E=A7=E5=88=B6=E5=8F=B0=E6=8A=A5=E8=AD=A6?= =?UTF-8?q?=E5=91=8A=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Table/src/hooks/useTableContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Table/src/hooks/useTableContext.ts b/src/components/Table/src/hooks/useTableContext.ts index b657bb27e47..daef7c5b1b0 100644 --- a/src/components/Table/src/hooks/useTableContext.ts +++ b/src/components/Table/src/hooks/useTableContext.ts @@ -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; }