You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

319 lines
8.1 KiB

2 years ago
  1. <script setup lang="ts">
  2. import path from "path";
  3. import { getConfig } from "@/config";
  4. import { menuType } from "../../types";
  5. import extraIcon from "./extraIcon.vue";
  6. import { useNav } from "@/layout/hooks/useNav";
  7. import { transformI18n } from "@/plugins/i18n";
  8. import { useRenderIcon } from "@/components/ReIcon/src/hooks";
  9. import { ref, toRaw, PropType, nextTick, computed, CSSProperties } from "vue";
  10. import ArrowUp from "@iconify-icons/ep/arrow-up-bold";
  11. import EpArrowDown from "@iconify-icons/ep/arrow-down-bold";
  12. import ArrowLeft from "@iconify-icons/ep/arrow-left-bold";
  13. import ArrowRight from "@iconify-icons/ep/arrow-right-bold";
  14. const { layout, isCollapse, tooltipEffect, getDivStyle } = useNav();
  15. const props = defineProps({
  16. item: {
  17. type: Object as PropType<menuType>
  18. },
  19. isNest: {
  20. type: Boolean,
  21. default: false
  22. },
  23. basePath: {
  24. type: String,
  25. default: ""
  26. }
  27. });
  28. const getSpanStyle = computed((): CSSProperties => {
  29. return {
  30. width: "100%",
  31. textAlign: "center"
  32. };
  33. });
  34. const getNoDropdownStyle = computed((): CSSProperties => {
  35. return {
  36. display: "flex",
  37. alignItems: "center"
  38. };
  39. });
  40. const getMenuTextStyle = computed(() => {
  41. return {
  42. overflow: "hidden",
  43. textOverflow: "ellipsis",
  44. outline: "none"
  45. };
  46. });
  47. const getsubMenuIconStyle = computed((): CSSProperties => {
  48. return {
  49. display: "flex",
  50. justifyContent: "center",
  51. alignItems: "center",
  52. margin:
  53. layout.value === "horizontal"
  54. ? "0 5px 0 0"
  55. : isCollapse.value
  56. ? "0 auto"
  57. : "0 5px 0 0"
  58. };
  59. });
  60. const getSubTextStyle = computed((): CSSProperties => {
  61. if (!isCollapse.value) {
  62. return {
  63. width: "210px",
  64. display: "inline-block",
  65. overflow: "hidden",
  66. textOverflow: "ellipsis"
  67. };
  68. } else {
  69. return {
  70. width: ""
  71. };
  72. }
  73. });
  74. const getSubMenuDivStyle = computed((): any => {
  75. return item => {
  76. return !isCollapse.value
  77. ? {
  78. width: "100%",
  79. display: "flex",
  80. alignItems: "center",
  81. justifyContent: "space-between",
  82. overflow: "hidden"
  83. }
  84. : {
  85. width: "100%",
  86. textAlign:
  87. item?.parentId === null
  88. ? "center"
  89. : layout.value === "mix" && item?.pathList?.length === 2
  90. ? "center"
  91. : ""
  92. };
  93. };
  94. });
  95. const expandCloseIcon = computed(() => {
  96. if (!getConfig()?.MenuArrowIconNoTransition) return "";
  97. return {
  98. "expand-close-icon": useRenderIcon(EpArrowDown),
  99. "expand-open-icon": useRenderIcon(ArrowUp),
  100. "collapse-close-icon": useRenderIcon(ArrowRight),
  101. "collapse-open-icon": useRenderIcon(ArrowLeft)
  102. };
  103. });
  104. const onlyOneChild: menuType = ref(null);
  105. // 存放菜单是否存在showTooltip属性标识
  106. const hoverMenuMap = new WeakMap();
  107. // 存储菜单文本dom元素
  108. const menuTextRef = ref(null);
  109. function hoverMenu(key) {
  110. // 如果当前菜单showTooltip属性已存在,退出计算
  111. if (hoverMenuMap.get(key)) return;
  112. nextTick(() => {
  113. // 如果文本内容的整体宽度大于其可视宽度,则文本溢出
  114. menuTextRef.value?.scrollWidth > menuTextRef.value?.clientWidth
  115. ? Object.assign(key, {
  116. showTooltip: true
  117. })
  118. : Object.assign(key, {
  119. showTooltip: false
  120. });
  121. hoverMenuMap.set(key, true);
  122. });
  123. }
  124. // 左侧菜单折叠后,当菜单没有图标时只显示第一个文字并加上省略号
  125. function overflowSlice(text, item?: any) {
  126. const newText =
  127. (text?.length > 1 ? text.toString().slice(0, 1) : text) + "...";
  128. if (item && !(isCollapse.value && item?.parentId === null)) {
  129. return layout.value === "mix" &&
  130. item?.pathList?.length === 2 &&
  131. isCollapse.value
  132. ? newText
  133. : text;
  134. }
  135. return newText;
  136. }
  137. function hasOneShowingChild(children: menuType[] = [], parent: menuType) {
  138. const showingChildren = children.filter((item: any) => {
  139. onlyOneChild.value = item;
  140. return true;
  141. });
  142. if (showingChildren[0]?.meta?.showParent) {
  143. return false;
  144. }
  145. if (showingChildren.length === 1) {
  146. return true;
  147. }
  148. if (showingChildren.length === 0) {
  149. onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
  150. return true;
  151. }
  152. return false;
  153. }
  154. function resolvePath(routePath) {
  155. const httpReg = /^http(s?):\/\//;
  156. if (httpReg.test(routePath) || httpReg.test(props.basePath)) {
  157. return routePath || props.basePath;
  158. } else {
  159. // 使用path.posix.resolve替代path.resolve 避免windows环境下使用electron出现盘符问题
  160. return path.posix.resolve(props.basePath, routePath);
  161. }
  162. }
  163. </script>
  164. <template>
  165. <el-menu-item
  166. v-if="
  167. hasOneShowingChild(props.item.children, props.item) &&
  168. (!onlyOneChild.children || onlyOneChild.noShowingChildren)
  169. "
  170. :index="resolvePath(onlyOneChild.path)"
  171. :class="{ 'submenu-title-noDropdown': !isNest }"
  172. :style="getNoDropdownStyle"
  173. >
  174. <div
  175. v-if="toRaw(props.item.meta.icon)"
  176. class="sub-menu-icon"
  177. :style="getsubMenuIconStyle"
  178. >
  179. <component
  180. :is="
  181. useRenderIcon(
  182. toRaw(onlyOneChild.meta.icon) ||
  183. (props.item.meta && toRaw(props.item.meta.icon))
  184. )
  185. "
  186. />
  187. </div>
  188. <span
  189. v-if="
  190. !props.item?.meta.icon &&
  191. isCollapse &&
  192. layout === 'vertical' &&
  193. props.item?.pathList?.length === 1
  194. "
  195. :style="getSpanStyle"
  196. >
  197. {{ overflowSlice(transformI18n(onlyOneChild.meta.title)) }}
  198. </span>
  199. <span
  200. v-if="
  201. !onlyOneChild.meta.icon &&
  202. isCollapse &&
  203. layout === 'mix' &&
  204. props.item?.pathList?.length === 2
  205. "
  206. :style="getSpanStyle"
  207. >
  208. {{ overflowSlice(transformI18n(onlyOneChild.meta.title)) }}
  209. </span>
  210. <template #title>
  211. <div :style="getDivStyle">
  212. <span v-if="layout === 'horizontal'">
  213. {{ transformI18n(onlyOneChild.meta.title) }}
  214. </span>
  215. <el-tooltip
  216. v-else
  217. placement="top"
  218. :effect="tooltipEffect"
  219. :offset="-10"
  220. :disabled="!onlyOneChild.showTooltip"
  221. >
  222. <template #content>
  223. {{ transformI18n(onlyOneChild.meta.title) }}
  224. </template>
  225. <span
  226. ref="menuTextRef"
  227. :style="getMenuTextStyle"
  228. @mouseover="hoverMenu(onlyOneChild)"
  229. >
  230. {{ transformI18n(onlyOneChild.meta.title) }}
  231. </span>
  232. </el-tooltip>
  233. <extraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
  234. </div>
  235. </template>
  236. </el-menu-item>
  237. <el-sub-menu
  238. v-else
  239. ref="subMenu"
  240. v-bind="expandCloseIcon"
  241. :index="resolvePath(props.item.path)"
  242. >
  243. <template #title>
  244. <div
  245. v-if="toRaw(props.item.meta.icon)"
  246. :style="getsubMenuIconStyle"
  247. class="sub-menu-icon"
  248. >
  249. <component
  250. :is="useRenderIcon(props.item.meta && toRaw(props.item.meta.icon))"
  251. />
  252. </div>
  253. <span v-if="layout === 'horizontal'">
  254. {{ transformI18n(props.item.meta.title) }}
  255. </span>
  256. <div
  257. :style="getSubMenuDivStyle(props.item)"
  258. v-if="
  259. !(
  260. isCollapse &&
  261. toRaw(props.item.meta.icon) &&
  262. props.item.parentId === null
  263. )
  264. "
  265. >
  266. <el-tooltip
  267. v-if="layout !== 'horizontal'"
  268. placement="top"
  269. :effect="tooltipEffect"
  270. :offset="-10"
  271. :disabled="!props.item.showTooltip"
  272. >
  273. <template #content>
  274. {{ transformI18n(props.item.meta.title) }}
  275. </template>
  276. <span
  277. ref="menuTextRef"
  278. :style="getSubTextStyle"
  279. @mouseover="hoverMenu(props.item)"
  280. >
  281. {{
  282. overflowSlice(transformI18n(props.item.meta.title), props.item)
  283. }}
  284. </span>
  285. </el-tooltip>
  286. <extraIcon v-if="!isCollapse" :extraIcon="props.item.meta.extraIcon" />
  287. </div>
  288. </template>
  289. <sidebar-item
  290. v-for="child in props.item.children"
  291. :key="child.path"
  292. :is-nest="true"
  293. :item="child"
  294. :base-path="resolvePath(child.path)"
  295. class="nest-menu"
  296. />
  297. </el-sub-menu>
  298. </template>