Commit 6e03e05032474c858151b3835eb5318486a56729

Authored by vben
1 parent 41d79008

perf: perf context menu

src/components/ContextMenu/index.ts
1   -import contextMenuVue from './src/index';
2   -import { isClient } from '/@/utils/is';
3   -import { Options, Props } from './src/types';
4   -import { createVNode, render } from 'vue';
5   -const menuManager: {
6   - domList: Element[];
7   - resolve: Fn;
8   -} = {
9   - domList: [],
10   - resolve: () => {},
11   -};
12   -export const createContextMenu = function (options: Options) {
13   - const { event } = options || {};
14   - try {
15   - event.preventDefault();
16   - } catch (e) {
17   - console.log(e);
18   - }
19   -
20   - if (!isClient) return;
21   - return new Promise((resolve) => {
22   - const container = document.createElement('div');
23   - const propsData: Partial<Props> = {};
24   - if (options.styles !== undefined) propsData.styles = options.styles;
25   - if (options.items !== undefined) propsData.items = options.items;
26   - if (options.event !== undefined) {
27   - propsData.customEvent = event;
28   - propsData.axis = { x: event.clientX, y: event.clientY };
29   - }
30   - const vm = createVNode(contextMenuVue, propsData);
31   - render(vm, container);
32   - const bodyClick = function () {
33   - menuManager.resolve('');
34   - };
35   - menuManager.domList.push(container);
36   - const remove = function () {
37   - menuManager.domList.forEach((dom: Element) => {
38   - try {
39   - document.body.removeChild(dom);
40   - } catch (error) {}
41   - });
42   - document.body.removeEventListener('click', bodyClick);
43   - document.body.removeEventListener('scroll', bodyClick);
44   - };
45   - menuManager.resolve = function (...arg: any) {
46   - resolve(arg[0]);
47   - remove();
48   - };
49   - remove();
50   - document.body.appendChild(container);
51   - document.body.addEventListener('click', bodyClick);
52   - document.body.addEventListener('scroll', bodyClick);
53   - });
54   -};
55   -export const unMountedContextMenu = function () {
56   - if (menuManager) {
57   - menuManager.resolve('');
58   - menuManager.domList = [];
59   - }
60   -};
61   -
  1 +export { createContextMenu, destroyContextMenu } from './src/createContextMenu';
62 2 export * from './src/types';
... ...
src/components/ContextMenu/src/createContextMenu.ts 0 → 100644
  1 +import contextMenuVue from './index';
  2 +import { isClient } from '/@/utils/is';
  3 +import { CreateContextOptions, ContextMenuProps } from './types';
  4 +import { createVNode, render } from 'vue';
  5 +
  6 +const menuManager: {
  7 + domList: Element[];
  8 + resolve: Fn;
  9 +} = {
  10 + domList: [],
  11 + resolve: () => {},
  12 +};
  13 +
  14 +export const createContextMenu = function (options: CreateContextOptions) {
  15 + const { event } = options || {};
  16 +
  17 + event && event?.preventDefault();
  18 +
  19 + if (!isClient) return;
  20 + return new Promise((resolve) => {
  21 + const body = document.body;
  22 +
  23 + const container = document.createElement('div');
  24 + const propsData: Partial<ContextMenuProps> = {};
  25 + if (options.styles) {
  26 + propsData.styles = options.styles;
  27 + }
  28 +
  29 + if (options.items) {
  30 + propsData.items = options.items;
  31 + }
  32 +
  33 + if (options.event) {
  34 + propsData.customEvent = event;
  35 + propsData.axis = { x: event.clientX, y: event.clientY };
  36 + }
  37 +
  38 + const vm = createVNode(contextMenuVue, propsData);
  39 + render(vm, container);
  40 +
  41 + const handleClick = function () {
  42 + menuManager.resolve('');
  43 + };
  44 +
  45 + menuManager.domList.push(container);
  46 +
  47 + const remove = function () {
  48 + menuManager.domList.forEach((dom: Element) => {
  49 + try {
  50 + dom && body.removeChild(dom);
  51 + } catch (error) {}
  52 + });
  53 + body.removeEventListener('click', handleClick);
  54 + body.removeEventListener('scroll', handleClick);
  55 + };
  56 +
  57 + menuManager.resolve = function (...arg: any) {
  58 + remove();
  59 + resolve(arg[0]);
  60 + };
  61 + remove();
  62 + body.appendChild(container);
  63 + body.addEventListener('click', handleClick);
  64 + body.addEventListener('scroll', handleClick);
  65 + });
  66 +};
  67 +
  68 +export const destroyContextMenu = function () {
  69 + if (menuManager) {
  70 + menuManager.resolve('');
  71 + menuManager.domList = [];
  72 + }
  73 +};
... ...
src/components/ContextMenu/src/index.less
1 1 @import (reference) '../../../design/index.less';
2 2  
  3 +@default-height: 42px !important;
  4 +
  5 +@small-height: 36px !important;
  6 +
  7 +@large-height: 36px !important;
  8 +
3 9 .item-style() {
4 10 li {
5 11 display: inline-block;
6 12 width: 100%;
7   - height: 46px !important;
  13 + height: @default-height;
8 14 margin: 0 !important;
9   - line-height: 46px;
  15 + line-height: @default-height;
10 16  
11 17 span {
12   - line-height: 46px;
  18 + line-height: @default-height;
13 19 }
14 20  
15 21 > div {
16 22 margin: 0 !important;
17 23 }
18 24  
19   - &:hover {
  25 + &:not(.ant-menu-item-disabled):hover {
20 26 color: @text-color-base;
21 27 background: #eee;
22 28 }
... ... @@ -27,10 +33,9 @@
27 33 position: fixed;
28 34 top: 0;
29 35 left: 0;
30   - z-index: 1500;
  36 + z-index: 200;
31 37 display: block;
32 38 width: 156px;
33   - min-width: 10rem;
34 39 margin: 0;
35 40 list-style: none;
36 41 background-color: #fff;
... ...
src/components/ContextMenu/src/index.tsx
1   -import {
2   - defineComponent,
3   - nextTick,
4   - onMounted,
5   - reactive,
6   - computed,
7   - ref,
8   - unref,
9   - onUnmounted,
10   -} from 'vue';
  1 +import './index.less';
  2 +
  3 +import type { ContextMenuItem, ItemContentProps } from './types';
  4 +import type { FunctionalComponent, CSSProperties } from 'vue';
  5 +
  6 +import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue';
11 7  
12   -import { props } from './props';
13 8 import Icon from '/@/components/Icon';
14 9 import { Menu, Divider } from 'ant-design-vue';
15 10  
16   -import type { ContextMenuItem } from './types';
  11 +import { props } from './props';
17 12  
18   -import './index.less';
19 13 const prefixCls = 'context-menu';
  14 +
  15 +const ItemContent: FunctionalComponent<ItemContentProps> = (props) => {
  16 + const { item } = props;
  17 + return (
  18 + <span style="display: inline-block; width: 100%;" onClick={props.handler.bind(null, item)}>
  19 + {props.showIcon && item.icon && <Icon class="mr-2" icon={item.icon} />}
  20 + <span>{item.label}</span>
  21 + </span>
  22 + );
  23 +};
  24 +
20 25 export default defineComponent({
21 26 name: 'ContextMenu',
22 27 props,
23 28 setup(props) {
24   - const wrapRef = ref<Nullable<HTMLDivElement>>(null);
25   - const state = reactive({
26   - show: false,
27   - });
  29 + const wrapRef = ref<ElRef>(null);
  30 + const showRef = ref(false);
  31 +
  32 + const getStyle = computed(
  33 + (): CSSProperties => {
  34 + const { axis, items, styles, width } = props;
  35 + const { x, y } = axis || { x: 0, y: 0 };
  36 + const menuHeight = (items || []).length * 40;
  37 + const menuWidth = width;
  38 + const body = document.body;
  39 +
  40 + const left = body.clientWidth < x + menuWidth ? x - menuWidth : x;
  41 + const top = body.clientHeight < y + menuHeight ? y - menuHeight : y;
  42 + return {
  43 + ...styles,
  44 + width: `${width}px`,
  45 + left: `${left + 1}px`,
  46 + top: `${top + 1}px`,
  47 + };
  48 + }
  49 + );
28 50  
29 51 onMounted(() => {
30   - nextTick(() => {
31   - state.show = true;
32   - });
  52 + nextTick(() => (showRef.value = true));
33 53 });
34 54  
35 55 onUnmounted(() => {
36 56 const el = unref(wrapRef);
37 57 el && document.body.removeChild(el);
38 58 });
39   - const getStyle = computed(() => {
40   - const { axis, items, styles, width } = props;
41   - const { x, y } = axis || { x: 0, y: 0 };
42   - const menuHeight = (items || []).length * 40;
43   - const menuWidth = width;
44   - const body = document.body;
45   - return {
46   - ...(styles as any),
47   - width: `${width}px`,
48   - left: (body.clientWidth < x + menuWidth ? x - menuWidth : x) + 'px',
49   - top: (body.clientHeight < y + menuHeight ? y - menuHeight : y) + 'px',
50   - };
51   - });
52 59  
53 60 function handleAction(item: ContextMenuItem, e: MouseEvent) {
54   - state.show = false;
55 61 const { handler, disabled } = item;
56   - if (disabled) {
57   - return;
58   - }
59   - if (e) {
60   - e.stopPropagation();
61   - e.preventDefault();
62   - }
  62 + if (disabled) return;
  63 + showRef.value = false;
63 64  
64   - handler && handler();
65   - }
66   -
67   - function renderContent(item: ContextMenuItem) {
68   - const { icon, label } = item;
69   -
70   - const { showIcon } = props;
71   - return (
72   - <span style="display: inline-block; width: 100%;" onClick={handleAction.bind(null, item)}>
73   - {showIcon && icon && <Icon class="mr-2" icon={icon} />}
74   - <span>{label}</span>
75   - </span>
76   - );
  65 + e?.stopPropagation();
  66 + e?.preventDefault();
  67 + handler?.();
77 68 }
78 69  
79 70 function renderMenuItem(items: ContextMenuItem[]) {
80   - return items.map((item, index) => {
  71 + return items.map((item) => {
81 72 const { disabled, label, children, divider = false } = item;
82 73  
83   - const DividerComp = divider ? <Divider key={`d-${index}`} /> : null;
  74 + const DividerComp = divider ? <Divider key={`d-${label}`} /> : null;
84 75 if (!children || children.length === 0) {
85   - return [
86   - <Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}>
87   - {() => [renderContent(item)]}
88   - </Menu.Item>,
89   - DividerComp,
90   - ];
  76 + return (
  77 + <>
  78 + <Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}>
  79 + {() => [
  80 + <ItemContent showIcon={props.showIcon} item={item} handler={handleAction} />,
  81 + ]}
  82 + </Menu.Item>
  83 + {DividerComp}
  84 + </>
  85 + );
91 86 }
92   - return !state.show ? null : (
93   - <Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup `}>
  87 + if (!unref(showRef)) return null;
  88 +
  89 + return (
  90 + <Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup`}>
94 91 {{
95   - title: () => renderContent(item),
96   - default: () => [renderMenuItem(children)],
  92 + title: () => (
  93 + <ItemContent showIcon={props.showIcon} item={item} handler={handleAction} />
  94 + ),
  95 + default: () => renderMenuItem(children),
97 96 }}
98 97 </Menu.SubMenu>
99 98 );
... ... @@ -101,11 +100,12 @@ export default defineComponent({
101 100 }
102 101 return () => {
103 102 const { items } = props;
104   - return !state.show ? null : (
  103 + if (!unref(showRef)) return null;
  104 + return (
105 105 <Menu
106 106 inlineIndent={12}
107 107 mode="vertical"
108   - class={[prefixCls]}
  108 + class={prefixCls}
109 109 ref={wrapRef}
110 110 style={unref(getStyle)}
111 111 >
... ...
src/components/ContextMenu/src/props.ts
1   -import type { PropType } from 'vue';
  1 +import type { PropType, CSSProperties } from 'vue';
2 2 import type { Axis, ContextMenuItem } from './types';
3 3 export const props = {
4 4 width: {
5 5 type: Number as PropType<number>,
6   - default: 180,
  6 + default: 156,
7 7 },
8 8 customEvent: {
9 9 type: Object as PropType<Event>,
10 10 default: null,
11 11 },
12 12 styles: {
13   - type: Object as PropType<any>,
  13 + type: Object as PropType<CSSProperties>,
14 14 default: null,
15 15 },
16 16 showIcon: {
... ... @@ -31,8 +31,4 @@ export const props = {
31 31 return [];
32 32 },
33 33 },
34   - resolve: {
35   - type: Function as PropType<any>,
36   - default: null,
37   - },
38 34 };
... ...
src/components/ContextMenu/src/types.ts
... ... @@ -11,15 +11,14 @@ export interface ContextMenuItem {
11 11 divider?: boolean;
12 12 children?: ContextMenuItem[];
13 13 }
14   -export interface Options {
  14 +export interface CreateContextOptions {
15 15 event: MouseEvent;
16 16 icon?: string;
17 17 styles?: any;
18 18 items?: ContextMenuItem[];
19 19 }
20 20  
21   -export type Props = {
22   - resolve?: (...arg: any) => void;
  21 +export interface ContextMenuProps {
23 22 event?: MouseEvent;
24 23 styles?: any;
25 24 items: ContextMenuItem[];
... ... @@ -27,4 +26,10 @@ export type Props = {
27 26 axis?: Axis;
28 27 width?: number;
29 28 showIcon?: boolean;
30   -};
  29 +}
  30 +
  31 +export interface ItemContentProps {
  32 + showIcon: boolean;
  33 + item: ContextMenuItem;
  34 + handler: Fn;
  35 +}
... ...
src/components/Drawer/src/BasicDrawer.tsx
... ... @@ -22,7 +22,7 @@ export default defineComponent({
22 22 props: basicProps,
23 23 emits: ['visible-change', 'ok', 'close', 'register'],
24 24 setup(props, { slots, emit, attrs }) {
25   - const scrollRef = ref<any>(null);
  25 + const scrollRef = ref<ElRef>(null);
26 26  
27 27 const visibleRef = ref(false);
28 28 const propsRef = ref<Partial<DrawerProps> | null>(null);
... ...
src/components/Modal/src/BasicModal.tsx
... ... @@ -22,7 +22,7 @@ export default defineComponent({
22 22 setup(props, { slots, emit, attrs }) {
23 23 const visibleRef = ref(false);
24 24 const propsRef = ref<Partial<ModalProps> | null>(null);
25   - const modalWrapperRef = ref<any>(null);
  25 + const modalWrapperRef = ref<ComponentRef>(null);
26 26 // modal Bottom and top height
27 27 const extHeightRef = ref(0);
28 28 // Unexpanded height of the popup
... ...
src/components/Modal/src/ModalWrapper.tsx
... ... @@ -55,7 +55,7 @@ export default defineComponent({
55 55 emits: ['heightChange', 'getExtHeight'],
56 56 setup(props: ModalWrapperProps, { slots, emit }) {
57 57 const wrapperRef = ref<HTMLElement | null>(null);
58   - const spinRef = ref<any>(null);
  58 + const spinRef = ref<ComponentRef>(null);
59 59 const realHeightRef = ref(0);
60 60 // 重试次数
61 61 // let tryCount = 0;
... ... @@ -126,6 +126,8 @@ export default defineComponent({
126 126 await nextTick();
127 127 const spinEl = unref(spinRef);
128 128  
  129 + if (!spinEl) return;
  130 +
129 131 const spinContainerEl = spinEl.$el.querySelector('.ant-spin-container') as HTMLElement;
130 132 if (!spinContainerEl) return;
131 133  
... ...
src/components/Table/src/BasicTable.vue
... ... @@ -74,7 +74,7 @@
74 74 components: { Table, BasicForm },
75 75 emits: ['fetch-success', 'fetch-error', 'selection-change', 'register'],
76 76 setup(props, { attrs, emit, slots }) {
77   - const tableElRef = ref<any>(null);
  77 + const tableElRef = ref<ComponentRef>(null);
78 78 const wrapRef = ref<Nullable<HTMLDivElement>>(null);
79 79 const innerPropsRef = ref<Partial<BasicTableProps>>();
80 80 const [registerForm, { getFieldsValue }] = useForm();
... ... @@ -241,10 +241,8 @@
241 241 if (unref(getMergeProps).showSummary) {
242 242 nextTick(() => {
243 243 const tableEl = unref(tableElRef);
244   - if (!tableEl) {
245   - return;
246   - }
247   - const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body') as HTMLDivElement[];
  244 + if (!tableEl) return;
  245 + const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body');
248 246 const bodyDom = bodyDomList[0];
249 247 useEventListener({
250 248 el: bodyDom,
... ...
src/hooks/web/useContextMenu.ts
1 1 import { onUnmounted, getCurrentInstance } from 'vue';
2   -import { createContextMenu, unMountedContextMenu } from '/@/components/ContextMenu';
  2 +import { createContextMenu, destroyContextMenu } from '/@/components/ContextMenu';
3 3 import type { ContextMenuItem } from '/@/components/ContextMenu';
4 4 export type { ContextMenuItem };
5 5 export function useContextMenu(authRemove = true) {
6 6 if (getCurrentInstance() && authRemove) {
7 7 onUnmounted(() => {
8   - unMountedContextMenu();
  8 + destroyContextMenu();
9 9 });
10 10 }
11   - return [createContextMenu, unMountedContextMenu];
  11 + return [createContextMenu, destroyContextMenu];
12 12 }
... ...
src/layouts/default/content/index.less
... ... @@ -19,7 +19,7 @@
19 19 }
20 20  
21 21 > .basic-loading {
22   - margin-bottom: 30%;
  22 + margin-bottom: 15%;
23 23 }
24 24 }
25 25 }
... ...
src/layouts/default/header/LayoutHeader.tsx
... ... @@ -60,10 +60,10 @@ export default defineComponent({
60 60 },
61 61 },
62 62 setup(props) {
63   - let logoEl: Element | null;
  63 + let logoEl: Element | null | undefined;
64 64  
65 65 const logoWidthRef = ref(200);
66   - const logoRef = ref<any>(null);
  66 + const logoRef = ref<ComponentRef>(null);
67 67 const { refreshPage } = useTabs();
68 68  
69 69 const { getShowTopMenu, getShowHeaderTrigger, getSplit, getTopMenuAlign } = useMenuSetting();
... ... @@ -91,7 +91,7 @@ export default defineComponent({
91 91 if (!unref(getShowTopMenu)) return;
92 92 let width = 0;
93 93 if (!logoEl) {
94   - logoEl = logoRef.value.$el;
  94 + logoEl = unref(logoRef)?.$el;
95 95 } else {
96 96 width += logoEl.clientWidth;
97 97 }
... ...
src/layouts/default/header/LayoutMultipleHeader.tsx
... ... @@ -39,26 +39,30 @@ export default defineComponent({
39 39 return unref(getShowMultipleTab) && !unref(getFullContent);
40 40 });
41 41  
42   - const getPlaceholderDomStyle = computed(() => {
43   - return {
44   - height: `${unref(placeholderHeightRef)}px`,
45   - };
46   - });
  42 + const getPlaceholderDomStyle = computed(
  43 + (): CSSProperties => {
  44 + return {
  45 + height: `${unref(placeholderHeightRef)}px`,
  46 + };
  47 + }
  48 + );
47 49  
48 50 const getIsShowPlaceholderDom = computed(() => {
49 51 return unref(getFixed) || unref(getShowFullHeaderRef);
50 52 });
51 53  
52   - const getWrapStyle = computed(() => {
53   - const style: CSSProperties = {};
54   - if (unref(getFixed)) {
55   - style.width = unref(getCalcContentWidth);
  54 + const getWrapStyle = computed(
  55 + (): CSSProperties => {
  56 + const style: CSSProperties = {};
  57 + if (unref(getFixed)) {
  58 + style.width = unref(getCalcContentWidth);
  59 + }
  60 + if (unref(getShowFullHeaderRef)) {
  61 + style.top = `${unref(fullHeaderHeightRef)}px`;
  62 + }
  63 + return style;
56 64 }
57   - if (unref(getShowFullHeaderRef)) {
58   - style.top = `${unref(fullHeaderHeightRef)}px`;
59   - }
60   - return style;
61   - });
  65 + );
62 66  
63 67 const getIsFixed = computed(() => {
64 68 return unref(getFixed) || unref(getShowFullHeaderRef);
... ...
src/layouts/default/header/index.less
... ... @@ -20,7 +20,6 @@
20 20  
21 21 &__left {
22 22 display: flex;
23   - // flex-grow: 1;
24 23 align-items: center;
25 24  
26 25 .layout-trigger {
... ...
src/layouts/default/multitabs/index.less
... ... @@ -40,9 +40,12 @@
40 40 height: 12px;
41 41 font-size: 12px;
42 42 color: inherit;
  43 + visibility: hidden;
43 44 transition: none;
44 45  
45 46 &:hover {
  47 + visibility: visible;
  48 +
46 49 svg {
47 50 width: 0.8em;
48 51 }
... ... @@ -69,6 +72,10 @@
69 72 display: none;
70 73 }
71 74  
  75 + .ant-tabs-close-x {
  76 + visibility: visible;
  77 + }
  78 +
72 79 svg {
73 80 width: 0.7em;
74 81 fill: @white;
... ...