gaoluyang
2 天以前 a88eab39f6b2f5c33913d51fac4e885c052ed4a1
整体样式修改
已修改11个文件
1407 ■■■■ 文件已修改
src/assets/styles/sidebar.scss 67 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/variables.module.scss 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 242 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Logo.vue 123 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/SidebarItem.vue 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/index.vue 130 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/index.vue 513 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/index.vue 108 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/settings.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/app.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/settings.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/sidebar.scss
@@ -12,21 +12,18 @@
  }
  .sidebar-container {
    -webkit-transition: width .28s;
    transition: width 0.28s;
    width: $base-sidebar-width !important;
    background-color: $base-menu-background;
    height: 100%;
    position: fixed;
    font-size: 0px;
    top: 50px;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 1001;
    overflow: hidden;
    -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
    box-shadow: none;
    margin: 0 auto;
    box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
    // reset element-ui css
    .horizontal-collapse-transition {
@@ -42,7 +39,7 @@
    }
    .el-scrollbar {
      height: 92%;
      height: 100%;
    }
    &.has-logo {
@@ -54,29 +51,24 @@
    .is-horizontal {
      display: none;
    }
    a {
      display: inline-block;
      width: 80px;
      height: 80px;
      width: 100%;
      overflow: hidden;
      margin-top: 20px;
    }
    .svg-icon {
      //margin-right: 16px;
      margin-right: 16px;
    }
    .el-menu {
      border: none;
      height: 100%;
      width: 100% !important;
      padding: 0 20px !important;
    }
    .el-menu-item, .menu-title {
      line-height: 20px;
      font-size: 14px;
      overflow: hidden !important;
      text-overflow: ellipsis !important;
      white-space: nowrap !important;
@@ -89,69 +81,52 @@
    // menu hover
    .sub-menu-title-noDropdown,
    .el-sub-menu__title {
      border-radius: 10px 10px 10px 10px;
      &:hover {
        background-color: #ffffff !important;
        border-radius: 10px 10px 10px 10px;
        color: $base-menu-color-active !important;
        background-color: rgba(0, 0, 0, 0.06) !important;
      }
    }
    & .theme-dark .is-active > .el-sub-menu__title {
      color: $base-menu-color-active !important;
    }
    & .nest-menu > a {
      right: 20px;
      position: relative;
    }
    & .nest-menu> .el-sub-menu > .el-sub-menu__title {
      right: 20px;
      position: relative;
    }
    & .nest-menu .el-sub-menu>.el-sub-menu__title,
    & .el-sub-menu .el-menu-item {
      min-width: $base-sidebar-width !important;
      height: 80px;
      min-width: 80px !important;
      border-radius: 10px 10px 10px 10px;
      &:hover {
        background-color: #ffffff !important;
        border-radius: 10px 10px 10px 10px;
        color: $base-menu-color-active !important;
        background-color: rgba(0, 0, 0, 0.06) !important;
      }
    }
    & .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,
    & .theme-dark .el-sub-menu .el-menu-item {
      background-color: $base-sub-menu-background;
      border-radius: 10px 10px 10px 10px;
      &:hover {
        background-color: #ffffff !important;
        border-radius: 10px 10px 10px 10px;
        color: $base-menu-color-active !important;
        background-color: $base-sub-menu-hover !important;
      }
    }
  }
  .hideSidebar {
    .sidebar-container {
      width: 120px !important;
      width: 54px !important;
    }
    .main-container {
      margin-left: 120px;
      margin-left: 54px;
    }
    .sub-menu-title-noDropdown {
      padding: 0 0 0 20px !important;
      padding: 0 !important;
      position: relative;
      .el-tooltip {
        padding: 0 !important;
        .svg-icon {
          //margin-left: 20px;
          margin-left: 20px;
        }
      }
    }
@@ -163,7 +138,7 @@
        padding: 0 !important;
        .svg-icon {
          //margin-left: 20px;
          margin-left: 20px;
        }
      }
@@ -226,21 +201,17 @@
// when menu collapsed
.el-menu--vertical {
  width: 120px !important; /* 设置一个固定的宽度 */
  &>.el-menu {
    .svg-icon {
      //margin-right: 16px;
      margin-right: 16px;
    }
  }
  .nest-menu .el-sub-menu>.el-sub-menu__title,
  .el-menu-item {
    border-radius: 10px 10px 10px 10px;
    &:hover {
      // you can use $sub-menuHover
      background-color: #ffffff !important;
      border-radius: 10px 10px 10px 10px;
      color: $base-menu-color-active !important;
      background-color: rgba(0, 0, 0, 0.06) !important;
    }
  }
src/assets/styles/variables.module.scss
@@ -9,30 +9,30 @@
$panGreen: #30B08F;
// 默认主题变量
$menuText: #ffffff;
$menuActiveText: #165DFF;
$menuBg: #165DFF;
$menuHover: #ffffff;
$menuText: #bfcbd9;
$menuActiveText: #409eff;
$menuBg: #304156;
$menuHover: #263445;
// 浅色主题theme-light
$menuLightBg: #165DFF;
$menuLightHover: #ffffff;
$menuLightText: #ffffff;
$menuLightActiveText: #165DFF;
$menuLightBg: #ffffff;
$menuLightHover: #f0f1f5;
$menuLightText: #303133;
$menuLightActiveText: #409EFF;
// 基础变量
$base-sidebar-width: 120px;
$sideBarWidth: 120px;
$base-sidebar-width: 200px;
$sideBarWidth: 200px;
// 菜单暗色变量
$base-menu-color: #bfcbd9;
$base-menu-color-active: #165DFF;
$base-menu-background: #165DFF;
$base-menu-color-active: #f4f4f5;
$base-menu-background: #304156;
$base-sub-menu-background: #1f2d3d;
$base-sub-menu-hover: #001528;
// 组件变量
$--color-primary: #165DFF;
$--color-primary: #409EFF;
$--color-success: #67C23A;
$--color-warning: #E6A23C;
$--color-danger: #F56C6C;
@@ -71,10 +71,10 @@
  --sidebar-bg: #{$menuBg};
  --sidebar-text: #{$menuText};
  --menu-hover: #{$menuHover};
  --navbar-bg: #ffffff;
  --navbar-text: #303133;
  /* splitpanes default-theme 变量 */
  --splitpanes-default-bg: #ffffff;
@@ -119,7 +119,7 @@
  --blockquote-bg: #1d1e1f;
  --blockquote-border: #303030;
  --blockquote-text: #d0d0d0;
  /* Cron 时间表达式 模式变量 */
  --cron-border: #303030;
@@ -127,7 +127,7 @@
  --splitpanes-default-bg: #141414;
  /* 侧边栏菜单覆盖 */
   .sidebar-container {
  .sidebar-container {
    .el-menu-item, .menu-title {
      color: var(--el-text-color-regular);
    }
@@ -199,7 +199,7 @@
      background-color: var(--el-bg-color-overlay);
    }
  }
  /* 下拉菜单样式覆盖 */
  .el-dropdown-menu__item:not(.is-disabled):focus, .el-dropdown-menu__item:not(.is-disabled):hover{
    background-color: var(--navbar-hover) !important;
@@ -211,7 +211,7 @@
    border-left-color: var(--blockquote-border) !important;
    color: var(--blockquote-text) !important;
  }
  /* 时间表达式标题样式覆盖 */
  .popup-result .title {
    background: var(--cron-border);
src/layout/components/Navbar.vue
@@ -1,13 +1,13 @@
<template>
  <div class="navbar">
<!--    <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />-->
<!--    <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />-->
    <div v-if="sidebar.hide">
      <top-nav id="topmenu-container" class="topmenu-container" />
    </div>
    <div class="logo" v-if="!sidebar.hide">
      <img src="@/assets/logo/logo.png" alt=""/>
    </div>
    <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
    <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
<!--    <div v-if="sidebar.hide">-->
<!--      <top-nav id="topmenu-container" class="topmenu-container" />-->
<!--    </div>-->
<!--    <div class="logo" v-if="!sidebar.hide">-->
<!--      <img src="@/assets/logo/logo.png" alt=""/>-->
<!--    </div>-->
    <div class="right-menu">
      <template v-if="appStore.device !== 'mobile'">
        <header-search id="header-search" class="right-menu-item" />
@@ -23,9 +23,9 @@
            <router-link to="/user/profile">
              <el-dropdown-item>个人中心</el-dropdown-item>
            </router-link>
<!--            <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">-->
<!--              <span>布局设置</span>-->
<!--            </el-dropdown-item>-->
            <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
              <span>布局设置</span>
            </el-dropdown-item>
            <el-dropdown-item divided command="logout">
              <span>退出登录</span>
            </el-dropdown-item>
@@ -135,116 +135,114 @@
<style lang='scss' scoped>
.navbar {
  height: 50px;
  overflow: hidden;
  position: fixed; /* 将头部固定 */
  top: 0; /* 在顶部固定 */
  width: 100%; /* 宽度100%,覆盖整个视口 */
  //background-color: #f8f9fa; /* 设置背景颜色,以便更明显地看到效果 */
  z-index: 1000; /* 确保头部在其他内容之上 */
  background: var(--navbar-bg);
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  display: flex;
  justify-content: space-between;
  padding: 0 20px;
  .logo {
    height: 50px;
    line-height: 50px;
    img {
      cursor: pointer;
      width: 146px;
      height: 46px;
    }
  }
  .breadcrumb-container {
    float: left;
  }
  .topmenu-container {
    position: absolute;
  }
  .errLog-container {
    display: inline-block;
    vertical-align: top;
  }
  .right-menu {
    float: right;
    height: 100%;
    line-height: 50px;
    display: flex;
    &:focus {
      outline: none;
    }
    .right-menu-item {
      display: inline-block;
      padding: 0 8px;
      height: 100%;
      font-size: 18px;
      color: #5a5e66;
      vertical-align: text-bottom;
      &.hover-effect {
        cursor: pointer;
        transition: background 0.3s;
        &:hover {
          background: rgba(0, 0, 0, 0.025);
        }
      }
      &.theme-switch-wrapper {
        display: flex;
        align-items: center;
        svg {
          transition: transform 0.3s;
          &:hover {
            transform: scale(1.15);
          }
        }
      }
    }
    .avatar-container {
      margin-right: 0px;
      padding-right: 0px;
      .avatar-wrapper {
        margin-top: 10px;
        right: 5px;
        position: relative;
        .user-avatar {
          cursor: pointer;
          width: 30px;
          height: 30px;
          border-radius: 50%;
        }
        .user-nickname{
          position: relative;
          left: 5px;
          bottom: 10px;
          font-size: 14px;
          font-weight: bold;
        }
        i {
          cursor: pointer;
          position: absolute;
          right: -20px;
          top: 25px;
          font-size: 12px;
        }
      }
    }
  }
    height: 50px;
    overflow: hidden;
    position: relative;
    background: var(--navbar-bg);
    box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
    .hamburger-container {
        line-height: 46px;
        height: 100%;
        float: left;
        cursor: pointer;
        transition: background 0.3s;
        -webkit-tap-highlight-color: transparent;
        &:hover {
            background: rgba(0, 0, 0, 0.025);
        }
    }
    .breadcrumb-container {
        float: left;
    }
    .topmenu-container {
        position: absolute;
        left: 50px;
    }
    .errLog-container {
        display: inline-block;
        vertical-align: top;
    }
    .right-menu {
        float: right;
        height: 100%;
        line-height: 50px;
        display: flex;
        margin-right: 30px;
        &:focus {
            outline: none;
        }
        .right-menu-item {
            display: inline-block;
            padding: 0 8px;
            height: 100%;
            font-size: 18px;
            color: #5a5e66;
            vertical-align: text-bottom;
            &.hover-effect {
                cursor: pointer;
                transition: background 0.3s;
                &:hover {
                    background: rgba(0, 0, 0, 0.025);
                }
            }
            &.theme-switch-wrapper {
                display: flex;
                align-items: center;
                svg {
                    transition: transform 0.3s;
                    &:hover {
                        transform: scale(1.15);
                    }
                }
            }
        }
        .avatar-container {
            margin-right: 0px;
            padding-right: 0px;
            .avatar-wrapper {
                margin-top: 10px;
                right: 5px;
                position: relative;
                .user-avatar {
                    cursor: pointer;
                    width: 30px;
                    height: 30px;
                    border-radius: 50%;
                }
                .user-nickname{
                    position: relative;
                    left: 5px;
                    bottom: 10px;
                    font-size: 14px;
                    font-weight: bold;
                }
                i {
                    cursor: pointer;
                    position: absolute;
                    right: -20px;
                    top: 25px;
                    font-size: 12px;
                }
            }
        }
    }
}
</style>
src/layout/components/Sidebar/Logo.vue
@@ -1,16 +1,16 @@
<template>
  <div class="sidebar-logo-container" :class="{ 'collapse': collapse }">
    <transition name="sidebarLogoFade">
      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
        <img v-if="logo" :src="logo" class="sidebar-logo" />
        <h1 v-else class="sidebar-title">{{ title }}</h1>
      </router-link>
      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
        <img v-if="logo" :src="logo" class="sidebar-logo" />
        <h1 class="sidebar-title">{{ title }}</h1>
      </router-link>
    </transition>
  </div>
    <div class="sidebar-logo-container" :class="{ 'collapse': collapse }">
        <transition name="sidebarLogoFade">
            <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
                <img v-if="logo" :src="logo" class="sidebar-logo" />
                <h1 v-else class="sidebar-title">{{ title }}</h1>
            </router-link>
            <router-link v-else key="expand" class="sidebar-logo-link" to="/">
                <img v-if="logo" :src="logo" class="sidebar-logo" />
                <h1 class="sidebar-title">{{ title }}</h1>
            </router-link>
        </transition>
    </div>
</template>
<script setup>
@@ -19,10 +19,10 @@
import variables from '@/assets/styles/variables.module.scss'
defineProps({
  collapse: {
    type: Boolean,
    required: true
  }
    collapse: {
        type: Boolean,
        required: true
    }
})
const title = import.meta.env.VITE_APP_TITLE
@@ -31,18 +31,18 @@
// 获取Logo背景色
const getLogoBackground = computed(() => {
  if (settingsStore.isDark) {
    return 'var(--sidebar-bg)'
  }
  return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBg
    if (settingsStore.isDark) {
        return 'var(--sidebar-bg)'
    }
    return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBg
})
// 获取Logo文字颜色
const getLogoTextColor = computed(() => {
  if (settingsStore.isDark) {
    return 'var(--sidebar-text)'
  }
  return sideTheme.value === 'theme-dark' ? '#fff' : variables.menuLightText
    if (settingsStore.isDark) {
        return 'var(--sidebar-text)'
    }
    return sideTheme.value === 'theme-dark' ? '#fff' : variables.menuLightText
})
</script>
@@ -50,50 +50,49 @@
@import '@/assets/styles/variables.module.scss';
.sidebarLogoFade-enter-active {
  transition: opacity 1.5s;
    transition: opacity 1.5s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
  opacity: 0;
    opacity: 0;
}
.sidebar-logo-container {
  position: relative;
  width: 100%;
  height: 50px;
  line-height: 50px;
  background: v-bind(getLogoBackground);
  text-align: center;
  overflow: hidden;
  & .sidebar-logo-link {
    height: 100%;
    width: 100%;
    & .sidebar-logo {
      width: 32px;
      height: 32px;
      vertical-align: middle;
      margin-right: 12px;
    }
    & .sidebar-title {
      display: inline-block;
      margin: 0;
      color: v-bind(getLogoTextColor);
      font-weight: 600;
      line-height: 50px;
      font-size: 14px;
      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
      vertical-align: middle;
    }
  }
  &.collapse {
    .sidebar-logo {
      margin-right: 0px;
    }
  }
    position: relative;
    width: 100%;
    height: 50px;
    line-height: 50px;
    background: v-bind(getLogoBackground);
    text-align: center;
    overflow: hidden;
    & .sidebar-logo-link {
        height: 100%;
        width: 100%;
        & .sidebar-logo {
            height: 32px;
            vertical-align: middle;
            margin-right: 12px;
        }
        & .sidebar-title {
            display: inline-block;
            margin: 0;
            color: v-bind(getLogoTextColor);
            font-weight: 600;
            line-height: 50px;
            font-size: 14px;
            font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
            vertical-align: middle;
        }
    }
    &.collapse {
        .sidebar-logo {
            margin-right: 0px;
        }
    }
}
</style>
src/layout/components/Sidebar/SidebarItem.vue
@@ -1,30 +1,30 @@
<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 }" style="display: flex;flex-direction: column;justify-content: center;height: 80px;padding: 0;width: 80px">
          <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" style="width: 30px;height: 30px;margin-bottom: 6px"/>
          <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)" teleported>
      <template v-if="item.meta" #title>
        <svg-icon :icon-class="item.meta && item.meta.icon" style="width: 30px;height: 30px;margin-bottom: 6px"/>
        <span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title}}</span>
      </template>
      <sidebar-item
        v-for="(child, index) in item.children"
        :key="child.path + index"
        :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>
                </el-menu-item>
            </app-link>
        </template>
        <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
            <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, index) in item.children"
                :key="child.path + index"
                :is-nest="true"
                :item="child"
                :base-path="resolvePath(child.path)"
                class="nest-menu"
            />
        </el-sub-menu>
    </div>
</template>
<script setup>
@@ -33,93 +33,71 @@
import { getNormalPath } from '@/utils/ruoyi'
const props = defineProps({
  // route object
  item: {
    type: Object,
    required: true
  },
  isNest: {
    type: Boolean,
    default: false
  },
  basePath: {
    type: String,
    default: ''
  }
    // 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
    }
    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
    if (!children) {
        children = []
    }
    const showingChildren = children.filter(item => {
        if (item.hidden) {
            return false
        }
        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)
    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 ""
  }
    if (title.length > 5) {
        return title
    } else {
        return ""
    }
}
</script>
<style scoped>
:deep(.el-sub-menu__title) {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 !important;
  height: 80px;
  margin-top: 20px;
}
:deep(.submenu-title-noDropdown) {
  padding: 0 !important;
}
:deep(.router-link-exact-active) {
  width: 80px;
  height: 80px;
  background: #FFFFFF;
  border-radius: 10px 10px 10px 10px;
}
:deep(.el-sub-menu__icon-arrow) {
  right: -12px !important;
  &:hover {
    color: #ffffff !important;
  }
}
</style>
src/layout/components/Sidebar/index.vue
@@ -1,25 +1,27 @@
<template>
  <div :class="{ 'has-logo': showLogo }" class="sidebar-container">
<!--    <logo v-if="showLogo" :collapse="isCollapse" />-->
    <el-scrollbar wrap-class="scrollbar-wrapper">
      <el-menu
        :default-active="activeMenu"
        :background-color="getMenuBackground"
        :text-color="getMenuTextColor"
        :unique-opened="true"
        :active-text-color="theme"
        :collapse="false"
        mode="vertical"
      >
        <sidebar-item
          v-for="(route, index) in sidebarRouters"
          :key="route.path + index"
          :item="route"
          :base-path="route.path"
        />
      </el-menu>
    </el-scrollbar>
  </div>
    <div :class="{ 'has-logo': showLogo }" class="sidebar-container">
        <logo v-if="showLogo" :collapse="isCollapse" />
        <el-scrollbar wrap-class="scrollbar-wrapper">
            <el-menu
                :default-active="activeMenu"
                :collapse="isCollapse"
                :background-color="getMenuBackground"
                :text-color="getMenuTextColor"
                :unique-opened="true"
                :active-text-color="theme"
                :collapse-transition="false"
                mode="vertical"
                :class="sideTheme"
            >
                <sidebar-item
                    v-for="(route, index) in sidebarRouters"
                    :key="route.path + index"
                    :item="route"
                    :base-path="route.path"
                />
            </el-menu>
        </el-scrollbar>
    </div>
</template>
<script setup>
@@ -39,62 +41,64 @@
const showLogo = computed(() => settingsStore.sidebarLogo)
const sideTheme = computed(() => settingsStore.sideTheme)
const theme = computed(() => settingsStore.theme)
const isCollapse = computed(() => !appStore.sidebar.opened)
// 获取菜单背景色
const getMenuBackground = computed(() => {
  if (settingsStore.isDark) {
    return 'var(--sidebar-bg)'
  }
  return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBg
    if (settingsStore.isDark) {
        return 'var(--sidebar-bg)'
    }
    return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBg
})
// 获取菜单文字颜色
const getMenuTextColor = computed(() => {
  if (settingsStore.isDark) {
    return 'var(--sidebar-text)'
  }
  return sideTheme.value === 'theme-dark' ? variables.menuText : variables.menuLightText
    if (settingsStore.isDark) {
        return 'var(--sidebar-text)'
    }
    return sideTheme.value === 'theme-dark' ? variables.menuText : variables.menuLightText
})
const activeMenu = computed(() => {
  const { meta, path } = route
  if (meta.activeMenu) {
    return meta.activeMenu
  }
  return path
    const { meta, path } = route
    if (meta.activeMenu) {
        return meta.activeMenu
    }
    return path
})
</script>
<style lang="scss" scoped>
.sidebar-container {
  background-color: v-bind(getMenuBackground);
  .scrollbar-wrapper {
    background-color: v-bind(getMenuBackground);
  }
  .el-menu {
    border: none;
    height: 100%;
    width: 100% !important;
    .el-menu-item, .el-sub-menu__title {
      &:hover {
        background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
      }
    }
    .el-menu-item {
      color: v-bind(getMenuTextColor);
      &.is-active {
        color: var(--menu-active-text, #409eff);
        background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
      }
    }
    .el-sub-menu__title {
      color: v-bind(getMenuTextColor);
    }
  }
    background-color: v-bind(getMenuBackground);
    .scrollbar-wrapper {
        background-color: v-bind(getMenuBackground);
    }
    .el-menu {
        border: none;
        height: 100%;
        width: 100% !important;
        .el-menu-item, .el-sub-menu__title {
            &:hover {
                background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
            }
        }
        .el-menu-item {
            color: v-bind(getMenuTextColor);
            &.is-active {
                color: var(--menu-active-text, #409eff);
                background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
            }
        }
        .el-sub-menu__title {
            color: v-bind(getMenuTextColor);
        }
    }
}
</style>
src/layout/components/TagsView/index.vue
@@ -1,44 +1,44 @@
<template>
  <div id="tags-view-container" class="tags-view-container">
    <scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
      <router-link
        v-for="tag in visitedViews"
        :key="tag.path"
        :data-path="tag.path"
        :class="isActive(tag) ? 'active' : ''"
        :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
        class="tags-view-item"
        :style="activeStyle(tag)"
        @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
        @contextmenu.prevent="openMenu(tag, $event)"
      >
        {{ tag.title }}
        <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
    <div id="tags-view-container" class="tags-view-container">
        <scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
            <router-link
                v-for="tag in visitedViews"
                :key="tag.path"
                :data-path="tag.path"
                :class="isActive(tag) ? 'active' : ''"
                :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
                class="tags-view-item"
                :style="activeStyle(tag)"
                @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
                @contextmenu.prevent="openMenu(tag, $event)"
            >
                {{ tag.title }}
                <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
          <close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
        </span>
      </router-link>
    </scroll-pane>
    <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
      <li @click="refreshSelectedTag(selectedTag)">
        <refresh-right style="width: 1em; height: 1em;" /> 刷新页面
      </li>
      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
        <close style="width: 1em; height: 1em;" /> 关闭当前
      </li>
      <li @click="closeOthersTags">
        <circle-close style="width: 1em; height: 1em;" /> 关闭其他
      </li>
      <li v-if="!isFirstView()" @click="closeLeftTags">
        <back style="width: 1em; height: 1em;" /> 关闭左侧
      </li>
      <li v-if="!isLastView()" @click="closeRightTags">
        <right style="width: 1em; height: 1em;" /> 关闭右侧
      </li>
      <li @click="closeAllTags(selectedTag)">
        <circle-close style="width: 1em; height: 1em;" /> 全部关闭
      </li>
    </ul>
  </div>
            </router-link>
        </scroll-pane>
        <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
            <li @click="refreshSelectedTag(selectedTag)">
                <refresh-right style="width: 1em; height: 1em;" /> 刷新页面
            </li>
            <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
                <close style="width: 1em; height: 1em;" /> 关闭当前
            </li>
            <li @click="closeOthersTags">
                <circle-close style="width: 1em; height: 1em;" /> 关闭其他
            </li>
            <li v-if="!isFirstView()" @click="closeLeftTags">
                <back style="width: 1em; height: 1em;" /> 关闭左侧
            </li>
            <li v-if="!isLastView()" @click="closeRightTags">
                <right style="width: 1em; height: 1em;" /> 关闭右侧
            </li>
            <li @click="closeAllTags(selectedTag)">
                <circle-close style="width: 1em; height: 1em;" /> 全部关闭
            </li>
        </ul>
    </div>
</template>
<script setup>
@@ -64,293 +64,302 @@
const theme = computed(() => useSettingsStore().theme)
watch(route, () => {
  addTags()
  moveToCurrentTag()
    addTags()
    moveToCurrentTag()
})
watch(visible, (value) => {
  if (value) {
    document.body.addEventListener('click', closeMenu)
  } else {
    document.body.removeEventListener('click', closeMenu)
  }
    if (value) {
        document.body.addEventListener('click', closeMenu)
    } else {
        document.body.removeEventListener('click', closeMenu)
    }
})
onMounted(() => {
  initTags()
  addTags()
    initTags()
    addTags()
})
function isActive(r) {
  return r.path === route.path
    return r.path === route.path
}
function activeStyle(tag) {
  if (!isActive(tag)) return {}
  return {
    "background-color": theme.value,
    "border-color": theme.value
  }
    if (!isActive(tag)) return {}
    return {
        "background-color": theme.value,
        "border-color": theme.value
    }
}
function isAffix(tag) {
  return tag.meta && tag.meta.affix
    return tag.meta && tag.meta.affix
}
function isFirstView() {
  try {
    return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath
  } catch (err) {
    return false
  }
    try {
        return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath
    } catch (err) {
        return false
    }
}
function isLastView() {
  try {
    return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath
  } catch (err) {
    return false
  }
    try {
        return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath
    } catch (err) {
        return false
    }
}
function filterAffixTags(routes, basePath = '') {
  let tags = []
  routes.forEach(route => {
    if (route.meta && route.meta.affix) {
      const tagPath = getNormalPath(basePath + '/' + route.path)
      tags.push({
        fullPath: tagPath,
        path: tagPath,
        name: route.name,
        meta: { ...route.meta }
      })
    }
    if (route.children) {
      const tempTags = filterAffixTags(route.children, route.path)
      if (tempTags.length >= 1) {
        tags = [...tags, ...tempTags]
      }
    }
  })
  return tags
    let tags = []
    routes.forEach(route => {
        if (route.meta && route.meta.affix) {
            const tagPath = getNormalPath(basePath + '/' + route.path)
            tags.push({
                fullPath: tagPath,
                path: tagPath,
                name: route.name,
                meta: { ...route.meta }
            })
        }
        if (route.children) {
            const tempTags = filterAffixTags(route.children, route.path)
            if (tempTags.length >= 1) {
                tags = [...tags, ...tempTags]
            }
        }
    })
    return tags
}
function initTags() {
  const res = filterAffixTags(routes.value)
  affixTags.value = res
  for (const tag of res) {
    // Must have tag name
    if (tag.name) {
       useTagsViewStore().addVisitedView(tag)
    }
  }
    const res = filterAffixTags(routes.value)
    affixTags.value = res
    for (const tag of res) {
        // Must have tag name
        if (tag.name) {
            useTagsViewStore().addVisitedView(tag)
        }
    }
}
function addTags() {
  const { name } = route
  if (name) {
    useTagsViewStore().addView(route)
  }
    const { name } = route
    if (name) {
        useTagsViewStore().addView(route)
    }
}
function moveToCurrentTag() {
  nextTick(() => {
    for (const r of visitedViews.value) {
      if (r.path === route.path) {
        scrollPaneRef.value.moveToTarget(r)
        // when query is different then update
        if (r.fullPath !== route.fullPath) {
          useTagsViewStore().updateVisitedView(route)
        }
      }
    }
  })
    nextTick(() => {
        for (const r of visitedViews.value) {
            if (r.path === route.path) {
                scrollPaneRef.value.moveToTarget(r)
                // when query is different then update
                if (r.fullPath !== route.fullPath) {
                    useTagsViewStore().updateVisitedView(route)
                }
            }
        }
    })
}
function refreshSelectedTag(view) {
  proxy.$tab.refreshPage(view)
  if (route.meta.link) {
    useTagsViewStore().delIframeView(route)
  }
    proxy.$tab.refreshPage(view)
    if (route.meta.link) {
        useTagsViewStore().delIframeView(route)
    }
}
function closeSelectedTag(view) {
  proxy.$tab.closePage(view).then(({ visitedViews }) => {
    if (isActive(view)) {
      toLastView(visitedViews, view)
    }
  })
    proxy.$tab.closePage(view).then(({ visitedViews }) => {
        if (isActive(view)) {
            toLastView(visitedViews, view)
        }
    })
}
function closeRightTags() {
  proxy.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
    if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
      toLastView(visitedViews)
    }
  })
    proxy.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
        if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
            toLastView(visitedViews)
        }
    })
}
function closeLeftTags() {
  proxy.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
    if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
      toLastView(visitedViews)
    }
  })
    proxy.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
        if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
            toLastView(visitedViews)
        }
    })
}
function closeOthersTags() {
  router.push(selectedTag.value).catch(() => { })
  proxy.$tab.closeOtherPage(selectedTag.value).then(() => {
    moveToCurrentTag()
  })
    router.push(selectedTag.value).catch(() => { })
    proxy.$tab.closeOtherPage(selectedTag.value).then(() => {
        moveToCurrentTag()
    })
}
function closeAllTags(view) {
  proxy.$tab.closeAllPage().then(({ visitedViews }) => {
    if (affixTags.value.some(tag => tag.path === route.path)) {
      return
    }
    toLastView(visitedViews, view)
  })
    proxy.$tab.closeAllPage().then(({ visitedViews }) => {
        if (affixTags.value.some(tag => tag.path === route.path)) {
            return
        }
        toLastView(visitedViews, view)
    })
}
function toLastView(visitedViews, view) {
  const latestView = visitedViews.slice(-1)[0]
  if (latestView) {
    router.push(latestView.fullPath)
  } else {
    // now the default is to redirect to the home page if there is no tags-view,
    // you can adjust it according to your needs.
    if (view.name === 'Dashboard') {
      // to reload home page
      router.replace({ path: '/redirect' + view.fullPath })
    } else {
      router.push('/')
    }
  }
    const latestView = visitedViews.slice(-1)[0]
    if (latestView) {
        router.push(latestView.fullPath)
    } else {
        // now the default is to redirect to the home page if there is no tags-view,
        // you can adjust it according to your needs.
        if (view.name === 'Dashboard') {
            // to reload home page
            router.replace({ path: '/redirect' + view.fullPath })
        } else {
            router.push('/')
        }
    }
}
function openMenu(tag, e) {
  const menuMinWidth = 105
  const offsetLeft = proxy.$el.getBoundingClientRect().left // container margin left
  const offsetWidth = proxy.$el.offsetWidth // container width
  const maxLeft = offsetWidth - menuMinWidth // left boundary
  const l = e.clientX - offsetLeft + 15 // 15: margin right
  if (l > maxLeft) {
    left.value = maxLeft
  } else {
    left.value = l
  }
  top.value = e.clientY
  visible.value = true
  selectedTag.value = tag
    const menuMinWidth = 105
    const offsetLeft = proxy.$el.getBoundingClientRect().left // container margin left
    const offsetWidth = proxy.$el.offsetWidth // container width
    const maxLeft = offsetWidth - menuMinWidth // left boundary
    const l = e.clientX - offsetLeft + 15 // 15: margin right
    if (l > maxLeft) {
        left.value = maxLeft
    } else {
        left.value = l
    }
    top.value = e.clientY
    visible.value = true
    selectedTag.value = tag
}
function closeMenu() {
  visible.value = false
    visible.value = false
}
function handleScroll() {
  closeMenu()
    closeMenu()
}
</script>
<style lang="scss" scoped>
.tags-view-container {
  height: 34px;
  width: 100%;
  position: fixed; /* 将头部固定 */
  top: 50px; /* 在顶部固定 */
  z-index: 1000; /* 确保头部在其他内容之上 */
  background: #fff;
  box-shadow: none;
  .tags-view-wrapper {
    .tags-view-item {
      display: inline-block;
      position: relative;
      cursor: pointer;
      height: 30px;
      line-height: 26px;
      //border: 1px solid var(--tags-item-border, #d8dce5);
      color: var(--tags-item-text, #495060);
      background: var(--tags-item-bg, #fff);
      padding: 2px 16px;
      font-size: 12px;
      //margin-left: 5px;
      margin-top: 4px;
      &:first-of-type {
        margin-left: 15px;
      }
      &:last-of-type {
        margin-right: 15px;
      }
      &.active {
        border-radius: 10px 10px 0px 0px;
        background-color: #F7F7F7 !important;
        color: #165DFF;
      }
    }
  }
  .contextmenu {
    margin: 0;
    background: var(--el-bg-color-overlay, #fff);
    z-index: 3000;
    position: absolute;
    list-style-type: none;
    padding: 5px 0;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 400;
    color: var(--tags-item-text, #333);
    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
    border: 1px solid var(--el-border-color-light, #e4e7ed);
    li {
      margin: 0;
      padding: 7px 16px;
      cursor: pointer;
      &:hover {
        background: var(--tags-item-hover, #eee);
      }
    }
  }
    height: 34px;
    width: 100%;
    background: var(--tags-bg, #fff);
    border-bottom: 1px solid var(--tags-item-border, #d8dce5);
    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
    .tags-view-wrapper {
        .tags-view-item {
            display: inline-block;
            position: relative;
            cursor: pointer;
            height: 26px;
            line-height: 26px;
            border: 1px solid var(--tags-item-border, #d8dce5);
            color: var(--tags-item-text, #495060);
            background: var(--tags-item-bg, #fff);
            padding: 0 8px;
            font-size: 12px;
            margin-left: 5px;
            margin-top: 4px;
            &:first-of-type {
                margin-left: 15px;
            }
            &: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 {
        margin: 0;
        background: var(--el-bg-color-overlay, #fff);
        z-index: 3000;
        position: absolute;
        list-style-type: none;
        padding: 5px 0;
        border-radius: 4px;
        font-size: 12px;
        font-weight: 400;
        color: var(--tags-item-text, #333);
        box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
        border: 1px solid var(--el-border-color-light, #e4e7ed);
        li {
            margin: 0;
            padding: 7px 16px;
            cursor: pointer;
            &:hover {
                background: var(--tags-item-hover, #eee);
            }
        }
    }
}
</style>
<style lang="scss">
//reset element css of el-icon-close
.tags-view-wrapper {
  .tags-view-item {
    .el-icon-close {
      width: 16px;
      height: 16px;
      vertical-align: 2px;
      border-radius: 50%;
      text-align: center;
      transition: all .3s cubic-bezier(.645, .045, .355, 1);
      transform-origin: 100% 50%;
      &:before {
        transform: scale(.6);
        display: inline-block;
        vertical-align: -3px;
      }
      &:hover {
        background-color: var(--tags-close-hover, #b4bccc);
        color: #fff;
        width: 12px !important;
        height: 12px !important;
      }
    }
  }
    .tags-view-item {
        .el-icon-close {
            width: 16px;
            height: 16px;
            vertical-align: 2px;
            border-radius: 50%;
            text-align: center;
            transition: all .3s cubic-bezier(.645, .045, .355, 1);
            transform-origin: 100% 50%;
            &:before {
                transform: scale(.6);
                display: inline-block;
                vertical-align: -3px;
            }
            &:hover {
                background-color: var(--tags-close-hover, #b4bccc);
                color: #fff;
                width: 12px !important;
                height: 12px !important;
            }
        }
    }
}
</style>
src/layout/index.vue
@@ -1,16 +1,16 @@
<template>
  <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
    <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
    <navbar @setLayout="setLayout" />
    <sidebar v-if="!sidebar.hide" class="sidebar-container" />
    <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
      <div :class="{ 'fixed-header': fixedHeader }">
        <tags-view v-if="needTagsView" />
      </div>
      <app-main />
      <settings ref="settingRef" />
    </div>
  </div>
    <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
        <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
        <sidebar v-if="!sidebar.hide" class="sidebar-container" />
        <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
            <div :class="{ 'fixed-header': fixedHeader }">
                <navbar @setLayout="setLayout" />
                <tags-view v-if="needTagsView" />
            </div>
            <app-main />
            <settings ref="settingRef" />
        </div>
    </div>
</template>
<script setup>
@@ -29,84 +29,84 @@
const fixedHeader = computed(() => settingsStore.fixedHeader)
const classObj = computed(() => ({
  hideSidebar: !sidebar.value.opened,
  openSidebar: sidebar.value.opened,
  withoutAnimation: sidebar.value.withoutAnimation,
  mobile: device.value === 'mobile'
    hideSidebar: !sidebar.value.opened,
    openSidebar: sidebar.value.opened,
    withoutAnimation: sidebar.value.withoutAnimation,
    mobile: device.value === 'mobile'
}))
const { width, height } = useWindowSize()
const WIDTH = 992 // refer to Bootstrap's responsive design
watch(() => device.value, () => {
  if (device.value === 'mobile' && sidebar.value.opened) {
    useAppStore().closeSideBar({ withoutAnimation: false })
  }
    if (device.value === 'mobile' && sidebar.value.opened) {
        useAppStore().closeSideBar({ withoutAnimation: false })
    }
})
watchEffect(() => {
  if (width.value - 1 < WIDTH) {
    useAppStore().toggleDevice('mobile')
    useAppStore().closeSideBar({ withoutAnimation: true })
  } else {
    useAppStore().toggleDevice('desktop')
  }
    if (width.value - 1 < WIDTH) {
        useAppStore().toggleDevice('mobile')
        useAppStore().closeSideBar({ withoutAnimation: true })
    } else {
        useAppStore().toggleDevice('desktop')
    }
})
function handleClickOutside() {
  useAppStore().closeSideBar({ withoutAnimation: false })
    useAppStore().closeSideBar({ withoutAnimation: false })
}
const settingRef = ref(null)
function setLayout() {
  settingRef.value.openSetting()
    settingRef.value.openSetting()
}
</script>
<style lang="scss" scoped>
  @import "@/assets/styles/mixin.scss";
  @import "@/assets/styles/variables.module.scss";
@import "@/assets/styles/mixin.scss";
@import "@/assets/styles/variables.module.scss";
.app-wrapper {
  @include clearfix;
  position: relative;
  height: 100%;
  width: 100%;
  &.mobile.openSidebar {
    position: fixed;
    top: 0;
  }
    @include clearfix;
    position: relative;
    height: 100%;
    width: 100%;
    &.mobile.openSidebar {
        position: fixed;
        top: 0;
    }
}
.drawer-bg {
  background: #000;
  opacity: 0.3;
  width: 100%;
  top: 0;
  height: 100%;
  position: absolute;
  z-index: 999;
    background: #000;
    opacity: 0.3;
    width: 100%;
    top: 0;
    height: 100%;
    position: absolute;
    z-index: 999;
}
.fixed-header {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 9;
  width: calc(100% - #{$base-sidebar-width});
  transition: width 0.28s;
    position: fixed;
    top: 0;
    right: 0;
    z-index: 9;
    width: calc(100% - #{$base-sidebar-width});
    transition: width 0.28s;
}
.hideSidebar .fixed-header {
  width: calc(100% - 120px);
    width: calc(100% - 54px);
}
.sidebarHide .fixed-header {
  width: 100%;
    width: 100%;
}
.mobile .fixed-header {
  width: 100%;
    width: 100%;
}
</style>
src/settings.js
@@ -37,7 +37,7 @@
  /**
   * 是否显示动态标题
   */
  dynamicTitle: false,
  dynamicTitle: true,
  /**
   * @type {string | array} 'production' | ['production', 'development']
src/store/modules/app.js
@@ -5,7 +5,7 @@
  {
    state: () => ({
      sidebar: {
        opened: true,
        opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
        withoutAnimation: false,
        hide: false
      },
src/store/modules/settings.js
@@ -14,7 +14,7 @@
  {
    state: () => ({
      title: '',
      theme: storageSetting.theme || '#165DFF',
      theme: storageSetting.theme || '#409EFF',
      sideTheme: storageSetting.sideTheme || sideTheme,
      showSettings: showSettings,
      topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,