From 928c61ccddebc8d2c697b86ee9bee0c207330a8c Mon Sep 17 00:00:00 2001
From: schangxiang@126.com <schangxiang@126.com>
Date: 周二, 06 5月 2025 07:18:22 +0800
Subject: [PATCH] 222

---
 HIAWms/web/src/components/vue3-context-menu/ContextMenuItem.vue |  380 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 380 insertions(+), 0 deletions(-)

diff --git a/HIAWms/web/src/components/vue3-context-menu/ContextMenuItem.vue b/HIAWms/web/src/components/vue3-context-menu/ContextMenuItem.vue
new file mode 100644
index 0000000..85252d1
--- /dev/null
+++ b/HIAWms/web/src/components/vue3-context-menu/ContextMenuItem.vue
@@ -0,0 +1,380 @@
+<template>
+  <div v-if="!hidden" class="mx-context-menu-item-wrapper" ref="menuItemRef" data-type="ContextMenuItem">
+    <!--Custom render-->
+    <VNodeRender v-if="globalHasSlot('itemRender')" :vnode="() => globalRenderSlot('itemRender', getItemDataForChildren())" />
+    <VNodeRender v-else-if="customRender" :vnode="customRender" :data="getItemDataForChildren()" />
+    <!--Default item-->
+    <div 
+      v-else
+      :class="[
+        'mx-context-menu-item',
+        (disabled ? 'disabled' : ''),
+        (keyBoardFocusMenu ? 'keyboard-focus' : ''),
+        (customClass ? (' ' + customClass) : ''),
+        (showSubMenu ? 'open' : ''),
+      ]"
+      @click="onClick"
+      @mouseenter="onMouseEnter"
+    >
+      <slot>
+        <div class="mx-item-row">
+          <div :class="[
+            'mx-icon-placeholder',
+            preserveIconWidth ? 'preserve-width': '',
+          ]">
+            <slot name="icon">
+              <VNodeRender v-if="globalHasSlot('itemIconRender')" :vnode="() => globalRenderSlot('itemIconRender', getItemDataForChildren())" />
+              <svg v-else-if="typeof svgIcon === 'string' && svgIcon" class="icon svg" v-bind="svgProps">
+                <use :xlink:href="svgIcon"></use>
+              </svg>
+              <VNodeRender v-else-if="(typeof icon !== 'string')" :vnode="icon" :data="icon" />
+              <i v-else-if="typeof icon === 'string' && icon !== ''" :class="icon + ' icon '+ iconFontClass + ' ' + globalIconFontClass"></i>
+            </slot>
+            <slot v-if="checked" name="check">
+              <VNodeRender v-if="globalHasSlot('itemCheckRender')" :vnode="() => globalRenderSlot('itemCheckRender', getItemDataForChildren())" />
+              <ContextMenuIconCheck />
+            </slot>
+          </div>
+          <slot name="label">
+            <VNodeRender v-if="globalHasSlot('itemLabelRender')" :vnode="() => globalRenderSlot('itemLabelRender', getItemDataForChildren())" />
+            <span class="label" v-else-if="typeof label === 'string'">{{ label }}</span>
+            <VNodeRender v-else :vnode="label" :data="label" />
+          </slot>
+        </div>
+        <div class="mx-item-row">
+          <slot v-if="shortcut" name="shortcut">
+            <VNodeRender v-if="globalHasSlot('itemShortcutRender')" :vnode="() => globalRenderSlot('itemShortcutRender', getItemDataForChildren())" />
+            <span class="mx-shortcut">{{ shortcut }}</span>
+          </slot>
+          <slot v-if="showRightArrow" name="rightArrow">
+            <VNodeRender v-if="globalHasSlot('itemRightArrowRender')" :vnode="() => globalRenderSlot('itemRightArrowRender', getItemDataForChildren())" />
+            <ContextMenuIconRight />
+          </slot>
+        </div>
+      </slot>
+    </div>
+    
+    <!--Sub menu render-->
+    <Transition v-if="globalMenuTransitionProps" v-bind="globalMenuTransitionProps">
+      <slot v-if="showSubMenu" name="submenu"></slot>
+    </Transition>
+    <slot v-else-if="showSubMenu" name="submenu"></slot>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { inject, nextTick, onBeforeUnmount, onMounted, type PropType, ref, type SVGAttributes, toRefs, type TransitionProps } from 'vue'
+import type { MenuItemContext, SubMenuParentContext } from './ContextSubMenu.vue'
+import type { GlobalHasSlot, GlobalRenderSlot } from './ContextMenu.vue'
+import type { MenuItem } from './ContextMenuDefine'
+import { VNodeRender } from './ContextMenuUtils'
+import ContextMenuIconCheck from './ContextMenuIconCheck.vue'
+import ContextMenuIconRight from './ContextMenuIconRight.vue'
+
+/**
+ * Menu Item
+ */
+
+const props = defineProps({
+  /**
+   * Is this menu disabled? 
+   */
+  disabled: {
+    type: Boolean,
+    default: false
+  },
+  /**
+   * Is this menu hidden? 
+   */
+  hidden: {
+    type: Boolean,
+    default: false
+  },
+  customRender: {
+    type: Function,
+    default: null
+  },
+  /**
+   * Custom css class for submenu
+   */
+  customClass: {
+    type: String,
+    default: ''
+  },
+  clickHandler: {
+    type: Function as PropType<(e: MouseEvent|KeyboardEvent) => void>,
+    default: null
+  },
+  /**
+   * Menu label
+   */
+  label: {
+    type: [String, Object, Function],
+    default: ''
+  },
+  /**
+   * Menu icon (for icon class)
+   */
+  icon: {
+    type: [String, Object, Function],
+    default: ''
+  },
+  /**
+   * Custom icon library font class name.
+   * 
+   * Only for css font icon, If you use the svg icon, you do not need to use this.
+   */
+  iconFontClass: {
+    type: String,
+    default: 'iconfont'
+  },
+  /**
+   * Is this menu item checked?
+   * 
+   * The check mark are displayed on the left side of the icon, so it is not recommended to display the icon at the same time.
+   */
+  checked: {
+    type: Boolean,
+    default: false
+  },
+  /**
+   * Shortcut key text display on the right.
+   * 
+   * The shortcut keys here are only for display. You need to handle the key events by yourself.
+   */
+  shortcut: {
+    type: String,
+    default: ''
+  },
+  /**
+   * Display icons use svg symbol (`<use xlink:href="#icon-symbol-name">`) 锛� only valid when icon attribute is empty.
+   */
+  svgIcon: {
+    type: String,
+    default: ''
+  },
+  /**
+   * The user-defined attribute of the svg tag, which is valid when using `svgIcon`.
+   */
+  svgProps: {
+    type: Object as PropType<SVGAttributes>,
+    default: null
+  },
+  /**
+   * Should a fixed-width icon area be reserved for menu items without icon. (this item)
+   * 
+   * Default is true .
+   * 
+   * The width of icon area can be override with css var `--mx-menu-placeholder-width`.
+   */
+  preserveIconWidth: {
+    type: Boolean,
+    default: true,
+  },
+  /**
+   * Show right arrow on this menu?
+   */
+  showRightArrow: {
+    type: Boolean,
+    default: false
+  },
+  hasChildren: {
+    type: Boolean,
+    default: false
+  },
+  /**
+   * Should close menu when Click this menu item ?
+   */
+  clickClose: {
+    type: Boolean,
+    default: true
+  },
+  /**
+   * When there are subitems in this item, is it allowed to trigger its own click event? Default is false
+   */
+  clickableWhenHasChildren: {
+    type: Boolean,
+    default: false
+  },
+  rawMenuItem: {
+    type: Object as PropType<MenuItem>,
+    default: undefined
+  },
+});
+const emit = defineEmits([
+  'click',
+  'subMenuOpen',
+  'subMenuClose',
+])
+
+const { 
+  clickHandler, clickClose, clickableWhenHasChildren, disabled, hidden,
+  label, icon, iconFontClass,
+  showRightArrow, shortcut,
+  hasChildren,
+} = toRefs(props);
+const showSubMenu = ref(false);
+const keyBoardFocusMenu = ref(false);
+
+const menuItemRef = ref<HTMLElement>();
+
+const globalHasSlot = inject('globalHasSlot') as GlobalHasSlot;
+const globalRenderSlot = inject('globalRenderSlot') as GlobalRenderSlot;
+const globalTheme = inject('globalTheme') as string;
+const globalIconFontClass = inject('globalIconFontClass') as string;
+const globalMenuTransitionProps = inject('globalMenuTransitionProps') as TransitionProps;
+const globalClickCloseClassName = inject('globalClickCloseClassName') as string;
+const globalIgnoreClickClassName = inject('globalIgnoreClickClassName') as string;
+const globalCloseMenu = inject('globalCloseMenu') as (fromItem: MenuItem|undefined) => void;
+
+const menuContext = inject('menuContext') as SubMenuParentContext;
+
+//Instance Contet for keyboadr control
+const menuItemInstance : MenuItemContext = {
+  showSubMenu: () => {
+    if (showSubMenu.value) {
+      //Mark current item
+      menuContext.markActiveMenuItem(menuItemInstance, true);
+      return true;
+    } else if (hasChildren.value) {
+      onMouseEnter();
+      return true;
+    }
+    return false;
+  },
+  isDisabledOrHidden: () => disabled.value || hidden.value,
+  getElement: () => menuItemRef.value,
+  focus: () => keyBoardFocusMenu.value = true,
+  blur: () => keyBoardFocusMenu.value = false,
+  click: onClick,
+}
+
+onMounted(() => {
+  if (menuContext.isMenuItemDataCollectedFlag()) {
+    //褰撳墠鑿滃崟鏉$洰鏄湪鏁翠綋鍔犺浇瀹屾垚鍚庢墠鏄剧ず鐨勶紝姝ゆ椂鑿滃崟椤哄簭宸茬粡鏃犳硶鐭ラ亾锛�
+    //鎵�浠ヨ繖閲岄渶瑕佸湪鐖剁骇鍏冪礌涓煡鎵惧緱鍑哄綋鍓嶈彍鍗曠殑浣嶇疆銆�
+    //
+    //The current menu item is displayed after the overall loading is completed. 
+    //At this time, the menu order cannot be known, so here we need to 
+    //find the position of the current menu in the parent element.
+    nextTick(() => {
+      let index = 0;
+      const parentEl = menuContext.getElement();
+      if (parentEl) {
+        let indexCounting = 0;
+        for (let i = 0; i < parentEl.children.length; i++) {
+          const el = parentEl.children[i];
+          if (el.getAttribute('data-type') === 'ContextMenuItem') {
+            if (el === menuItemRef.value) {
+              index = indexCounting;
+              break;
+            }
+            indexCounting++;
+          }
+        }
+      }
+      //Insert to pos
+      menuContext.addChildMenuItem(menuItemInstance, index);
+    });
+  } else
+    menuContext.addChildMenuItem(menuItemInstance);
+});
+onBeforeUnmount(() => {
+  menuContext.removeChildMenuItem(menuItemInstance);
+});
+
+//Click handler
+function onClick(e: MouseEvent|KeyboardEvent) {
+  //Ignore clicking when disabled
+  if (disabled.value)
+    return;
+  //Ignore clicking when click on some special elements
+  if (e) {
+    const currentTarget = e.target as HTMLElement;
+    if (currentTarget.classList.contains('mx-context-no-clickable'))
+      return;
+    if (globalIgnoreClickClassName && currentTarget.classList.contains(globalIgnoreClickClassName))
+      return;
+    if (globalClickCloseClassName && currentTarget.classList.contains(globalClickCloseClassName)) {
+      e.stopPropagation();
+      globalCloseMenu(props.rawMenuItem);
+      return;
+    }
+  }
+  //Has submenu?
+  if (hasChildren.value) {
+    if (clickableWhenHasChildren.value) {
+      if (typeof clickHandler.value === 'function')
+        clickHandler.value(e);
+      emit('click', e);
+    }
+    else if (!showSubMenu.value)
+      onMouseEnter();
+  } else {
+    //Call hander from options
+    if (typeof clickHandler.value === 'function') 
+      clickHandler.value(e);
+    emit('click', e);
+    if (clickClose.value) {
+      //emit close
+      globalCloseMenu(props.rawMenuItem);
+    }
+  }
+}
+//MouseEnter handler: show item submenu
+function onMouseEnter(e?: MouseEvent) {
+  //Clear keyBoard focus style
+  keyBoardFocusMenu.value = false;
+
+  //绛夊緟涓�涓欢鏃讹紝浠ラ槻姝㈢敤鎴疯繃蹇Щ鍔ㄩ紶鏍囧鑷磋彍鍗曢殣钘�
+  //Wait for a delay to prevent the menu from being hidden due to the user moving the mouse too fast
+  if (!menuContext.checkCloseOtherSubMenuTimeOut())
+    menuContext.closeOtherSubMenu();
+
+  if (!disabled.value) {
+    //Mark current item
+    menuContext.markActiveMenuItem(menuItemInstance);
+
+    if (hasChildren.value) {
+      if (!e)
+        menuContext.markThisOpenedByKeyBoard();
+      //Open sub menu
+      menuContext.addOpenedSubMenu(() => {
+        keyBoardFocusMenu.value = false;
+        showSubMenu.value = false;
+        emit('subMenuClose');
+      });
+      showSubMenu.value = true;
+      emit('subMenuOpen');
+    }
+  }
+}
+ 
+//Data for custom render
+function getItemDataForChildren() {
+  return {
+    disabled: disabled.value,
+    label: label.value,
+    icon: icon.value,
+    iconFontClass: iconFontClass.value,
+    showRightArrow: showRightArrow.value,
+    clickClose: clickClose.value,
+    clickableWhenHasChildren: clickableWhenHasChildren.value,
+    shortcut: shortcut.value,
+    theme: globalTheme,
+    isOpen: showSubMenu,
+    hasChildren: hasChildren,
+    onClick,
+    onMouseEnter,
+    closeMenu: globalCloseMenu,
+  }
+}
+
+defineExpose({
+  showSubMenu,
+  keyBoardFocusMenu,
+});
+</script>
+
+<style>
+</style>

--
Gitblit v1.9.3