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

perf: perf context menu

上级 41d79008
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';
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';
@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;
......
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)}
>
......
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,
},
};
......@@ -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;
}
......@@ -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);
......
......@@ -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
......
......@@ -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;
......
......@@ -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,
......
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];
}
......@@ -19,7 +19,7 @@
}
> .basic-loading {
margin-bottom: 30%;
margin-bottom: 15%;
}
}
}
......@@ -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;
}
......
......@@ -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);
......
......@@ -20,7 +20,6 @@
&__left {
display: flex;
// flex-grow: 1;
align-items: center;
.layout-trigger {
......
......@@ -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;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册