zhouwx
2026-06-12 5d0e4bcaf5d44a9ce2ff196e38c77676aa8ea880
src/layout/components/Sidebar/SidebarItem.vue
@@ -1,33 +1,147 @@
<template>
<!--  <div v-if="!item.hidden">-->
<!--    <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">-->
<!--      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">-->
<!--        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">-->
<!--          <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/>-->
<!--          <template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span></template>-->
<!--        </el-menu-item>-->
<!--      </app-link>-->
<!--    </template>-->
<!--    <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>-->
<!--      <template v-if="item.meta" #title>-->
<!--        <svg-icon :icon-class="item.meta && item.meta.icon" />-->
<!--        <span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span>-->
<!--      </template>-->
<!--      <sidebar-item-->
<!--        v-for="child in item.children"-->
<!--        :key="child.path"-->
<!--        :is-nest="true"-->
<!--        :item="child"-->
<!--        :base-path="resolvePath(child.path)"-->
<!--        class="nest-menu"-->
<!--      />-->
<!--    </el-sub-menu>-->
<!--  </div>-->
  <div v-if="!item.hidden">
    <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
          <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/>
          <template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span></template>
    <!-- 优化条件:优先判定【仅有1个可见子菜单】,直接走一级菜单渲染 -->
    <template v-if="calcSingleChild(item) && item.path != '/system'">
      <app-link v-if="onlyOneChild?.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
        <el-menu-item
            :index="resolvePath(onlyOneChild.path)"
            :class="{ 'submenu-title-noDropdown': !isNest }"
        >
          <svg-icon :icon-class="item.meta?.icon || onlyOneChild.meta?.icon"/>
          <template #title>
            <span class="menu-title" :title="hasTitle(item.meta?.title || onlyOneChild.meta?.title)">
              {{ item.meta?.title || onlyOneChild.meta?.title }}
            </span>
          </template>
        </el-menu-item>
      </app-link>
    </template>
    <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
    <!-- 多个子菜单 / 强制目录:正常展开二级菜单 -->
    <el-sub-menu
        v-else
        ref="subMenu"
        :index="resolvePath(item.path)"
        popper-append-to-body
    >
      <template v-if="item.meta" #title>
        <svg-icon :icon-class="item.meta && item.meta.icon" />
        <span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span>
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
          v-for="child in item.children"
          :key="child.path"
          :is-nest="true"
          :item="child"
          :base-path="resolvePath(child.path)"
          class="nest-menu"
      />
    </el-sub-menu>
  </div>
</template>
<!--<script setup>-->
<!--import { isExternal } from '@/utils/validate'-->
<!--import AppLink from './Link'-->
<!--import { getNormalPath } from '@/utils/ruoyi'-->
<!--const props = defineProps({-->
<!--  // route object-->
<!--  item: {-->
<!--    type: Object,-->
<!--    required: true-->
<!--  },-->
<!--  isNest: {-->
<!--    type: Boolean,-->
<!--    default: false-->
<!--  },-->
<!--  basePath: {-->
<!--    type: String,-->
<!--    default: ''-->
<!--  }-->
<!--})-->
<!--const onlyOneChild = ref({});-->
<!--function hasOneShowingChild(children = [], parent) {-->
<!--  if (!children) {-->
<!--    children = [];-->
<!--  }-->
<!--  const showingChildren = children.filter(item => {-->
<!--    if (item.hidden) {-->
<!--      return false-->
<!--    } else {-->
<!--      // Temp set(will be used if only has one showing child)-->
<!--      onlyOneChild.value = item-->
<!--      return true-->
<!--    }-->
<!--  })-->
<!--  // When there is only one child router, the child router is displayed by default-->
<!--  if (showingChildren.length === 1) {-->
<!--    return true-->
<!--  }-->
<!--  // Show parent if there are no child router to display-->
<!--  if (showingChildren.length === 0) {-->
<!--    onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }-->
<!--    return true-->
<!--  }-->
<!--  return false-->
<!--};-->
<!--function resolvePath(routePath, routeQuery) {-->
<!--  if (isExternal(routePath)) {-->
<!--    return routePath-->
<!--  }-->
<!--  if (isExternal(props.basePath)) {-->
<!--    return props.basePath-->
<!--  }-->
<!--  if (routeQuery) {-->
<!--    let query = JSON.parse(routeQuery);-->
<!--    return { path: getNormalPath(props.basePath + '/' + routePath), query: query }-->
<!--  }-->
<!--  return getNormalPath(props.basePath + '/' + routePath)-->
<!--}-->
<!--function hasTitle(title){-->
<!--  if (title.length > 5) {-->
<!--    return title;-->
<!--  } else {-->
<!--    return "";-->
<!--  }-->
<!--}-->
<!--</script>-->
<script setup>
import { ref } from 'vue'
import { isExternal } from '@/utils/validate'
import AppLink from './Link'
import { getNormalPath } from '@/utils/ruoyi'
@@ -48,35 +162,44 @@
  }
})
const onlyOneChild = ref({});
// 修复:初始化为 null,而非空对象
const onlyOneChild = ref(null);
/**
 * 修复版:判断是否只有一个可见子菜单(核心改动)
 */
function hasOneShowingChild(children = [], parent) {
  if (!children) {
    children = [];
  }
  const showingChildren = children.filter(item => {
    if (item.hidden) {
      return false
    } else {
      // Temp set(will be used if only has one showing child)
      onlyOneChild.value = item
      return true
    }
  })
  if (!children) children = []
  // When there is only one child router, the child router is displayed by default
  // 1. 先统一过滤出【未隐藏】的子菜单
  const showingChildren = children.filter(item => !item.hidden)
  // 2. 仅有 1 个可见子菜单:赋值 + 标记无下级,适配原有判断
  if (showingChildren.length === 1) {
    const child = showingChildren[0]
    // 标记:当前子菜单没有可展示的下级(关键,适配原模板逻辑)
    onlyOneChild.value = { ...child, noShowingChildren: true }
    return true
  }
  // Show parent if there are no child router to display
  // 3. 没有可见子菜单
  if (showingChildren.length === 0) {
    onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
    return true
  }
  // 4. 多个子菜单
  onlyOneChild.value = null
  return false
};
}
/**
 * 新增统一判断方法:简化模板条件,命中「单菜单合并逻辑」
 */
function calcSingleChild(item) {
// 存在子菜单 且 仅有一个可见子菜单
  return hasOneShowingChild(item.children, item)
}
function resolvePath(routePath, routeQuery) {
  if (isExternal(routePath)) {
@@ -93,7 +216,7 @@
}
function hasTitle(title){
  if (title.length > 5) {
  if (title && title.length > 5) {
    return title;
  } else {
    return "";