DYL0109
2025-04-16 725da078afc3398f49f1efdc25bc82b9ec1dac35
Merge pull request #64 from zhitan-cloud/develop1.0

Develop1.0
已添加2个文件
已删除1个文件
已修改22个文件
46242 ■■■■■ 文件已修改
sql/postgre.sql 3642 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/postgre20250416.sql 41159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/assets/styles/index.scss 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/assets/styles/menu-fix.scss 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/assets/styles/page.scss 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/assets/styles/sidebar.scss 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/components/Breadcrumb/index.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/components/Hamburger/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/components/TopNav/index.vue 103 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/layout/components/AppMain.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/layout/components/Navbar.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/layout/components/Sidebar/SidebarItem.vue 224 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/layout/components/Sidebar/index.vue 250 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/layout/components/TagsView/ScrollPane.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/layout/components/TagsView/index.vue 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/layout/index.vue 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/permission.js 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/router/index.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/store/modules/permission.js 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/views/deepanalysis/deepAnalysis.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/views/index.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/views/login.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/views/system/name/name.vue 85 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/views/system/user/profile/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/vite.config.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/postgre.sql
ÎļþÒÑɾ³ý
sql/postgre20250416.sql
¶Ô±ÈÐÂÎļþ
ÎļþÌ«´ó
zhitan-vue/src/assets/styles/index.scss
@@ -9,10 +9,12 @@
body {
  height: 100%;
  margin: 0;
  padding: 0;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
  overflow-x: hidden; /* é˜²æ­¢æ°´å¹³æ»šåŠ¨æ¡ */
}
label {
@@ -22,10 +24,17 @@
html {
  height: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  overflow-x: hidden; /* é˜²æ­¢æ°´å¹³æ»šåŠ¨æ¡ */
}
#app {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  overflow-x: hidden; /* é˜²æ­¢æ°´å¹³æ»šåŠ¨æ¡ */
}
*,
@@ -205,4 +214,41 @@
    // ::v-deep(.el-range-separator) {
    color: #999 !important;
  }
}
/* æŠ˜å èœå•样式调整,确保在所有情况下菜单项居中 */
.el-menu--collapse {
  width: 54px !important;
  .el-sub-menu, .el-menu-item {
    width: 36px !important;
    min-width: 36px !important;
    margin-left: 9px !important; /* å…³é”®ï¼šè®¾ç½®å›ºå®šçš„左边距9px使菜单项居中 */
    margin-right: 9px !important;
  }
  .el-menu-item, .el-sub-menu__title {
    display: flex !important;
    justify-content: center !important;
    align-items: center !important;
    padding: 0 !important;
    .svg-icon, .el-icon {
      margin: 0 !important; /* é‡è¦ï¼šç§»é™¤å›¾æ ‡çš„æ‰€æœ‰è¾¹è· */
    }
    .el-sub-menu__icon-arrow {
      display: none !important;
    }
    > span {
      display: none !important;
    }
  }
  /* é€‰ä¸­çŠ¶æ€æ ·å¼ */
  .el-menu-item.is-active, .el-sub-menu.is-active > .el-sub-menu__title {
    background-color: #3883FA !important;
    color: #fff !important;
  }
}
zhitan-vue/src/assets/styles/menu-fix.scss
zhitan-vue/src/assets/styles/page.scss
@@ -163,9 +163,9 @@
      display: flex;
      .page-container-left {
        width: 280px;
        width: 220px;
        min-height: calc(100vh - 148px);
        border-right: 1px solid #fff;
        border-right: 1px solid #e8e8e8;
        background: #f1f4fa;
        
        .el-tree {
@@ -194,8 +194,8 @@
        }
        .tree {
          height: calc(100vh - 170px) !important;
          max-height: calc(100vh - 170px) !important;
          height: calc(100vh - 170px);
          max-height: calc(100vh - 170px);
          overflow-y: auto;
        }
      }
zhitan-vue/src/assets/styles/sidebar.scss
@@ -13,6 +13,7 @@
    .main-container {
      background-color: #110f2e !important;
      height: 100%;
      transition: margin-left 0.28s;
      margin-left: $base-sidebar-width;
@@ -28,7 +29,7 @@
      transition: width 0.28s;
      width: $base-sidebar-width !important;
      background-color: $base-menu-background;
      height: 100%;
      height: calc(100% - 60px) !important;
      position: fixed;
      top: 60px;
      bottom: 0;
@@ -51,10 +52,11 @@
      .scrollbar-wrapper {
        overflow-x: hidden !important;
        height: calc(100% - 290px) !important;
      }
      .el-scrollbar__bar.is-vertical {
        right: 0px;
        right: 0 !important;
      }
      .el-scrollbar {
@@ -192,22 +194,32 @@
      }
      .el-menu--collapse {
        .el-sub-menu {
          & > .el-sub-menu__title {
            & > span {
              height: 0;
              width: 0;
              overflow: hidden;
              visibility: hidden;
              display: inline-block;
            }
            & > i {
              height: 0;
              width: 0;
              overflow: hidden;
              visibility: hidden;
              display: inline-block;
            }
        width: 54px !important;
        /* æ¸…除后代选择器中干扰居中的样式 */
        .el-sub-menu, .el-menu-item {
          margin: 0 !important;
          padding: 0 !important;
          width: 100% !important;
          /* è®©å›¾æ ‡å’Œèœå•项居中 */
          .el-menu-item, .el-sub-menu__title {
            width: 36px !important;
            min-width: 36px !important;
            height: 38px !important;
            line-height: 38px !important;
            margin: 4px 9px !important; /* ç²¾ç¡®è®¡ç®—居中边距 */
            padding: 0 !important;
            display: flex !important;
            justify-content: center !important;
            align-items: center !important;
            border-radius: 4px !important;
          }
          /* ç¡®ä¿å›¾æ ‡å±…中 */
          .svg-icon, .el-icon {
            margin: 0 !important;
            padding: 0 !important;
          }
        }
      }
@@ -295,6 +307,7 @@
    }
    .main-container {
      background-color: #f7f8fa;
      height: 100%;
      transition: margin-left 0.28s;
      margin-left: $base-sidebar-width;
@@ -310,7 +323,7 @@
      transition: width 0.28s;
      width: $base-sidebar-width !important;
      background-color: $base-menu-background;
      height: 100%;
      height: calc(100% - 60px) !important;
      position: fixed;
      top: 60px;
      bottom: 0;
@@ -378,7 +391,7 @@
      .menu-title {
        overflow: hidden !important;
        font-weight: 400 !important;
        font-size: 16px !important;
        font-size: 14px !important;
      }
      // @media (min-width: 1440px) {
@@ -479,22 +492,32 @@
      }
      .el-menu--collapse {
        .el-sub-menu {
          & > .el-sub-menu__title {
            & > span {
              height: 0;
              width: 0;
              overflow: hidden;
              visibility: hidden;
              display: inline-block;
            }
            & > i {
              height: 0;
              width: 0;
              overflow: hidden;
              visibility: hidden;
              display: inline-block;
            }
        width: 54px !important;
        /* æ¸…除后代选择器中干扰居中的样式 */
        .el-sub-menu, .el-menu-item {
          margin: 0 !important;
          padding: 0 !important;
          width: 100% !important;
          /* è®©å›¾æ ‡å’Œèœå•项居中 */
          .el-menu-item, .el-sub-menu__title {
            width: 36px !important;
            min-width: 36px !important;
            height: 38px !important;
            line-height: 38px !important;
            margin: 4px 9px !important; /* ç²¾ç¡®è®¡ç®—居中边距 */
            padding: 0 !important;
            display: flex !important;
            justify-content: center !important;
            align-items: center !important;
            border-radius: 4px !important;
          }
          /* ç¡®ä¿å›¾æ ‡å±…中 */
          .svg-icon, .el-icon {
            margin: 0 !important;
            padding: 0 !important;
          }
        }
      }
zhitan-vue/src/components/Breadcrumb/index.vue
@@ -19,20 +19,29 @@
  let matched = route.matched.filter(item => item.meta && item.meta.title);
  const first = matched[0]
  
  // ä¸è‡ªåŠ¨æ·»åŠ é¦–é¡µåˆ°é¢åŒ…å±‘ä¸­
  // if (!isDashboard(first)) {
  //   matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched)
  // }
  // æ·»åŠ è°ƒè¯•æ—¥å¿—
  console.log('Current route path:', route.path);
  console.log('Route matched:', route.matched);
  console.log('Filtered matched routes:', matched);
  // å¦‚果是首页看板路由,确保它被添加到面包屑中
  if (route.path === '/index' || route.path === '/index/index') {
    matched = [{ path: '/index', meta: { title: '首页看板' } }].concat(matched)
    console.log('Added index route to matched:', matched);
  }
  levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
  console.log('Final breadcrumb items:', levelList.value);
}
function isDashboard(route) {
  const name = route && route.name
  if (!name) {
    return false
  }
  return name.trim() === 'Index'
  return name.trim().toLowerCase() === 'index'
}
function handleLink(item) {
  const { redirect, path } = item
  if (redirect) {
@@ -62,20 +71,6 @@
  .no-redirect {
    color: #fff;
    cursor: text;
  }
  :deep(.el-breadcrumb__item) {
    .el-breadcrumb__inner {
      color: rgba(255, 255, 255, 0.8);
      &:hover {
        color: #fff;
      }
    }
    .el-breadcrumb__separator {
      color: rgba(255, 255, 255, 0.8);
    }
  }
}
</style>
zhitan-vue/src/components/Hamburger/index.vue
@@ -12,7 +12,7 @@
    </svg> -->
    
    <img src="/src/assets/images/nav-btn.png" width="26" v-if="settingsStore.sideTheme == 'theme-dark'">
    <img src="/src/assets/images/nav-btn2.png" width="26" v-else>
    <img src="/src/assets/images/nav-btn.png" width="26" v-else>
  </div>
</template>
zhitan-vue/src/components/TopNav/index.vue
@@ -135,6 +135,28 @@
  setTimeout(updateScrollButtons, 300);
}
/**
 * æŸ¥æ‰¾æœ€æ·±å±‚的子菜单(叶子节点)
 * é€’归查找第一个没有children的子菜单
 */
function findDeepestLeafMenu(route) {
  if (!route) return null;
  // å¦‚果没有子菜单或子菜单为空,则返回当前路由
  if (!route.children || route.children.length === 0) {
    return route;
  }
  // æ‰¾åˆ°ç¬¬ä¸€ä¸ªéžéšè—çš„子菜单
  const firstVisibleChild = route.children.find(child => !child.hidden);
  if (!firstVisibleChild) {
    return route; // å¦‚果所有子菜单都是隐藏的,返回当前路由
  }
  // é€’归查找这个子菜单的最深层子菜单
  return findDeepestLeafMenu(firstVisibleChild);
}
function handleSelect(key, keyPath) {
  currentIndex.value = key;
  const route = routers.value.find(item => item.path === key);
@@ -145,23 +167,76 @@
    return;
  } 
  
  if (key === '/index' || key === '/') {
    // é¦–页时显示折叠的侧边栏,而不是隐藏
    router.push({ path: key });
    appStore.showCollapsedSidebar();
    return;
  }
  
  // æ£€æŸ¥æ˜¯å¦æœ‰å­è·¯ç”±
  if (route && route.children && route.children.length > 0) {
    // æœ‰å­è·¯ç”±ï¼Œæ˜¾ç¤ºä¾§è¾¹æ 
    activeRoutes(key);
    const firstChild = route.children[0];
    const path = firstChild.path.startsWith('/') ? firstChild.path : `${key}/${firstChild.path}`;
    if (firstChild.query) {
      router.push({ path, query: firstChild.query });
    // æŒ‰ç…§æ­£ç¡®çš„路径构建层级,这里是特殊处理
    let targetPath = key; // ä»Žå½“前点击的菜单路径开始
    let targetQuery = null;
    let currentNode = route;
    let pathSegments = [];
    // å½“前路径是第一段
    pathSegments.push(currentNode.path);
    // é€å±‚添加子路径
    while (currentNode.children && currentNode.children.length > 0) {
      const firstChild = currentNode.children.find(child => !child.hidden);
      if (!firstChild) break;
      // è·³è¿‡ParentView类型的中间节点,直接使用其子节点的path
      if (firstChild.component === 'ParentView' || firstChild.component.name === 'ParentView') {
        currentNode = firstChild;
        pathSegments.push(firstChild.path);
        continue;
      }
      // æ™®é€šèŠ‚ç‚¹å¤„ç†
      currentNode = firstChild;
      // å¦‚果路径不是以/开头,则添加到路径片段中
      if (!firstChild.path.startsWith('/')) {
        pathSegments.push(firstChild.path);
      } else {
        // å¦‚果是绝对路径,则替换之前所有路径
        pathSegments = [firstChild.path];
      }
      targetQuery = firstChild.query;
      // å¦‚果到达叶子节点(没有子节点),则结束查找
      if (!firstChild.children || firstChild.children.length === 0) {
        break;
      }
    }
    // æž„建最终路径
    if (pathSegments.length > 0) {
      // å¦‚果第一段不是以/开头,添加/
      if (!pathSegments[0].startsWith('/')) {
        pathSegments[0] = '/' + pathSegments[0];
      }
      // ç»„合路径 - æŠŠæ•°ç»„中所有路径拼接起来,如果某段包含完整路径(以/开头)则从该段重新开始
      targetPath = pathSegments.reduce((fullPath, segment, index) => {
        if (segment.startsWith('/')) {
          return segment;
        } else if (index === 0) {
          return segment;
        } else {
          return `${fullPath}/${segment}`;
        }
      });
    }
    // å¯¼èˆªåˆ°ç›®æ ‡è·¯ç”±
    if (targetQuery) {
      router.push({ path: targetPath, query: targetQuery });
    } else {
      router.push({ path });
      router.push({ path: targetPath });
    }
  } else {
    // æ²¡æœ‰å­è·¯ç”±ï¼Œéšè—ä¾§è¾¹æ 
@@ -178,11 +253,7 @@
function activeRoutes(key) {
  let routes = [];
  if (key === '/index' || key === '/') {
    // é¦–页时显示折叠的侧边栏,而不是隐藏
    appStore.showCollapsedSidebar();
    return [];
  }
  
  // æŸ¥æ‰¾åŒ¹é…çš„路由
  if (childrenMenus.value && childrenMenus.value.length > 0) {
zhitan-vue/src/layout/components/AppMain.vue
@@ -29,6 +29,7 @@
    background: #110f2e;
    padding: 14px;
    box-sizing: border-box;
    padding-top: 8px;
  }
  .fixed-header + .app-main {
@@ -42,7 +43,7 @@
    }
    .fixed-header + .app-main {
      padding-top: 130px;
      padding-top: 108px; /* 60px(navbar) + 34px(tagsview) + 14px(内边距) */
    }
  }
}
@@ -70,10 +71,15 @@
    }
    .fixed-header + .app-main {
      padding-top: 130px;
      padding-top: 108px; /* 60px(navbar) + 34px(tagsview) + 14px(内边距) */
    }
  }
}
/* ç”¨æˆ·ä¸ªäººèµ„料页特殊高度处理 */
.user-profile-container {
  min-height: calc(100vh - 50px) !important;
}
</style>
<style lang="scss">
zhitan-vue/src/layout/components/Navbar.vue
@@ -41,22 +41,24 @@
import Hamburger from "@/components/Hamburger"
import useAppStore from "@/store/modules/app"
import useSettingsStore from "@/store/modules/settings"
import { useRouter } from "vue-router"
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const router = useRouter()
function toggleSideBar() {
  appStore.toggleSideBar()
}
function handleAlarm() {
  // å¤„理报警按钮点击事件
  console.log('报警按钮被点击')
  // è·³è½¬åˆ°æŠ¥è­¦ç®¡ç†é¡µé¢
  router.push('/alarmmanage/measuremen?modelCode=BJGL')
}
function handleRobot() {
  // å¤„理大模型按钮点击事件
  console.log('大模型按钮被点击')
  // è·³è½¬åˆ°æ™ºèƒ½åŠ©æ‰‹é¡µé¢
  window.open('https://deepseek.tan-zhonghe.com/chat', '_blank')
}
</script>
zhitan-vue/src/layout/components/Sidebar/SidebarItem.vue
@@ -9,7 +9,7 @@
      </app-link>
    </template>
    <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
    <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported @click="handleSubMenuClick">
      <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>
@@ -31,6 +31,9 @@
import { isExternal } from '@/utils/validate'
import AppLink from './Link'
import { getNormalPath } from '@/utils/ruoyi'
import { useRouter } from 'vue-router'
const router = useRouter();
const props = defineProps({
  // route object
@@ -50,6 +53,204 @@
const onlyOneChild = ref({});
/**
 * æŸ¥æ‰¾æœ€æ·±å±‚的子菜单(叶子节点)
 * é€’归查找第一个没有children的子菜单
 */
function findDeepestLeafMenu(route) {
  if (!route) return null;
  // å¦‚果没有子菜单或子菜单为空,则返回当前路由
  if (!route.children || route.children.length === 0) {
    return route;
  }
  // æ‰¾åˆ°ç¬¬ä¸€ä¸ªéžéšè—çš„子菜单
  const firstVisibleChild = route.children.find(child => !child.hidden);
  if (!firstVisibleChild) {
    return route; // å¦‚果所有子菜单都是隐藏的,返回当前路由
  }
  // é€’归查找这个子菜单的最深层子菜单
  return findDeepestLeafMenu(firstVisibleChild);
}
// å¤„理子菜单点击
function handleSubMenuClick(e) {
  // é˜»æ­¢äº‹ä»¶å†’泡
  e.stopPropagation();
  // å¦‚果点击的是子菜单标题,则自动导航到最深层的子菜单
  if (e.target.closest('.el-sub-menu__title')) {
    // æŒ‰ç…§æ­£ç¡®çš„路径构建层级
    let currentNode = props.item;
    console.log('当前点击的菜单项:', JSON.stringify(currentNode, null, 2));
    console.log('basePath:', props.basePath);
    // èŽ·å–ç¬¬ä¸€ä¸ªå¯è§å­èœå•ï¼Œå¦‚æžœæ²¡æœ‰å¯è§å­èœå•ï¼Œä¸è¿›è¡Œè·³è½¬
    if (!currentNode.children || currentNode.children.length === 0) {
      return;
    }
    const firstVisibleChild = currentNode.children.find(child => !child.hidden);
    if (!firstVisibleChild) {
      return;
    }
    console.log('第一个可见子菜单:', JSON.stringify(firstVisibleChild, null, 2));
    // æ—¥å¿—管理等三级菜单特殊处理
    // æ£€æŸ¥æ˜¯å¦æœ‰é¢„先写入的完整路径,如果有则直接使用
    if (firstVisibleChild.fullPath) {
      console.log('使用预先设置的完整路径:', firstVisibleChild.fullPath);
      router.push({ path: firstVisibleChild.fullPath });
      return;
    }
    // åˆ¤æ–­æ˜¯å¦æ˜¯ç³»ç»Ÿ/日志管理类型的三级菜单(例如,/system/log/operlog)
    // è¿™ç§æƒ…况下,直接跳转到第一个子菜单的完整路径
    if (firstVisibleChild.component === 'ParentView' ||
        (typeof firstVisibleChild.component === 'object' &&
         firstVisibleChild.component.name === 'ParentView')) {
      console.log('检测到ParentView组件,处理三级菜单');
      // æ˜¯æœ‰ä¸‰çº§èœå•的情况
      if (firstVisibleChild.children && firstVisibleChild.children.length > 0) {
        const grandChild = firstVisibleChild.children.find(child => !child.hidden);
        if (grandChild) {
          console.log('找到第三级菜单:', JSON.stringify(grandChild, null, 2));
          // åˆ¤æ–­æ˜¯å¦åº”该使用parentPath
          if (firstVisibleChild.parentPath && grandChild.path.startsWith('/')) {
            console.log('使用parentPath属性:', firstVisibleChild.parentPath);
            // å¦‚果子菜单是绝对路径,但有parentPath,则应该使用parentPath作为基础
            let fullPath = firstVisibleChild.parentPath;
            if (!fullPath.startsWith('/')) {
              fullPath = '/' + fullPath;
            }
            // ç¬¬äºŒçº§è·¯å¾„基于根路径
            if (firstVisibleChild.path.startsWith('/')) {
              // ç¬¬äºŒçº§å·²ç»æ˜¯ç»å¯¹è·¯å¾„,截取最后部分
              const pathParts = firstVisibleChild.path.split('/');
              const lastPart = pathParts[pathParts.length - 1];
              fullPath = fullPath + '/' + lastPart;
            } else {
              fullPath = buildFullPath(fullPath, firstVisibleChild.path);
            }
            console.log('二级路径:', fullPath);
            // ç¬¬ä¸‰çº§è·¯å¾„基于二级路径
            if (grandChild.path.startsWith('/')) {
              // ç¬¬ä¸‰çº§æ˜¯ç»å¯¹è·¯å¾„,截取最后部分
              const pathParts = grandChild.path.split('/');
              const lastPart = pathParts[pathParts.length - 1];
              fullPath = fullPath + '/' + lastPart;
            } else {
              fullPath = buildFullPath(fullPath, grandChild.path);
            }
            console.log('三级路径 (最终):', fullPath);
            // å¯¼èˆªåˆ°ç¬¬ä¸‰çº§èœå•
            if (grandChild.query) {
              router.push({ path: fullPath, query: grandChild.query });
            } else {
              router.push({ path: fullPath });
            }
            return;
          }
          // å¸¸è§„路径构建
          let fullPath;
          // ç¬¬ä¸€çº§è·¯å¾„必须是完整的(例如/system)
          if (currentNode.path.startsWith('/')) {
            fullPath = currentNode.path;
          } else {
            fullPath = '/' + currentNode.path;
          }
          console.log('一级路径:', fullPath);
          // ç¬¬äºŒçº§è·¯å¾„必须基于第一级路径(例如/system/log)
          fullPath = buildFullPath(fullPath, firstVisibleChild.path);
          console.log('二级路径:', fullPath);
          // ç¬¬ä¸‰çº§è·¯å¾„必须基于二级路径(例如/system/log/operlog)
          fullPath = buildFullPath(fullPath, grandChild.path);
          console.log('三级路径 (最终):', fullPath);
          // å¯¼èˆªåˆ°ç¬¬ä¸‰çº§èœå•
          if (grandChild.query) {
            console.log('跳转到:', fullPath, '带参数:', grandChild.query);
            router.push({ path: fullPath, query: grandChild.query });
          } else {
            console.log('跳转到:', fullPath);
            router.push({ path: fullPath });
          }
          return;
        }
      }
    }
    console.log('处理标准二级菜单');
    // æ£€æŸ¥æ˜¯å¦éœ€è¦ä½¿ç”¨parentPath
    if (firstVisibleChild.parentPath && firstVisibleChild.path.startsWith('/')) {
      console.log('使用parentPath属性:', firstVisibleChild.parentPath);
      // å¦‚果子菜单是绝对路径,但有parentPath,则应该使用parentPath作为基础
      let fullPath = firstVisibleChild.parentPath;
      if (!fullPath.startsWith('/')) {
        fullPath = '/' + fullPath;
      }
      // æž„建完整路径
      if (firstVisibleChild.path.startsWith('/')) {
        // æˆªå–子路径的最后部分
        const pathParts = firstVisibleChild.path.split('/');
        const lastPart = pathParts[pathParts.length - 1];
        fullPath = fullPath + '/' + lastPart;
      } else {
        fullPath = buildFullPath(fullPath, firstVisibleChild.path);
      }
      console.log('构建的最终路径:', fullPath);
      // å¯¼èˆªåˆ°ç›®æ ‡è·¯ç”±
      if (firstVisibleChild.query) {
        router.push({ path: fullPath, query: firstVisibleChild.query });
      } else {
        router.push({ path: fullPath });
      }
      return;
    }
    // æ ‡å‡†çš„二级菜单处理
    // æž„建正确的路径
    let fullPath;
    // å¤„理第一级路径(例如/system)- å¿…须是完整的路径
    if (currentNode.path.startsWith('/')) {
      fullPath = currentNode.path;
    } else {
      fullPath = '/' + currentNode.path;
    }
    console.log('一级路径:', fullPath);
    // å¤„理第二级路径(例如/system/user)- å¿…须基于第一级路径
    fullPath = buildFullPath(fullPath, firstVisibleChild.path);
    console.log('二级路径 (最终):', fullPath);
    // å¯¼èˆªåˆ°ç›®æ ‡è·¯ç”±
    if (firstVisibleChild.query) {
      console.log('跳转到:', fullPath, '带参数:', firstVisibleChild.query);
      router.push({ path: fullPath, query: firstVisibleChild.query });
    } else {
      console.log('跳转到:', fullPath);
      router.push({ path: fullPath });
    }
  }
}
function hasOneShowingChild(children = [], parent) {
  if (!children) {
    children = [];
@@ -65,8 +266,13 @@
  })
  // When there is only one child router, the child router is displayed by default
  if (showingChildren.length === 1) {
  if (showingChildren.length === 1 && !showingChildren[0].children) {
    return true
  }
  // If the single child also has children, don't treat it as a single showing child
  if (showingChildren.length === 1 && showingChildren[0].children && showingChildren[0].children.length > 0) {
    return false
  }
  // Show parent if there are no child router to display
@@ -100,6 +306,20 @@
  return getNormalPath(props.basePath + '/' + routePath)
}
// æ­£ç¡®æž„建路径
function buildFullPath(base, segment) {
  // å¦‚æžœsegment是绝对路径,直接返回
  if (segment.startsWith('/')) {
    return segment;
  }
  // ç¡®ä¿base有正确的开头斜杠
  const normalizedBase = base.startsWith('/') ? base : '/' + base;
  // æ‹¼æŽ¥è·¯å¾„,避免双斜杠
  return normalizedBase.endsWith('/') ? normalizedBase + segment : normalizedBase + '/' + segment;
}
function hasTitle(title){
  if (title.length > 5) {
    return title;
zhitan-vue/src/layout/components/Sidebar/index.vue
@@ -2,11 +2,11 @@
  <div
    :class="{ 'has-logo': showLogo }"
    :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
    class="sidebar-container-wrapper"
  >
    <el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
      <!-- é¦–页时不显示任何菜单项 -->
    <el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper" view-class="scrollbar-view">
      <!-- å§‹ç»ˆæ˜¾ç¤ºèœå•项,不再根据路径判断 -->
      <el-menu
        v-if="!isHomePage"
        :default-active="activeMenu"
        :collapse="isCollapse"
        :background-color="'transparent'"
@@ -17,15 +17,24 @@
        mode="vertical"
        class="custom-menu"
      >
        <sidebar-item
          v-for="(route, index) in sidebarRouters"
          :key="route.path + index"
          :item="route"
          :base-path="route.path"
        />
        <!-- å½“前是首页看板子路由时,渲染专用路由 -->
        <template v-if="isIndexSubRoute">
          <sidebar-item
            v-for="(route, index) in indexPageRouters"
            :key="route.path + index"
            :item="route"
            :base-path="route.path"
          />
        </template>
        <template v-else>
          <sidebar-item
            v-for="(route, index) in sidebarRouters"
            :key="route.path + index"
            :item="route"
            :base-path="route.path"
          />
        </template>
      </el-menu>
      <!-- é¦–页时的空白区域 -->
      <div v-else class="home-empty-menu"></div>
    </el-scrollbar>
    
    <!-- åº•部用户区域 -->
@@ -94,19 +103,21 @@
const sidebarRouters = computed(() => permissionStore.sidebarRouters)
// åˆ¤æ–­å½“前是否为首页
const isHomePage = computed(() => {
  return route.path === '/index' || route.path === '/' || route.fullPath.startsWith('/index')
// åˆ¤æ–­å½“前是否为首页子路由(/index/index)
const isIndexSubRoute = computed(() => {
  return route.path === '/index/index'
})
// é¦–页专用路由,只有首页一个菜单项
const homePageRouters = computed(() => {
  // ä»ŽåŽŸå§‹è·¯ç”±ä¸­ç­›é€‰å‡ºé¦–é¡µè·¯ç”±
  const homeRoute = sidebarRouters.value.find(route => {
    return route.children && route.children.find(child => child.path === '/index')
  })
  return homeRoute ? [homeRoute] : []
// åˆ¤æ–­å½“前是否为主首页路由(/index或/)
const isMainIndexRoute = computed(() => {
  return route.path === '/index' || route.path === '/'
})
// é¦–页专用路由,首页看板相关菜单
const indexPageRouters = computed(() => {
  // æŸ¥æ‰¾name为Index的路由
  const indexRoute = sidebarRouters.value.find(route => route.name === 'Index')
  return indexRoute ? [indexRoute] : []
})
const showLogo = computed(() => settingsStore.sidebarLogo)
@@ -152,9 +163,41 @@
}
</script>
<style lang="scss" scoped>
:deep(.custom-menu) {
.sidebar-container-wrapper {
  position: relative;
  height: 100%;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
:deep(.scrollbar-wrapper) {
  height: calc(100% - 220px) !important;
  overflow-x: hidden !important;
}
:deep(.scrollbar-view) {
  height: 100%;
  padding-bottom: 20px;
}
:deep(.el-scrollbar__bar.is-vertical) {
  right: 0;
  width: 6px;
}
:deep(.el-scrollbar__thumb) {
  background-color: rgba(144, 147, 153, 0.3);
  &:hover {
    background-color: rgba(144, 147, 153, 0.5);
  }
}
.custom-menu {
  width: 100%;
  padding: 6px 0;
  height: calc(100% - 150px); // ç•™å‡ºåº•部用户区域的空间
  height: auto !important; // æ”¹ä¸ºè‡ªé€‚应高度,避免固定高度导致内容溢出
  transition: all 0.3s ease;
  
  // Override Element Plus default menu styles
  .el-menu-item {
@@ -163,10 +206,17 @@
    border-radius: 4px; 
    margin: 4px 10px;
    width: calc(100% - 20px);
    transition: all 0.2s ease;
    
    &.is-active {
      background-color: #3883FA !important;
      color: #fff !important;
      font-weight: bold;
      position: relative;
      box-shadow: 0 2px 8px rgba(56, 131, 250, 0.5);
      // å·¦ä¾§æŒ‡ç¤ºæ¡
    }
    
    &:hover {
@@ -181,9 +231,17 @@
      border-radius: 4px;
      margin: 4px 10px;
      width: calc(100% - 20px);
      transition: all 0.2s ease;
      
      &:hover {
        background-color: rgba(56, 131, 250, 0.1) !important;
      }
    }
    &.is-active {
      > .el-sub-menu__title {
        color: #3883FA !important;
        font-weight: bold;
      }
    }
    
@@ -203,19 +261,42 @@
        height: 38px !important;
        line-height: 38px !important;
      }
      // Add styling for deeply nested submenus (level 3+)
      .el-sub-menu {
        .el-menu-item {
          padding-left: 65px !important;
          &.is-active {
            padding-left: 65px !important;
          }
        }
        // Level 4
        .el-menu {
          .el-menu-item {
            padding-left: 85px !important;
            &.is-active {
              padding-left: 85px !important;
            }
          }
        }
      }
    }
  }
}
// é¦–页空白菜单区域样式
.home-empty-menu {
  height: calc(100% - 150px);
  height: auto;
  min-height: 100px;
}
// åº•部用户区域样式
.sidebar-footer {
  position: absolute;
  bottom: 72px;
  bottom: 0;
  left: 0;
  width: 100%;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
@@ -390,10 +471,127 @@
  }
}
// æ·»åŠ æ·±è‰²æ¨¡å¼ä¸“ç”¨æ ·å¼
.theme-dark {
  :deep(.custom-menu) {
    // Override Element Plus menu styles for dark theme
    .el-menu-item {
      &.is-active {
        background-color: #4e77f8 !important;
        color: #ffffff !important;
        font-weight: bold;
        box-shadow: 0 2px 10px rgba(78, 119, 248, 0.6);
        position: relative;
        // å·¦ä¾§æŒ‡ç¤ºæ¡
      }
      &:hover {
        background-color: rgba(78, 119, 248, 0.2) !important;
      }
    }
    .el-sub-menu {
      &.is-active {
        > .el-sub-menu__title {
          color: #4e77f8 !important;
          font-weight: bold;
        }
      }
      .el-sub-menu__title {
        &:hover {
          background-color: rgba(78, 119, 248, 0.2) !important;
        }
      }
      // åµŒå¥—子菜单样式
      .el-menu {
        .el-menu-item {
          &.is-active {
            background-color: #4e77f8 !important;
            color: #ffffff !important;
          }
        }
        .el-sub-menu {
          &.is-active {
            > .el-sub-menu__title {
              color: #4e77f8 !important;
            }
          }
        }
      }
    }
  }
}
// Add global style to override Element Plus defaults
:global(.el-menu--vertical .el-menu-item),
:global(.el-menu--vertical .el-sub-menu__title) {
  height: 38px !important;
  line-height: 38px !important;
}
// Add styles for collapsed menu items
:deep(.custom-menu.el-menu--collapse) {
  width: 54px !important;
  .el-menu-item, .el-sub-menu__title {
    width: 36px !important;
    min-width: 36px !important;
    margin: 4px 9px !important; /* 9px是为了确保居中:(54px宽 - 36px菜单项) / 2 = 9px */
    padding: 0 !important;
    display: flex;
    justify-content: center;
    align-items: center;
    border-radius: 4px;
    &.is-active {
      background-color: #3883FA !important;
      color: #fff !important;
      box-shadow: 0 2px 6px rgba(56, 131, 250, 0.4);
      transform: scale(0.95);
      transition: all 0.2s ease;
    }
    .el-icon, .svg-icon {
      margin: 0 !important;
      font-size: 18px !important;
      svg {
        width: 1.2em;
        height: 1.2em;
      }
    }
    // ç¡®ä¿æŠ˜å æ—¶å­èœå•的标题也居中对齐
    .el-sub-menu__icon-arrow {
      display: none;
    }
  }
  // ç¡®ä¿æŠ˜å æ—¶å¼¹å‡ºçš„子菜单有正确样式
  .el-tooltip__trigger:focus:not(.focusing) {
    outline: none;
  }
}
// æ·±è‰²æ¨¡å¼ä¸‹æŠ˜å èœå•的样式
.theme-dark {
  :deep(.custom-menu.el-menu--collapse) {
    .el-menu-item, .el-sub-menu__title {
      &.is-active {
        background-color: #4e77f8 !important;
        color: #ffffff !important;
        box-shadow: 0 2px 8px rgba(78, 119, 248, 0.6);
      }
      &:hover {
        background-color: rgba(78, 119, 248, 0.2) !important;
      }
    }
  }
}
</style>
zhitan-vue/src/layout/components/TagsView/ScrollPane.vue
@@ -99,7 +99,7 @@
    bottom: 0px;
  }
  :deep(.el-scrollbar__wrap) {
    height: 39px;
    height: 34px !important;
  }
}
</style>
zhitan-vue/src/layout/components/TagsView/index.vue
@@ -1,5 +1,5 @@
<template>
  <div id="tags-view-container" class="tags-view-container">
  <div id="tags-view-container" class="tags-view-container" :class="{'theme-dark': sideTheme === 'theme-dark', 'theme-light': sideTheme === 'theme-light'}">
    <scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
      <router-link
        v-for="tag in visitedViews"
@@ -52,6 +52,7 @@
const visitedViews = computed(() => useTagsViewStore().visitedViews)
const routes = computed(() => usePermissionStore().routes)
const theme = computed(() => useSettingsStore().theme)
const sideTheme = computed(() => useSettingsStore().sideTheme)
watch(route, () => {
  addTags()
@@ -232,141 +233,96 @@
</script>
<style lang="scss" scoped>
.themeDark {
  .tags-view-container {
    height: 52px;
    width: 100%;
    background: #1a235d;
    // border-bottom: 1px solid #d8dce5;
    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
    .tags-view-wrapper {
      .tags-view-item {
        display: inline-block;
        position: relative;
        cursor: pointer;
        height: 32px;
        line-height: 30px;
        border: 1px solid #5278f5;
        color: #fff;
        // background: #3271eb;
        padding: 0 12px;
        font-size: 14px;
        margin-left: 6px;
        margin-top: 10px;
        border-radius: 4px;
        font-family: OPPOSans, OPPOSans;
        &:first-of-type {
          margin-left: 16px;
        }
        &:last-of-type {
          margin-right: 15px;
        }
        &.active {
          background-color: #4e77f8 !important;
          color: #fff;
          border-color: #4e77f8 !important;
          &::before {
            content: "";
            background: #fff;
            display: inline-block;
            width: 8px;
            height: 8px;
            border-radius: 50%;
            position: relative;
            margin-right: 5px;
          }
        }
.tags-view-container {
  height: 34px;
  width: calc(100% - 42px);
  margin-top: 10px;
  margin-left: 14px;
  box-sizing: border-box;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
  &.theme-dark {
    background: #0A3465;
    .tags-view-item {
      color: #fff;
      border: 1px solid #0c4685;
      background: rgba(10, 52, 101, .48);
      border-radius: 5px;
      &.active {
        background-color: var(--el-color-primary) !important;
        color: #fff !important;
      }
    }
    .contextmenu {
      margin: 0;
  }
  &.theme-light {
    background: #fff;
    .tags-view-item {
      color: #495060;
      background: #fff;
      z-index: 3000;
      position: absolute;
      list-style-type: none;
      padding: 5px 0;
      border-radius: 4px;
      border: 1px solid #d8dce5;
      &.active {
        background-color: var(--el-color-primary) !important;
        color: #fff !important;
      }
    }
  }
  .tags-view-wrapper {
    .tags-view-item {
      display: inline-block;
      position: relative;
      cursor: pointer;
      height: 26px;
      line-height: 26px;
      border-radius: 3px;
      padding: 0 10px;
      font-size: 12px;
      font-weight: 400;
      color: #333;
      box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
      li {
        margin: 0;
        padding: 7px 16px;
        cursor: pointer;
        &:hover {
          background: #eee;
      margin-left: 5px;
      margin-top: 4px;
      &:first-of-type {
        margin-left: 5px;
      }
      &:last-of-type {
        margin-right: 5px;
      }
      &.active {
        background-color: #42b983;
        color: #fff;
        border-color: #42b983;
        &::before {
          content: "";
          background: #fff;
          display: inline-block;
          width: 8px;
          height: 8px;
          border-radius: 50%;
          position: relative;
          margin-right: 2px;
        }
      }
    }
  }
}
.themeLight {
  .tags-view-container {
    height: 52px;
    width: 100%;
  .contextmenu {
    margin: 0;
    background: #fff;
    // border-bottom: 1px solid #d8dce5;
    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
    .tags-view-wrapper {
      .tags-view-item {
        display: inline-block;
        position: relative;
        cursor: pointer;
        height: 32px;
        line-height: 30px;
        border: 1px solid #d8dce5;
        color: #495060;
        background: #fff;
        padding: 0 12px;
        font-size: 14px;
        margin-left: 6px;
        margin-top: 10px;
        border-radius: 4px;
        font-family: OPPOSans, OPPOSans;
        &:first-of-type {
          margin-left: 16px;
        }
        &:last-of-type {
          margin-right: 15px;
        }
        &.active {
          background-color: #42b983;
          color: #fff;
          border-color: #42b983;
          &::before {
            content: "";
            background: #fff;
            display: inline-block;
            width: 8px;
            height: 8px;
            border-radius: 50%;
            position: relative;
            margin-right: 5px;
          }
        }
      }
    }
    .contextmenu {
    z-index: 3000;
    position: absolute;
    list-style-type: none;
    padding: 5px 0;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 400;
    color: #333;
    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
    li {
      margin: 0;
      background: #fff;
      z-index: 3000;
      position: absolute;
      list-style-type: none;
      padding: 5px 0;
      border-radius: 4px;
      font-size: 12px;
      font-weight: 400;
      color: #333;
      box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
      li {
        margin: 0;
        padding: 7px 16px;
        cursor: pointer;
        &:hover {
          background: #eee;
        }
      padding: 7px 16px;
      cursor: pointer;
      &:hover {
        background: #eee;
      }
    }
  }
@@ -401,6 +357,6 @@
}
.scroll-container .el-scrollbar__wrap {
  height: 50px !important;
  height: 34px !important;
}
</style>
zhitan-vue/src/layout/index.vue
@@ -9,7 +9,7 @@
              <img v-if="sideTheme === 'theme-dark'" :src="systemInfo.leftLogo" class="sidebar-logo" />
              <img v-else :src="systemInfo.leftLogo" class="sidebar-logo" />
            </div>
            <div class="name" v-if="sidebar.opened" :style="{ color: '#fff' }">
            <div class="name" v-if="sidebar.opened" :style="{ color: sideTheme === 'theme-dark' ? '#fff' : '#333' }">
              {{ title }}
            </div>
          </div>
@@ -86,13 +86,13 @@
// è·³è½¬åˆ°æŠ¥è­¦é¡µé¢
function goToAlarm() {
  router.push('/alarm/list')
  router.push('/alarmmanage/measuremen?modelCode=BJGL')
}
// æ‰“å¼€AI大模型对话框
function openAIModel() {
  // è¿™é‡Œå¯ä»¥å®žçŽ°æ‰“å¼€AI对话框的逻辑
  console.log('打开AI大模型对话框')
  // è·³è½¬åˆ°æŒ‡å®šçš„URL
  window.open('https://deepseek.tan-zhonghe.com/chat', '_blank')
}
const classObj = computed(() => ({
@@ -119,21 +119,26 @@
// ç›‘听路由变化,处理首页的侧边栏显示
watchEffect(() => {
  // æ£€æŸ¥æ˜¯å¦æ˜¯é¦–页路由
  if (route.path === '/index' || route.path === '/') {
  // æ£€æŸ¥æ˜¯å¦æ˜¯é¦–页路由,但排除/index/index子路由
  if ((route.path === '/index' || route.path === '/') && route.path !== '/index/index') {
    // é¦–页路由,确保侧边栏不隐藏,但状态是折叠的
    appStore.showCollapsedSidebar()
    appStore.toggleSideBarHide(false) // æ”¹ä¸ºä¸éšè—ä¾§è¾¹æ 
  } else if (route.meta && route.meta.showSidebar === false) {
    // å¦‚果路由明确指定隐藏侧边栏
    appStore.toggleSideBarHide(true)
  } else {
    // å…¶ä»–路由,确保侧边栏可见
    appStore.toggleSideBarHide(false)
  }
})
// ç»„件挂载时,确保首页侧边栏状态正确
onMounted(() => {
  // å¦‚果当前是首页,确保侧边栏是折叠的而不是隐藏的
  if (route.path === '/index' || route.path === '/') {
    appStore.showCollapsedSidebar()
  // å¦‚果当前是首页子页面,只确保侧边栏不被隐藏,但保持折叠/展开状态不变
  if (route.path === '/index/index') {
    // åªè®¾ç½®ä¸éšè—ä¾§è¾¹æ ï¼Œä½†ä¸æ”¹å˜å…¶å±•å¼€/折叠状态
    appStore.toggleSideBarHide(false)
    // ä¸å†å¼ºåˆ¶è®¾ç½®opened为true,保持用户之前的设置
  }
})
@@ -306,13 +311,19 @@
  top: 60px;
  right: 0;
  z-index: 9;
  width: 100%;
  width: calc(100% - #{$base-sidebar-width});
  transition: width 0.28s;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  padding: 0;
}
.hideSidebar .fixed-header {
  width: calc(100% - 54px);
}
.sidebarHide .fixed-header {
  width: 100%;
}
@@ -351,47 +362,12 @@
}
.themeDark {
  .navbar-container {
    background: #1a235d;
    border-bottom: 2px solid #110f2e;
  }
  .navbar {
    background: transparent !important;
    background: #002866 !important;
  }
  
  .sidebar-container {
    background-color: #002866 !important;
  }
}
.themeLight {
  .navbar-container {
    background: #3883FA;
  }
  .navbar {
    background: transparent !important;
    .left {
      .sidebar-logo-container {
        .name {
          color: #fff !important;
        }
      }
    }
    .right {
      .right-menu {
        .right-menu-item {
          color: #fff;
          .right-menu-icon {
            color: #fff;
          }
        }
      }
    }
  }
}
</style>
zhitan-vue/src/permission.js
@@ -14,6 +14,85 @@
const whiteList = ['/login', '/register', '/energy']
/**
 * æŸ¥æ‰¾æœ€æ·±å±‚的子菜单并构建完整路径
 */
function findDeepestPath(route) {
  if (!route) return { path: null, query: null };
  // é¦–先添加当前节点的路径
  let currentNode = route;
  let pathSegments = [];
  if (currentNode.path) {
    pathSegments.push(currentNode.path);
  }
  // é€å±‚添加子路径
  while (currentNode.children && currentNode.children.length > 0) {
    const firstChild = currentNode.children.find(child => !child.hidden);
    if (!firstChild) break;
    // è·³è¿‡ParentView类型的中间节点
    if (firstChild.component === 'ParentView' ||
        (typeof firstChild.component === 'object' &&
         firstChild.component.name === 'ParentView')) {
      currentNode = firstChild;
      // å¦‚果路径不是以/开头,则添加到路径片段中
      if (!firstChild.path.startsWith('/')) {
        pathSegments.push(firstChild.path);
      } else {
        // å¦‚果是绝对路径,则替换之前所有路径
        pathSegments = [firstChild.path];
      }
      continue;
    }
    // æ™®é€šèŠ‚ç‚¹å¤„ç†
    currentNode = firstChild;
    // å¦‚果路径不是以/开头,则添加到路径片段中
    if (!firstChild.path.startsWith('/')) {
      pathSegments.push(firstChild.path);
    } else {
      // å¦‚果是绝对路径,则替换之前所有路径
      pathSegments = [firstChild.path];
    }
    // å¦‚果到达叶子节点,则结束查找
    if (!firstChild.children || firstChild.children.length === 0) {
      break;
    }
  }
  // æž„建最终路径
  let targetPath = '';
  if (pathSegments.length > 0) {
    // å¦‚果第一段不是以/开头,添加/
    if (!pathSegments[0].startsWith('/')) {
      pathSegments[0] = '/' + pathSegments[0];
    }
    // ç»„合路径
    targetPath = pathSegments.reduce((fullPath, segment, index) => {
      if (segment.startsWith('/')) {
        return segment;
      } else if (index === 0) {
        return segment;
      } else {
        // ç¡®ä¿è·¯å¾„之间不会出现重复的斜杠
        const base = fullPath.endsWith('/') ? fullPath.slice(0, -1) : fullPath;
        const part = segment.startsWith('/') ? segment : '/' + segment;
        return `${base}${part}`;
      }
    });
  }
  return {
    path: targetPath,
    query: currentNode.query
  };
}
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
@@ -45,11 +124,23 @@
              if (topMenus.length > 0) {
                // è·³è½¬åˆ°ç¬¬ä¸€ä¸ªèœå•
                const firstMenu = topMenus[0]
                if (firstMenu.children && firstMenu.children.length > 0) {
                  // æœ‰å­èœå•,跳转到第一个子菜单
                // æŸ¥æ‰¾æœ€æ·±å±‚的子菜单并构建路径
                const { path, query } = findDeepestPath(firstMenu);
                if (path) {
                  // æœ‰æœ€æ·±å±‚子菜单,跳转到该菜单
                  if (query) {
                    next({ path, query, replace: true });
                  } else {
                    next({ path, replace: true });
                  }
                  return;
                } else if (firstMenu.children && firstMenu.children.length > 0) {
                  // ä½¿ç”¨åŽŸæœ‰é€»è¾‘
                  const firstChild = firstMenu.children[0]
                  const path = firstMenu.path.endsWith('/') ? firstMenu.path + firstChild.path : `${firstMenu.path}/${firstChild.path}`
                  next({ path: path, replace: true })
                  const childPath = firstMenu.path.endsWith('/') ? firstMenu.path + firstChild.path : `${firstMenu.path}/${firstChild.path}`
                  next({ path: childPath, replace: true })
                  return
                } else {
                  // æ²¡æœ‰å­èœå•,直接跳转
@@ -75,11 +166,23 @@
          if (topMenus.length > 0) {
            // è·³è½¬åˆ°ç¬¬ä¸€ä¸ªèœå•
            const firstMenu = topMenus[0]
            if (firstMenu.children && firstMenu.children.length > 0) {
              // æœ‰å­èœå•,跳转到第一个子菜单
            // æŸ¥æ‰¾æœ€æ·±å±‚的子菜单并构建路径
            const { path, query } = findDeepestPath(firstMenu);
            if (path) {
              // æœ‰æœ€æ·±å±‚子菜单,跳转到该菜单
              if (query) {
                next({ path, query, replace: true });
              } else {
                next({ path, replace: true });
              }
              return;
            } else if (firstMenu.children && firstMenu.children.length > 0) {
              // ä½¿ç”¨åŽŸæœ‰é€»è¾‘
              const firstChild = firstMenu.children[0]
              const path = firstMenu.path.endsWith('/') ? firstMenu.path + firstChild.path : `${firstMenu.path}/${firstChild.path}`
              next({ path: path, replace: true })
              const childPath = firstMenu.path.endsWith('/') ? firstMenu.path + firstChild.path : `${firstMenu.path}/${firstChild.path}`
              next({ path: childPath, replace: true })
              return
            } else {
              // æ²¡æœ‰å­èœå•,直接跳转
@@ -88,6 +191,26 @@
            }
          }
        }
        // è‡ªåŠ¨å¤„ç†å¸¦æœ‰é‡å®šå‘çš„è·¯ç”±
        if (to.matched.length > 0 && to.matched[0].path === to.path) {
          const currentRouteConfig = router.getRoutes().find(r => r.path === to.path);
          if (currentRouteConfig && currentRouteConfig.children && currentRouteConfig.children.length > 0) {
            // æœ‰å­è·¯ç”±ï¼Œè‡ªåŠ¨å¯¼èˆªåˆ°æœ€æ·±å±‚å­èœå•
            const { path, query } = findDeepestPath(currentRouteConfig);
            if (path && path !== to.path) {
              if (query) {
                next({ path, query, replace: true });
              } else {
                next({ path, replace: true });
              }
              return;
            }
          }
        }
        next()
      }
    }
zhitan-vue/src/router/index.js
@@ -67,13 +67,7 @@
        path: '/index',
        component: () => import('@/views/index'),
        name: 'Index',
        meta: { title: '首页', icon: 'dashboard', affix: true, showSidebar: true, breadcrumb: false },
        beforeEnter: (to, from, next) => {
          // èŽ·å–app store并设置侧边栏为折叠状态
          const appStore = useAppStore()
          appStore.showCollapsedSidebar()
          next()
        }
        meta: { title: '首页', icon: 'dashboard', affix: true, showSidebar: true, breadcrumb: false }
      }
    ]
  },
zhitan-vue/src/store/modules/permission.js
@@ -56,8 +56,27 @@
  })
// éåŽ†åŽå°ä¼ æ¥çš„è·¯ç”±å­—ç¬¦ä¸²ï¼Œè½¬æ¢ä¸ºç»„ä»¶å¯¹è±¡
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false, parentRoute = null) {
  return asyncRouterMap.filter(route => {
    // ä¸å†è¿‡æ»¤æŽ‰é¦–页看板相关路由
    /*
    // è¿‡æ»¤æŽ‰é¦–页看板相关路由
    if (route.name === 'Index' && route.meta && route.meta.title === '首页看板') {
      return false;
    }
    // å¦‚果是首页看板的子菜单,也过滤掉
    if (route.path === '/index' || route.path === 'index' ||
        (route.meta && route.meta.title === '首页看板')) {
      return false;
    }
    */
    // è®¾ç½®çˆ¶è·¯ç”±å¼•用
    if (parentRoute) {
      route.parent = parentRoute;
    }
    if (type && route.children) {
      route.children = filterChildren(route.children)
    }
@@ -82,7 +101,8 @@
      }
    }
    if (route.children != null && route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children, route, type)
      // å°†å½“前路由作为父路由传递给子路由
      route.children = filterAsyncRouter(route.children, route, type, route)
    } else {
      delete route['children']
      delete route['redirect']
@@ -97,7 +117,21 @@
    if (el.children && el.children.length) {
      if (el.component === 'ParentView' && !lastRouter) {
        el.children.forEach(c => {
          c.path = el.path + '/' + c.path
          // è®¾ç½®çˆ¶è·¯ç”±å¼•用
          c.parent = el;
          // ç¡®ä¿è·¯å¾„格式正确拼接
          if (el.path) {
            if (c.path.startsWith('/')) {
              // ç»å¯¹è·¯å¾„保持不变
              // ä½†ä¹Ÿè®¾ç½®åŽŸå§‹çˆ¶è·¯å¾„ç”¨äºŽèœå•å¯¼èˆª
              c.parentPath = el.path;
            } else {
              // ç›¸å¯¹è·¯å¾„需要拼接
              c.path = el.path.endsWith('/') ? el.path + c.path : el.path + '/' + c.path;
            }
          }
          if (c.children && c.children.length) {
            children = children.concat(filterChildren(c.children, c))
            return
@@ -108,7 +142,21 @@
      }
    }
    if (lastRouter) {
      el.path = lastRouter.path + '/' + el.path
      // è®¾ç½®çˆ¶è·¯ç”±å¼•用
      el.parent = lastRouter;
      // ç¡®ä¿è·¯å¾„格式正确拼接
      if (lastRouter.path) {
        if (el.path.startsWith('/')) {
          // ç»å¯¹è·¯å¾„保持不变
          // ä½†ä¹Ÿè®¾ç½®åŽŸå§‹çˆ¶è·¯å¾„ç”¨äºŽèœå•å¯¼èˆª
          el.parentPath = lastRouter.path;
        } else {
          // ç›¸å¯¹è·¯å¾„需要拼接
          el.path = lastRouter.path.endsWith('/') ? lastRouter.path + el.path : lastRouter.path + '/' + el.path;
        }
      }
      if (el.children && el.children.length) {
        children = children.concat(filterChildren(el.children, el))
        return
zhitan-vue/src/views/deepanalysis/deepAnalysis.vue
@@ -18,15 +18,23 @@
          </el-select>
        </el-form-item>
        <el-form-item label="时间选择" prop="dataTime">
          <el-date-picker v-if="form.timeType == 'YEAR'" v-model="form.dataTime" type="year" />
          <el-date-picker v-if="form.timeType == 'YEAR'" v-model="form.dataTime" type="year" :clearable="false" />
          <el-date-picker
            v-else-if="form.timeType == 'MONTH'"
            v-model="form.dataTime"
            type="month"
            format="YYYY-MM"
            value-format="YYYY-MM"
            :clearable="false"
          />
          <el-date-picker v-else v-model="form.dataTime" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
          <el-date-picker
            v-else
            v-model="form.dataTime"
            :clearable="false"
            type="date"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -141,7 +149,8 @@
  getFlowCharts({
    energyType: form.value.energyType,
    nodeId: "",
    queryTime: form.value.dataTime,
    queryTime: proxy.dayjs(new Date(form.value.dataTime)).format("YYYY-MM-DD"),
    dataTime: proxy.dayjs(new Date(form.value.dataTime)).format("YYYY-MM-DD"),
    timeType: form.value.timeType,
    modelCode: proxy.$route.query.modelCode,
  }).then((res) => {
@@ -298,6 +307,7 @@
    display: flex;
    justify-content: space-between;
    padding-top: 12px;
    padding: 12px 14px 0;
    .card-list-item {
      width: 24%;
@@ -365,6 +375,7 @@
    display: flex;
    justify-content: space-between;
    padding-top: 12px;
    padding: 12px 14px 0;
    .card-list-item {
      width: 24%;
zhitan-vue/src/views/index.vue
@@ -911,18 +911,20 @@
      margin-top: 14px;
      display: flex;
      width: 100%;
      flex-wrap: wrap;
      flex-wrap: nowrap;
      justify-content: space-between;
      gap: 15px;
      
      &:after {
        content: "";
        flex: auto;
        flex: 0 0 0;
      }
      .card-list-item {
        width: 320px;
        height: 127px;
        width: 0;
        flex: 1 1 320px;
        max-width: 320px;
        height: 135px;
        background: rgba(242, 246, 250, 0.1);
        box-sizing: border-box;
        padding: 16px;
@@ -937,9 +939,9 @@
        &:hover {
          background: rgba(242, 246, 250, 0.15);
        }
        .item-left {
          margin-right: 16px;
          margin-right: 20px;
          
          .top-icon {
            width: 73px;
@@ -965,9 +967,12 @@
              font-size: 14px;
              font-family: OPPOSans-Regular;
              color: rgba(255, 255, 255, 0.65);
              letter-spacing: 0.5px;
              margin-bottom: 4px;
              .unit {
                color: rgba(255, 255, 255, 0.65);
                margin-left: 2px;
                margin-left: 4px;
                font-size: 12px;
                font-weight: normal;
              }
@@ -976,29 +981,31 @@
            .right-value {
              font-weight: 500;
              font-size: 26px;
              margin-top: 4px;
              margin-top: 6px;
              font-family: OPPOSans-Medium;
              color: #fff;
              line-height: 1;
              line-height: 1.2;
              letter-spacing: 0.5px;
            }
          }
          
          .item-bottom {
            display: flex;
            justify-content: space-between;
            margin-top: 14px;
            margin-top: 18px;
            font-family: OPPOSans, OPPOSans;
            font-weight: normal;
            font-size: 12px;
            color: rgba(255, 255, 255, 0.5);
            line-height: 1;
            line-height: 1.2;
            
            .bottom-left, .bottom-right {
              display: flex;
              align-items: center;
              letter-spacing: 0.3px;
              
              :deep(.el-icon) {
                margin-left: 4px;
                margin-left: 6px;
                font-size: 12px;
              }
            }
@@ -1080,7 +1087,7 @@
.themeLight {
  .page {
    padding: 20px;
    background: #f7f8fa;
    background: #fff;
    .card-title {
      width: 132px;
@@ -1094,18 +1101,20 @@
      margin-top: 14px;
      display: flex;
      width: 100%;
      flex-wrap: wrap;
      flex-wrap: nowrap;
      justify-content: space-between;
      gap: 15px;
      
      &:after {
        content: "";
        flex: auto;
        flex: 0 0 0;
      }
      .card-list-item {
        width: 320px;
        height: 127px;
        width: 0;
        flex: 1 1 320px;
        max-width: 320px;
        height: 135px;
        background: #fff;
        box-sizing: border-box;
        padding: 16px;
@@ -1117,7 +1126,7 @@
        align-items: center;
        
        .item-left {
          margin-right: 16px;
          margin-right: 20px;
          
          .top-icon {
            width: 73px;
@@ -1143,9 +1152,12 @@
              font-size: 14px;
              font-family: OPPOSans-Regular;
              color: rgba(0, 0, 0, 0.65);
              letter-spacing: 0.5px;
              margin-bottom: 4px;
              .unit {
                color: rgba(0, 0, 0, 0.65);
                margin-left: 2px;
                margin-left: 4px;
                font-size: 12px;
                font-weight: normal;
              }
@@ -1154,29 +1166,31 @@
            .right-value {
              font-weight: 500;
              font-size: 26px;
              margin-top: 4px;
              margin-top: 6px;
              font-family: OPPOSans-Medium;
              color: #333;
              line-height: 1;
              line-height: 1.2;
              letter-spacing: 0.5px;
            }
          }
          
          .item-bottom {
            display: flex;
            justify-content: space-between;
            margin-top: 14px;
            margin-top: 18px;
            font-family: OPPOSans, OPPOSans;
            font-weight: normal;
            font-size: 12px;
            color: rgba(0, 0, 0, 0.5);
            line-height: 1;
            line-height: 1.2;
            
            .bottom-left, .bottom-right {
              display: flex;
              align-items: center;
              letter-spacing: 0.3px;
              
              :deep(.el-icon) {
                margin-left: 4px;
                margin-left: 6px;
                font-size: 12px;
              }
            }
zhitan-vue/src/views/login.vue
@@ -201,7 +201,7 @@
.login {
  display: flex;
  align-items: center;
  height: 100%;
  height: 100vh;
  background-image: url("@/assets/images/login-background.png");
  background-repeat: no-repeat;
  background-size: cover;
@@ -209,6 +209,7 @@
  position: relative;
  min-width: 700px;
  min-height: 700px;
  background-color:#001146
}
.middle-view {
zhitan-vue/src/views/system/name/name.vue
@@ -1,30 +1,30 @@
<template>
  <div class="app-container page">
    <div class="form-card">
      <el-form :model="form" label-width="80px">
        <el-row class="mb20 mt20">
          <el-col :offset="1" :span="18">
  <div class="app-container page name-settings-page">
    <div class="name-settings-card">
      <el-form :model="form" label-width="120px">
        <el-row class="form-row">
          <el-col :span="18">
            <el-form-item label="系统名称" prop="systemName">
              <el-input v-model="form.systemName" placeholder="请输入系统名称" maxlength="64" show-word-limit />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row class="mb20 mt20">
          <el-col :offset="1" :span="18">
        <el-row class="form-row">
          <el-col :span="18">
            <el-form-item label="登录logo" prop="homeLogo">
              <ImageUpload v-model="form.homeLogo" :fileType="['png']" :limit="1" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row class="mb20 mt20">
          <el-col :offset="1" :span="18">
        <el-row class="form-row">
          <el-col :span="18">
            <el-form-item label="后台logo" prop="leftLogo">
              <ImageUpload v-model="form.leftLogo" :fileType="['png']" :limit="1" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row class="mb20 mt20">
          <el-col :offset="1" :span="18">
        <el-row class="form-row">
          <el-col :span="18">
            <el-form-item label="copyRight" prop="copyRight">
              <el-input
                v-model="form.copyRight"
@@ -37,9 +37,11 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row class="mb20 mt20" style="padding-bottom: 20px">
          <el-col :offset="2" :span="18">
            <el-button type="primary" @click="handleSave">保存</el-button>
        <el-row class="form-row form-footer">
          <el-col :span="18">
            <el-form-item>
              <el-button type="primary" @click="handleSave">保存</el-button>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
@@ -68,6 +70,11 @@
}
getSystemName()
function handleSave() {
  if (!form.value.leftLogo || !form.value.homeLogo) {
    proxy.$modal.msgError("请上传logo")
    return
  }
  if (!form.value.leftLogo.includes(baseUrl)) {
    form.value.leftLogo = baseUrl + form.value.leftLogo
  }
@@ -87,7 +94,51 @@
<style lang="scss" scoped>
@import "@/assets/styles/page.scss";
:deep .avatar-uploader .el-upload {
.name-settings-page {
  height: 100%;
  min-height: calc(100vh - 60px);
  display: flex;
  flex-direction: column;
}
.name-settings-card {
  border-radius: 4px;
  margin: 16px;
  padding: 24px;
  flex: 1;
  height: calc(100vh - 100px);
  overflow-y: auto;
  .el-form {
    width: 100%;
    .form-row {
      margin-bottom: 24px;
      &.form-footer {
        margin-top: 40px;
      }
    }
    .el-form-item {
      margin-bottom: 0;
      .el-form-item__label {
        color: var(--el-text-color-primary, #fff);
      }
      .el-form-item__content {
        .el-button {
          padding: 10px 24px;
        }
      }
    }
  }
}
:deep(.avatar-uploader .el-upload) {
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  cursor: pointer;
@@ -96,11 +147,11 @@
  transition: var(--el-transition-duration-fast);
}
:deep .avatar-uploader .el-upload:hover {
:deep(.avatar-uploader .el-upload:hover) {
  border-color: var(--el-color-primary);
}
:deep .el-icon.avatar-uploader-icon {
:deep(.el-icon.avatar-uploader-icon) {
  font-size: 28px;
  color: #8c939d;
  width: 178px;
zhitan-vue/src/views/system/user/profile/index.vue
@@ -1,5 +1,5 @@
<template>
  <div class="app-container">
  <div class="app-container user-profile-container">
    <el-row :gutter="20">
      <el-col :span="6" :xs="24">
        <el-card class="box-card">
zhitan-vue/vite.config.js
@@ -41,7 +41,7 @@
          target: "https://demo-ems.zhitancloud.com",
          changeOrigin: true,
          secure: true,
        }
        },
      },
    },
    //fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file