提交 f645680a 编写于 作者: V vben

feat: right-click menu supports multiple levels

上级 c8021ef3
......@@ -3,6 +3,7 @@
### ✨ Features
- 全局 loading 添加文本
- 右键菜单支持多级
### 🎫 Chores
......@@ -13,7 +14,7 @@
- Layout 界面布局样式调整
- 优化表格渲染性能
- 表单折叠搜索添图标添加动画
- routeModule 可以忽略 layou 配置不写。方便配置一级菜单
- routeModule 可以忽略 layout 配置不写。方便配置一级菜单
### 🐛 Bug Fixes
......
@import (reference) '../../../design/index.less';
.item-style() {
li {
display: inline-block;
width: 100%;
height: 46px !important;
margin: 0 !important;
line-height: 46px;
span {
line-height: 46px;
}
> div {
margin: 0 !important;
}
&:hover {
color: @text-color-base;
background: #eee;
}
}
}
.context-menu {
position: fixed;
top: 0;
......@@ -18,32 +41,17 @@
background-clip: padding-box;
user-select: none;
&.hidden {
display: none !important;
}
.item-style();
&__item {
a {
display: inline-block;
width: 100%;
padding: 10px 14px;
.ant-divider {
margin: 0 0;
}
&:hover {
color: @text-color-base;
background: #eee;
}
&__popup {
.ant-divider {
margin: 0 0;
}
&.disabled {
a {
color: @disabled-color;
cursor: not-allowed;
&:hover {
color: @disabled-color;
background: unset;
}
}
}
.item-style();
}
}
......@@ -8,9 +8,13 @@ import {
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 './index.less';
const prefixCls = 'context-menu';
export default defineComponent({
......@@ -43,12 +47,13 @@ export default defineComponent({
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;
}
state.show = false;
if (e) {
e.stopPropagation();
e.preventDefault();
......@@ -61,31 +66,47 @@ export default defineComponent({
const { showIcon } = props;
return (
<span style="display: inline-block; width: 100%;">
<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[]) {
return items.map((item) => {
const { disabled, label } = item;
return items.map((item, index) => {
const { disabled, label, children, divider = false } = item;
return (
<li class={`${prefixCls}__item ${disabled ? 'disabled' : ''}`} key={label}>
<a onClick={handleAction.bind(null, item)} style="color:#333;">
{renderContent(item)}
</a>
</li>
const DividerComp = divider ? <Divider key={`d-${index}`} /> : null;
if (!children || children.length === 0) {
return [
<Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}>
{() => [renderContent(item)]}
</Menu.Item>,
DividerComp,
];
}
return !state.show ? null : (
<Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup `}>
{{
title: () => renderContent(item),
default: () => [renderMenuItem(children)],
}}
</Menu.SubMenu>
);
});
}
return () => {
const { items } = props;
return (
<ul class={[prefixCls, !state.show && 'hidden']} ref={wrapRef} style={unref(getStyle)}>
{renderMenuItem(items)}
</ul>
return !state.show ? null : (
<Menu
inlineIndent={12}
mode="vertical"
class={[prefixCls]}
ref={wrapRef}
style={unref(getStyle)}
>
{() => renderMenuItem(items)}
</Menu>
);
};
},
......
......@@ -23,6 +23,7 @@ export default defineComponent({
...unref(propsRef),
};
});
const getProps = computed(() => {
const opt = {
...props,
......@@ -31,12 +32,14 @@ export default defineComponent({
};
return opt;
});
/**
* @description: 是否使用标题
*/
const useWrapper = computed(() => {
return !!unref(getMergeProps).title;
});
/**
* @description: 获取配置Collapse
*/
......@@ -49,6 +52,7 @@ export default defineComponent({
};
}
);
/**
* @description:设置desc
*/
......@@ -57,9 +61,11 @@ export default defineComponent({
const mergeProps = deepMerge(unref(propsRef) || {}, descProps);
propsRef.value = cloneDeep(mergeProps);
}
const methods: DescInstance = {
setDescProps,
};
emit('register', methods);
// 防止换行
......@@ -95,6 +101,7 @@ export default defineComponent({
const width = contentMinWidth;
return (
// @ts-ignore
<Descriptions.Item label={renderLabel(item)} key={field} span={span}>
{() =>
contentMinWidth ? (
......@@ -113,13 +120,15 @@ export default defineComponent({
);
});
}
const renderDesc = () => {
return (
<Descriptions class={`${prefixCls}`} {...{ ...attrs, ...unref(getProps) }}>
<Descriptions class={`${prefixCls}`} {...{ ...attrs, ...(unref(getProps) as any) }}>
{() => renderItem()}
</Descriptions>
);
};
const renderContainer = () => {
const content = props.useCollapse ? renderDesc() : <div>{renderDesc()}</div>;
// 减少dom层级
......
......@@ -10,7 +10,7 @@ export function useDescription(props?: Partial<DescOptions>): UseDescReturnType
const descRef = ref<DescInstance | null>(null);
const loadedRef = ref(false);
function getDescription(instance: DescInstance) {
function register(instance: DescInstance) {
if (unref(loadedRef) && isProdMode()) {
return;
}
......@@ -18,10 +18,11 @@ export function useDescription(props?: Partial<DescOptions>): UseDescReturnType
props && instance.setDescProps(props);
loadedRef.value = true;
}
const methods: DescInstance = {
setDescProps: (descProps: Partial<DescOptions>): void => {
unref(descRef)!.setDescProps(descProps);
},
};
return [getDescription, methods];
return [register, methods];
}
......@@ -32,6 +32,7 @@ export default defineComponent({
const { icon, prefix } = props;
return `${prefix ? prefix + ':' : ''}${icon}`;
});
const update = async () => {
const el = unref(elRef);
if (el) {
......@@ -67,6 +68,7 @@ export default defineComponent({
});
watch(() => props.icon, update, { flush: 'post' });
onMounted(update);
return () => (
......
......@@ -55,6 +55,7 @@ export default defineComponent({
}
return menuState.openKeys;
});
// menu外层样式
const getMenuWrapStyle = computed((): any => {
const { showLogo, search } = props;
......@@ -130,6 +131,7 @@ export default defineComponent({
menuState.selectedKeys = [path];
emit('menuClick', menu);
}
function handleMenuChange() {
const { flatItems } = props;
if (!unref(flatItems) || flatItems.length === 0) {
......
......@@ -48,9 +48,11 @@ export function useSearchInput({
openKeys = es6Unique(openKeys);
menuState.openKeys = openKeys;
}
// 搜索框点击
function handleInputClick(e: any): void {
emit('clickSearchInput', e);
}
return { handleInputChange, handleInputClick };
}
......@@ -219,6 +219,7 @@ export default defineComponent({
</div>
);
};
const renderIndex = () => {
if (!unref(getIsMultipleImage)) {
return null;
......
......@@ -3,6 +3,7 @@ import { getCurrentInstance, onBeforeUnmount, ref, Ref, unref } from 'vue';
const domSymbol = Symbol('watermark-dom');
export function useWatermark(appendEl: Ref<HTMLElement | null> = ref(document.body)) {
let func: Fn = () => {};
const id = domSymbol.toString();
const clear = () => {
const domId = document.getElementById(id);
......@@ -10,6 +11,7 @@ export function useWatermark(appendEl: Ref<HTMLElement | null> = ref(document.bo
const el = unref(appendEl);
el && el.removeChild(domId);
}
window.addEventListener('resize', func);
};
const createWatermark = (str: string) => {
clear();
......@@ -45,7 +47,7 @@ export function useWatermark(appendEl: Ref<HTMLElement | null> = ref(document.bo
function setWatermark(str: string) {
createWatermark(str);
const func = () => {
func = () => {
createWatermark(str);
};
window.addEventListener('resize', func);
......@@ -53,7 +55,6 @@ export function useWatermark(appendEl: Ref<HTMLElement | null> = ref(document.bo
if (instance) {
onBeforeUnmount(() => {
clear();
window.addEventListener('resize', func);
});
}
}
......
......@@ -18,7 +18,7 @@ export default {
{
path: '/icon',
name: 'IconDemo',
component: () => import('/@/views/demo/comp/icon/index.vue'),
component: () => import('/@/views/demo/feat/icon/index.vue'),
meta: {
title: '图标',
},
......@@ -43,7 +43,7 @@ export default {
{
path: '/click-out-side',
name: 'ClickOutSideDemo',
component: () => import('/@/views/demo/comp/click-out-side/index.vue'),
component: () => import('/@/views/demo/feat/click-out-side/index.vue'),
meta: {
title: 'ClickOutSide组件',
},
......
......@@ -7,8 +7,6 @@
show-icon
/>
<Alert message="按钮扩展" type="info" show-icon class="mt-4" />
<div class="my-2">
<h3>success</h3>
<a-button color="success">成功</a-button>
......
<template>
<div class="px-10">
<Alert message="点内外部触发事件" show-icon class="mt-4"></Alert>
<div class="p-10">
<Alert message="点内外部触发事件" show-icon></Alert>
<ClickOutSide @clickOutside="handleClickOutside" class="flex justify-center mt-10">
<div @click="innerClick" class="demo-box">
{{ text }}
......
......@@ -3,6 +3,10 @@
<CollapseContainer title="Simple">
<a-button type="primary" @contextmenu="handleContext">Right Click on me</a-button>
</CollapseContainer>
<CollapseContainer title="Multiple" class="mt-4">
<a-button type="primary" @contextmenu="handleMultipleContext">Right Click on me</a-button>
</CollapseContainer>
</div>
</template>
<script lang="ts">
......@@ -36,7 +40,44 @@
],
});
}
return { handleContext };
function handleMultipleContext(e: MouseEvent) {
createContextMenu({
event: e,
items: [
{
label: 'New',
icon: 'ant-design:plus-outlined',
children: [
{
label: 'New1-1',
icon: 'ant-design:plus-outlined',
divider: true,
children: [
{
label: 'New1-1-1',
handler: () => {
createMessage.success('click new');
},
},
{
label: 'New1-2-1',
disabled: true,
},
],
},
{
label: 'New1-2',
icon: 'ant-design:plus-outlined',
},
],
},
],
});
}
return { handleContext, handleMultipleContext };
},
});
</script>
......@@ -12,7 +12,7 @@
</div>
</CollapseContainer>
<CollapseContainer title="IconIfy 组件使用" class="mt-5">
<CollapseContainer title="IconIfy 组件使用" class="my-5">
<div class="flex justify-around flex-wrap">
<Icon icon="fa-solid:address-book" :size="30" />
<Icon icon="mdi-light:bank" :size="30" />
......@@ -23,7 +23,6 @@
<Alert
show-icon
class="mt-5"
message="推荐使用Iconify组件"
description="Icon组件基本包含所有的图标,在下面网址内你可以查询到你想要的任何图标。并且打包只会打包所用到的图标。唯一不足的可能就是需要连接外网进行使用。"
/>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册