feat: refactor utils to optimize functionality

This commit is contained in:
YEL!ne 2024-11-01 14:23:56 +08:00
parent 3baf2f32cb
commit ada47b984c
10 changed files with 345 additions and 168 deletions

View File

@ -57,6 +57,10 @@ const bindEvents = (instance: IVTable) => {
};
//
const createTableInstance = (Type: new (container: HTMLElement, options: IOption) => IVTable, options: IOption) => {
vTableInstance.value = new Type(vTableContainer.value!, options);
};
const createVTable = () => {
if (!vTableContainer.value) return;
@ -68,10 +72,6 @@ const createVTable = () => {
return props.records !== undefined && props.records !== null && props.records.length > 0 ? props.records : props.options.records;
};
const createTableInstance = (Type: any, options: any) => {
vTableInstance.value = new Type(vTableContainer.value, options);
};
try {
switch (props.type) {
case 'list':
@ -96,8 +96,8 @@ const createVTable = () => {
bindEvents(vTableInstance.value);
props.onReady?.(vTableInstance.value, true);
} catch (err) {
// props.onError?.(err as Error);
console.error(err);
console.error('Error creating table instance:', err);
props.onError?.(err as Error);
}
};
@ -134,6 +134,7 @@ onBeforeUnmount(() => vTableInstance.value?.release());
// options
//
// deep tree
watch(
() => props.options,
(newOptions) => {
@ -143,7 +144,7 @@ watch(
createVTable();
}
},
{ deep: true },
// { deep: true },
);
// records

View File

@ -14,11 +14,9 @@
<script setup lang="ts">
import { ref, computed, defineProps, useSlots, defineExpose } from 'vue';
import { flattenVNodes, createCustomLayout , convertPropsToCamelCase } from './utils';
import { flattenVNodes, extractListSlotOptions } from '../utils';
import BaseTable from './base-table.vue';
import type { ColumnDefine } from '@visactor/vtable';
import type { TooltipProps } from '../components/component/tooltip';
import type { MenuProps } from '../components/component/menu';
//
interface Props {
@ -38,7 +36,7 @@ const slots = useSlots();
//
const computedOptions = computed(() => {
const flattenedSlots = flattenVNodes(slots.default?.() || []);
const slotOptions = extractSlotOptions(flattenedSlots);
const slotOptions = extractListSlotOptions(flattenedSlots);
return {
...props.options,
@ -48,58 +46,6 @@ const computedOptions = computed(() => {
};
});
//
function extractSlotOptions(vnodes: any[]) {
const options = {
columns: [] as ColumnDefine[],
tooltip: {} as TooltipProps,
menu: {} as MenuProps,
};
const typeMapping: Record<string, keyof typeof options> = {
ListColumn: 'columns',
Tooltip: 'tooltip',
Menu: 'menu',
};
vnodes.forEach(vnode => {
vnode.props = convertPropsToCamelCase(vnode.props);
const typeName = vnode.type?.symbol || vnode.type?.name;
const optionKey = typeMapping[typeName];
if (optionKey) {
if (optionKey === 'columns' && vnode.children) {
vnode.props.customLayout = createCustomLayoutHandler(vnode.children);
}
if (Array.isArray(options[optionKey])) {
(options[optionKey] as any[]).push(vnode.props);
} else {
options[optionKey] = vnode.props;
}
}
});
return options;
}
//
function createCustomLayoutHandler(children: any) {
return (args: any) => {
const { table, row, col, rect } = args;
const record = table.getCellOriginRecord(col, row);
const { height, width } = rect ?? table.getCellRect(col, row);
const rootContainer = children.customLayout({ table, row, col, rect, record, height, width })[0];
const { rootComponent } = createCustomLayout(rootContainer);
return {
rootContainer: rootComponent,
renderDefault: false,
};
};
}
//
defineExpose({
vTableInstance: computed(() => baseTableRef.value?.vTableInstance || null),

View File

@ -13,11 +13,8 @@
<script setup lang="ts">
import { shallowRef, computed, defineProps, useSlots } from 'vue';
import { flattenVNodes , convertPropsToCamelCase } from './utils';
import { flattenVNodes, extractPivotSlotOptions } from '../utils';
import BaseTable from './base-table.vue';
import type { ICornerDefine as PivotCornerProps, IIndicator as PivotIndicatorsProps, IDimension as PivotColumnDimensionProps, IDimension as PivotRowDimensionProps, ITitleDefine } from '@visactor/vtable';
import type { TooltipProps } from '../components/component/tooltip';
import type { MenuProps } from '../components/component/menu';
interface Props {
options: Record<string, unknown>;
@ -33,57 +30,18 @@ const slots = useSlots();
const computedOptions = computed(() => {
const flattenedSlots = flattenVNodes(slots.default?.() || []);
const options = {
columns: [] as PivotColumnDimensionProps[],
columnHeaderTitle: [] as ITitleDefine[],
rows: [] as PivotRowDimensionProps[],
rowHeaderTitle: [] as ITitleDefine[],
indicators: [] as PivotIndicatorsProps[],
corner: Object as PivotCornerProps | null,
tooltip: Object as TooltipProps | null,
menu: Object as MenuProps | null,
};
const typeMapping: Record<string, keyof typeof options> = {
'PivotColumnDimension': 'columns',
'PivotColumnHeaderTitle': 'columnHeaderTitle',
'PivotRowDimension': 'rows',
'PivotRowHeaderTitle': 'rowHeaderTitle',
'PivotCorner': 'corner',
'PivotIndicator': 'indicators',
'Tooltip': 'tooltip',
'Menu': 'menu',
};
flattenedSlots.forEach(vnode => {
vnode.props = convertPropsToCamelCase(vnode.props);
const typeName = vnode.type?.symbol || vnode.type?.name ;
const optionKey = typeMapping[typeName];
if (optionKey) {
if (Array.isArray(options[optionKey])) {
if (vnode.props.hasOwnProperty('objectHandler')) {
(options[optionKey] as any[]).push(vnode.props.objectHandler);
} else {
(options[optionKey] as any[]).push(vnode.props);
}
}else {
options[optionKey] = vnode.props;
}
}
});
const slotOptions = extractPivotSlotOptions(flattenedSlots);
return {
...props.options,
columns: options.columns.length ? options.columns : props.options.columns,
columnHeaderTitle: options.columnHeaderTitle.length ? options.columnHeaderTitle : props.options.columnHeaderTitle,
rows: options.rows.length ? options.rows : props.options.rows,
rowHeaderTitle: options.rowHeaderTitle.length ? options.rowHeaderTitle : props.options.rowHeaderTitle,
indicators: options.indicators.length ? options.indicators : props.options.indicators,
corner: options.corner || props.options.corner,
tooltip: options.tooltip || props.options.tooltip,
menu: options.menu || props.options.menu,
columns: slotOptions.columns.length ? slotOptions.columns : props.options.columns,
columnHeaderTitle: slotOptions.columnHeaderTitle.length ? slotOptions.columnHeaderTitle : props.options.columnHeaderTitle,
rows: slotOptions.rows.length ? slotOptions.rows : props.options.rows,
rowHeaderTitle: slotOptions.rowHeaderTitle.length ? slotOptions.rowHeaderTitle : props.options.rowHeaderTitle,
indicators: slotOptions.indicators.length ? slotOptions.indicators : props.options.indicators,
corner: props.options.corner || slotOptions.corner,
tooltip: props.options.tooltip || slotOptions.tooltip,
menu: props.options.menu || slotOptions.menu,
};
});

View File

@ -13,11 +13,8 @@
<script setup lang="ts">
import { shallowRef, computed, defineProps, useSlots } from 'vue';
import { flattenVNodes , convertPropsToCamelCase } from './utils';
import { flattenVNodes, extractPivotSlotOptions } from '../utils';
import BaseTable from './base-table.vue';
import type { ICornerDefine as PivotCornerProps, IIndicator as PivotIndicatorsProps, IDimension as PivotColumnDimensionProps, IDimension as PivotRowDimensionProps, ITitleDefine } from '@visactor/vtable';
import type { TooltipProps } from '../components/component/tooltip';
import type { MenuProps } from '../components/component/menu';
interface Props {
options: Record<string, unknown>;
@ -33,53 +30,18 @@ const slots = useSlots();
const computedOptions = computed(() => {
const flattenedSlots = flattenVNodes(slots.default?.() || []);
const slotOptions = extractPivotSlotOptions(flattenedSlots);
const options = {
columns: [] as PivotColumnDimensionProps[],
columnHeaderTitle: [] as ITitleDefine[],
rows: [] as PivotRowDimensionProps[],
rowHeaderTitle: [] as ITitleDefine[],
indicators: [] as PivotIndicatorsProps[],
corner: Object as PivotCornerProps | null,
tooltip: Object as TooltipProps | null,
menu: Object as MenuProps | null,
};
const typeMapping: Record<string, keyof typeof options> = {
'PivotColumnDimension': 'columns',
'PivotColumnHeaderTitle': 'columnHeaderTitle',
'PivotRowDimension': 'rows',
'PivotRowHeaderTitle': 'rowHeaderTitle',
'PivotCorner': 'corner',
'PivotIndicator': 'indicators',
'Tooltip': 'tooltip',
'Menu': 'menu',
};
flattenedSlots.forEach(vnode => {
vnode.props = convertPropsToCamelCase(vnode.props);
const typeName = vnode.type?.symbol || vnode.type?.name ;
const optionKey = typeMapping[typeName];
if (optionKey) {
if (Array.isArray(options[optionKey])) {
(options[optionKey] as any[]).push(vnode.props);
} else {
options[optionKey] = vnode.props;
}
}
});
return {
...props.options,
columns: options.columns.length ? options.columns : props.options.columns,
columnHeaderTitle: options.columnHeaderTitle.length ? options.columnHeaderTitle : props.options.columnHeaderTitle,
rows: options.rows.length ? options.rows : props.options.rows,
rowHeaderTitle: options.rowHeaderTitle.length ? options.rowHeaderTitle : props.options.rowHeaderTitle,
indicators: options.indicators.length ? options.indicators : props.options.indicators,
corner: props.options.corner || options.corner,
tooltip: props.options.tooltip || options.tooltip,
menu: props.options.menu|| options.menu,
columns: slotOptions.columns.length ? slotOptions.columns : props.options.columns,
columnHeaderTitle: slotOptions.columnHeaderTitle.length ? slotOptions.columnHeaderTitle : props.options.columnHeaderTitle,
rows: slotOptions.rows.length ? slotOptions.rows : props.options.rows,
rowHeaderTitle: slotOptions.rowHeaderTitle.length ? slotOptions.rowHeaderTitle : props.options.rowHeaderTitle,
indicators: slotOptions.indicators.length ? slotOptions.indicators : props.options.indicators,
corner: props.options.corner || slotOptions.corner,
tooltip: props.options.tooltip || slotOptions.tooltip,
menu: props.options.menu || slotOptions.menu,
};
});

View File

@ -1,9 +1,19 @@
// 已经被废弃,不再使用,后续会删除 :)
// was deprecated, no longer used, will be deleted later
// 已经被废弃,不再使用,后续会删除 :)
// was deprecated, no longer used, will be deleted later
import * as VTable from '@visactor/vtable';
import { isFunction, merge } from '@visactor/vutils';
import { application, REACT_TO_CANOPUS_EVENTS } from '@visactor/vtable/es/vrender';
import type { Graphic, IGraphic, IGraphicCreator } from '@visactor/vtable/es/vrender';
import { CheckBox, Radio, Tag } from '@visactor/vtable/es/vrender';
import { reactive, watch } from 'vue';
import type { ColumnDefine, ICornerDefine, IIndicator, IDimension, ITitleDefine } from '@visactor/vtable';
import type { TooltipProps } from '../components/component/tooltip';
import type { MenuProps } from '../components/component/menu';
// import { application, REACT_TO_CANOPUS_EVENTS } from '@visactor/vtable/es/vrender';
// import type { Graphic, IGraphic, IGraphicCreator } from '@visactor/vtable/es/vrender';
// import { CheckBox, Radio, Tag } from '@visactor/vtable/es/vrender';
// import { reactive, watch } from 'vue';
// 展平嵌套的虚拟节点
export function flattenVNodes(vnodes: any[]): any[] {
@ -108,3 +118,97 @@ export function createCustomLayout(children: any): any {
// 返回root组件和refs
return { rootComponent: createComponent(children) };
}
// 创建自定义布局
export function extractPivotSlotOptions(vnodes: any[]) {
const options = {
columns: [] as ColumnDefine[],
columnHeaderTitle: [] as ITitleDefine[],
rows: [] as IDimension[],
rowHeaderTitle: [] as ITitleDefine[],
indicators: [] as IIndicator[],
corner: {} as ICornerDefine | null,
tooltip: {} as TooltipProps | null,
menu: {} as MenuProps | null
};
const typeMapping: Record<string, keyof typeof options> = {
PivotColumnDimension: 'columns',
PivotColumnHeaderTitle: 'columnHeaderTitle',
PivotRowDimension: 'rows',
PivotRowHeaderTitle: 'rowHeaderTitle',
PivotCorner: 'corner',
PivotIndicator: 'indicators',
Tooltip: 'tooltip',
Menu: 'menu'
};
vnodes.forEach(vnode => {
vnode.props = convertPropsToCamelCase(vnode.props);
const typeName = vnode.type?.symbol || vnode.type?.name;
const optionKey = typeMapping[typeName];
if (optionKey) {
if (Array.isArray(options[optionKey])) {
if (vnode.props.hasOwnProperty('objectHandler')) {
(options[optionKey] as any[]).push(vnode.props.objectHandler);
} else {
(options[optionKey] as any[]).push(vnode.props);
}
} else {
options[optionKey] = vnode.props;
}
}
});
return options;
}
function createCustomLayoutHandler(children: any) {
return (args: any) => {
const { table, row, col, rect } = args;
const record = table.getCellOriginRecord(col, row);
const { height, width } = rect ?? table.getCellRect(col, row);
const rootContainer = children.customLayout({ table, row, col, rect, record, height, width })[0];
const { rootComponent } = createCustomLayout(rootContainer);
return {
rootContainer: rootComponent,
renderDefault: false
};
};
}
export function extractListSlotOptions(vnodes: any[]) {
const options = {
columns: [] as ColumnDefine[],
tooltip: {} as TooltipProps,
menu: {} as MenuProps
};
const typeMapping: Record<string, keyof typeof options> = {
ListColumn: 'columns',
Tooltip: 'tooltip',
Menu: 'menu'
};
vnodes.forEach(vnode => {
vnode.props = convertPropsToCamelCase(vnode.props);
const typeName = vnode.type?.symbol || vnode.type?.name;
const optionKey = typeMapping[typeName];
if (optionKey) {
if (optionKey === 'columns' && vnode.children) {
vnode.props.customLayout = createCustomLayoutHandler(vnode.children);
}
if (Array.isArray(options[optionKey])) {
(options[optionKey] as any[]).push(vnode.props);
} else {
options[optionKey] = vnode.props;
}
}
});
return options;
}

View File

@ -0,0 +1,4 @@
export * from './utils/stringUtils';
export * from './utils/vnodeUtils';
export * from './utils/customLayoutUtils';
export * from './utils/slotUtils';

View File

@ -0,0 +1,100 @@
import * as VTable from '@visactor/vtable';
import { convertPropsToCamelCase, toCamelCase } from './stringUtils';
import { isFunction } from '@visactor/vutils';
// 检查属性是否为事件
function isEventProp(key: string, props: any) {
return key.startsWith('on') && isFunction(props[key]);
}
// 创建自定义布局
export function createCustomLayout(children: any): any {
// 组件映射
const componentMap: Record<string, any> = {
Group: VTable.CustomLayout.Group,
Image: VTable.CustomLayout.Image,
Text: VTable.CustomLayout.Text,
Tag: VTable.CustomLayout.Tag,
Radio: VTable.CustomLayout.Radio,
CheckBox: VTable.CustomLayout.CheckBox
};
// 创建组件的函数
function createComponent(child: any): any {
if (!child) {
return null;
}
const { type, children: childChildren } = child;
const props = convertPropsToCamelCase(child.props);
const componentName = type?.symbol || type?.name;
const ComponentClass = componentMap[componentName];
if (!ComponentClass) {
return null;
}
// 创建组件实例
const component = new ComponentClass({ ...props });
// 绑定组件事件
bindComponentEvents(component, props);
// 递归创建子组件
const subChildren = resolveChildren(childChildren);
subChildren.forEach((subChild: any) => {
const subComponent = createComponent(subChild);
if (subComponent) {
component.add(subComponent);
} else if (subChild.type === Symbol.for('v-fgt')) {
subChild.children.forEach((nestedChild: any) => {
const nestedComponent = createComponent(nestedChild);
if (nestedComponent) {
component.add(nestedComponent);
}
});
}
});
return component;
}
// 处理子节点
function resolveChildren(childChildren: any): any[] {
return childChildren?.default?.() || childChildren || [];
}
// 绑定组件事件
function bindComponentEvents(component: any, props: any): void {
Object.keys(props).forEach(key => {
if (isEventProp(key, props)) {
let eventName: string;
if (key.startsWith('on')) {
eventName = key.slice(2).toLowerCase(); // 去掉'on'前缀并转换为小写
} else {
eventName = toCamelCase(key.slice(2)).toLowerCase(); // 转换为camelCase
}
component.addEventListener(eventName, props[key]);
}
});
}
// 返回root组件和refs
return { rootComponent: createComponent(children) };
}
export function createCustomLayoutHandler(children: any) {
return (args: any) => {
const { table, row, col, rect } = args;
const record = table.getCellOriginRecord(col, row);
const { height, width } = rect ?? table.getCellRect(col, row);
const rootContainer = children.customLayout({ table, row, col, rect, record, height, width })[0];
const { rootComponent } = createCustomLayout(rootContainer);
return {
rootContainer: rootComponent,
renderDefault: false
};
};
}

View File

@ -0,0 +1,82 @@
import { convertPropsToCamelCase } from './stringUtils';
import { createCustomLayoutHandler } from './customLayoutUtils';
import type { ColumnDefine, ICornerDefine, IIndicator, IDimension, ITitleDefine } from '@visactor/vtable';
import type { TooltipProps } from '../components/component/tooltip';
import type { MenuProps } from '../components/component/menu';
export function extractPivotSlotOptions(vnodes: any[]) {
const options = {
columns: [] as ColumnDefine[],
columnHeaderTitle: [] as ITitleDefine[],
rows: [] as IDimension[],
rowHeaderTitle: [] as ITitleDefine[],
indicators: [] as IIndicator[],
corner: {} as ICornerDefine | null,
tooltip: {} as TooltipProps | null,
menu: {} as MenuProps | null
};
const typeMapping: Record<string, keyof typeof options> = {
PivotColumnDimension: 'columns',
PivotColumnHeaderTitle: 'columnHeaderTitle',
PivotRowDimension: 'rows',
PivotRowHeaderTitle: 'rowHeaderTitle',
PivotCorner: 'corner',
PivotIndicator: 'indicators',
Tooltip: 'tooltip',
Menu: 'menu'
};
vnodes.forEach(vnode => {
vnode.props = convertPropsToCamelCase(vnode.props);
const typeName = vnode.type?.symbol || vnode.type?.name;
const optionKey = typeMapping[typeName];
if (optionKey) {
if (Array.isArray(options[optionKey])) {
if (vnode.props.hasOwnProperty('objectHandler')) {
(options[optionKey] as any[]).push(vnode.props.objectHandler);
} else {
(options[optionKey] as any[]).push(vnode.props);
}
} else {
options[optionKey] = vnode.props;
}
}
});
return options;
}
export function extractListSlotOptions(vnodes: any[]) {
const options = {
columns: [] as ColumnDefine[],
tooltip: {} as TooltipProps,
menu: {} as MenuProps
};
const typeMapping: Record<string, keyof typeof options> = {
ListColumn: 'columns',
Tooltip: 'tooltip',
Menu: 'menu'
};
vnodes.forEach(vnode => {
vnode.props = convertPropsToCamelCase(vnode.props);
const typeName = vnode.type?.symbol || vnode.type?.name;
const optionKey = typeMapping[typeName];
if (optionKey) {
if (optionKey === 'columns' && vnode.children) {
vnode.props.customLayout = createCustomLayoutHandler(vnode.children);
}
if (Array.isArray(options[optionKey])) {
(options[optionKey] as any[]).push(vnode.props);
} else {
options[optionKey] = vnode.props;
}
}
});
return options;
}

View File

@ -0,0 +1,16 @@
// 将连字符形式的字符串转换为驼峰形式
export function toCamelCase(str: string): string {
return str.replace(/-([a-z])/g, g => g[1].toUpperCase());
}
// 将 vnode.props 中的所有属性名转换为驼峰形式
export function convertPropsToCamelCase(props: Record<string, any>): Record<string, any> {
const newProps: Record<string, any> = {};
for (const key in props) {
if (props.hasOwnProperty(key)) {
const camelCaseKey = toCamelCase(key);
newProps[camelCaseKey] = props[key];
}
}
return newProps;
}

View File

@ -0,0 +1,4 @@
// 展平嵌套的虚拟节点
export function flattenVNodes(vnodes: any[]): any[] {
return vnodes.flatMap(vnode => (Array.isArray(vnode.children) ? flattenVNodes(vnode.children) : vnode));
}