Browse Source

perf: perf context menu

vben 4 years ago
parent
commit
6e03e05032

+ 1 - 61
src/components/ContextMenu/index.ts

@@ -1,62 +1,2 @@
-import contextMenuVue from './src/index';
-import { isClient } from '/@/utils/is';
-import { Options, Props } from './src/types';
-import { createVNode, render } from 'vue';
-const menuManager: {
-  domList: Element[];
-  resolve: Fn;
-} = {
-  domList: [],
-  resolve: () => {},
-};
-export const createContextMenu = function (options: Options) {
-  const { event } = options || {};
-  try {
-    event.preventDefault();
-  } catch (e) {
-    console.log(e);
-  }
-
-  if (!isClient) return;
-  return new Promise((resolve) => {
-    const container = document.createElement('div');
-    const propsData: Partial<Props> = {};
-    if (options.styles !== undefined) propsData.styles = options.styles;
-    if (options.items !== undefined) propsData.items = options.items;
-    if (options.event !== undefined) {
-      propsData.customEvent = event;
-      propsData.axis = { x: event.clientX, y: event.clientY };
-    }
-    const vm = createVNode(contextMenuVue, propsData);
-    render(vm, container);
-    const bodyClick = function () {
-      menuManager.resolve('');
-    };
-    menuManager.domList.push(container);
-    const remove = function () {
-      menuManager.domList.forEach((dom: Element) => {
-        try {
-          document.body.removeChild(dom);
-        } catch (error) {}
-      });
-      document.body.removeEventListener('click', bodyClick);
-      document.body.removeEventListener('scroll', bodyClick);
-    };
-    menuManager.resolve = function (...arg: any) {
-      resolve(arg[0]);
-      remove();
-    };
-    remove();
-    document.body.appendChild(container);
-    document.body.addEventListener('click', bodyClick);
-    document.body.addEventListener('scroll', bodyClick);
-  });
-};
-export const unMountedContextMenu = function () {
-  if (menuManager) {
-    menuManager.resolve('');
-    menuManager.domList = [];
-  }
-};
-
+export { createContextMenu, destroyContextMenu } from './src/createContextMenu';
 export * from './src/types';

+ 73 - 0
src/components/ContextMenu/src/createContextMenu.ts

@@ -0,0 +1,73 @@
+import contextMenuVue from './index';
+import { isClient } from '/@/utils/is';
+import { CreateContextOptions, ContextMenuProps } from './types';
+import { createVNode, render } from 'vue';
+
+const menuManager: {
+  domList: Element[];
+  resolve: Fn;
+} = {
+  domList: [],
+  resolve: () => {},
+};
+
+export const createContextMenu = function (options: CreateContextOptions) {
+  const { event } = options || {};
+
+  event && event?.preventDefault();
+
+  if (!isClient) return;
+  return new Promise((resolve) => {
+    const body = document.body;
+
+    const container = document.createElement('div');
+    const propsData: Partial<ContextMenuProps> = {};
+    if (options.styles) {
+      propsData.styles = options.styles;
+    }
+
+    if (options.items) {
+      propsData.items = options.items;
+    }
+
+    if (options.event) {
+      propsData.customEvent = event;
+      propsData.axis = { x: event.clientX, y: event.clientY };
+    }
+
+    const vm = createVNode(contextMenuVue, propsData);
+    render(vm, container);
+
+    const handleClick = function () {
+      menuManager.resolve('');
+    };
+
+    menuManager.domList.push(container);
+
+    const remove = function () {
+      menuManager.domList.forEach((dom: Element) => {
+        try {
+          dom && body.removeChild(dom);
+        } catch (error) {}
+      });
+      body.removeEventListener('click', handleClick);
+      body.removeEventListener('scroll', handleClick);
+    };
+
+    menuManager.resolve = function (...arg: any) {
+      remove();
+      resolve(arg[0]);
+    };
+    remove();
+    body.appendChild(container);
+    body.addEventListener('click', handleClick);
+    body.addEventListener('scroll', handleClick);
+  });
+};
+
+export const destroyContextMenu = function () {
+  if (menuManager) {
+    menuManager.resolve('');
+    menuManager.domList = [];
+  }
+};

+ 11 - 6
src/components/ContextMenu/src/index.less

@@ -1,22 +1,28 @@
 @import (reference) '../../../design/index.less';
 
+@default-height: 42px !important;
+
+@small-height: 36px !important;
+
+@large-height: 36px !important;
+
 .item-style() {
   li {
     display: inline-block;
     width: 100%;
-    height: 46px !important;
+    height: @default-height;
     margin: 0 !important;
-    line-height: 46px;
+    line-height: @default-height;
 
     span {
-      line-height: 46px;
+      line-height: @default-height;
     }
 
     > div {
       margin: 0 !important;
     }
 
-    &:hover {
+    &:not(.ant-menu-item-disabled):hover {
       color: @text-color-base;
       background: #eee;
     }
@@ -27,10 +33,9 @@
   position: fixed;
   top: 0;
   left: 0;
-  z-index: 1500;
+  z-index: 200;
   display: block;
   width: 156px;
-  min-width: 10rem;
   margin: 0;
   list-style: none;
   background-color: #fff;

+ 68 - 68
src/components/ContextMenu/src/index.tsx

@@ -1,99 +1,98 @@
-import {
-  defineComponent,
-  nextTick,
-  onMounted,
-  reactive,
-  computed,
-  ref,
-  unref,
-  onUnmounted,
-} from 'vue';
+import './index.less';
+
+import type { ContextMenuItem, ItemContentProps } from './types';
+import type { FunctionalComponent, CSSProperties } from 'vue';
+
+import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue';
 
-import { props } from './props';
 import Icon from '/@/components/Icon';
 import { Menu, Divider } from 'ant-design-vue';
 
-import type { ContextMenuItem } from './types';
+import { props } from './props';
 
-import './index.less';
 const prefixCls = 'context-menu';
+
+const ItemContent: FunctionalComponent<ItemContentProps> = (props) => {
+  const { item } = props;
+  return (
+    <span style="display: inline-block; width: 100%;" onClick={props.handler.bind(null, item)}>
+      {props.showIcon && item.icon && <Icon class="mr-2" icon={item.icon} />}
+      <span>{item.label}</span>
+    </span>
+  );
+};
+
 export default defineComponent({
   name: 'ContextMenu',
   props,
   setup(props) {
-    const wrapRef = ref<Nullable<HTMLDivElement>>(null);
-    const state = reactive({
-      show: false,
-    });
+    const wrapRef = ref<ElRef>(null);
+    const showRef = ref(false);
+
+    const getStyle = computed(
+      (): CSSProperties => {
+        const { axis, items, styles, width } = props;
+        const { x, y } = axis || { x: 0, y: 0 };
+        const menuHeight = (items || []).length * 40;
+        const menuWidth = width;
+        const body = document.body;
+
+        const left = body.clientWidth < x + menuWidth ? x - menuWidth : x;
+        const top = body.clientHeight < y + menuHeight ? y - menuHeight : y;
+        return {
+          ...styles,
+          width: `${width}px`,
+          left: `${left + 1}px`,
+          top: `${top + 1}px`,
+        };
+      }
+    );
 
     onMounted(() => {
-      nextTick(() => {
-        state.show = true;
-      });
+      nextTick(() => (showRef.value = true));
     });
 
     onUnmounted(() => {
       const el = unref(wrapRef);
       el && document.body.removeChild(el);
     });
-    const getStyle = computed(() => {
-      const { axis, items, styles, width } = props;
-      const { x, y } = axis || { x: 0, y: 0 };
-      const menuHeight = (items || []).length * 40;
-      const menuWidth = width;
-      const body = document.body;
-      return {
-        ...(styles as any),
-        width: `${width}px`,
-        left: (body.clientWidth < x + menuWidth ? x - menuWidth : x) + 'px',
-        top: (body.clientHeight < y + menuHeight ? y - menuHeight : y) + 'px',
-      };
-    });
 
     function handleAction(item: ContextMenuItem, e: MouseEvent) {
-      state.show = false;
       const { handler, disabled } = item;
-      if (disabled) {
-        return;
-      }
-      if (e) {
-        e.stopPropagation();
-        e.preventDefault();
-      }
+      if (disabled) return;
+      showRef.value = false;
 
-      handler && handler();
-    }
-
-    function renderContent(item: ContextMenuItem) {
-      const { icon, label } = item;
-
-      const { showIcon } = props;
-      return (
-        <span style="display: inline-block; width: 100%;" onClick={handleAction.bind(null, item)}>
-          {showIcon && icon && <Icon class="mr-2" icon={icon} />}
-          <span>{label}</span>
-        </span>
-      );
+      e?.stopPropagation();
+      e?.preventDefault();
+      handler?.();
     }
 
     function renderMenuItem(items: ContextMenuItem[]) {
-      return items.map((item, index) => {
+      return items.map((item) => {
         const { disabled, label, children, divider = false } = item;
 
-        const DividerComp = divider ? <Divider key={`d-${index}`} /> : null;
+        const DividerComp = divider ? <Divider key={`d-${label}`} /> : null;
         if (!children || children.length === 0) {
-          return [
-            <Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}>
-              {() => [renderContent(item)]}
-            </Menu.Item>,
-            DividerComp,
-          ];
+          return (
+            <>
+              <Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}>
+                {() => [
+                  <ItemContent showIcon={props.showIcon} item={item} handler={handleAction} />,
+                ]}
+              </Menu.Item>
+              {DividerComp}
+            </>
+          );
         }
-        return !state.show ? null : (
-          <Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup `}>
+        if (!unref(showRef)) return null;
+
+        return (
+          <Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup`}>
             {{
-              title: () => renderContent(item),
-              default: () => [renderMenuItem(children)],
+              title: () => (
+                <ItemContent showIcon={props.showIcon} item={item} handler={handleAction} />
+              ),
+              default: () => renderMenuItem(children),
             }}
           </Menu.SubMenu>
         );
@@ -101,11 +100,12 @@ export default defineComponent({
     }
     return () => {
       const { items } = props;
-      return !state.show ? null : (
+      if (!unref(showRef)) return null;
+      return (
         <Menu
           inlineIndent={12}
           mode="vertical"
-          class={[prefixCls]}
+          class={prefixCls}
           ref={wrapRef}
           style={unref(getStyle)}
         >

+ 3 - 7
src/components/ContextMenu/src/props.ts

@@ -1,16 +1,16 @@
-import type { PropType } from 'vue';
+import type { PropType, CSSProperties } from 'vue';
 import type { Axis, ContextMenuItem } from './types';
 export const props = {
   width: {
     type: Number as PropType<number>,
-    default: 180,
+    default: 156,
   },
   customEvent: {
     type: Object as PropType<Event>,
     default: null,
   },
   styles: {
-    type: Object as PropType<any>,
+    type: Object as PropType<CSSProperties>,
     default: null,
   },
   showIcon: {
@@ -31,8 +31,4 @@ export const props = {
       return [];
     },
   },
-  resolve: {
-    type: Function as PropType<any>,
-    default: null,
-  },
 };

+ 9 - 4
src/components/ContextMenu/src/types.ts

@@ -11,15 +11,14 @@ export interface ContextMenuItem {
   divider?: boolean;
   children?: ContextMenuItem[];
 }
-export interface Options {
+export interface CreateContextOptions {
   event: MouseEvent;
   icon?: string;
   styles?: any;
   items?: ContextMenuItem[];
 }
 
-export type Props = {
-  resolve?: (...arg: any) => void;
+export interface ContextMenuProps {
   event?: MouseEvent;
   styles?: any;
   items: ContextMenuItem[];
@@ -27,4 +26,10 @@ export type Props = {
   axis?: Axis;
   width?: number;
   showIcon?: boolean;
-};
+}
+
+export interface ItemContentProps {
+  showIcon: boolean;
+  item: ContextMenuItem;
+  handler: Fn;
+}

+ 1 - 1
src/components/Drawer/src/BasicDrawer.tsx

@@ -22,7 +22,7 @@ export default defineComponent({
   props: basicProps,
   emits: ['visible-change', 'ok', 'close', 'register'],
   setup(props, { slots, emit, attrs }) {
-    const scrollRef = ref<any>(null);
+    const scrollRef = ref<ElRef>(null);
 
     const visibleRef = ref(false);
     const propsRef = ref<Partial<DrawerProps> | null>(null);

+ 1 - 1
src/components/Modal/src/BasicModal.tsx

@@ -22,7 +22,7 @@ export default defineComponent({
   setup(props, { slots, emit, attrs }) {
     const visibleRef = ref(false);
     const propsRef = ref<Partial<ModalProps> | null>(null);
-    const modalWrapperRef = ref<any>(null);
+    const modalWrapperRef = ref<ComponentRef>(null);
     // modal   Bottom and top height
     const extHeightRef = ref(0);
     // Unexpanded height of the popup

+ 3 - 1
src/components/Modal/src/ModalWrapper.tsx

@@ -55,7 +55,7 @@ export default defineComponent({
   emits: ['heightChange', 'getExtHeight'],
   setup(props: ModalWrapperProps, { slots, emit }) {
     const wrapperRef = ref<HTMLElement | null>(null);
-    const spinRef = ref<any>(null);
+    const spinRef = ref<ComponentRef>(null);
     const realHeightRef = ref(0);
     // 重试次数
     // let tryCount = 0;
@@ -126,6 +126,8 @@ export default defineComponent({
         await nextTick();
         const spinEl = unref(spinRef);
 
+        if (!spinEl) return;
+
         const spinContainerEl = spinEl.$el.querySelector('.ant-spin-container') as HTMLElement;
         if (!spinContainerEl) return;
 

+ 3 - 5
src/components/Table/src/BasicTable.vue

@@ -74,7 +74,7 @@
     components: { Table, BasicForm },
     emits: ['fetch-success', 'fetch-error', 'selection-change', 'register'],
     setup(props, { attrs, emit, slots }) {
-      const tableElRef = ref<any>(null);
+      const tableElRef = ref<ComponentRef>(null);
       const wrapRef = ref<Nullable<HTMLDivElement>>(null);
       const innerPropsRef = ref<Partial<BasicTableProps>>();
       const [registerForm, { getFieldsValue }] = useForm();
@@ -241,10 +241,8 @@
         if (unref(getMergeProps).showSummary) {
           nextTick(() => {
             const tableEl = unref(tableElRef);
-            if (!tableEl) {
-              return;
-            }
-            const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body') as HTMLDivElement[];
+            if (!tableEl) return;
+            const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body');
             const bodyDom = bodyDomList[0];
             useEventListener({
               el: bodyDom,

+ 3 - 3
src/hooks/web/useContextMenu.ts

@@ -1,12 +1,12 @@
 import { onUnmounted, getCurrentInstance } from 'vue';
-import { createContextMenu, unMountedContextMenu } from '/@/components/ContextMenu';
+import { createContextMenu, destroyContextMenu } from '/@/components/ContextMenu';
 import type { ContextMenuItem } from '/@/components/ContextMenu';
 export type { ContextMenuItem };
 export function useContextMenu(authRemove = true) {
   if (getCurrentInstance() && authRemove) {
     onUnmounted(() => {
-      unMountedContextMenu();
+      destroyContextMenu();
     });
   }
-  return [createContextMenu, unMountedContextMenu];
+  return [createContextMenu, destroyContextMenu];
 }

+ 1 - 1
src/layouts/default/content/index.less

@@ -19,7 +19,7 @@
     }
 
     > .basic-loading {
-      margin-bottom: 30%;
+      margin-bottom: 15%;
     }
   }
 }

+ 3 - 3
src/layouts/default/header/LayoutHeader.tsx

@@ -60,10 +60,10 @@ export default defineComponent({
     },
   },
   setup(props) {
-    let logoEl: Element | null;
+    let logoEl: Element | null | undefined;
 
     const logoWidthRef = ref(200);
-    const logoRef = ref<any>(null);
+    const logoRef = ref<ComponentRef>(null);
     const { refreshPage } = useTabs();
 
     const { getShowTopMenu, getShowHeaderTrigger, getSplit, getTopMenuAlign } = useMenuSetting();
@@ -91,7 +91,7 @@ export default defineComponent({
           if (!unref(getShowTopMenu)) return;
           let width = 0;
           if (!logoEl) {
-            logoEl = logoRef.value.$el;
+            logoEl = unref(logoRef)?.$el;
           } else {
             width += logoEl.clientWidth;
           }

+ 18 - 14
src/layouts/default/header/LayoutMultipleHeader.tsx

@@ -39,26 +39,30 @@ export default defineComponent({
       return unref(getShowMultipleTab) && !unref(getFullContent);
     });
 
-    const getPlaceholderDomStyle = computed(() => {
-      return {
-        height: `${unref(placeholderHeightRef)}px`,
-      };
-    });
+    const getPlaceholderDomStyle = computed(
+      (): CSSProperties => {
+        return {
+          height: `${unref(placeholderHeightRef)}px`,
+        };
+      }
+    );
 
     const getIsShowPlaceholderDom = computed(() => {
       return unref(getFixed) || unref(getShowFullHeaderRef);
     });
 
-    const getWrapStyle = computed(() => {
-      const style: CSSProperties = {};
-      if (unref(getFixed)) {
-        style.width = unref(getCalcContentWidth);
+    const getWrapStyle = computed(
+      (): CSSProperties => {
+        const style: CSSProperties = {};
+        if (unref(getFixed)) {
+          style.width = unref(getCalcContentWidth);
+        }
+        if (unref(getShowFullHeaderRef)) {
+          style.top = `${unref(fullHeaderHeightRef)}px`;
+        }
+        return style;
       }
-      if (unref(getShowFullHeaderRef)) {
-        style.top = `${unref(fullHeaderHeightRef)}px`;
-      }
-      return style;
-    });
+    );
 
     const getIsFixed = computed(() => {
       return unref(getFixed) || unref(getShowFullHeaderRef);

+ 0 - 1
src/layouts/default/header/index.less

@@ -20,7 +20,6 @@
 
   &__left {
     display: flex;
-    // flex-grow: 1;
     align-items: center;
 
     .layout-trigger {

+ 7 - 0
src/layouts/default/multitabs/index.less

@@ -40,9 +40,12 @@
           height: 12px;
           font-size: 12px;
           color: inherit;
+          visibility: hidden;
           transition: none;
 
           &:hover {
+            visibility: visible;
+
             svg {
               width: 0.8em;
             }
@@ -69,6 +72,10 @@
           display: none;
         }
 
+        .ant-tabs-close-x {
+          visibility: visible;
+        }
+
         svg {
           width: 0.7em;
           fill: @white;