From f645680a3b9a1f75395329970551d9e5d6bd845b Mon Sep 17 00:00:00 2001 From: vben Date: Sun, 1 Nov 2020 17:28:38 +0800 Subject: [PATCH] feat: right-click menu supports multiple levels --- CHANGELOG.zh_CN.md | 3 +- src/components/ContextMenu/src/index.less | 54 +++++++++++-------- src/components/ContextMenu/src/index.tsx | 49 ++++++++++++----- src/components/Description/src/index.tsx | 11 +++- .../Description/src/useDescription.ts | 5 +- src/components/Icon/index.tsx | 2 + src/components/Menu/src/BasicMenu.tsx | 2 + src/components/Menu/src/useSearchInput.ts | 2 + src/components/Preview/src/index.tsx | 1 + src/hooks/web/useWatermark.ts | 5 +- src/router/routes/modules/demo/feat.ts | 4 +- src/views/demo/comp/button/index.vue | 2 - .../{comp => feat}/click-out-side/index.vue | 4 +- src/views/demo/feat/context-menu/index.vue | 43 ++++++++++++++- src/views/demo/{comp => feat}/icon/index.vue | 3 +- 15 files changed, 138 insertions(+), 52 deletions(-) rename src/views/demo/{comp => feat}/click-out-side/index.vue (90%) rename src/views/demo/{comp => feat}/icon/index.vue (96%) diff --git a/CHANGELOG.zh_CN.md b/CHANGELOG.zh_CN.md index d10f7e2724d..8769ed2c4f7 100644 --- a/CHANGELOG.zh_CN.md +++ b/CHANGELOG.zh_CN.md @@ -3,6 +3,7 @@ ### ✨ Features - 全局 loading 添加文本 +- 右键菜单支持多级 ### 🎫 Chores @@ -13,7 +14,7 @@ - Layout 界面布局样式调整 - 优化表格渲染性能 - 表单折叠搜索添图标添加动画 -- routeModule 可以忽略 layou 配置不写。方便配置一级菜单 +- routeModule 可以忽略 layout 配置不写。方便配置一级菜单 ### 🐛 Bug Fixes diff --git a/src/components/ContextMenu/src/index.less b/src/components/ContextMenu/src/index.less index acda218e77e..30c5774d383 100644 --- a/src/components/ContextMenu/src/index.less +++ b/src/components/ContextMenu/src/index.less @@ -1,5 +1,28 @@ @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(); } } diff --git a/src/components/ContextMenu/src/index.tsx b/src/components/ContextMenu/src/index.tsx index ba9f8df6ff0..4e6413c7233 100644 --- a/src/components/ContextMenu/src/index.tsx +++ b/src/components/ContextMenu/src/index.tsx @@ -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 ( - + {showIcon && icon && } {label} ); } 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 ( -
  • - - {renderContent(item)} - -
  • + const DividerComp = divider ? : null; + if (!children || children.length === 0) { + return [ + + {() => [renderContent(item)]} + , + DividerComp, + ]; + } + return !state.show ? null : ( + + {{ + title: () => renderContent(item), + default: () => [renderMenuItem(children)], + }} + ); }); } return () => { const { items } = props; - return ( -
      - {renderMenuItem(items)} -
    + return !state.show ? null : ( + + {() => renderMenuItem(items)} + ); }; }, diff --git a/src/components/Description/src/index.tsx b/src/components/Description/src/index.tsx index 84803d7e556..e49bd351f76 100644 --- a/src/components/Description/src/index.tsx +++ b/src/components/Description/src/index.tsx @@ -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 {() => contentMinWidth ? ( @@ -113,13 +120,15 @@ export default defineComponent({ ); }); } + const renderDesc = () => { return ( - + {() => renderItem()} ); }; + const renderContainer = () => { const content = props.useCollapse ? renderDesc() :
    {renderDesc()}
    ; // 减少dom层级 diff --git a/src/components/Description/src/useDescription.ts b/src/components/Description/src/useDescription.ts index ddf3512c30c..590c5ce4bbe 100644 --- a/src/components/Description/src/useDescription.ts +++ b/src/components/Description/src/useDescription.ts @@ -10,7 +10,7 @@ export function useDescription(props?: Partial): UseDescReturnType const descRef = ref(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): UseDescReturnType props && instance.setDescProps(props); loadedRef.value = true; } + const methods: DescInstance = { setDescProps: (descProps: Partial): void => { unref(descRef)!.setDescProps(descProps); }, }; - return [getDescription, methods]; + return [register, methods]; } diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx index 500c4a09da3..e684435454f 100644 --- a/src/components/Icon/index.tsx +++ b/src/components/Icon/index.tsx @@ -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 () => ( diff --git a/src/components/Menu/src/BasicMenu.tsx b/src/components/Menu/src/BasicMenu.tsx index 3d9a2663cc2..dee140b555c 100644 --- a/src/components/Menu/src/BasicMenu.tsx +++ b/src/components/Menu/src/BasicMenu.tsx @@ -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) { diff --git a/src/components/Menu/src/useSearchInput.ts b/src/components/Menu/src/useSearchInput.ts index 248f775dddc..1b39c4ff977 100644 --- a/src/components/Menu/src/useSearchInput.ts +++ b/src/components/Menu/src/useSearchInput.ts @@ -48,9 +48,11 @@ export function useSearchInput({ openKeys = es6Unique(openKeys); menuState.openKeys = openKeys; } + // 搜索框点击 function handleInputClick(e: any): void { emit('clickSearchInput', e); } + return { handleInputChange, handleInputClick }; } diff --git a/src/components/Preview/src/index.tsx b/src/components/Preview/src/index.tsx index 2e05a1be3cf..bcca1972988 100644 --- a/src/components/Preview/src/index.tsx +++ b/src/components/Preview/src/index.tsx @@ -219,6 +219,7 @@ export default defineComponent({ ); }; + const renderIndex = () => { if (!unref(getIsMultipleImage)) { return null; diff --git a/src/hooks/web/useWatermark.ts b/src/hooks/web/useWatermark.ts index e641cf8e589..700d93f6607 100644 --- a/src/hooks/web/useWatermark.ts +++ b/src/hooks/web/useWatermark.ts @@ -3,6 +3,7 @@ import { getCurrentInstance, onBeforeUnmount, ref, Ref, unref } from 'vue'; const domSymbol = Symbol('watermark-dom'); export function useWatermark(appendEl: Ref = 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 = 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 = 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 = ref(document.bo if (instance) { onBeforeUnmount(() => { clear(); - window.addEventListener('resize', func); }); } } diff --git a/src/router/routes/modules/demo/feat.ts b/src/router/routes/modules/demo/feat.ts index 3cec49d9682..7d5eaff1aeb 100644 --- a/src/router/routes/modules/demo/feat.ts +++ b/src/router/routes/modules/demo/feat.ts @@ -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组件', }, diff --git a/src/views/demo/comp/button/index.vue b/src/views/demo/comp/button/index.vue index 2c428c5c327..671eccc3ffc 100644 --- a/src/views/demo/comp/button/index.vue +++ b/src/views/demo/comp/button/index.vue @@ -7,8 +7,6 @@ show-icon /> - -

    success

    成功 diff --git a/src/views/demo/comp/click-out-side/index.vue b/src/views/demo/feat/click-out-side/index.vue similarity index 90% rename from src/views/demo/comp/click-out-side/index.vue rename to src/views/demo/feat/click-out-side/index.vue index b4cdba05718..ad2a73c5ca5 100644 --- a/src/views/demo/comp/click-out-side/index.vue +++ b/src/views/demo/feat/click-out-side/index.vue @@ -1,6 +1,6 @@ diff --git a/src/views/demo/comp/icon/index.vue b/src/views/demo/feat/icon/index.vue similarity index 96% rename from src/views/demo/comp/icon/index.vue rename to src/views/demo/feat/icon/index.vue index d242464b256..0ed56af2fa0 100644 --- a/src/views/demo/comp/icon/index.vue +++ b/src/views/demo/feat/icon/index.vue @@ -12,7 +12,7 @@
    - +
    @@ -23,7 +23,6 @@