提交 6e03e050 编写于 作者: V vben

perf: perf context menu

上级 41d79008
import contextMenuVue from './src/index'; export { createContextMenu, destroyContextMenu } from './src/createContextMenu';
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 * from './src/types'; export * from './src/types';
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 = [];
}
};
@import (reference) '../../../design/index.less'; @import (reference) '../../../design/index.less';
@default-height: 42px !important;
@small-height: 36px !important;
@large-height: 36px !important;
.item-style() { .item-style() {
li { li {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
height: 46px !important; height: @default-height;
margin: 0 !important; margin: 0 !important;
line-height: 46px; line-height: @default-height;
span { span {
line-height: 46px; line-height: @default-height;
} }
> div { > div {
margin: 0 !important; margin: 0 !important;
} }
&:hover { &:not(.ant-menu-item-disabled):hover {
color: @text-color-base; color: @text-color-base;
background: #eee; background: #eee;
} }
...@@ -27,10 +33,9 @@ ...@@ -27,10 +33,9 @@
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
z-index: 1500; z-index: 200;
display: block; display: block;
width: 156px; width: 156px;
min-width: 10rem;
margin: 0; margin: 0;
list-style: none; list-style: none;
background-color: #fff; background-color: #fff;
......
import { import './index.less';
defineComponent,
nextTick, import type { ContextMenuItem, ItemContentProps } from './types';
onMounted, import type { FunctionalComponent, CSSProperties } from 'vue';
reactive,
computed, import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue';
ref,
unref,
onUnmounted,
} from 'vue';
import { props } from './props';
import Icon from '/@/components/Icon'; import Icon from '/@/components/Icon';
import { Menu, Divider } from 'ant-design-vue'; import { Menu, Divider } from 'ant-design-vue';
import type { ContextMenuItem } from './types'; import { props } from './props';
import './index.less';
const prefixCls = 'context-menu'; 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({ export default defineComponent({
name: 'ContextMenu', name: 'ContextMenu',
props, props,
setup(props) { setup(props) {
const wrapRef = ref<Nullable<HTMLDivElement>>(null); const wrapRef = ref<ElRef>(null);
const state = reactive({ const showRef = ref(false);
show: false,
});
onMounted(() => {
nextTick(() => {
state.show = true;
});
});
onUnmounted(() => { const getStyle = computed(
const el = unref(wrapRef); (): CSSProperties => {
el && document.body.removeChild(el);
});
const getStyle = computed(() => {
const { axis, items, styles, width } = props; const { axis, items, styles, width } = props;
const { x, y } = axis || { x: 0, y: 0 }; const { x, y } = axis || { x: 0, y: 0 };
const menuHeight = (items || []).length * 40; const menuHeight = (items || []).length * 40;
const menuWidth = width; const menuWidth = width;
const body = document.body; const body = document.body;
const left = body.clientWidth < x + menuWidth ? x - menuWidth : x;
const top = body.clientHeight < y + menuHeight ? y - menuHeight : y;
return { return {
...(styles as any), ...styles,
width: `${width}px`, width: `${width}px`,
left: (body.clientWidth < x + menuWidth ? x - menuWidth : x) + 'px', left: `${left + 1}px`,
top: (body.clientHeight < y + menuHeight ? y - menuHeight : y) + 'px', top: `${top + 1}px`,
}; };
}
);
onMounted(() => {
nextTick(() => (showRef.value = true));
});
onUnmounted(() => {
const el = unref(wrapRef);
el && document.body.removeChild(el);
}); });
function handleAction(item: ContextMenuItem, e: MouseEvent) { function handleAction(item: ContextMenuItem, e: MouseEvent) {
state.show = false;
const { handler, disabled } = item; const { handler, disabled } = item;
if (disabled) { if (disabled) return;
return; showRef.value = false;
}
if (e) {
e.stopPropagation();
e.preventDefault();
}
handler && handler(); e?.stopPropagation();
} e?.preventDefault();
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>
);
} }
function renderMenuItem(items: ContextMenuItem[]) { function renderMenuItem(items: ContextMenuItem[]) {
return items.map((item, index) => { return items.map((item) => {
const { disabled, label, children, divider = false } = 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) { if (!children || children.length === 0) {
return [ return (
<>
<Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}> <Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}>
{() => [renderContent(item)]} {() => [
</Menu.Item>, <ItemContent showIcon={props.showIcon} item={item} handler={handleAction} />,
DividerComp, ]}
]; </Menu.Item>
{DividerComp}
</>
);
} }
return !state.show ? null : ( if (!unref(showRef)) return null;
<Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup `}>
return (
<Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup`}>
{{ {{
title: () => renderContent(item), title: () => (
default: () => [renderMenuItem(children)], <ItemContent showIcon={props.showIcon} item={item} handler={handleAction} />
),
default: () => renderMenuItem(children),
}} }}
</Menu.SubMenu> </Menu.SubMenu>
); );
...@@ -101,11 +100,12 @@ export default defineComponent({ ...@@ -101,11 +100,12 @@ export default defineComponent({
} }
return () => { return () => {
const { items } = props; const { items } = props;
return !state.show ? null : ( if (!unref(showRef)) return null;
return (
<Menu <Menu
inlineIndent={12} inlineIndent={12}
mode="vertical" mode="vertical"
class={[prefixCls]} class={prefixCls}
ref={wrapRef} ref={wrapRef}
style={unref(getStyle)} style={unref(getStyle)}
> >
......
import type { PropType } from 'vue'; import type { PropType, CSSProperties } from 'vue';
import type { Axis, ContextMenuItem } from './types'; import type { Axis, ContextMenuItem } from './types';
export const props = { export const props = {
width: { width: {
type: Number as PropType<number>, type: Number as PropType<number>,
default: 180, default: 156,
}, },
customEvent: { customEvent: {
type: Object as PropType<Event>, type: Object as PropType<Event>,
default: null, default: null,
}, },
styles: { styles: {
type: Object as PropType<any>, type: Object as PropType<CSSProperties>,
default: null, default: null,
}, },
showIcon: { showIcon: {
...@@ -31,8 +31,4 @@ export const props = { ...@@ -31,8 +31,4 @@ export const props = {
return []; return [];
}, },
}, },
resolve: {
type: Function as PropType<any>,
default: null,
},
}; };
...@@ -11,15 +11,14 @@ export interface ContextMenuItem { ...@@ -11,15 +11,14 @@ export interface ContextMenuItem {
divider?: boolean; divider?: boolean;
children?: ContextMenuItem[]; children?: ContextMenuItem[];
} }
export interface Options { export interface CreateContextOptions {
event: MouseEvent; event: MouseEvent;
icon?: string; icon?: string;
styles?: any; styles?: any;
items?: ContextMenuItem[]; items?: ContextMenuItem[];
} }
export type Props = { export interface ContextMenuProps {
resolve?: (...arg: any) => void;
event?: MouseEvent; event?: MouseEvent;
styles?: any; styles?: any;
items: ContextMenuItem[]; items: ContextMenuItem[];
...@@ -27,4 +26,10 @@ export type Props = { ...@@ -27,4 +26,10 @@ export type Props = {
axis?: Axis; axis?: Axis;
width?: number; width?: number;
showIcon?: boolean; showIcon?: boolean;
}; }
export interface ItemContentProps {
showIcon: boolean;
item: ContextMenuItem;
handler: Fn;
}
...@@ -22,7 +22,7 @@ export default defineComponent({ ...@@ -22,7 +22,7 @@ export default defineComponent({
props: basicProps, props: basicProps,
emits: ['visible-change', 'ok', 'close', 'register'], emits: ['visible-change', 'ok', 'close', 'register'],
setup(props, { slots, emit, attrs }) { setup(props, { slots, emit, attrs }) {
const scrollRef = ref<any>(null); const scrollRef = ref<ElRef>(null);
const visibleRef = ref(false); const visibleRef = ref(false);
const propsRef = ref<Partial<DrawerProps> | null>(null); const propsRef = ref<Partial<DrawerProps> | null>(null);
......
...@@ -22,7 +22,7 @@ export default defineComponent({ ...@@ -22,7 +22,7 @@ export default defineComponent({
setup(props, { slots, emit, attrs }) { setup(props, { slots, emit, attrs }) {
const visibleRef = ref(false); const visibleRef = ref(false);
const propsRef = ref<Partial<ModalProps> | null>(null); const propsRef = ref<Partial<ModalProps> | null>(null);
const modalWrapperRef = ref<any>(null); const modalWrapperRef = ref<ComponentRef>(null);
// modal Bottom and top height // modal Bottom and top height
const extHeightRef = ref(0); const extHeightRef = ref(0);
// Unexpanded height of the popup // Unexpanded height of the popup
......
...@@ -55,7 +55,7 @@ export default defineComponent({ ...@@ -55,7 +55,7 @@ export default defineComponent({
emits: ['heightChange', 'getExtHeight'], emits: ['heightChange', 'getExtHeight'],
setup(props: ModalWrapperProps, { slots, emit }) { setup(props: ModalWrapperProps, { slots, emit }) {
const wrapperRef = ref<HTMLElement | null>(null); const wrapperRef = ref<HTMLElement | null>(null);
const spinRef = ref<any>(null); const spinRef = ref<ComponentRef>(null);
const realHeightRef = ref(0); const realHeightRef = ref(0);
// 重试次数 // 重试次数
// let tryCount = 0; // let tryCount = 0;
...@@ -126,6 +126,8 @@ export default defineComponent({ ...@@ -126,6 +126,8 @@ export default defineComponent({
await nextTick(); await nextTick();
const spinEl = unref(spinRef); const spinEl = unref(spinRef);
if (!spinEl) return;
const spinContainerEl = spinEl.$el.querySelector('.ant-spin-container') as HTMLElement; const spinContainerEl = spinEl.$el.querySelector('.ant-spin-container') as HTMLElement;
if (!spinContainerEl) return; if (!spinContainerEl) return;
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
components: { Table, BasicForm }, components: { Table, BasicForm },
emits: ['fetch-success', 'fetch-error', 'selection-change', 'register'], emits: ['fetch-success', 'fetch-error', 'selection-change', 'register'],
setup(props, { attrs, emit, slots }) { setup(props, { attrs, emit, slots }) {
const tableElRef = ref<any>(null); const tableElRef = ref<ComponentRef>(null);
const wrapRef = ref<Nullable<HTMLDivElement>>(null); const wrapRef = ref<Nullable<HTMLDivElement>>(null);
const innerPropsRef = ref<Partial<BasicTableProps>>(); const innerPropsRef = ref<Partial<BasicTableProps>>();
const [registerForm, { getFieldsValue }] = useForm(); const [registerForm, { getFieldsValue }] = useForm();
...@@ -241,10 +241,8 @@ ...@@ -241,10 +241,8 @@
if (unref(getMergeProps).showSummary) { if (unref(getMergeProps).showSummary) {
nextTick(() => { nextTick(() => {
const tableEl = unref(tableElRef); const tableEl = unref(tableElRef);
if (!tableEl) { if (!tableEl) return;
return; const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body');
}
const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body') as HTMLDivElement[];
const bodyDom = bodyDomList[0]; const bodyDom = bodyDomList[0];
useEventListener({ useEventListener({
el: bodyDom, el: bodyDom,
......
import { onUnmounted, getCurrentInstance } from 'vue'; import { onUnmounted, getCurrentInstance } from 'vue';
import { createContextMenu, unMountedContextMenu } from '/@/components/ContextMenu'; import { createContextMenu, destroyContextMenu } from '/@/components/ContextMenu';
import type { ContextMenuItem } from '/@/components/ContextMenu'; import type { ContextMenuItem } from '/@/components/ContextMenu';
export type { ContextMenuItem }; export type { ContextMenuItem };
export function useContextMenu(authRemove = true) { export function useContextMenu(authRemove = true) {
if (getCurrentInstance() && authRemove) { if (getCurrentInstance() && authRemove) {
onUnmounted(() => { onUnmounted(() => {
unMountedContextMenu(); destroyContextMenu();
}); });
} }
return [createContextMenu, unMountedContextMenu]; return [createContextMenu, destroyContextMenu];
} }
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
} }
> .basic-loading { > .basic-loading {
margin-bottom: 30%; margin-bottom: 15%;
} }
} }
} }
...@@ -60,10 +60,10 @@ export default defineComponent({ ...@@ -60,10 +60,10 @@ export default defineComponent({
}, },
}, },
setup(props) { setup(props) {
let logoEl: Element | null; let logoEl: Element | null | undefined;
const logoWidthRef = ref(200); const logoWidthRef = ref(200);
const logoRef = ref<any>(null); const logoRef = ref<ComponentRef>(null);
const { refreshPage } = useTabs(); const { refreshPage } = useTabs();
const { getShowTopMenu, getShowHeaderTrigger, getSplit, getTopMenuAlign } = useMenuSetting(); const { getShowTopMenu, getShowHeaderTrigger, getSplit, getTopMenuAlign } = useMenuSetting();
...@@ -91,7 +91,7 @@ export default defineComponent({ ...@@ -91,7 +91,7 @@ export default defineComponent({
if (!unref(getShowTopMenu)) return; if (!unref(getShowTopMenu)) return;
let width = 0; let width = 0;
if (!logoEl) { if (!logoEl) {
logoEl = logoRef.value.$el; logoEl = unref(logoRef)?.$el;
} else { } else {
width += logoEl.clientWidth; width += logoEl.clientWidth;
} }
......
...@@ -39,17 +39,20 @@ export default defineComponent({ ...@@ -39,17 +39,20 @@ export default defineComponent({
return unref(getShowMultipleTab) && !unref(getFullContent); return unref(getShowMultipleTab) && !unref(getFullContent);
}); });
const getPlaceholderDomStyle = computed(() => { const getPlaceholderDomStyle = computed(
(): CSSProperties => {
return { return {
height: `${unref(placeholderHeightRef)}px`, height: `${unref(placeholderHeightRef)}px`,
}; };
}); }
);
const getIsShowPlaceholderDom = computed(() => { const getIsShowPlaceholderDom = computed(() => {
return unref(getFixed) || unref(getShowFullHeaderRef); return unref(getFixed) || unref(getShowFullHeaderRef);
}); });
const getWrapStyle = computed(() => { const getWrapStyle = computed(
(): CSSProperties => {
const style: CSSProperties = {}; const style: CSSProperties = {};
if (unref(getFixed)) { if (unref(getFixed)) {
style.width = unref(getCalcContentWidth); style.width = unref(getCalcContentWidth);
...@@ -58,7 +61,8 @@ export default defineComponent({ ...@@ -58,7 +61,8 @@ export default defineComponent({
style.top = `${unref(fullHeaderHeightRef)}px`; style.top = `${unref(fullHeaderHeightRef)}px`;
} }
return style; return style;
}); }
);
const getIsFixed = computed(() => { const getIsFixed = computed(() => {
return unref(getFixed) || unref(getShowFullHeaderRef); return unref(getFixed) || unref(getShowFullHeaderRef);
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
&__left { &__left {
display: flex; display: flex;
// flex-grow: 1;
align-items: center; align-items: center;
.layout-trigger { .layout-trigger {
......
...@@ -40,9 +40,12 @@ ...@@ -40,9 +40,12 @@
height: 12px; height: 12px;
font-size: 12px; font-size: 12px;
color: inherit; color: inherit;
visibility: hidden;
transition: none; transition: none;
&:hover { &:hover {
visibility: visible;
svg { svg {
width: 0.8em; width: 0.8em;
} }
...@@ -69,6 +72,10 @@ ...@@ -69,6 +72,10 @@
display: none; display: none;
} }
.ant-tabs-close-x {
visibility: visible;
}
svg { svg {
width: 0.7em; width: 0.7em;
fill: @white; fill: @white;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册