AliooWang
2025-04-14 b8ced8ec593ad002e06173952acf73f405926bd9
Merge pull request #60 from zhitan-cloud/jiayu1.0

Jiayu1.0
已修改8个文件
436 ■■■■ 文件已修改
zhitan-vue/src/assets/styles/page.scss 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/assets/styles/sidebar.scss 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/components/Breadcrumb/index.vue 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/layout/components/Sidebar/SidebarItem.vue 220 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/layout/components/TagsView/index.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/permission.js 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/store/modules/permission.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/views/index.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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
@@ -307,6 +307,7 @@
    }
    .main-container {
      background-color: #f7f8fa;
      height: 100%;
      transition: margin-left 0.28s;
      margin-left: $base-sidebar-width;
@@ -390,7 +391,7 @@
      .menu-title {
        overflow: hidden !important;
        font-weight: 400 !important;
        font-size: 16px !important;
        font-size: 14px !important;
      }
      // @media (min-width: 1440px) {
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) {
zhitan-vue/src/layout/components/Sidebar/SidebarItem.vue
@@ -84,65 +84,169 @@
  if (e.target.closest('.el-sub-menu__title')) {
    // 按照正确的路径构建层级
    let currentNode = props.item;
    let pathSegments = [];
    
    // 首先添加当前节点的路径
    if (currentNode.path) {
      pathSegments.push(currentNode.path);
    console.log('当前点击的菜单项:', JSON.stringify(currentNode, null, 2));
    console.log('basePath:', props.basePath);
    // 获取第一个可见子菜单,如果没有可见子菜单,不进行跳转
    if (!currentNode.children || currentNode.children.length === 0) {
      return;
    }
    
    // 逐层添加子路径
    while (currentNode.children && currentNode.children.length > 0) {
      const firstChild = currentNode.children.find(child => !child.hidden);
      if (!firstChild) break;
      // 跳过ParentView类型的中间节点
      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];
      }
      // 如果到达叶子节点,则结束查找
      if (!firstChild.children || firstChild.children.length === 0) {
        break;
      }
    const firstVisibleChild = currentNode.children.find(child => !child.hidden);
    if (!firstVisibleChild) {
      return;
    }
    
    // 构建最终路径
    if (pathSegments.length > 0) {
      // 如果第一段不是以/开头,添加/
      if (!pathSegments[0].startsWith('/')) {
        pathSegments[0] = '/' + pathSegments[0];
      }
    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组件,处理三级菜单');
      
      // 组合路径
      const targetPath = pathSegments.reduce((fullPath, segment, index) => {
        if (segment.startsWith('/')) {
          return segment;
        } else if (index === 0) {
          return segment;
        } else {
          return `${fullPath}/${segment}`;
      // 是有三级菜单的情况
      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;
        }
      });
      // 导航到目标路由,如果有查询参数则添加
      if (currentNode.query) {
        router.push({ path: targetPath, query: currentNode.query });
      } else {
        router.push({ path: targetPath });
      }
    }
    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 });
    }
  }
}
@@ -202,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/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()
@@ -238,10 +239,37 @@
  margin-top: 10px;
  margin-left: 14px;
  box-sizing: border-box;
 // 添加水平内边距与app-main的内边距一致
 background: #0A3465;
  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;
      }
    }
  }
  &.theme-light {
    background: #fff;
    .tags-view-item {
      color: #495060;
      background: #fff;
      border: 1px solid #d8dce5;
      &.active {
        background-color: var(--el-color-primary) !important;
        color: #fff !important;
      }
    }
  }
  .tags-view-wrapper {
    .tags-view-item {
      display: inline-block;
@@ -249,10 +277,8 @@
      cursor: pointer;
      height: 26px;
      line-height: 26px;
      border: 1px solid #d8dce5;
      color: #495060;
      background: #fff;
      padding: 0 8px;
      border-radius: 3px;
      padding: 0 10px;
      font-size: 12px;
      margin-left: 5px;
      margin-top: 4px;
@@ -278,25 +304,25 @@
        }
      }
    }
    .contextmenu {
  }
  .contextmenu {
    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;
      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;
      }
    }
  }
zhitan-vue/src/permission.js
@@ -35,9 +35,16 @@
    
    // 跳过ParentView类型的中间节点
    if (firstChild.component === 'ParentView' || 
        (typeof firstChild.component === 'object' && firstChild.component.name === 'ParentView')) {
        (typeof firstChild.component === 'object' &&
         firstChild.component.name === 'ParentView')) {
      currentNode = firstChild;
      pathSegments.push(firstChild.path);
      // 如果路径不是以/开头,则添加到路径片段中
      if (!firstChild.path.startsWith('/')) {
        pathSegments.push(firstChild.path);
      } else {
        // 如果是绝对路径,则替换之前所有路径
        pathSegments = [firstChild.path];
      }
      continue;
    }
    
@@ -72,7 +79,10 @@
      } else if (index === 0) {
        return segment;
      } else {
        return `${fullPath}/${segment}`;
        // 确保路径之间不会出现重复的斜杠
        const base = fullPath.endsWith('/') ? fullPath.slice(0, -1) : fullPath;
        const part = segment.startsWith('/') ? segment : '/' + segment;
        return `${base}${part}`;
      }
    });
  }
zhitan-vue/src/store/modules/permission.js
@@ -120,7 +120,18 @@
          // 设置父路由引用
          c.parent = el;
          
          c.path = el.path + '/' + c.path
          // 确保路径格式正确拼接
          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
@@ -134,7 +145,18 @@
      // 设置父路由引用
      el.parent = lastRouter;
      
      el.path = lastRouter.path + '/' + el.path
      // 确保路径格式正确拼接
      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/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;
              }
            }