sidebarItem.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <script setup lang="ts">
  2. import path from "path";
  3. import { childrenType } from "../../types";
  4. import { useNav } from "@/layout/hooks/useNav";
  5. import { transformI18n } from "@/plugins/i18n";
  6. import { useRenderIcon } from "@/components/ReIcon/src/hooks";
  7. import { ref, toRaw, PropType, nextTick, computed, CSSProperties } from "vue";
  8. const { layout, isCollapse } = useNav();
  9. const props = defineProps({
  10. item: {
  11. type: Object as PropType<childrenType>
  12. },
  13. isNest: {
  14. type: Boolean,
  15. default: false
  16. },
  17. basePath: {
  18. type: String,
  19. default: ""
  20. }
  21. });
  22. const getExtraIconStyle = computed((): CSSProperties => {
  23. if (!isCollapse.value) {
  24. return {
  25. position: "absolute",
  26. right: "10px"
  27. };
  28. } else {
  29. return {
  30. position: "static"
  31. };
  32. }
  33. });
  34. const getNoDropdownStyle = computed((): CSSProperties => {
  35. return {
  36. display: "flex",
  37. alignItems: "center"
  38. };
  39. });
  40. const getDivStyle = computed((): CSSProperties => {
  41. return {
  42. width: !isCollapse.value ? "" : "100%",
  43. display: "flex",
  44. alignItems: "center",
  45. justifyContent: "space-between",
  46. overflow: "hidden"
  47. };
  48. });
  49. const getMenuTextStyle = computed(() => {
  50. return {
  51. overflow: "hidden",
  52. textOverflow: "ellipsis",
  53. outline: "none"
  54. };
  55. });
  56. const getSubTextStyle = computed((): CSSProperties => {
  57. return {
  58. width: !isCollapse.value ? "210px" : "",
  59. display: "inline-block",
  60. overflow: "hidden",
  61. textOverflow: "ellipsis"
  62. };
  63. });
  64. const getSpanStyle = computed(() => {
  65. return {
  66. overflow: "hidden",
  67. textOverflow: "ellipsis"
  68. };
  69. });
  70. const onlyOneChild: childrenType = ref(null);
  71. // 存放菜单是否存在showTooltip属性标识
  72. const hoverMenuMap = new WeakMap();
  73. // 存储菜单文本dom元素
  74. const menuTextRef = ref(null);
  75. function hoverMenu(key) {
  76. // 如果当前菜单showTooltip属性已存在,退出计算
  77. if (hoverMenuMap.get(key)) return;
  78. nextTick(() => {
  79. // 如果文本内容的整体宽度大于其可视宽度,则文本溢出
  80. menuTextRef.value?.scrollWidth > menuTextRef.value?.clientWidth
  81. ? Object.assign(key, {
  82. showTooltip: true
  83. })
  84. : Object.assign(key, {
  85. showTooltip: false
  86. });
  87. hoverMenuMap.set(key, true);
  88. });
  89. }
  90. function hasOneShowingChild(
  91. children: childrenType[] = [],
  92. parent: childrenType
  93. ) {
  94. const showingChildren = children.filter((item: any) => {
  95. onlyOneChild.value = item;
  96. return true;
  97. });
  98. if (showingChildren[0]?.meta?.showParent) {
  99. return false;
  100. }
  101. if (showingChildren.length === 1) {
  102. return true;
  103. }
  104. if (showingChildren.length === 0) {
  105. onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
  106. return true;
  107. }
  108. return false;
  109. }
  110. function resolvePath(routePath) {
  111. const httpReg = /^http(s?):\/\//;
  112. if (httpReg.test(routePath) || httpReg.test(props.basePath)) {
  113. return routePath || props.basePath;
  114. } else {
  115. return path.resolve(props.basePath, routePath);
  116. }
  117. }
  118. </script>
  119. <template>
  120. <template
  121. v-if="
  122. hasOneShowingChild(props.item.children, props.item) &&
  123. (!onlyOneChild.children || onlyOneChild.noShowingChildren)
  124. "
  125. >
  126. <el-menu-item
  127. :index="resolvePath(onlyOneChild.path)"
  128. :class="{ 'submenu-title-noDropdown': !isNest }"
  129. :style="getNoDropdownStyle"
  130. >
  131. <div class="sub-menu-icon" v-if="toRaw(props.item.meta.icon)">
  132. <component
  133. :is="
  134. useRenderIcon(
  135. toRaw(onlyOneChild.meta.icon) ||
  136. (props.item.meta && toRaw(props.item.meta.icon))
  137. )
  138. "
  139. />
  140. </div>
  141. <div
  142. v-if="
  143. isCollapse &&
  144. layout === 'vertical' &&
  145. props.item?.pathList?.length === 1
  146. "
  147. :style="getDivStyle"
  148. >
  149. <span :style="getMenuTextStyle">
  150. {{ transformI18n(onlyOneChild.meta.title) }}
  151. </span>
  152. </div>
  153. <div
  154. v-if="
  155. isCollapse && layout === 'mix' && props.item?.pathList?.length === 2
  156. "
  157. :style="getDivStyle"
  158. >
  159. <span :style="getMenuTextStyle">
  160. {{ transformI18n(onlyOneChild.meta.title) }}
  161. </span>
  162. </div>
  163. <template #title>
  164. <div :style="getDivStyle">
  165. <span v-if="layout === 'horizontal'">
  166. {{ transformI18n(onlyOneChild.meta.title) }}
  167. </span>
  168. <el-tooltip
  169. v-else
  170. placement="top"
  171. :offset="-10"
  172. :disabled="!onlyOneChild.showTooltip"
  173. >
  174. <template #content>
  175. {{ transformI18n(onlyOneChild.meta.title) }}
  176. </template>
  177. <span
  178. ref="menuTextRef"
  179. :style="getMenuTextStyle"
  180. @mouseover="hoverMenu(onlyOneChild)"
  181. >
  182. {{ transformI18n(onlyOneChild.meta.title) }}
  183. </span>
  184. </el-tooltip>
  185. <FontIcon
  186. v-if="onlyOneChild.meta.extraIcon"
  187. width="30px"
  188. height="30px"
  189. :style="getExtraIconStyle"
  190. :icon="onlyOneChild.meta.extraIcon.name"
  191. :svg="onlyOneChild.meta.extraIcon.svg ? true : false"
  192. />
  193. </div>
  194. </template>
  195. </el-menu-item>
  196. </template>
  197. <el-sub-menu v-else ref="subMenu" :index="resolvePath(props.item.path)">
  198. <template #title>
  199. <div v-if="toRaw(props.item.meta.icon)" class="sub-menu-icon">
  200. <component
  201. :is="useRenderIcon(props.item.meta && toRaw(props.item.meta.icon))"
  202. />
  203. </div>
  204. <span v-if="layout === 'horizontal'">
  205. {{ transformI18n(props.item.meta.title) }}
  206. </span>
  207. <el-tooltip
  208. v-else
  209. placement="top"
  210. :offset="-10"
  211. :disabled="!isCollapse || !props.item.showTooltip"
  212. >
  213. <template #content>
  214. {{ transformI18n(props.item.meta.title) }}
  215. </template>
  216. <div
  217. ref="menuTextRef"
  218. :style="getSubTextStyle"
  219. @mouseover="hoverMenu(props.item)"
  220. >
  221. <span :style="getSpanStyle">
  222. {{ transformI18n(props.item.meta.title) }}
  223. </span>
  224. </div>
  225. </el-tooltip>
  226. <FontIcon
  227. v-if="props.item.meta.extraIcon"
  228. width="30px"
  229. height="30px"
  230. style="position: absolute; right: 10px"
  231. :icon="props.item.meta.extraIcon.name"
  232. :svg="props.item.meta.extraIcon.svg ? true : false"
  233. />
  234. </template>
  235. <sidebar-item
  236. v-for="child in props.item.children"
  237. :key="child.path"
  238. :is-nest="true"
  239. :item="child"
  240. :base-path="resolvePath(child.path)"
  241. class="nest-menu"
  242. />
  243. </el-sub-menu>
  244. </template>