|
@@ -1,75 +1,292 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <el-container class-name="main-container">
|
|
|
|
|
- <el-aside :class="asideClass">
|
|
|
|
|
- <LeftAside />
|
|
|
|
|
|
|
+ <el-container class="admin-wrapper">
|
|
|
|
|
+ <el-aside :width="collapsed ? '64px' : '240px'" class="admin-aside">
|
|
|
|
|
+ <div class="logo-wrapper">
|
|
|
|
|
+ <router-link to="/bg" class="logo-link">
|
|
|
|
|
+ <img src="@/assets/img/logo.png" alt="Logo" class="logo-img">
|
|
|
|
|
+ <span v-if="!collapsed" class="logo-title">DevOps</span>
|
|
|
|
|
+ </router-link>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-menu
|
|
|
|
|
+ :default-active="$route.path"
|
|
|
|
|
+ router
|
|
|
|
|
+ class="admin-menu"
|
|
|
|
|
+ background-color="#334157"
|
|
|
|
|
+ text-color="#b5bcc7"
|
|
|
|
|
+ active-text-color="#fff"
|
|
|
|
|
+ :unique-opened="true"
|
|
|
|
|
+ :collapse="collapsed"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-submenu v-for="item in menuList" :key="item.path" :index="item.path">
|
|
|
|
|
+ <template slot="title">
|
|
|
|
|
+ <i :class="item.icon || 'el-icon-menu'" />
|
|
|
|
|
+ <span slot="title">{{ item.title }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <el-menu-item v-for="child in item.children" :key="child.path" :index="child.path">
|
|
|
|
|
+ <i :class="child.icon || 'el-icon-document'" />
|
|
|
|
|
+ <span slot="title">{{ child.title }}</span>
|
|
|
|
|
+ </el-menu-item>
|
|
|
|
|
+ </el-submenu>
|
|
|
|
|
+ </el-menu>
|
|
|
</el-aside>
|
|
</el-aside>
|
|
|
- <el-container>
|
|
|
|
|
- <el-header class-name="main-header">
|
|
|
|
|
- <TopNav />
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <el-container class="main-layout">
|
|
|
|
|
+ <el-header class="admin-header" height="50px">
|
|
|
|
|
+ <div class="header-left">
|
|
|
|
|
+ <i :class="['collapse-btn', collapsed ? 'el-icon-s-unfold' : 'el-icon-s-fold']" @click="toggleSidebar" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="header-right">
|
|
|
|
|
+ <el-dropdown trigger="click">
|
|
|
|
|
+ <div class="user-info">
|
|
|
|
|
+ <el-avatar size="small" icon="el-icon-user-solid" class="user-avatar" />
|
|
|
|
|
+ <span class="username">{{ user ? user.username : '管理员' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-dropdown-menu slot="dropdown">
|
|
|
|
|
+ <el-dropdown-item @click.native="backToHome">回到前台</el-dropdown-item>
|
|
|
|
|
+ <el-dropdown-item divided @click.native="goToLogout">安全登出</el-dropdown-item>
|
|
|
|
|
+ </el-dropdown-menu>
|
|
|
|
|
+ </el-dropdown>
|
|
|
|
|
+ </div>
|
|
|
</el-header>
|
|
</el-header>
|
|
|
- <el-main class-name="main-center">
|
|
|
|
|
- <router-view />
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <div class="tags-view-container">
|
|
|
|
|
+ <div class="tags-view-wrapper">
|
|
|
|
|
+ <router-link
|
|
|
|
|
+ v-for="tag in visitedViews"
|
|
|
|
|
+ :key="tag.path"
|
|
|
|
|
+ :to="{ path: tag.path, query: tag.query }"
|
|
|
|
|
+ tag="span"
|
|
|
|
|
+ class="tags-view-item"
|
|
|
|
|
+ :class="isActive(tag) ? 'active' : ''"
|
|
|
|
|
+ @contextmenu.prevent.native="openMenu(tag, $event)"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ tag.title }}
|
|
|
|
|
+ <span v-if="visitedViews.length > 1" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
|
|
|
|
|
+ </router-link>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <ul v-show="menuVisible" :style="{left: left + 'px', top: top + 'px'}" class="contextmenu">
|
|
|
|
|
+ <li @click="refreshSelectedTag(selectedTag)">刷新页面</li>
|
|
|
|
|
+ <li @click="closeSelectedTag(selectedTag)" v-if="visitedViews.length > 1">关闭当前</li>
|
|
|
|
|
+ <li @click="closeOthersTags">关闭其他</li>
|
|
|
|
|
+ <li @click="closeAllTags" v-if="visitedViews.length > 1">关闭所有</li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <el-main class="admin-main">
|
|
|
|
|
+ <transition name="fade-transform" mode="out-in">
|
|
|
|
|
+ <keep-alive>
|
|
|
|
|
+ <router-view :key="$route.path" />
|
|
|
|
|
+ </keep-alive>
|
|
|
|
|
+ </transition>
|
|
|
</el-main>
|
|
</el-main>
|
|
|
</el-container>
|
|
</el-container>
|
|
|
</el-container>
|
|
</el-container>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
-import TopNav from '@/views/admin/TopNav.vue'
|
|
|
|
|
-import LeftAside from '@/views/admin/LeftAside.vue'
|
|
|
|
|
|
|
+import store from '@/store'
|
|
|
|
|
+import { userMixin } from 'assets/js/mixin'
|
|
|
|
|
+import { getAuthedUser } from '@/utils/auth'
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
|
name: 'Background',
|
|
name: 'Background',
|
|
|
- components: {
|
|
|
|
|
- TopNav,
|
|
|
|
|
- LeftAside
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ mixins: [userMixin],
|
|
|
data() {
|
|
data() {
|
|
|
return {
|
|
return {
|
|
|
- collapsed: false
|
|
|
|
|
|
|
+ collapsed: false,
|
|
|
|
|
+ user: null,
|
|
|
|
|
+ menuList: [],
|
|
|
|
|
+ visitedViews: [],
|
|
|
|
|
+ // 右键菜单相关
|
|
|
|
|
+ menuVisible: false,
|
|
|
|
|
+ top: 0,
|
|
|
|
|
+ left: 0,
|
|
|
|
|
+ selectedTag: {}
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- computed: { // 计算属性
|
|
|
|
|
- asideClass() { // 如果collapsed属性为true就展开不样式 反之就展开样式
|
|
|
|
|
- return this.collapsed ? 'main-aside-collapsed' : 'main-aside'
|
|
|
|
|
|
|
+ watch: {
|
|
|
|
|
+ $route() {
|
|
|
|
|
+ this.addTags()
|
|
|
|
|
+ },
|
|
|
|
|
+ // 监听菜单显示,开启全局点击关闭事件
|
|
|
|
|
+ menuVisible(value) {
|
|
|
|
|
+ if (value) {
|
|
|
|
|
+ document.body.addEventListener('click', this.closeMenu)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ document.body.removeEventListener('click', this.closeMenu)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- created() { // 钩子函数
|
|
|
|
|
|
|
+ created() {
|
|
|
|
|
+ this.initMenuList()
|
|
|
|
|
+ this.user = getAuthedUser()
|
|
|
|
|
+ this.addTags()
|
|
|
this.$root.Bus.$on('HandleSideMenu', value => {
|
|
this.$root.Bus.$on('HandleSideMenu', value => {
|
|
|
this.collapsed = value
|
|
this.collapsed = value
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
methods: {
|
|
|
|
|
+ initMenuList() {
|
|
|
|
|
+ const routes = store.getters.addRoutes
|
|
|
|
|
+ routes.forEach(route => {
|
|
|
|
|
+ if (route.path === '/bg' && route.children) {
|
|
|
|
|
+ this.menuList.push(...route.children)
|
|
|
|
|
+ } else if (route.path.startsWith('/bg/')) {
|
|
|
|
|
+ this.menuList.push(route)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ isActive(route) {
|
|
|
|
|
+ return route.path === this.$route.path
|
|
|
|
|
+ },
|
|
|
|
|
+ // 增加标签逻辑(含 10 个限制)
|
|
|
|
|
+ addTags() {
|
|
|
|
|
+ const { name, path, meta, query } = this.$route
|
|
|
|
|
+ if (!path || path.includes('login')) return
|
|
|
|
|
+
|
|
|
|
|
+ const title = (meta && meta.title) ? meta.title : path.split('/').pop()
|
|
|
|
|
+ const isExist = this.visitedViews.some(v => v.path === path)
|
|
|
|
|
+
|
|
|
|
|
+ if (!isExist) {
|
|
|
|
|
+ // 限制最多 10 个,如果满了,删除第一个(最早的)
|
|
|
|
|
+ if (this.visitedViews.length >= 10) {
|
|
|
|
|
+ this.visitedViews.shift()
|
|
|
|
|
+ }
|
|
|
|
|
+ this.visitedViews.push({ name, path, title, query })
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 刷新当前标签
|
|
|
|
|
+ refreshSelectedTag(view) {
|
|
|
|
|
+ this.$router.replace({ path: '/redirect' + view.path }) // 需要配置 redirect 路由,或简单使用 go(0)
|
|
|
|
|
+ if (!view.path) this.$router.go(0)
|
|
|
|
|
+ },
|
|
|
|
|
+ // 关闭单个标签
|
|
|
|
|
+ closeSelectedTag(view) {
|
|
|
|
|
+ const index = this.visitedViews.indexOf(view)
|
|
|
|
|
+ if (this.visitedViews.length <= 1) return // 限制最少 1 个
|
|
|
|
|
+
|
|
|
|
|
+ this.visitedViews.splice(index, 1)
|
|
|
|
|
+ if (this.isActive(view)) {
|
|
|
|
|
+ const lastView = this.visitedViews.slice(-1)[0]
|
|
|
|
|
+ this.$router.push(lastView ? lastView.path : '/bg')
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 右键:关闭其他
|
|
|
|
|
+ closeOthersTags() {
|
|
|
|
|
+ this.visitedViews = [this.selectedTag]
|
|
|
|
|
+ if (!this.isActive(this.selectedTag)) {
|
|
|
|
|
+ this.$router.push(this.selectedTag.path)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 右键:关闭所有(保留最后一个)
|
|
|
|
|
+ closeAllTags() {
|
|
|
|
|
+ const lastTag = this.visitedViews[this.visitedViews.length - 1]
|
|
|
|
|
+ this.visitedViews = [lastTag]
|
|
|
|
|
+ this.$router.push(lastTag.path)
|
|
|
|
|
+ },
|
|
|
|
|
+ // 右键菜单控制
|
|
|
|
|
+ openMenu(tag, e) {
|
|
|
|
|
+ const menuMinWidth = 105
|
|
|
|
|
+ const offsetLeft = this.$el.getBoundingClientRect().left
|
|
|
|
|
+ const offsetWidth = this.$el.offsetWidth
|
|
|
|
|
+ const maxLeft = offsetWidth - menuMinWidth
|
|
|
|
|
+ const left = e.clientX - offsetLeft + 15
|
|
|
|
|
+
|
|
|
|
|
+ if (left > maxLeft) {
|
|
|
|
|
+ this.left = maxLeft
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.left = left
|
|
|
|
|
+ }
|
|
|
|
|
+ this.top = e.clientY - 10 // 稍微偏移避开鼠标
|
|
|
|
|
+ this.menuVisible = true
|
|
|
|
|
+ this.selectedTag = tag
|
|
|
|
|
+ },
|
|
|
|
|
+ closeMenu() {
|
|
|
|
|
+ this.menuVisible = false
|
|
|
|
|
+ },
|
|
|
|
|
+ toggleSidebar() {
|
|
|
|
|
+ this.collapsed = !this.collapsed
|
|
|
|
|
+ this.$root.Bus.$emit('HandleSideMenu', this.collapsed)
|
|
|
|
|
+ },
|
|
|
|
|
+ backToHome() {
|
|
|
|
|
+ // this.$router.push('/vod')
|
|
|
|
|
+ this.$message.info('get user info')
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
-.main-container {
|
|
|
|
|
- height: 100%;
|
|
|
|
|
|
|
+/* 基础布局样式 */
|
|
|
|
|
+.admin-wrapper { height: 100vh; width: 100%; overflow: hidden; }
|
|
|
|
|
+.admin-aside { background-color: #334157; transition: width 0.3s; display: flex; flex-direction: column; z-index: 10; }
|
|
|
|
|
+.logo-wrapper { height: 50px; line-height: 50px; background-color: #2b3648; display: flex; justify-content: center; align-items: center; }
|
|
|
|
|
+.logo-img { height: 28px; }
|
|
|
|
|
+.logo-title { color: #fff; font-weight: 600; font-size: 14px; margin-left: 10px; }
|
|
|
|
|
+.admin-menu { border: none !important; flex: 1; }
|
|
|
|
|
+
|
|
|
|
|
+.admin-header { background: #fff; border-bottom: 1px solid #e6e6e6; display: flex; justify-content: space-between; align-items: center; padding: 0 15px; }
|
|
|
|
|
+.collapse-btn { font-size: 20px; cursor: pointer; }
|
|
|
|
|
+.user-info { display: flex; align-items: center; cursor: pointer; }
|
|
|
|
|
+.username { font-size: 14px; color: #606266; margin-left: 8px; }
|
|
|
|
|
+
|
|
|
|
|
+/* TagsView 样式 */
|
|
|
|
|
+.tags-view-container {
|
|
|
|
|
+ height: 34px;
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
- box-sizing: border-box;
|
|
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-bottom: 1px solid #d8dce5;
|
|
|
|
|
+ box-shadow: 0 1px 3px 0 rgba(0,0,0,.12);
|
|
|
|
|
+ position: relative;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-/* 不展开样式*/
|
|
|
|
|
-.main-aside-collapsed {
|
|
|
|
|
- /* 在CSS中,通过对某一样式声明! important ,可以更改默认的CSS样式优先级规则,使该条样式属性声明具有最高优先级 */
|
|
|
|
|
- width: 64px !important;
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- background-color: #334157;
|
|
|
|
|
- margin: 0px;
|
|
|
|
|
|
|
+.tags-view-wrapper { padding: 4px 10px; display: flex; }
|
|
|
|
|
+.tags-view-item {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ height: 25px;
|
|
|
|
|
+ line-height: 25px;
|
|
|
|
|
+ border: 1px solid #d8dce5;
|
|
|
|
|
+ color: #495060;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ padding: 0 8px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ margin-left: 5px;
|
|
|
|
|
+ border-radius: 2px;
|
|
|
|
|
+ position: relative;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-/* 展开样式*/
|
|
|
|
|
-.main-aside {
|
|
|
|
|
- width: 240px !important;
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- background-color: #334157;
|
|
|
|
|
- margin: 0px;
|
|
|
|
|
|
|
+.tags-view-item.active { background-color: #409EFF; color: #fff; border-color: #409EFF; }
|
|
|
|
|
+.tags-view-item.active::before {
|
|
|
|
|
+ content: ''; background: #fff; display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 5px;
|
|
|
}
|
|
}
|
|
|
|
|
+.el-icon-close { margin-left: 5px; border-radius: 50%; padding: 1px; transition: all .2s; }
|
|
|
|
|
+.el-icon-close:hover { background-color: #b4bccc; color: #fff; }
|
|
|
|
|
|
|
|
-.main-header, .main-center {
|
|
|
|
|
- padding: 0px;
|
|
|
|
|
- border-left: 2px solid #333;
|
|
|
|
|
|
|
+/* 右键菜单样式 */
|
|
|
|
|
+.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, .3);
|
|
|
|
|
+}
|
|
|
|
|
+.contextmenu li {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: 7px 16px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
}
|
|
}
|
|
|
|
|
+.contextmenu li:hover { background: #eee; color: #409EFF; }
|
|
|
|
|
+
|
|
|
|
|
+.admin-main { background-color: #f0f2f5; padding: 15px; }
|
|
|
|
|
+
|
|
|
|
|
+/* 动画 */
|
|
|
|
|
+.fade-transform-enter-active, .fade-transform-leave-active { transition: all .3s; }
|
|
|
|
|
+.fade-transform-enter { opacity: 0; transform: translateX(-20px); }
|
|
|
|
|
+.fade-transform-leave-to { opacity: 0; transform: translateX(20px); }
|
|
|
</style>
|
|
</style>
|