- 首先需要了解的一些开发规则
- 整个系统支持三种类型的子页面(在数据库里必须以menu.url字段来存储):
(1) user/login:这个是针对的Vue框架下的页面组件,注意不能以/开头,对应views/user/login.vue
(2) iframe:****:这个是Spring Boot框架提供的一些页面,比如swagger和druid监控页面。
(3) http[s]://www.baidu.com,这个是第三方页面。 - 菜单按钮权限的获取被设计在路由守卫中执行初始化。
- 本系统保留动态菜单与路由守卫的功能,但在前期不进行启用,启用的时机要看具体的情况。
- 在store/modules下的app.js中添加一个动态菜单设置状态、动态菜单、用户权限设置状态、用户权限。
export default {
state: {
dynamicRouteLoaded: false, // 菜单和路由是否已经加载
dynamicRoutes: [], // 用户的权限菜单
permissionLoaded: false, // 用户权限是否已经加载
permissions: [] // 用户的按钮权限
},
getters: {
dynamicRouteLoaded (state) {
return state.dynamicRouteLoaded
},
dynamicRoutes (state) {
return state.dynamicRoutes
},
permissionLoaded (state) {
return state.permissionLoaded
},
permissions (state) {
return state.permissions
}
},
mutations: {
dynamicRouteLoaded (state, dynamicRouteLoaded) { // 改变菜单和路由的加载状态
state.dynamicRouteLoaded = dynamicRouteLoaded
},
dynamicRoutes (state, dynamicRoutes) { // 设置用户的权限菜单
state.dynamicRoutes = dynamicRoutes
},
permissionLoaded (state, permissionLoaded) { // 改变用户权限的加载状态
state.permissionLoaded = permissionLoaded
},
permissions (state, permissions) { // 设置用户的按钮权限
state.permissions = permissions
}
},
actions: {
}
}
- 重写登录方法
login () {
var data = {account: '', password: ''}
this.$api.login.login(data).then(res => {
if (res.code !== 200) {
// 其它处理
} else {
Cookies.set('token', res.data.token) // 放置token到cookie
sessionStorage.setItem('user', data.account) // 保存用户到本地会话
this.$store.commit('dynamicRouteLoaded', false) // 要求重新加载导航菜单
this.$store.commit('permissionLoaded', false) // 要求重新加载导航菜单
this.$router.push('/') // 登录成功, 跳转到主页
}
this.loading = false
})
}
- 导航守卫src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import store from '@/store'
import api from '@/http/api'
import { getIFramePath, getIFrameUrl } from '@/utils/iframe'
Vue.use(Router)
const router = new Router({
routes: [
{
path: '/',
name: '首页',
component: HelloWorld,
children: [
{
path: '',
name: '系统介绍',
component: HelloWorld,
meta: {
icon: 'fa fa-home fa-lg',
index: 0
}
}
]
}
]
})
/**
* 导航守卫
*/
router.beforeEach((to, from, next) => {
// 登录成功之后,会把用户信息保存在会话,用户信息的存在时间为会话生命周期,页面关闭即失效
let userName = sessionStorage.getItem('user')
if (to.path === '/login') {
// 如果是访问登录界面,如果用户会话信息存在,代表已登录过,跳转到主页
if (userName) {
next({ path: '/' })
} else {
next()
}
} else {
if (!userName) {
// 如果访问非登录界面,且户会话信息不存在,代表未登录,则跳转到登录界面
next({ path: '/login' })
} else {
// 加载动态菜单和路由
addDynamicMenuAndRoutes(userName, to, from)
next()
}
}
})
/**
* 加载动态菜单和路由
*/
function addDynamicMenuAndRoutes (userName, to, from) {
// 处理IFrame嵌套页面
handleIFrameUrl(to.path)
if (store.state.app.menuRouteLoaded) {
return
}
api.menu.findNavTree({'userName': userName}).then(res => {
// 添加动态路由
let dynamicRoutes = addDynamicRoutes(res.data)
// 处理静态组件绑定路由
router.options.routes[0].children = router.options.routes[0].children.concat(dynamicRoutes)
router.addRoutes(router.options.routes)
// 保存加载状态
store.commit('menuRouteLoaded', true)
// 保存菜单树
store.commit('setNavTree', res.data)
}).then(res => {
api.user.findPermissions({'name': userName}).then(res => {
// 保存用户权限标识集合
store.commit('setPerms', res.data)
})
}).catch(function (res) {
})
}
/**
* 处理IFrame嵌套页面
* 此处主要去匹配在store中存储的iframe路径列表, 通过匹配Path获取iframe真正的url,将url存储到store中
*/
function handleIFrameUrl (path) {
// 嵌套页面,保存iframeUrl到store,供IFrame组件读取展示
let url = path
let length = store.state.iframe.iframeUrls.length
for (let i = 0; i < length; i++) {
let iframe = store.state.iframe.iframeUrls[i]
if (path != null && path.endsWith(iframe.path)) {
url = iframe.url
store.commit('setIFrameUrl', url)
break
}
}
}
/**
* 添加动态(菜单)路由
* @param {*} menuList 菜单列表
* @param {*} routes 递归创建的动态(菜单)路由
*/
function addDynamicRoutes (menuList = [], routes = []) {
var temp = []
for (var i = 0; i < menuList.length; i++) {
if (menuList[i].children && menuList[i].children.length >= 1) {
temp = temp.concat(menuList[i].children)
} else if (menuList[i].url && /\S/.test(menuList[i].url)) {
menuList[i].url = menuList[i].url.replace(/^\//, '')
// 创建路由配置
var route = {
path: menuList[i].url,
component: null,
name: menuList[i].name,
meta: {
icon: menuList[i].icon,
index: menuList[i].id
}
}
let path = getIFramePath(menuList[i].url)
if (path) {
// 如果是嵌套页面, 通过iframe展示
route['path'] = path
route['component'] = resolve => require([`@/views/IFrame/IFrame`], resolve)
// 存储嵌套页面路由路径和访问URL
let url = getIFrameUrl(menuList[i].url)
let iFrameUrl = {'path': path, 'url': url}
store.commit('addIFrameUrl', iFrameUrl)
} else {
try {
// 根据菜单URL动态加载vue组件,这里要求vue组件须按照url路径存储
// 如url="sys/user",则组件路径应是"@/views/sys/user.vue",否则组件加载不到
let array = menuList[i].url.split('/')
let url = ''
for (let i = 0; i < array.length; i++) {
url += array[i].substring(0, 1).toUpperCase() + array[i].substring(1) + '/'
}
url = url.substring(0, url.length - 1)
route['component'] = resolve => require([`@/views/${url}`], resolve)
} catch (e) {}
}
routes.push(route)
}
}
if (temp.length >= 1) {
addDynamicRoutes(temp, routes)
} else {
console.log(routes)
}
return routes
}
export default router
- src\utils\iframe.js
/**
* 嵌套页面IFrame模块
*/
import { baseUrl } from '@/utils/global'
/**
* 嵌套页面URL地址
* iframe:**** -> ****
* http[s]://**** -> ****
* @param {*} url
*/
export function getIFramePath (url) {
let iframeUrl = ''
// 匹配以iframe:开头的任意长度的字符串
if (/^iframe:.*/.test(url)) {
iframeUrl = url.replace('iframe:', '')
} else if (/^http[s]?:\/\/.*/.test(url)) {
iframeUrl = url.replace('http://', '')
iframeUrl = url.replace('https://', '')
if (iframeUrl.indexOf(':') !== -1) {
iframeUrl = iframeUrl.substring(iframeUrl.lastIndexOf(':') + 1)
}
}
return iframeUrl
}
/**
* 获取嵌套页面路由的路径
* iframe:druid/index.html -> baseUrl + druid/index.html
* http[s]://druid/index.html -> 不变
* @param {*} url
*/
export function getIFrameUrl (url) {
let iframeUrl = ''
if (/^iframe:.*/.test(url)) {
iframeUrl = baseUrl + url.replace('iframe:', '')
} else if (/^http[s]?:\/\/.*/.test(url)) {
iframeUrl = url
}
return iframeUrl
}
- src/views/IFrame/IFrame.js
<template>
<div class="iframe-container">
<iframe :src="src" scrolling="auto" frameborder="0" class="frame" :onload="onloaded()">
</iframe>
</div>
</template>
<script>
export default {
data () {
return {
src: '',
loading: null
}
},
methods: {
// 获取路径
resetSrc: function (url) {
this.src = url
this.load()
},
load: function () {
this.loading = this.$loading({
lock: true,
text: 'loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.5)',
// fullscreen: false,
target: document.querySelector('#main-container ')
})
},
onloaded: function () {
if (this.loading) {
this.loading.close()
}
}
},
mounted () {
this.resetSrc(this.$store.state.iframe.iframeUrl)
},
watch: {
$route: {
handler: function (val, oldVal) {
// 如果是跳转到嵌套页面,切换iframe的url
this.resetSrc(this.$store.state.iframe.iframeUrl)
}
}
}
}
</script>
<style lang="scss">
.iframe-container {
position: absolute;
top: 0px;
left: 0px;
right: 0px;;
bottom: 0px;
.frame {
width: 100%;
height: 100%;
}
}
</style>
真正投入使用的router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
const router = new Router({
routes: [
{
path: '/',
name: '首页',
component: HelloWorld,
children: [
{
path: '',
name: '系统介绍',
component: HelloWorld,
meta: {
icon: 'fa fa-home fa-lg',
index: 0
}
}
]
}
]
})
/**
* 导航守卫
*/
router.beforeEach((to, from, next) => {
let userName = sessionStorage.getItem('user')
if (to.path === '/login') {
if (userName) {
next({ path: '/' })
} else {
next()
}
} else {
if (!userName) {
next({ path: '/login' })
} else {
next()
}
}
})
export default router
导航事件:
let path = getIFramePath(menu.url)
if (!path)
path = menu.url
this.$router.push("/" + path);
七、页面权限控制
权限标识是对页面资源进行权限控制的唯一标识,主要是增、删、改、查的权限控制。权限标识主要包含四种,以用户管理为例,权限标识包括sys:user:add(新增)、sys:user:edit(编辑)、sys:user:delete(删除)、sys:user:view(查看)。
src\permission/index.js
import store from '@/store'
/**
* 判断用户是否拥有操作权限
* 根据传入的权限标识,查看是否存在用户权限标识集合
* @param perms
*/
export function hasPermission (perms) {
let hasPermission = false
let permissions = store.state.user.perms
for(let i=0, len=permissions.length; i<len; i++) {
if(permissions[i] === perms) {
hasPermission = true;
break
}
}
return hasPermission
}
页面操作按钮提供perms属性绑定权限标识,使用disable属性绑定权限判断方法的返回值,权限判断方法hasPerms(perms)通过查找上一步保存的用户权限标识集合是否包含perms来包含说明用户拥有此相关权限,否则设置当前操作按钮为不可用状态。
新建一个权限按钮的组件:
<template>
<el-button :size="size" :type="type" :icon="icon"
:loading="loading" :disabled="!hasPerms(perms)" @click="handleClick">
{{label}}
</el-button>
</template>
<script>
import { hasPermission } from '@/permission/index.js'
export default {
name: 'KtButton',
props: {
label: { // 按钮显示文本
type: String,
default: 'Button'
},
icon: { // 按钮显示图标
type: String,
default: ''
},
size: { // 按钮尺寸
type: String,
default: 'mini'
},
type: { // 按钮类型
type: String,
default: null
},
loading: { // 按钮加载标识
type: Boolean,
default: false
},
disabled: { // 按钮是否禁用
type: Boolean,
default: false
},
perms: { // 按钮权限标识,外部使用者传入
type: String,
default: null
}
},
data() {
return {
}
},
methods: {
handleClick: function () {
// 按钮操作处理函数
this.$emit('click', {})
},
hasPerms: function (perms) {
// 根据权限标识和外部指示状态进行权限判断
return hasPermission(perms) & !this.disabled
}
},
mounted() {
}
}
</script>
<style scoped>
</style>
表格组件
六、框架页面设计
- Home页设计
Home.vue主页由导航菜单、头部区域和主内容区域组成。
<template>
<div class="container">
<!-- 导航菜单栏 -->
<nav-bar></nav-bar>
<!-- 头部区域 -->
<head-bar></head-bar>
<!-- 主内容区域 -->
<main-content></main-content>
</div>
</template>
<script>
import HeadBar from "./HeadBar"
import NavBar from "./NavBar"
import MainContent from "./MainContent"
export default {
components:{
HeadBar,
NavBar,
MainContent
}
};
</script>
<style scoped lang="scss">
.container {
position:absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
// background: rgba(224, 234, 235, 0.1);
}
</style>
- 顶部菜单栏设计Headbar
<template>
<div class="headbar" :style="{'background':themeColor}"
:class="collapse?'position-collapse-left':'position-left'">
<!-- 导航收缩 -->
<span class="hamburg">
<el-menu class="el-menu-demo" :background-color="themeColor" text-color="#fff"
:active-text-color="themeColor" mode="horizontal">
<el-menu-item index="1" @click="onCollapse">
<hamburger :isActive="collapse"></hamburger>
</el-menu-item>
</el-menu>
</span>
<!-- 导航菜单 -->
<span class="navbar">
<el-menu :default-active="activeIndex" class="el-menu-demo"
:background-color="themeColor" text-color="#fff" active-text-color="#ffd04b" mode="horizontal" @select="selectNavBar()">
<el-menu-item index="1" @click="$router.push('/')">{{$t("common.home")}}</el-menu-item>
<el-menu-item index="2" @click="openWindow('https://gitee.com/liuge1988/kitty/wikis/Home')">{{$t("common.doc")}}</el-menu-item>
<el-menu-item index="3" @click="openWindow('https://www.cnblogs.com/xifengxiaoma/')">{{$t("common.blog")}}</el-menu-item>
</el-menu>
</span>
<!-- 工具栏 -->
<span class="toolbar">
<el-menu class="el-menu-demo" :background-color="themeColor" text-color="#14889A"
:active-text-color="themeColor" mode="horizontal">
<el-menu-item index="1">
<!-- 主题切换 -->
<theme-picker class="theme-picker" :default="themeColor"
@onThemeChange="onThemeChange">
</theme-picker>
</el-menu-item>
<el-menu-item index="2" v-popover:popover-lang>
<!-- 语言切换 -->
<li style="color:#fff;" class="fa fa-language fa-lg"></li>
<el-popover ref="popover-lang" placement="bottom-start" trigger="click" v-model="langVisible">
<div class="lang-item" @click="changeLanguage('zh_cn')">简体中文</div>
<div class="lang-item" @click="changeLanguage('en_us')">English</div>
</el-popover>
</el-menu-item>
<el-menu-item index="3" v-popover:popover-message>
<!-- 我的私信 -->
<el-badge :value="5" :max="99" class="badge">
<li style="color:#fff;" class="fa fa-envelope-o fa-lg"></li>
</el-badge>
<el-popover ref="popover-message" placement="bottom-end" trigger="click">
<message-panel></message-panel>
</el-popover>
</el-menu-item>
<el-menu-item index="4" v-popover:popover-notice>
<!-- 系统通知 -->
<el-badge :value="4" :max="99" class="badge">
<li style="color:#fff;" class="fa fa-bell-o fa-lg"></li>
</el-badge>
<el-popover ref="popover-notice" placement="bottom-end" trigger="click">
<notice-panel></notice-panel>
</el-popover>
</el-menu-item>
<el-menu-item index="5" v-popover:popover-personal>
<!-- 用户信息 -->
<span class="user-info"><img :src="user.avatar" />{{user.nickName}}</span>
<el-popover ref="popover-personal" placement="bottom-end" trigger="click" :visible-arrow="false">
<personal-panel :user="user"></personal-panel>
</el-popover>
</el-menu-item>
</el-menu>
</span>
</div>
</template>
<script>
import { mapState } from 'vuex'
import mock from "@/mock/index"
import Hamburger from "@/components/Hamburger"
import ThemePicker from "@/components/ThemePicker"
import NoticePanel from "@/views/Core/NoticePanel"
import MessagePanel from "@/views/Core/MessagePanel"
import PersonalPanel from "@/views/Core/PersonalPanel"
export default {
components:{
Hamburger,
ThemePicker,
NoticePanel,
MessagePanel,
PersonalPanel
},
data() {
return {
user: {
},
activeIndex: '1',
langVisible: false
}
},
methods: {
openWindow(url) {
window.open(url)
},
selectNavBar(key, keyPath) {
console.log(key, keyPath)
},
// 折叠导航栏
onCollapse: function() {
this.$store.commit('onCollapse')
},
// 切换主题
onThemeChange: function(themeColor) {
this.$store.commit('setThemeColor', themeColor)
},
// 语言切换
changeLanguage(lang) {
lang === '' ? 'zh_cn' : lang
this.$i18n.locale = lang
this.langVisible = false
}
},
mounted() {
var user = sessionStorage.getItem("user")
if (user) {
let params = {name:user}
this.$api.user.findByName(params).then((res) => {
if(res.code == 200) {
this.user = res.data
this.user.avatar = require("@/assets/user.png")
}
})
}
},
computed:{
...mapState({
themeColor: state=>state.app.themeColor,
collapse: state=>state.app.collapse
})
}
}
</script>
<style scoped lang="scss">
.headbar {
position: fixed;
top: 0;
right: 0;
z-index: 1030;
height: 60px;
line-height: 60px;
border-color: rgba(180, 190, 190, 0.8);
border-left-width: 1px;
border-left-style: solid;
}
.hamburg {
float: left;
}
.navbar {
float: left;
}
.toolbar {
float: right;
}
.lang-item {
font-size: 16px;
padding-left: 8px;
padding-top: 8px;
padding-bottom: 8px;
cursor: pointer;
}
.lang-item:hover {
font-size: 18px;
background: #b0d6ce4d;
}
.user-info {
font-size: 20px;
color: #fff;
cursor: pointer;
img {
width: 40px;
height: 40px;
border-radius: 10px;
margin: 10px 0px 10px 10px;
float: right;
}
}
.badge {
line-height: 18px;
}
.position-left {
left: 200px;
}
.position-collapse-left {
left: 65px;
}
</style>
- 菜单栏设计NavBar.vue
<template>
<div class="menu-bar-container">
<!-- logo -->
<div class="logo" :style="{'background-color':themeColor}" :class="collapse?'menu-bar-collapse-width':'menu-bar-width'"
@click="$router.push('/')">
<img v-if="collapse" src="@/assets/logo.png"/> <div>{{collapse?'':appName}}</div>
</div>
<!-- 导航菜单 -->
<el-menu ref="navmenu" default-active="1" :class="collapse?'menu-bar-collapse-width':'menu-bar-width'"
:collapse="collapse" :collapse-transition="false" :unique-opened="true "
@open="handleopen" @close="handleclose" @select="handleselect">
<!-- 导航菜单树组件,动态加载菜单 -->
<menu-tree v-for="item in navTree" :key="item.id" :menu="item"></menu-tree>
</el-menu>
</div>
</template>
<script>
import { mapState } from 'vuex'
import MenuTree from "@/components/MenuTree"
export default {
components:{
MenuTree
},
computed: {
...mapState({
appName: state=>state.app.appName,
themeColor: state=>state.app.themeColor,
collapse: state=>state.app.collapse,
navTree: state=>state.menu.navTree
}),
mainTabs: {
get () { return this.$store.state.tab.mainTabs },
set (val) { this.$store.commit('updateMainTabs', val) }
},
mainTabsActiveName: {
get () { return this.$store.state.tab.mainTabsActiveName },
set (val) { this.$store.commit('updateMainTabsActiveName', val) }
}
},
watch: {
$route: 'handleRoute'
},
created () {
this.handleRoute(this.$route)
},
methods: {
handleopen() {
console.log('handleopen')
},
handleclose() {
console.log('handleclose')
},
handleselect(a, b) {
console.log('handleselect')
},
// 路由操作处理
handleRoute (route) {
// tab标签页选中, 如果不存在则先添加
var tab = this.mainTabs.filter(item => item.name === route.name)[0]
if (!tab) {
tab = {
name: route.name,
title: route.name,
icon: route.meta.icon
}
this.mainTabs = this.mainTabs.concat(tab)
}
this.mainTabsActiveName = tab.name
// 切换标签页时同步更新高亮菜单
if(this.$refs.navmenu != null) {
this.$refs.navmenu.activeIndex = '' + route.meta.index
this.$refs.navmenu.initOpenedMenu()
}
}
}
}
</script>
<style scoped lang="scss">
.menu-bar-container {
position: fixed;
top: 0px;
left: 0;
bottom: 0;
z-index: 1020;
.el-menu {
position:absolute;
top: 60px;
bottom: 0px;
text-align: left;
// background-color: #2968a30c;
}
.logo {
position:absolute;
top: 0px;
height: 60px;
line-height: 60px;
background: #545c64;
cursor:pointer;
img {
width: 40px;
height: 40px;
border-radius: 0px;
margin: 10px 10px 10px 10px;
float: left;
}
div {
font-size: 22px;
color: white;
text-align: left;
padding-left: 20px;
}
}
.menu-bar-width {
width: 200px;
}
.menu-bar-collapse-width {
width: 65px;
}
}
</style>
- 子页面框架设计MainContent
<template>
<div id="main-container" class="main-container" :class="$store.state.app.collapse?'position-collapse-left':'position-left'">
<!-- 标签页 -->
<div class="tab-container">
<el-tabs class="tabs" :class="$store.state.app.collapse?'position-collapse-left':'position-left'"
v-model="mainTabsActiveName" :closable="true" type="card"
@tab-click="selectedTabHandle" @tab-remove="removeTabHandle">
<el-dropdown class="tabs-tools" :show-timeout="0" trigger="hover">
<div style="font-size:20px;width:50px;"><i class="el-icon-arrow-down"></i></div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="tabsCloseCurrentHandle">关闭当前标签</el-dropdown-item>
<el-dropdown-item @click.native="tabsCloseOtherHandle">关闭其它标签</el-dropdown-item>
<el-dropdown-item @click.native="tabsCloseAllHandle">关闭全部标签</el-dropdown-item>
<el-dropdown-item @click.native="tabsRefreshCurrentHandle">刷新当前标签</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-tab-pane v-for="item in mainTabs"
:key="item.name" :label="item.title" :name="item.name">
<span slot="label"><i :class="item.icon"></i> {{item.title}} </span>
</el-tab-pane>
</el-tabs>
</div>
<!-- 主内容区域 -->
<div class="main-content">
<keep-alive>
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
</keep-alive>
</div>
</div>
</template>
<script>
export default {
data () {
return {
}
},
computed: {
mainTabs: {
get () { return this.$store.state.tab.mainTabs },
set (val) { this.$store.commit('updateMainTabs', val) }
},
mainTabsActiveName: {
get () { return this.$store.state.tab.mainTabsActiveName },
set (val) { this.$store.commit('updateMainTabsActiveName', val) }
}
},
methods: {
// tabs, 选中tab
selectedTabHandle (tab) {
tab = this.mainTabs.filter(item => item.name === tab.name)
if (tab.length >= 1) {
this.$router.push({ name: tab[0].name })
}
},
// tabs, 删除tab
removeTabHandle (tabName) {
this.mainTabs = this.mainTabs.filter(item => item.name !== tabName)
if (this.mainTabs.length >= 1) {
// 当前选中tab被删除
if (tabName === this.mainTabsActiveName) {
this.$router.push({ name: this.mainTabs[this.mainTabs.length - 1].name }, () => {
this.mainTabsActiveName = this.$route.name
})
}
} else {
this.$router.push("/")
}
},
// tabs, 关闭当前
tabsCloseCurrentHandle () {
this.removeTabHandle(this.mainTabsActiveName)
},
// tabs, 关闭其它
tabsCloseOtherHandle () {
this.mainTabs = this.mainTabs.filter(item => item.name === this.mainTabsActiveName)
},
// tabs, 关闭全部
tabsCloseAllHandle () {
this.mainTabs = []
this.$router.push("/")
},
// tabs, 刷新当前
tabsRefreshCurrentHandle () {
var tempTabName = this.mainTabsActiveName
this.removeTabHandle(tempTabName)
this.$nextTick(() => {
this.$router.push({ name: tempTabName })
})
}
}
}
</script>
<style scoped lang="scss">
.main-container {
padding: 0 5px 5px;
position: absolute;
top: 60px;
left: 1px;
right: 1px;
bottom: 0px;
// background: rgba(56, 5, 114, 0.5);
.tabs {
position: fixed;
top: 60px;
right: 50px;
padding-left: 0px;
padding-right: 2px;
z-index: 1020;
height: 40px;
line-height: 40px;
font-size: 14px;
background: rgb(255, 253, 255);
border-color: rgba(200, 206, 206, 0.5);
// border-left-width: 1px;
// border-left-style: solid;
border-bottom-width: 1px;
border-bottom-style: solid;
}
.tabs-tools {
position: fixed;
top: 60px;
right: 0;
z-index: 1020;
height: 40px;
// padding: 0 10px;
font-size: 14px;
line-height: 40px;
cursor: pointer;
border-color: rgba(200, 206, 206, 0.5);
border-left-width: 1px;
border-left-style: solid;
border-bottom-width: 1px;
border-bottom-style: solid;
background: rgba(255, 255, 255, 1);
}
.tabs-tools:hover {
background: rgba(200, 206, 206, 1);
}
.main-content {
position: absolute;
top: 45px;
left: 5px;
right: 5px;
bottom: 5px;
padding: 5px;
// background: rgba(209, 212, 212, 0.5);
}
}
.position-left {
left: 200px;
}
.position-collapse-left {
left: 65px;
}
</style>