From 7c3a2b7aa4e0552cd017e70d4a3208351ace1a94 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Fri, 20 Jun 2025 18:19:09 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20table=20actions=20?= =?UTF-8?q?=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/table-action/table-action.vue | 181 ++++++++++++++---- 1 file changed, 146 insertions(+), 35 deletions(-) diff --git a/apps/web-antd/src/components/table-action/table-action.vue b/apps/web-antd/src/components/table-action/table-action.vue index fe0bfe8fb..21c8197df 100644 --- a/apps/web-antd/src/components/table-action/table-action.vue +++ b/apps/web-antd/src/components/table-action/table-action.vue @@ -4,7 +4,7 @@ import type { PropType } from 'vue'; import type { ActionItem, PopConfirm } from './typing'; -import { computed, toRaw } from 'vue'; +import { computed, ref, toRaw, unref, watchEffect } from 'vue'; import { useAccess } from '@vben/access'; import { IconifyIcon } from '@vben/icons'; @@ -41,6 +41,14 @@ const props = defineProps({ const { hasAccessByCodes } = useAccess(); +/** 缓存处理后的actions */ +const processedActions = ref([]); +const processedDropdownActions = ref([]); + +/** 用于比较的字符串化版本 */ +const actionsStringified = ref(''); +const dropdownActionsStringified = ref(''); + function isIfShow(action: ActionItem): boolean { const ifShow = action.ifShow; let isIfShow = true; @@ -57,8 +65,8 @@ function isIfShow(action: ActionItem): boolean { return isIfShow; } -const getActions = computed(() => { - const actions = toRaw(props.actions) || []; +/** 处理actions的纯函数 */ +function processActions(actions: ActionItem[]): any[] { return actions .filter((action: ActionItem) => { return isIfShow(action); @@ -74,30 +82,101 @@ const getActions = computed(() => { enable: !!popConfirm, }; }); -}); +} -const getDropdownList = computed((): any[] => { - const dropDownActions = toRaw(props.dropDownActions) || []; +/** 处理下拉菜单actions的纯函数 */ +function processDropdownActions( + dropDownActions: ActionItem[], + divider: boolean, +): any[] { return dropDownActions .filter((action: ActionItem) => { return isIfShow(action); }) .map((action: ActionItem, index: number) => { const { label, popConfirm } = action; - delete action.icon; + const processedAction = { ...action }; + delete processedAction.icon; return { - ...action, + ...processedAction, ...popConfirm, onConfirm: popConfirm?.confirm, onCancel: popConfirm?.cancel, text: label, - divider: index < dropDownActions.length - 1 ? props.divider : false, + divider: index < dropDownActions.length - 1 ? divider : false, }; }); +} + +/** 监听actions变化并更新缓存 */ +watchEffect(() => { + const rawActions = toRaw(props.actions) || []; + const currentStringified = JSON.stringify( + rawActions.map((a) => ({ + ...a, + onClick: undefined, // 排除函数以便比较 + popConfirm: a.popConfirm + ? { ...a.popConfirm, confirm: undefined, cancel: undefined } + : undefined, + })), + ); + + if (currentStringified !== actionsStringified.value) { + actionsStringified.value = currentStringified; + processedActions.value = processActions(rawActions); + } }); +/** 监听dropDownActions变化并更新缓存 */ +watchEffect(() => { + const rawDropDownActions = toRaw(props.dropDownActions) || []; + const currentStringified = JSON.stringify({ + actions: rawDropDownActions.map((a) => ({ + ...a, + onClick: undefined, // 排除函数以便比较 + popConfirm: a.popConfirm + ? { ...a.popConfirm, confirm: undefined, cancel: undefined } + : undefined, + })), + divider: props.divider, + }); + + if (currentStringified !== dropdownActionsStringified.value) { + dropdownActionsStringified.value = currentStringified; + processedDropdownActions.value = processDropdownActions( + rawDropDownActions, + props.divider, + ); + } +}); + +const getActions = computed(() => processedActions.value); + +const getDropdownList = computed(() => processedDropdownActions.value); + +/** 缓存Space组件的size计算结果 */ +const spaceSize = computed(() => { + return unref(getActions)?.some((item: ActionItem) => item.type === 'link') + ? 0 + : 8; +}); + +/** 缓存PopConfirm属性 */ +const popConfirmPropsMap = new Map(); + function getPopConfirmProps(attrs: PopConfirm) { - const originAttrs: any = attrs; + const key = JSON.stringify({ + title: attrs.title, + okText: attrs.okText, + cancelText: attrs.cancelText, + disabled: attrs.disabled, + }); + + if (popConfirmPropsMap.has(key)) { + return popConfirmPropsMap.get(key); + } + + const originAttrs: any = { ...attrs }; delete originAttrs.icon; if (attrs.confirm && isFunction(attrs.confirm)) { originAttrs.onConfirm = attrs.confirm; @@ -107,34 +186,76 @@ function getPopConfirmProps(attrs: PopConfirm) { originAttrs.onCancel = attrs.cancel; delete originAttrs.cancel; } + + popConfirmPropsMap.set(key, originAttrs); return originAttrs; } +/** 缓存Button属性 */ +const buttonPropsMap = new Map(); + function getButtonProps(action: ActionItem) { + const key = JSON.stringify({ + type: action.type, + disabled: action.disabled, + loading: action.loading, + size: action.size, + }); + + if (buttonPropsMap.has(key)) { + return { ...buttonPropsMap.get(key) }; + } + const res = { type: action.type || 'primary', - ...action, + disabled: action.disabled, + loading: action.loading, + size: action.size, }; - delete res.icon; + + buttonPropsMap.set(key, res); return res; } +/** 缓存Tooltip属性 */ +const tooltipPropsMap = new Map(); + +function getTooltipProps(tooltip: any | string) { + if (!tooltip) return {}; + + const key = typeof tooltip === 'string' ? tooltip : JSON.stringify(tooltip); + + if (tooltipPropsMap.has(key)) { + return tooltipPropsMap.get(key); + } + + const result = + typeof tooltip === 'string' ? { title: tooltip } : { ...tooltip }; + + tooltipPropsMap.set(key, result); + return result; +} + function handleMenuClick(e: any) { - const action = getDropdownList.value[e.key]; + const action = unref(getDropdownList)[e.key]; if (action.onClick && isFunction(action.onClick)) { action.onClick(); } } + +/** 生成稳定的key */ +function getActionKey(action: ActionItem, index: number) { + return `${action.label || ''}-${action.type || ''}-${index}`; +}