main
			
			
		
		
							parent
							
								
									064cba6373
								
							
						
					
					
						commit
						c5180c0dfe
					
				| 
						 | 
				
			
			@ -116,9 +116,13 @@
 | 
			
		|||
      width: 54px !important;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    .main-container {
 | 
			
		||||
      margin-left: 54px;
 | 
			
		||||
    }
 | 
			
		||||
    .layMain-container {
 | 
			
		||||
      margin-left: 0 !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .sub-menu-title-noDropdown {
 | 
			
		||||
      padding: 0 !important;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,12 @@ const appStore = useAppStore()
 | 
			
		|||
const settingsStore = useSettingsStore()
 | 
			
		||||
const permissionStore = usePermissionStore()
 | 
			
		||||
 | 
			
		||||
const sidebarRouters =  computed(() => permissionStore.sidebarRouters);
 | 
			
		||||
const sidebarRouters =  computed(() => permissionStore.sidebarRouters.filter((item) => {
 | 
			
		||||
  return item.path === localStorage.getItem('CURRENT_MENU');
 | 
			
		||||
}));
 | 
			
		||||
// const sidebarRouters =  computed(() => permissionStore.sidebarRouters.filter((item) => {
 | 
			
		||||
//   return item.path === localStorage.getItem('CURRENT_MENU')
 | 
			
		||||
// }));
 | 
			
		||||
const showLogo = computed(() => settingsStore.sidebarLogo);
 | 
			
		||||
const sideTheme = computed(() => settingsStore.sideTheme);
 | 
			
		||||
const theme = computed(() => settingsStore.theme);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,68 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <section class="app-main">
 | 
			
		||||
    <router-view v-slot="{ Component, route }">
 | 
			
		||||
      <transition name="fade-transform" mode="out-in">
 | 
			
		||||
        <keep-alive :include="tagsViewStore.cachedViews">
 | 
			
		||||
          <component v-if="!route.meta.link" :is="Component" :key="route.path"/>
 | 
			
		||||
        </keep-alive>
 | 
			
		||||
      </transition>
 | 
			
		||||
    </router-view>
 | 
			
		||||
    <iframe-toggle />
 | 
			
		||||
  </section>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import iframeToggle from "./IframeToggle/index"
 | 
			
		||||
import useTagsViewStore from '@/store/modules/tagsView'
 | 
			
		||||
 | 
			
		||||
const tagsViewStore = useTagsViewStore()
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.app-main {
 | 
			
		||||
  /* 50= navbar  50  */
 | 
			
		||||
  min-height: calc(100vh - 50px);
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fixed-header + .app-main {
 | 
			
		||||
  padding-top: 50px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hasTagsView {
 | 
			
		||||
  .app-main {
 | 
			
		||||
    /* 84 = navbar + tags-view = 50 + 34 */
 | 
			
		||||
    min-height: calc(100vh - 84px);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .fixed-header + .app-main {
 | 
			
		||||
    padding-top: 84px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
// fix css style bug in open el-dialog
 | 
			
		||||
.el-popup-parent--hidden {
 | 
			
		||||
  .fixed-header {
 | 
			
		||||
    padding-right: 6px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::-webkit-scrollbar {
 | 
			
		||||
  width: 6px;
 | 
			
		||||
  height: 6px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::-webkit-scrollbar-track {
 | 
			
		||||
  background-color: #f1f1f1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::-webkit-scrollbar-thumb {
 | 
			
		||||
  background-color: #c0c0c0;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <transition-group name="fade-transform" mode="out-in">
 | 
			
		||||
    <inner-link
 | 
			
		||||
      v-for="(item, index) in tagsViewStore.iframeViews"
 | 
			
		||||
      :key="item.path"
 | 
			
		||||
      :iframeId="'iframe' + index"
 | 
			
		||||
      v-show="route.path === item.path"
 | 
			
		||||
      :src="iframeUrl(item.meta.link, item.query)"
 | 
			
		||||
    ></inner-link>
 | 
			
		||||
  </transition-group>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import InnerLink from "../InnerLink/index";
 | 
			
		||||
import useTagsViewStore from "@/store/modules/tagsView";
 | 
			
		||||
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const tagsViewStore = useTagsViewStore();
 | 
			
		||||
 | 
			
		||||
function iframeUrl(url, query) {
 | 
			
		||||
  if (Object.keys(query).length > 0) {
 | 
			
		||||
    let params = Object.keys(query).map((key) => key + "=" + query[key]).join("&");
 | 
			
		||||
    return url + "?" + params;
 | 
			
		||||
  }
 | 
			
		||||
  return url;
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div :style="'height:' + height">
 | 
			
		||||
    <iframe
 | 
			
		||||
      :id="iframeId"
 | 
			
		||||
      style="width: 100%; height: 100%"
 | 
			
		||||
      :src="src"
 | 
			
		||||
      frameborder="no"
 | 
			
		||||
    ></iframe>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  src: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: "/"
 | 
			
		||||
  },
 | 
			
		||||
  iframeId: {
 | 
			
		||||
    type: String
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const height = ref(document.documentElement.clientHeight - 94.5 + "px");
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,188 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="navbar">
 | 
			
		||||
    <top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav" />
 | 
			
		||||
    <div class="right-menu">
 | 
			
		||||
      <template v-if="appStore.device !== 'mobile'">
 | 
			
		||||
        <header-search id="header-search" class="right-menu-item" />
 | 
			
		||||
 | 
			
		||||
        <el-tooltip content="源码地址" effect="dark" placement="bottom">
 | 
			
		||||
          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
 | 
			
		||||
        <el-tooltip content="文档地址" effect="dark" placement="bottom">
 | 
			
		||||
          <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
 | 
			
		||||
        <screenfull id="screenfull" class="right-menu-item hover-effect" />
 | 
			
		||||
 | 
			
		||||
        <el-tooltip content="布局大小" effect="dark" placement="bottom">
 | 
			
		||||
          <size-select id="size-select" class="right-menu-item hover-effect" />
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
      </template>
 | 
			
		||||
      <div class="avatar-container">
 | 
			
		||||
        <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
 | 
			
		||||
          <div class="avatar-wrapper">
 | 
			
		||||
            <img :src="userStore.avatar" class="user-avatar" />
 | 
			
		||||
            <el-icon><caret-bottom /></el-icon>
 | 
			
		||||
          </div>
 | 
			
		||||
          <template #dropdown>
 | 
			
		||||
            <el-dropdown-menu>
 | 
			
		||||
              <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 divided command="logout">
 | 
			
		||||
                <span>退出登录</span>
 | 
			
		||||
              </el-dropdown-item>
 | 
			
		||||
            </el-dropdown-menu>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-dropdown>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { ElMessageBox } from 'element-plus'
 | 
			
		||||
import Breadcrumb from '@/components/Breadcrumb'
 | 
			
		||||
import TopNav from '@/components/TopNav'
 | 
			
		||||
import Hamburger from '@/components/Hamburger'
 | 
			
		||||
import Screenfull from '@/components/Screenfull'
 | 
			
		||||
import SizeSelect from '@/components/SizeSelect'
 | 
			
		||||
import HeaderSearch from '@/components/HeaderSearch'
 | 
			
		||||
import RuoYiGit from '@/components/RuoYi/Git'
 | 
			
		||||
import RuoYiDoc from '@/components/RuoYi/Doc'
 | 
			
		||||
import useAppStore from '@/store/modules/app'
 | 
			
		||||
import useUserStore from '@/store/modules/user'
 | 
			
		||||
import useSettingsStore from '@/store/modules/settings'
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
const userStore = useUserStore()
 | 
			
		||||
const settingsStore = useSettingsStore()
 | 
			
		||||
 | 
			
		||||
function toggleSideBar() {
 | 
			
		||||
  appStore.toggleSideBar()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleCommand(command) {
 | 
			
		||||
  switch (command) {
 | 
			
		||||
    case "setLayout":
 | 
			
		||||
      setLayout();
 | 
			
		||||
      break;
 | 
			
		||||
    case "logout":
 | 
			
		||||
      logout();
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function logout() {
 | 
			
		||||
  ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
 | 
			
		||||
    confirmButtonText: '确定',
 | 
			
		||||
    cancelButtonText: '取消',
 | 
			
		||||
    type: 'warning'
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    userStore.logOut().then(() => {
 | 
			
		||||
      location.href = '/index';
 | 
			
		||||
    })
 | 
			
		||||
  }).catch(() => { });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['setLayout'])
 | 
			
		||||
function setLayout() {
 | 
			
		||||
  emits('setLayout');
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang='scss' scoped>
 | 
			
		||||
.navbar {
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  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;
 | 
			
		||||
 | 
			
		||||
    &: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);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .avatar-container {
 | 
			
		||||
      margin-right: 40px;
 | 
			
		||||
 | 
			
		||||
      .avatar-wrapper {
 | 
			
		||||
        margin-top: 5px;
 | 
			
		||||
        position: relative;
 | 
			
		||||
 | 
			
		||||
        .user-avatar {
 | 
			
		||||
          cursor: pointer;
 | 
			
		||||
          width: 40px;
 | 
			
		||||
          height: 40px;
 | 
			
		||||
          border-radius: 10px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        i {
 | 
			
		||||
          cursor: pointer;
 | 
			
		||||
          position: absolute;
 | 
			
		||||
          right: -20px;
 | 
			
		||||
          top: 25px;
 | 
			
		||||
          font-size: 12px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,241 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <el-drawer v-model="showSettings" :withHeader="false" direction="rtl" size="300px">
 | 
			
		||||
    <div class="setting-drawer-title">
 | 
			
		||||
      <h3 class="drawer-title">主题风格设置</h3>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="setting-drawer-block-checbox">
 | 
			
		||||
      <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-dark')">
 | 
			
		||||
        <img src="@/assets/images/dark.svg" alt="dark" />
 | 
			
		||||
        <div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
 | 
			
		||||
          <i aria-label="图标: check" class="anticon anticon-check">
 | 
			
		||||
            <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
 | 
			
		||||
              <path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z" />
 | 
			
		||||
            </svg>
 | 
			
		||||
          </i>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-light')">
 | 
			
		||||
        <img src="@/assets/images/light.svg" alt="light" />
 | 
			
		||||
        <div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
 | 
			
		||||
          <i aria-label="图标: check" class="anticon anticon-check">
 | 
			
		||||
            <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
 | 
			
		||||
              <path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z" />
 | 
			
		||||
            </svg>
 | 
			
		||||
          </i>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="drawer-item">
 | 
			
		||||
      <span>主题颜色</span>
 | 
			
		||||
      <span class="comp-style">
 | 
			
		||||
        <el-color-picker v-model="theme" :predefine="predefineColors" @change="themeChange"/>
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <el-divider />
 | 
			
		||||
 | 
			
		||||
    <h3 class="drawer-title">系统布局配置</h3>
 | 
			
		||||
 | 
			
		||||
    <div class="drawer-item">
 | 
			
		||||
      <span>开启 TopNav</span>
 | 
			
		||||
      <span class="comp-style">
 | 
			
		||||
        <el-switch v-model="topNav" class="drawer-switch" />
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="drawer-item">
 | 
			
		||||
      <span>开启 Tags-Views</span>
 | 
			
		||||
      <span class="comp-style">
 | 
			
		||||
        <el-switch v-model="tagsView" class="drawer-switch" />
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="drawer-item">
 | 
			
		||||
      <span>固定 Header</span>
 | 
			
		||||
      <span class="comp-style">
 | 
			
		||||
        <el-switch v-model="fixedHeader" class="drawer-switch" />
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="drawer-item">
 | 
			
		||||
      <span>显示 Logo</span>
 | 
			
		||||
      <span class="comp-style">
 | 
			
		||||
        <el-switch v-model="sidebarLogo" class="drawer-switch" />
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="drawer-item">
 | 
			
		||||
      <span>动态标题</span>
 | 
			
		||||
      <span class="comp-style">
 | 
			
		||||
        <el-switch v-model="dynamicTitle" class="drawer-switch" />
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <el-divider />
 | 
			
		||||
 | 
			
		||||
    <el-button type="primary" plain icon="DocumentAdd" @click="saveSetting">保存配置</el-button>
 | 
			
		||||
    <el-button plain icon="Refresh" @click="resetSetting">重置配置</el-button>
 | 
			
		||||
  </el-drawer>
 | 
			
		||||
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import variables from '@/assets/styles/variables.module.scss'
 | 
			
		||||
import originElementPlus from 'element-plus/theme-chalk/index.css'
 | 
			
		||||
import axios from 'axios'
 | 
			
		||||
import { ElLoading, ElMessage } from 'element-plus'
 | 
			
		||||
import { useDynamicTitle } from '@/utils/dynamicTitle'
 | 
			
		||||
import useAppStore from '@/store/modules/app'
 | 
			
		||||
import useSettingsStore from '@/store/modules/settings'
 | 
			
		||||
import usePermissionStore from '@/store/modules/permission'
 | 
			
		||||
import { handleThemeStyle } from '@/utils/theme'
 | 
			
		||||
 | 
			
		||||
const { proxy } = getCurrentInstance();
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
const settingsStore = useSettingsStore()
 | 
			
		||||
const permissionStore = usePermissionStore()
 | 
			
		||||
const showSettings = ref(false);
 | 
			
		||||
const theme = ref(settingsStore.theme);
 | 
			
		||||
const sideTheme = ref(settingsStore.sideTheme);
 | 
			
		||||
const storeSettings = computed(() => settingsStore);
 | 
			
		||||
const predefineColors = ref(["#409EFF", "#ff4500", "#ff8c00", "#ffd700", "#90ee90", "#00ced1", "#1e90ff", "#c71585"]);
 | 
			
		||||
 | 
			
		||||
/** 是否需要topnav */
 | 
			
		||||
const topNav = computed({
 | 
			
		||||
  get: () => storeSettings.value.topNav,
 | 
			
		||||
  set: (val) => {
 | 
			
		||||
    settingsStore.changeSetting({ key: 'topNav', value: val })
 | 
			
		||||
    if (!val) {
 | 
			
		||||
      appStore.toggleSideBarHide(false);
 | 
			
		||||
      permissionStore.setSidebarRouters(permissionStore.defaultRoutes);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
/** 是否需要tagview */
 | 
			
		||||
const tagsView = computed({
 | 
			
		||||
  get: () => storeSettings.value.tagsView,
 | 
			
		||||
  set: (val) => {
 | 
			
		||||
    settingsStore.changeSetting({ key: 'tagsView', value: val })
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
/**是否需要固定头部 */
 | 
			
		||||
const fixedHeader = computed({
 | 
			
		||||
  get: () => storeSettings.value.fixedHeader,
 | 
			
		||||
  set: (val) => {
 | 
			
		||||
    settingsStore.changeSetting({ key: 'fixedHeader', value: val })
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
/**是否需要侧边栏的logo */
 | 
			
		||||
const sidebarLogo = computed({
 | 
			
		||||
  get: () => storeSettings.value.sidebarLogo,
 | 
			
		||||
  set: (val) => {
 | 
			
		||||
    settingsStore.changeSetting({ key: 'sidebarLogo', value: val })
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
/**是否需要侧边栏的动态网页的title */
 | 
			
		||||
const dynamicTitle = computed({
 | 
			
		||||
  get: () => storeSettings.value.dynamicTitle,
 | 
			
		||||
  set: (val) => {
 | 
			
		||||
    settingsStore.changeSetting({ key: 'dynamicTitle', value: val })
 | 
			
		||||
    // 动态设置网页标题
 | 
			
		||||
    useDynamicTitle()
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function themeChange(val) {
 | 
			
		||||
  settingsStore.changeSetting({ key: 'theme', value: val })
 | 
			
		||||
  theme.value = val;
 | 
			
		||||
  handleThemeStyle(val);
 | 
			
		||||
}
 | 
			
		||||
function handleTheme(val) {
 | 
			
		||||
  settingsStore.changeSetting({ key: 'sideTheme', value: val })
 | 
			
		||||
  sideTheme.value = val;
 | 
			
		||||
}
 | 
			
		||||
function saveSetting() {
 | 
			
		||||
  proxy.$modal.loading("正在保存到本地,请稍候...");
 | 
			
		||||
  let layoutSetting = {
 | 
			
		||||
    "topNav": storeSettings.value.topNav,
 | 
			
		||||
    "tagsView": storeSettings.value.tagsView,
 | 
			
		||||
    "fixedHeader": storeSettings.value.fixedHeader,
 | 
			
		||||
    "sidebarLogo": storeSettings.value.sidebarLogo,
 | 
			
		||||
    "dynamicTitle": storeSettings.value.dynamicTitle,
 | 
			
		||||
    "sideTheme": storeSettings.value.sideTheme,
 | 
			
		||||
    "theme": storeSettings.value.theme
 | 
			
		||||
  };
 | 
			
		||||
  localStorage.setItem("layout-setting", JSON.stringify(layoutSetting));
 | 
			
		||||
  setTimeout(proxy.$modal.closeLoading(), 1000)
 | 
			
		||||
}
 | 
			
		||||
function resetSetting() {
 | 
			
		||||
  proxy.$modal.loading("正在清除设置缓存并刷新,请稍候...");
 | 
			
		||||
  localStorage.removeItem("layout-setting")
 | 
			
		||||
  setTimeout("window.location.reload()", 1000)
 | 
			
		||||
}
 | 
			
		||||
function openSetting() {
 | 
			
		||||
  showSettings.value = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  openSetting,
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang='scss' scoped>
 | 
			
		||||
.setting-drawer-title {
 | 
			
		||||
  margin-bottom: 12px;
 | 
			
		||||
  color: rgba(0, 0, 0, 0.85);
 | 
			
		||||
  line-height: 22px;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  .drawer-title {
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.setting-drawer-block-checbox {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: flex-start;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  margin-top: 10px;
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
 | 
			
		||||
  .setting-drawer-block-checbox-item {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    margin-right: 16px;
 | 
			
		||||
    border-radius: 2px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
 | 
			
		||||
    img {
 | 
			
		||||
      width: 48px;
 | 
			
		||||
      height: 48px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .custom-img {
 | 
			
		||||
      width: 48px;
 | 
			
		||||
      height: 38px;
 | 
			
		||||
      border-radius: 5px;
 | 
			
		||||
      box-shadow: 1px 1px 2px #898484;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .setting-drawer-block-checbox-selectIcon {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 0;
 | 
			
		||||
      right: 0;
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      padding-top: 15px;
 | 
			
		||||
      padding-left: 24px;
 | 
			
		||||
      color: #1890ff;
 | 
			
		||||
      font-weight: 700;
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.drawer-item {
 | 
			
		||||
  color: rgba(0, 0, 0, 0.65);
 | 
			
		||||
  padding: 12px 0;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
 | 
			
		||||
  .comp-style {
 | 
			
		||||
    float: right;
 | 
			
		||||
    margin: -3px 8px 0px 0px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <component :is="type" v-bind="linkProps()">
 | 
			
		||||
    <slot />
 | 
			
		||||
  </component>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { isExternal } from '@/utils/validate'
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  to: {
 | 
			
		||||
    type: [String, Object],
 | 
			
		||||
    required: true
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const isExt = computed(() => {
 | 
			
		||||
  return isExternal(props.to)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const type = computed(() => {
 | 
			
		||||
  if (isExt.value) {
 | 
			
		||||
    return 'a'
 | 
			
		||||
  }
 | 
			
		||||
  return 'router-link'
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function linkProps() {
 | 
			
		||||
  if (isExt.value) {
 | 
			
		||||
    return {
 | 
			
		||||
      href: props.to,
 | 
			
		||||
      target: '_blank',
 | 
			
		||||
      rel: 'noopener'
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return {
 | 
			
		||||
    to: props.to
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,81 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="sidebar-logo-container" :class="{ 'collapse': collapse }" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
 | 
			
		||||
    <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" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ 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" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }}</h1>
 | 
			
		||||
      </router-link>
 | 
			
		||||
    </transition>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import variables from '@/assets/styles/variables.module.scss'
 | 
			
		||||
import logo from '@/assets/logo/logo.png'
 | 
			
		||||
import useSettingsStore from '@/store/modules/settings'
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  collapse: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    required: true
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const title = import.meta.env.VITE_APP_TITLE;
 | 
			
		||||
const settingsStore = useSettingsStore();
 | 
			
		||||
const sideTheme = computed(() => settingsStore.sideTheme);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.sidebarLogoFade-enter-active {
 | 
			
		||||
  transition: opacity 1.5s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidebarLogoFade-enter,
 | 
			
		||||
.sidebarLogoFade-leave-to {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidebar-logo-container {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  line-height: 50px;
 | 
			
		||||
  background: #2b2f3a;
 | 
			
		||||
  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: #fff;
 | 
			
		||||
      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>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,102 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div v-if="!item.hidden">
 | 
			
		||||
    <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
 | 
			
		||||
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
 | 
			
		||||
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
 | 
			
		||||
          <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/>
 | 
			
		||||
          <template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span></template>
 | 
			
		||||
        </el-menu-item>
 | 
			
		||||
      </app-link>
 | 
			
		||||
    </template>
 | 
			
		||||
 | 
			
		||||
    <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
 | 
			
		||||
      <template v-if="item.meta" #title>
 | 
			
		||||
        <svg-icon :icon-class="item.meta && item.meta.icon" />
 | 
			
		||||
        <span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span>
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
      <sidebar-item
 | 
			
		||||
        v-for="(child, 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>
 | 
			
		||||
import { isExternal } from '@/utils/validate'
 | 
			
		||||
import AppLink from './Link'
 | 
			
		||||
import { getNormalPath } from '@/utils/ruoyi'
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  // route object
 | 
			
		||||
  item: {
 | 
			
		||||
    type: Object,
 | 
			
		||||
    required: true
 | 
			
		||||
  },
 | 
			
		||||
  isNest: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false
 | 
			
		||||
  },
 | 
			
		||||
  basePath: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: ''
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const onlyOneChild = ref({});
 | 
			
		||||
 | 
			
		||||
function hasOneShowingChild(children = [], parent) {
 | 
			
		||||
  if (!children) {
 | 
			
		||||
    children = [];
 | 
			
		||||
  }
 | 
			
		||||
  const showingChildren = children.filter(item => {
 | 
			
		||||
    if (item.hidden) {
 | 
			
		||||
      return false
 | 
			
		||||
    } else {
 | 
			
		||||
      // Temp set(will be used if only has one showing child)
 | 
			
		||||
      onlyOneChild.value = item
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  // When there is only one child router, the child router is displayed by default
 | 
			
		||||
  if (showingChildren.length === 1) {
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Show parent if there are no child router to display
 | 
			
		||||
  if (showingChildren.length === 0) {
 | 
			
		||||
    onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function resolvePath(routePath, routeQuery) {
 | 
			
		||||
  if (isExternal(routePath)) {
 | 
			
		||||
    return routePath
 | 
			
		||||
  }
 | 
			
		||||
  if (isExternal(props.basePath)) {
 | 
			
		||||
    return props.basePath
 | 
			
		||||
  }
 | 
			
		||||
  if (routeQuery) {
 | 
			
		||||
    let query = JSON.parse(routeQuery);
 | 
			
		||||
    return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
 | 
			
		||||
  }
 | 
			
		||||
  return getNormalPath(props.basePath + '/' + routePath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hasTitle(title){
 | 
			
		||||
  if (title.length > 5) {
 | 
			
		||||
    return title;
 | 
			
		||||
  } else {
 | 
			
		||||
    return "";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div :class="{ 'has-logo': showLogo }" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
 | 
			
		||||
    <logo v-if="showLogo" :collapse="isCollapse" />
 | 
			
		||||
    <el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
 | 
			
		||||
      <el-menu
 | 
			
		||||
        :default-active="activeMenu"
 | 
			
		||||
        :collapse="isCollapse"
 | 
			
		||||
        :background-color="sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
 | 
			
		||||
        :text-color="sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
 | 
			
		||||
        :unique-opened="true"
 | 
			
		||||
        :active-text-color="theme"
 | 
			
		||||
        :collapse-transition="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>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import Logo from './Logo'
 | 
			
		||||
import SidebarItem from './SidebarItem'
 | 
			
		||||
import variables from '@/assets/styles/variables.module.scss'
 | 
			
		||||
import useAppStore from '@/store/modules/app'
 | 
			
		||||
import useSettingsStore from '@/store/modules/settings'
 | 
			
		||||
import usePermissionStore from '@/store/modules/permission'
 | 
			
		||||
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
const settingsStore = useSettingsStore()
 | 
			
		||||
const permissionStore = usePermissionStore()
 | 
			
		||||
 | 
			
		||||
const sidebarRouters =  computed(() => permissionStore.sidebarRouters);
 | 
			
		||||
const showLogo = computed(() => settingsStore.sidebarLogo);
 | 
			
		||||
const sideTheme = computed(() => settingsStore.sideTheme);
 | 
			
		||||
const theme = computed(() => settingsStore.theme);
 | 
			
		||||
const isCollapse = computed(() => !appStore.sidebar.opened);
 | 
			
		||||
 | 
			
		||||
const activeMenu = computed(() => {
 | 
			
		||||
  const { meta, path } = route;
 | 
			
		||||
  // if set path, the sidebar will highlight the path you set
 | 
			
		||||
  if (meta.activeMenu) {
 | 
			
		||||
    return meta.activeMenu;
 | 
			
		||||
  }
 | 
			
		||||
  return path;
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,105 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <el-scrollbar
 | 
			
		||||
    ref="scrollContainer"
 | 
			
		||||
    :vertical="false"
 | 
			
		||||
    class="scroll-container"
 | 
			
		||||
    @wheel.prevent="handleScroll"
 | 
			
		||||
  >
 | 
			
		||||
    <slot />
 | 
			
		||||
  </el-scrollbar>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import useTagsViewStore from '@/store/modules/tagsView'
 | 
			
		||||
 | 
			
		||||
const tagAndTagSpacing = ref(4);
 | 
			
		||||
const { proxy } = getCurrentInstance();
 | 
			
		||||
 | 
			
		||||
const scrollWrapper = computed(() => proxy.$refs.scrollContainer.$refs.wrapRef);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  scrollWrapper.value.addEventListener('scroll', emitScroll, true)
 | 
			
		||||
})
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
  scrollWrapper.value.removeEventListener('scroll', emitScroll)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function handleScroll(e) {
 | 
			
		||||
  const eventDelta = e.wheelDelta || -e.deltaY * 40
 | 
			
		||||
  const $scrollWrapper = scrollWrapper.value;
 | 
			
		||||
  $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
 | 
			
		||||
}
 | 
			
		||||
const emits = defineEmits()
 | 
			
		||||
const emitScroll = () => {
 | 
			
		||||
  emits('scroll')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const tagsViewStore = useTagsViewStore()
 | 
			
		||||
const visitedViews = computed(() => tagsViewStore.visitedViews);
 | 
			
		||||
 | 
			
		||||
function moveToTarget(currentTag) {
 | 
			
		||||
  const $container = proxy.$refs.scrollContainer.$el
 | 
			
		||||
  const $containerWidth = $container.offsetWidth
 | 
			
		||||
  const $scrollWrapper = scrollWrapper.value;
 | 
			
		||||
 | 
			
		||||
  let firstTag = null
 | 
			
		||||
  let lastTag = null
 | 
			
		||||
 | 
			
		||||
  // find first tag and last tag
 | 
			
		||||
  if (visitedViews.value.length > 0) {
 | 
			
		||||
    firstTag = visitedViews.value[0]
 | 
			
		||||
    lastTag = visitedViews.value[visitedViews.value.length - 1]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (firstTag === currentTag) {
 | 
			
		||||
    $scrollWrapper.scrollLeft = 0
 | 
			
		||||
  } else if (lastTag === currentTag) {
 | 
			
		||||
    $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
 | 
			
		||||
  } else {
 | 
			
		||||
    const tagListDom = document.getElementsByClassName('tags-view-item');
 | 
			
		||||
    const currentIndex = visitedViews.value.findIndex(item => item === currentTag)
 | 
			
		||||
    let prevTag = null
 | 
			
		||||
    let nextTag = null
 | 
			
		||||
    for (const k in tagListDom) {
 | 
			
		||||
      if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
 | 
			
		||||
        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex - 1].path) {
 | 
			
		||||
          prevTag = tagListDom[k];
 | 
			
		||||
        }
 | 
			
		||||
        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex + 1].path) {
 | 
			
		||||
          nextTag = tagListDom[k];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // the tag's offsetLeft after of nextTag
 | 
			
		||||
    const afterNextTagOffsetLeft = nextTag.offsetLeft + nextTag.offsetWidth + tagAndTagSpacing.value
 | 
			
		||||
 | 
			
		||||
    // the tag's offsetLeft before of prevTag
 | 
			
		||||
    const beforePrevTagOffsetLeft = prevTag.offsetLeft - tagAndTagSpacing.value
 | 
			
		||||
    if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
 | 
			
		||||
      $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
 | 
			
		||||
    } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
 | 
			
		||||
      $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  moveToTarget,
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang='scss' scoped>
 | 
			
		||||
.scroll-container {
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  :deep(.el-scrollbar__bar) {
 | 
			
		||||
    bottom: 0px;
 | 
			
		||||
  }
 | 
			
		||||
  :deep(.el-scrollbar__wrap) {
 | 
			
		||||
    height: 39px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,338 @@
 | 
			
		|||
<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)">
 | 
			
		||||
          <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>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import ScrollPane from './ScrollPane'
 | 
			
		||||
import { getNormalPath } from '@/utils/ruoyi'
 | 
			
		||||
import useTagsViewStore from '@/store/modules/tagsView'
 | 
			
		||||
import useSettingsStore from '@/store/modules/settings'
 | 
			
		||||
import usePermissionStore from '@/store/modules/permission'
 | 
			
		||||
 | 
			
		||||
const visible = ref(false);
 | 
			
		||||
const top = ref(0);
 | 
			
		||||
const left = ref(0);
 | 
			
		||||
const selectedTag = ref({});
 | 
			
		||||
const affixTags = ref([]);
 | 
			
		||||
const scrollPaneRef = ref(null);
 | 
			
		||||
 | 
			
		||||
const { proxy } = getCurrentInstance();
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
 | 
			
		||||
const visitedViews = computed(() => useTagsViewStore().visitedViews);
 | 
			
		||||
const routes = computed(() => usePermissionStore().routes);
 | 
			
		||||
const theme = computed(() => useSettingsStore().theme);
 | 
			
		||||
 | 
			
		||||
watch(route, () => {
 | 
			
		||||
  addTags()
 | 
			
		||||
  moveToCurrentTag()
 | 
			
		||||
})
 | 
			
		||||
watch(visible, (value) => {
 | 
			
		||||
  if (value) {
 | 
			
		||||
    document.body.addEventListener('click', closeMenu)
 | 
			
		||||
  } else {
 | 
			
		||||
    document.body.removeEventListener('click', closeMenu)
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  initTags()
 | 
			
		||||
  addTags()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function isActive(r) {
 | 
			
		||||
  return r.path === route.path
 | 
			
		||||
}
 | 
			
		||||
function activeStyle(tag) {
 | 
			
		||||
  if (!isActive(tag)) return {};
 | 
			
		||||
  return {
 | 
			
		||||
    "background-color": theme.value,
 | 
			
		||||
    "border-color": theme.value
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
function isAffix(tag) {
 | 
			
		||||
  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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function isLastView() {
 | 
			
		||||
  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
 | 
			
		||||
}
 | 
			
		||||
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)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function addTags() {
 | 
			
		||||
  const { name } = route
 | 
			
		||||
  if (name) {
 | 
			
		||||
    useTagsViewStore().addView(route)
 | 
			
		||||
    if (route.meta.link) {
 | 
			
		||||
      useTagsViewStore().addIframeView(route);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
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)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
function refreshSelectedTag(view) {
 | 
			
		||||
  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)
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
function closeRightTags() {
 | 
			
		||||
  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)
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
function closeOthersTags() {
 | 
			
		||||
  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)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
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('/')
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
function closeMenu() {
 | 
			
		||||
  visible.value = false
 | 
			
		||||
}
 | 
			
		||||
function handleScroll() {
 | 
			
		||||
  closeMenu()
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang='scss' scoped>
 | 
			
		||||
.tags-view-container {
 | 
			
		||||
  height: 34px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  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: 26px;
 | 
			
		||||
      line-height: 26px;
 | 
			
		||||
      border: 1px solid #d8dce5;
 | 
			
		||||
      color: #495060;
 | 
			
		||||
      background: #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: #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;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</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 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
 | 
			
		||||
      transform-origin: 100% 50%;
 | 
			
		||||
      &:before {
 | 
			
		||||
        transform: scale(0.6);
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        vertical-align: -3px;
 | 
			
		||||
      }
 | 
			
		||||
      &:hover {
 | 
			
		||||
        background-color: #b4bccc;
 | 
			
		||||
        color: #fff;
 | 
			
		||||
        width: 12px !important;
 | 
			
		||||
        height: 12px !important;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
export { default as AppMain } from './AppMain'
 | 
			
		||||
export { default as Navbar } from './Navbar'
 | 
			
		||||
export { default as Settings } from './Settings'
 | 
			
		||||
export { default as TagsView } from './TagsView/index.vue'
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,108 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
 | 
			
		||||
    <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container layMain-container">
 | 
			
		||||
      <div :class="{ 'fixed-header': fixedHeader }">
 | 
			
		||||
        <navbar @setLayout="setLayout" />
 | 
			
		||||
      </div>
 | 
			
		||||
      <app-main />
 | 
			
		||||
      <settings ref="settingRef" />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { useWindowSize } from '@vueuse/core'
 | 
			
		||||
import Sidebar from './components/Sidebar/index.vue'
 | 
			
		||||
import { AppMain, Navbar, Settings, TagsView } from './components'
 | 
			
		||||
import defaultSettings from '@/settings'
 | 
			
		||||
 | 
			
		||||
import useAppStore from '@/store/modules/app'
 | 
			
		||||
import useSettingsStore from '@/store/modules/settings'
 | 
			
		||||
 | 
			
		||||
const settingsStore = useSettingsStore()
 | 
			
		||||
const theme = computed(() => settingsStore.theme);
 | 
			
		||||
const sideTheme = computed(() => settingsStore.sideTheme);
 | 
			
		||||
const sidebar = computed(() => useAppStore().sidebar);
 | 
			
		||||
const device = computed(() => useAppStore().device);
 | 
			
		||||
const needTagsView = computed(() => settingsStore.tagsView);
 | 
			
		||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
 | 
			
		||||
 | 
			
		||||
const classObj = computed(() => ({
 | 
			
		||||
  hideSidebar: true,
 | 
			
		||||
  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
 | 
			
		||||
 | 
			
		||||
watchEffect(() => {
 | 
			
		||||
  if (device.value === 'mobile' && sidebar.value.opened) {
 | 
			
		||||
    useAppStore().closeSideBar({ withoutAnimation: false })
 | 
			
		||||
  }
 | 
			
		||||
  if (width.value - 1 < WIDTH) {
 | 
			
		||||
    useAppStore().toggleDevice('mobile')
 | 
			
		||||
    useAppStore().closeSideBar({ withoutAnimation: true })
 | 
			
		||||
  } else {
 | 
			
		||||
    useAppStore().toggleDevice('desktop')
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function handleClickOutside() {
 | 
			
		||||
  useAppStore().closeSideBar({ withoutAnimation: false })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const settingRef = ref(null);
 | 
			
		||||
function setLayout() {
 | 
			
		||||
  settingRef.value.openSetting();
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
  @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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.drawer-bg {
 | 
			
		||||
  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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hideSidebar .fixed-header {
 | 
			
		||||
  width: calc(100% - 54px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidebarHide .fixed-header {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mobile .fixed-header {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ import {
 | 
			
		|||
} from 'vue-router'
 | 
			
		||||
/* Layout */
 | 
			
		||||
import Layout from '@/layout'
 | 
			
		||||
import LayoutMain from '@/layoutMain'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Note: 路由配置项
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +60,7 @@ export const constantRoutes = [{
 | 
			
		|||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '',
 | 
			
		||||
    component: Layout,
 | 
			
		||||
    component: LayoutMain,
 | 
			
		||||
    redirect: '/index',
 | 
			
		||||
    children: [{
 | 
			
		||||
      path: '/index',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1031
									
								
								src/views/index.vue
								
								
								
								
							
							
						
						
									
										1031
									
								
								src/views/index.vue
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue