提交
This commit is contained in:
65
src/layout/components/AppMain.vue
Normal file
65
src/layout/components/AppMain.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'app-main',
|
||||
computed: {
|
||||
cachedViews() {
|
||||
// console.log(this.$store.state.tagsView.cachedViews)
|
||||
return this.$store.state.tagsView.cachedViews
|
||||
},
|
||||
key() {
|
||||
return this.$route.path
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route: {
|
||||
handler(to) {
|
||||
if (this.$store.state.settings.tagsView || !to.name) return
|
||||
this.$store.dispatch('tagsView/noTagsAddView', to)
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
render(h) {
|
||||
const { cachedViews, key } = this
|
||||
const keepAlive = (
|
||||
<keep-alive include={cachedViews}>
|
||||
<router-view key={key} />
|
||||
</keep-alive>
|
||||
)
|
||||
window.__KEEP_ALIVE__ = keepAlive
|
||||
return (
|
||||
<section
|
||||
class='app-main-container'
|
||||
id='app-main'
|
||||
>
|
||||
<transition
|
||||
name='fade-transform'
|
||||
mode='out-in'
|
||||
onBeforeLeave={() => this.$el.parentNode.style.overflow = 'hidden'}
|
||||
onAfterEnter={() => this.$el.parentNode.style.overflow = ''}
|
||||
>
|
||||
{keepAlive}
|
||||
</transition>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-main-container {
|
||||
/* BFC */
|
||||
float: left;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
// fix css style bug in open el-dialog
|
||||
.el-popup-parent--hidden {
|
||||
.fixed-header {
|
||||
padding-right: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
src/layout/components/Breadcrumb/index.vue
Normal file
65
src/layout/components/Breadcrumb/index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<el-breadcrumb id="breadcrumb-container" class="app-breadcrumb" separator="/">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
|
||||
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
|
||||
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
levelList: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getBreadcrumb()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getBreadcrumb()
|
||||
},
|
||||
methods: {
|
||||
getBreadcrumb() {
|
||||
// only show routes with meta.title
|
||||
const matched = this.$route.matched.filter(item => item.meta && item.meta.title)
|
||||
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||
},
|
||||
pathCompile(path) {
|
||||
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
|
||||
const { params } = this.$route
|
||||
var toPath = pathToRegexp.compile(path)
|
||||
return toPath(params)
|
||||
},
|
||||
handleLink(item) {
|
||||
const { redirect, path } = item
|
||||
if (redirect) {
|
||||
this.$router.push(redirect)
|
||||
return
|
||||
}
|
||||
this.$router.push(this.pathCompile(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 50px;
|
||||
|
||||
.no-redirect {
|
||||
color: #97a8be;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
34
src/layout/components/Hamburger/index.vue
Normal file
34
src/layout/components/Hamburger/index.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div id="hamburger-container" style="padding: 0 18px;" @click="toggleClick">
|
||||
<svg-icon icon-class="hamburger" class="hamburger" :class="{'is-active':isActive}" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleClick() {
|
||||
this.$emit('toggleClick')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='less' scoped>
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
.hamburger.is-active {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
</style>
|
||||
91
src/layout/components/Logo.vue
Normal file
91
src/layout/components/Logo.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
|
||||
<div key="collapse" class="sidebar-logo-link" to="/dashboard">
|
||||
<!-- <img v-if="logo" :src="settings.title=='电能质量监测系统'?logo:logo1" class="sidebar-logo" :style="{ width: settings.layout === 'layout1' ? '35px' : '50px',height: settings.layout === 'layout1' ? '35px' : '50px'}"> -->
|
||||
<img :src="logourl" class="sidebar-logo" :style="{ width: settings.layout === 'layout1' ? '35px' : '180px',height: settings.layout === 'layout1' ? '35px' : '50px'}">
|
||||
<!-- <svg-icon icon-class="logo" class="sidebar-logo" /> -->
|
||||
<transition name="sidebarLogoFade">
|
||||
<h1
|
||||
v-show="!collapse"
|
||||
class="sidebar-title"
|
||||
:style="{ fontSize: settings.layout === 'layout1' && settings.title.length >= 8 ? '14px' : '24px'}"
|
||||
>{{ settings.title }} </h1>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'sidebar-logo',
|
||||
props: {
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['settings'])
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// logo: require('../../assets/login/'+window.logo),
|
||||
logourl:window.sessionStorage.logo
|
||||
}
|
||||
},
|
||||
created(){
|
||||
// console.log(this.logourl)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "~@/styles/variables.scss";
|
||||
.sidebarLogoFade-enter-active {
|
||||
transition: opacity 1.5s;
|
||||
}
|
||||
|
||||
.sidebarLogoFade-enter,
|
||||
.sidebarLogoFade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sidebar-logo-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
padding-left: 20px;
|
||||
overflow: hidden;
|
||||
line-height: 60px;
|
||||
|
||||
& .sidebar-logo-link {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
& .sidebar-logo {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
margin-right: 8px;
|
||||
color: #fff;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
& .sidebar-title {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 50px;
|
||||
color: #fff;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&.collapse {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
src/layout/components/Navbar/Item.vue
Normal file
29
src/layout/components/Navbar/Item.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'menu-item',
|
||||
functional: true,
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
render(h, context) {
|
||||
const { icon, title } = context.props
|
||||
const vnodes = []
|
||||
|
||||
if (icon) {
|
||||
vnodes.push(<svg-icon icon-class={icon}/>)
|
||||
}
|
||||
|
||||
if (title) {
|
||||
vnodes.push(<span>{(title)}</span>)
|
||||
}
|
||||
return vnodes
|
||||
}
|
||||
}
|
||||
</script>
|
||||
61
src/layout/components/Navbar/NavbarItem.vue
Normal file
61
src/layout/components/Navbar/NavbarItem.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<el-menu-item v-if="!item.children || item.children.every(it=>it.hidden)" :index="item.routePath || item.url">
|
||||
<app-link :to="to(item)">
|
||||
<item :icon="item.icon" :title="item.title" />
|
||||
</app-link>
|
||||
</el-menu-item>
|
||||
<el-submenu v-else :class="{'no-arrow': item.title === '...'}" :index="item.routePath">
|
||||
<span slot="title">
|
||||
<item :icon="item.icon" :title="item.title" />
|
||||
</span>
|
||||
<navbar-item
|
||||
v-for="(child,idx) in item.children"
|
||||
:key="idx"
|
||||
:index="idx"
|
||||
:item="child"
|
||||
/>
|
||||
</el-submenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Item from './Item.vue'
|
||||
import AppLink from '../Sidebar/Link.vue'
|
||||
|
||||
export default {
|
||||
name: 'navbar-item',
|
||||
components: { Item, AppLink },
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
to(menuItem) {
|
||||
const visitedTags = this.$store.state.tagsView.visitedTags
|
||||
const tag = visitedTags.find(t => t.routeName === menuItem.routeName)
|
||||
return tag ? tag.routePath : menuItem.routePath
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.el-menu--horizontal .svg-icon {
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.no-arrow ::v-deep .el-submenu__title {
|
||||
width: 60px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
i {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
123
src/layout/components/Navbar/index.vue
Normal file
123
src/layout/components/Navbar/index.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="navbar-menu">
|
||||
<el-menu
|
||||
:default-active="customActiveMenu || activeMenu"
|
||||
unique-opened
|
||||
mode="horizontal"
|
||||
>
|
||||
<navbar-item v-for="(item,index) in renderMenu" :key="index" :item="item" :index="index" />
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import NavbarItem from './NavbarItem'
|
||||
import variables from '@/styles/variables.scss'
|
||||
import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'
|
||||
|
||||
export default {
|
||||
components: { NavbarItem },
|
||||
props: {
|
||||
menus: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
customActiveMenu: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
renderMenu: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'sidebar'
|
||||
]),
|
||||
activeMenu() {
|
||||
if (this.$route.meta.activeMenu) {
|
||||
return this.$route.meta.activeMenu
|
||||
}
|
||||
return this.$route.path
|
||||
},
|
||||
variables() {
|
||||
return variables
|
||||
},
|
||||
isCollapse() {
|
||||
return !this.sidebar.opened
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
menus() {
|
||||
this.setMenus()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.setMenus()
|
||||
}, 200)
|
||||
addResizeListener(this.$el, this.setMenus)
|
||||
},
|
||||
beforeDestroy() {
|
||||
removeResizeListener(this.$el, this.setMenus)
|
||||
},
|
||||
methods: {
|
||||
setMenus() {
|
||||
const wrapperW = this.$el.offsetWidth
|
||||
const topMenu = []
|
||||
const otherMenu = []
|
||||
let nowW = 0
|
||||
this.menus.forEach(item => {
|
||||
nowW += this.getItemW(item)
|
||||
if (nowW + 100 > wrapperW) {
|
||||
otherMenu.push(item)
|
||||
} else {
|
||||
topMenu.push(item)
|
||||
}
|
||||
})
|
||||
if (otherMenu.length > 0) {
|
||||
topMenu.push({
|
||||
title: '...',
|
||||
routePath: '/',
|
||||
children: otherMenu
|
||||
})
|
||||
}
|
||||
this.renderMenu = topMenu
|
||||
},
|
||||
getItemW(item) {
|
||||
// 20 14 8 20
|
||||
let w = 0
|
||||
if (item.icon) w += 22 // 图标长度
|
||||
if (item.children) w += 20 // 右边下箭头
|
||||
w += item.title.length * 14 // 文字长度
|
||||
w += 40 // padding
|
||||
return w
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.navbar-menu {
|
||||
flex: 1;
|
||||
& ::v-deep .el-menu.el-menu--horizontal {
|
||||
display: flex;
|
||||
border-bottom: none;
|
||||
.el-submenu__title {
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
}
|
||||
}
|
||||
& ::v-deep .el-menu-item {
|
||||
padding: 0;
|
||||
& > a {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
574
src/layout/components/RightMenu.vue
Normal file
574
src/layout/components/RightMenu.vue
Normal file
@@ -0,0 +1,574 @@
|
||||
<template>
|
||||
<div class="right-menu">
|
||||
<div class="menu-item">
|
||||
<span style="width: 210px;font-size: 14px;font-weight: 500;color:#05fd3c">当前时间:{{nowtime}}</span>
|
||||
<!-- <el-button type="primary" round @click="ddd" size="mini" >关闭告警</el-button>
|
||||
<el-badge :value="200" :max="99" class="item" >
|
||||
<i class="el-icon-message-solid" style="font-size: 20px;" @click="message"/>
|
||||
</el-badge> -->
|
||||
</div>
|
||||
<el-dropdown trigger="click" size="medium">
|
||||
<div id="right-menu-set" class="menu-item">
|
||||
<i class="el-icon-s-custom" /><span style="margin-left: 5px" v-if="flag"
|
||||
>{{ userInfo.name
|
||||
}}<i style="margin-left: 5px" class="el-icon-caret-bottom"
|
||||
/></span>
|
||||
</div>
|
||||
<!-- <el-dropdown-menu slot="dropdown" id="dropdown" class="user-dropdown" :style="device==1?'zoom:1':'zoom:1.06'"> -->
|
||||
<el-dropdown-menu slot="dropdown" id="dropdown" class="user-dropdown">
|
||||
<!-- <el-dropdown-menu slot="dropdown" id="dropdown" class="user-dropdown"> -->
|
||||
<el-dropdown-item>
|
||||
<span style="display: block" @click="userclickd()">用户信息</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="handleResetPassword()">
|
||||
<span style="display: block">修改密码</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<span style="dispblock" @click="goto">帮助文档</span>
|
||||
</el-dropdown-item>
|
||||
<!-- divided closelostar-->
|
||||
<el-dropdown-item @click.native="logout(1)">
|
||||
<span style="display: block">清理缓存</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="logout(0)">
|
||||
<span style="display: block">退出登录</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<el-dialog
|
||||
:close-on-click-modal="false"
|
||||
title="修改密码"
|
||||
:visible.sync="dialogVisible1"
|
||||
append-to-body
|
||||
width="30%"
|
||||
:before-close="closepw"
|
||||
>
|
||||
<el-form ref="passwordform" :model="passwordform" :rules="rules" label-width="100px">
|
||||
<el-form-item label="旧密码:" prop="password">
|
||||
<el-input v-model="passwordform.password" type="password" placeholder="请输入旧密码"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码:" prop="newPwd" class="top" >
|
||||
<el-input v-model="passwordform.newPwd" type="password" placeholder="请输入新密码"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码:" prop="confirmPwd" class="top">
|
||||
<el-input v-model="passwordform.confirmPwd" type="password" placeholder="请输入确认密码"/>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button
|
||||
text-align="center"
|
||||
|
||||
@click="resetForm('passwordform')"
|
||||
>取 消</el-button
|
||||
>
|
||||
<el-button text-align="center" type="primary" @click="submint"
|
||||
>确认</el-button
|
||||
>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
:close-on-click-modal="false"
|
||||
title="用户信息"
|
||||
append-to-body
|
||||
:visible.sync="dialogVisible2"
|
||||
width="30%"
|
||||
>
|
||||
<el-form ref="form" :model="form" label-width="100px">
|
||||
<el-form-item label="用户名称:">
|
||||
<el-input v-model="form.name" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="登录名称:" class="top" >
|
||||
<el-input v-model="form.loginName" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="归属部门名称:" class="top" >
|
||||
<el-input v-model="form.deptName" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="拥有的角色:" class="top" >
|
||||
<el-input v-model="form.roles" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="电话号码:" class="top" >
|
||||
<el-input v-model="form.iphone" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="电子邮箱:" class="top" >
|
||||
<el-input v-model="form.emirl" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<!-- <el-form-item label="系统类型:">
|
||||
<el-input v-model="form.systype" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:">
|
||||
<el-input v-model="form.status" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="登录时间:">
|
||||
<el-input v-model="form.lofintime" :disabled="true"></el-input>
|
||||
</el-form-item> -->
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from "vuex";
|
||||
import {
|
||||
add,
|
||||
list,
|
||||
getById,
|
||||
deleteById,
|
||||
dept,
|
||||
edit,
|
||||
detptree,
|
||||
rolelist,
|
||||
selecRoleDetail,
|
||||
passwordConfirm,
|
||||
deluser,
|
||||
updatePassword,
|
||||
activateUser,
|
||||
exportUser,
|
||||
exportUserExcel,
|
||||
} from "@/api/admin/user";
|
||||
import { gongkey, userfunction } from "@/api/user";
|
||||
import axios from "axios";
|
||||
import { sm3Digest } from "@/assets/commjs/sm3";
|
||||
import { sm2, encrypt } from "@/assets/commjs/sm2.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
var validatePass = (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error("请输入密码"));
|
||||
} else {
|
||||
if (this.passwordform.confirmPwd !== "") {
|
||||
this.$refs.passwordform.validateField("confirmPwd");
|
||||
}
|
||||
callback();
|
||||
}
|
||||
};
|
||||
var validatePass2 = (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback(new Error("请再次输入密码"));
|
||||
} else if (value !== this.passwordform.newPwd) {
|
||||
callback(new Error("两次输入密码不一致!"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
flag: true,
|
||||
dialogVisible2: false,
|
||||
dialogVisible1: false,
|
||||
passwordform: {
|
||||
password: "",
|
||||
newPwd:'',
|
||||
confirmPwd:''
|
||||
},
|
||||
nowtime:'',
|
||||
LocationProvince:"正在定位所在省", //给渲染层定义一个初始值
|
||||
LocationCity:"正在定位所在市" , //给渲染层定义一个初始值
|
||||
form: {
|
||||
name: "",
|
||||
deptName: "",
|
||||
iphone: "",
|
||||
emirl: "",
|
||||
roles: '',
|
||||
loginName: "",
|
||||
systype: "",
|
||||
status: "",
|
||||
lofintime: "",
|
||||
},
|
||||
password3: "",
|
||||
id: "",
|
||||
device: 1,
|
||||
pd: "",
|
||||
|
||||
rules: {
|
||||
password: [
|
||||
{ required: true, message: "请输入旧密码", trigger: "blur" },
|
||||
{
|
||||
min: 6,
|
||||
max: 16,
|
||||
message: "长度在 6 到 16 个字符",
|
||||
trigger: "blur",
|
||||
},
|
||||
{ validator: validatePass, trigger: "blur" },
|
||||
],
|
||||
newPwd: [
|
||||
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||
{
|
||||
min: 6,
|
||||
max: 16,
|
||||
message: "长度在 6 到 16 个字符",
|
||||
trigger: "blur",
|
||||
},
|
||||
{ validator: validatePass, trigger: "blur" },
|
||||
],
|
||||
confirmPwd: [
|
||||
{ required: true, message: "请确认密码", trigger: "blur" },
|
||||
{
|
||||
min: 6,
|
||||
max: 16,
|
||||
message: "长度在 6 到 16 个字符",
|
||||
trigger: "blur",
|
||||
},
|
||||
{ validator: validatePass2, trigger: "blur", required: true },
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["userInfo"]),
|
||||
},
|
||||
created() {
|
||||
this.device = window.devicePixelRatio;
|
||||
|
||||
|
||||
},
|
||||
mounted() {
|
||||
// this.city() //触发获取城市方法
|
||||
setInterval(()=>{
|
||||
this.getnowtime()
|
||||
},500)
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
changeSetting: "settings/changeSetting",
|
||||
}),
|
||||
|
||||
async logout(t) {
|
||||
this.flag = false;
|
||||
await this.$store.dispatch("user/logout");
|
||||
if(t==1){
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: "缓存清理成功!",
|
||||
});
|
||||
}else{
|
||||
return
|
||||
}
|
||||
},
|
||||
toggleSettings() {
|
||||
const showSettings = this.$store.state.app.showSettings;
|
||||
this.$store.state.app.showSettings = !showSettings;
|
||||
},
|
||||
|
||||
goto(t) {
|
||||
//this.$router.push({path:'/algorithm/algorithm1',query:{name:'wtzhpg',path:'/algorithm/algorithm1'}})
|
||||
this.$router.push({ path: "/algorithm/algorithm1" });
|
||||
|
||||
|
||||
},
|
||||
message() {
|
||||
// alert(9)
|
||||
this.$router.push("/BusinessAdministrator/alarManagement/AlarmInformation");
|
||||
},
|
||||
ddd() {
|
||||
this.$notify.closeAll();
|
||||
},
|
||||
closelostar() {
|
||||
this.$confirm("此操作将清理所有信息缓存, 是否继续?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
})
|
||||
.then(() => {
|
||||
window.localStorage.removeItem("loglevel:webpack-dev-server");
|
||||
window.localStorage.removeItem("CUSTOM_SETTINGS");
|
||||
//window.localStorage.clear()
|
||||
//window.sessionStorage.clear()
|
||||
this.$store.dispatch("user/logout");
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: "缓存清理成功!",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message({
|
||||
type: "info",
|
||||
message: "已取消缓存清理",
|
||||
});
|
||||
});
|
||||
},
|
||||
userclickd() {
|
||||
this.dialogVisible2 = true;
|
||||
var info = window.sessionStorage.getItem("Info");
|
||||
info = eval("(" + info + ")");
|
||||
// var gNamelist=[]
|
||||
// for (var i = 0; i < info.role.length; i++) {
|
||||
// gNamelist.push(row.role[i]);
|
||||
// }
|
||||
// gNamelist.join();
|
||||
this.form.name = info.name;
|
||||
this.form.deptName = info.deptName;
|
||||
if (info.type == 1) {
|
||||
this.form.systype = "省级系统";
|
||||
} else {
|
||||
this.form.systype = "数据中心";
|
||||
}
|
||||
this.form.emirl = info.emirl;
|
||||
let str = info.loginTime
|
||||
let str1=str.split('T')
|
||||
//console.log(str1)
|
||||
this.form.lofintime = str1[0]+" "+str1[1];
|
||||
// let r = ''
|
||||
// info.role.forEach((m,index) => {
|
||||
// r+=m+', '
|
||||
// });
|
||||
this.form.roles = info.role.join()
|
||||
//this.form.roles =JSON.stringify(info.role);
|
||||
|
||||
|
||||
if (info.state == 1) {
|
||||
this.form.status = "正常";
|
||||
}
|
||||
this.form.loginName = info.loginName;
|
||||
},
|
||||
resetForm(formName){
|
||||
this.$refs[formName].resetFields();
|
||||
},
|
||||
closepw(){
|
||||
this.$refs.passwordform.resetFields();
|
||||
this.dialogVisible1 = false;
|
||||
},
|
||||
handleResetPassword() {
|
||||
this.passwordform.password = "";
|
||||
this.passwordform.newPwd = "";
|
||||
this.passwordform.confirmPwd = "";
|
||||
|
||||
this.pd = "pw";
|
||||
this.dialogVisible1 = true;
|
||||
var info = window.sessionStorage.getItem("Info");
|
||||
info = eval("(" + info + ")");
|
||||
this.id = info.id;
|
||||
|
||||
this.gongKey();
|
||||
},
|
||||
submint() {
|
||||
this.gonxxKey();
|
||||
},
|
||||
gonxxKey() {
|
||||
gongkey({ loginName: window.sessionStorage.cnloginname }).then((response) => {
|
||||
if (response.code === "A0000") {
|
||||
var publicKey = response.data;
|
||||
// console.log("获取公钥后赋值" + publicKey);
|
||||
var sm3Pwd = sm3Digest(this.passwordform.password); //SM3加密
|
||||
var jiamipassword = "";
|
||||
jiamipassword = sm2(
|
||||
sm3Pwd + "|" + this.passwordform.password,
|
||||
publicKey,
|
||||
0
|
||||
); //SM2公钥加密
|
||||
this.password3 = jiamipassword;
|
||||
// console.log("密码加密后hhhhh:" + jiamipassword);
|
||||
// console.log("测试" + this.password3);
|
||||
if (this.pd == "del") {
|
||||
var password = this.password3;
|
||||
this.dialogVisible1 = false;
|
||||
passwordConfirm(password).then((response) => {
|
||||
if (response.code === "A0000") {
|
||||
this.$confirm("此操作将永久删除该用户, 是否继续?", "提示", {
|
||||
confirmButtonText: "删除",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
})
|
||||
.then(() => {
|
||||
let data = {
|
||||
id: this.id,
|
||||
};
|
||||
deluser(data).then((response) => {
|
||||
this.$message({ type: "success", message: "删除成功" });
|
||||
});
|
||||
this.handleQuery();
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message({
|
||||
type: "info",
|
||||
message: "已取消删除",
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.pd === "pw") {
|
||||
var password = this.password3;
|
||||
this.dialogVisible1 = false;
|
||||
passwordConfirm(password).then((response) => {
|
||||
if (response.code === "A0000") {
|
||||
// this.$prompt("请输入新密码", "提示", {
|
||||
// confirmButtonText: "确定",
|
||||
// cancelButtonText: "取消",
|
||||
// })
|
||||
// .then(({ value }) => {
|
||||
gongkey({loginName: window.sessionStorage.cnloginname}).then((response) => {
|
||||
if (response.code === "A0000") {
|
||||
var publicKey = response.data;
|
||||
// console.log("获取公钥后赋值" + publicKey);
|
||||
var sm3Pwd = sm3Digest(this.passwordform.newPwd); //SM3加密
|
||||
var jiamipassword = "";
|
||||
jiamipassword = sm2( sm3Pwd + "|" + this.passwordform.newPwd, publicKey, 0); //SM2公钥加密
|
||||
let id = window.sessionStorage.getItem("Info").id;
|
||||
let data = {
|
||||
id: this.id,
|
||||
newPassword: jiamipassword,
|
||||
};
|
||||
updatePassword(data).then((response) => {
|
||||
if (response.code === "A0000") {
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: "修改成功",
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// })
|
||||
// .catch(() => {
|
||||
// this.$message({
|
||||
// type: "info",
|
||||
// message: "取消输入",
|
||||
// });
|
||||
// });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
getnowtime(){
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = ('0' + (now.getMonth() + 1)).slice(-2);
|
||||
const day = ('0' + now.getDate()).slice(-2);
|
||||
const hours = ('0' + now.getHours()).slice(-2);
|
||||
const minutes = ('0' + now.getMinutes()).slice(-2);
|
||||
const seconds = ('0' + now.getSeconds()).slice(-2);
|
||||
|
||||
this.nowtime = year +"-"+ month +"-"+ day +":"+ hours +":"+ minutes +":"+ seconds;
|
||||
},
|
||||
city(){ //定义获取城市方法
|
||||
|
||||
const geolocation = new BMap.Geolocation();
|
||||
|
||||
var _this = this
|
||||
|
||||
geolocation.getCurrentPosition(function getinfo(position){
|
||||
|
||||
let city = position.address.city; //获取城市信息
|
||||
|
||||
let province = position.address.province; //获取省份信息
|
||||
|
||||
_this.LocationProvince = province
|
||||
|
||||
_this.LocationCity = city
|
||||
|
||||
}, function(e) {
|
||||
|
||||
_this.LocationCity = "定位失败"
|
||||
|
||||
}, {provider: 'baidu'});
|
||||
|
||||
}
|
||||
|
||||
|
||||
// delAllCookie() {
|
||||
// //清空全部cookie
|
||||
// var keys = document.cookie.match(/[^ =;]+(?=\=)/g);
|
||||
// if (keys) {
|
||||
// for (var i = keys.length; i--; ) {
|
||||
// document.cookie =
|
||||
// keys[i] + "=0;path=/;expires=" + new Date(0).toUTCString(); //清除当前域名下
|
||||
// document.cookie =
|
||||
// keys[i] +"=0;path=/;domain=" +document.domain +";expires=" +
|
||||
// new Date(0).toUTCString();
|
||||
// document.cookie =
|
||||
// keys[i] +"=0;path=示例:/index.vue(不同域的path,也就是你清除不了的cookie);domain=示例:10.10.10.208(不同域的domain,也就是你清除不了的cookie);expires=" +
|
||||
// new Date(0).toUTCString();
|
||||
// // **document.cookie可加多条!!!!**
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import url("../../styles/comStyle.less");
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.item {
|
||||
margin-top: 0px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.right-menu {
|
||||
display: flex;
|
||||
border-left: 1px solid #d4d4d400 !important;
|
||||
//background: red;
|
||||
height: 100%;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.right-menu-item {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
padding: 0 8px;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 0 10px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.el-icon-caret-bottom {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.user-dropdown {
|
||||
min-width: 100px;
|
||||
// display: flex;
|
||||
position: absolute;
|
||||
z-index: 9999999999999 !important;
|
||||
left: 99%;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.theme-picker-dropdown .el-color-dropdown__link-btn {
|
||||
display: none;
|
||||
}
|
||||
.v-modal {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.5;
|
||||
/* background: #7a6464; */
|
||||
}
|
||||
</style>
|
||||
189
src/layout/components/Settings/index.vue
Normal file
189
src/layout/components/Settings/index.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
title="页面设置"
|
||||
:visible.sync="$store.state.app.showSettings"
|
||||
direction="rtl"
|
||||
append-to-body
|
||||
custom-class="settings-drawer"
|
||||
>
|
||||
<div class="drawer-container">
|
||||
<div class="drawer-item">
|
||||
<span>布局类型</span>
|
||||
<ul class="layout">
|
||||
<li @click="changeSetting({layout:'layout1'})">
|
||||
<img src="../../../assets/layout/layout1.svg" alt="layout1">
|
||||
<i v-show="settings.layout === 'layout1'" class="el-icon-check" />
|
||||
</li>
|
||||
<li @click="changeSetting({layout:'layout2'})">
|
||||
<img src="../../../assets/layout/layout2.svg" alt="layout2">
|
||||
<i v-show="settings.layout === 'layout2'" class="el-icon-check" />
|
||||
</li>
|
||||
<li @click="changeSetting({layout:'layout3'})">
|
||||
<img src="../../../assets/layout/layout3.svg" alt="layout3">
|
||||
<i v-show="settings.layout === 'layout3'" class="el-icon-check" />
|
||||
</li>
|
||||
<li @click="changeSetting({layout:'layout4'})">
|
||||
<img src="../../../assets/layout/layout4.svg" alt="layout4">
|
||||
<i v-show="settings.layout === 'layout4'" class="el-icon-check" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="drawer-item">
|
||||
<span>主题色</span>
|
||||
<el-color-picker
|
||||
:value="settings.themeColor || '#4451B2'"
|
||||
:predefine="['#4451B2', '#12B1B1', '#409EFF', '#E84B16', '#fa541c','#EBA313', '#4CB418', '#2f54eb', '#722ed1']"
|
||||
popper-class="theme-picker-dropdown"
|
||||
style="float: right; height: 26px; margin: -3px 8px 0 0;"
|
||||
class="theme-picker"
|
||||
@change="themeColor=>changeSetting({themeColor})"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>布局大小</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
class="drawer-select"
|
||||
:value="settings.size"
|
||||
@change="sizeChange"
|
||||
>
|
||||
<el-option label="小" value="mini" />
|
||||
<el-option label="中" value="small" />
|
||||
<el-option label="大" value="medium" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>开启页签</span>
|
||||
<el-switch
|
||||
:value="settings.tagsView"
|
||||
class="drawer-switch"
|
||||
@input="tagsView=>changeSetting({tagsView})"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>固定头部</span>
|
||||
<el-switch
|
||||
:value="settings.layout !== 'layout2' && settings.layout !== 'layout1' ? true : settings.fixedHeader"
|
||||
class="drawer-switch"
|
||||
:disabled="settings.layout !== 'layout2' && settings.layout !== 'layout1' "
|
||||
@input="fixedHeader=>changeSetting({fixedHeader})"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex'
|
||||
import { addClass } from '@/utils/className.js'
|
||||
import { removeClass } from '@/utils/className'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return this.$store.state.settings
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
changeSetting: 'settings/changeSetting'
|
||||
}),
|
||||
sizeChange(size) {
|
||||
this.$ELEMENT.size = size
|
||||
//console.log(`PAGE_SIZE-${this.settings.size}`)
|
||||
removeClass(document.body, `PAGE-SIZE-${this.settings.size}`)
|
||||
addClass(document.body, `PAGE-SIZE-${size}`)
|
||||
this.changeSetting({ size })
|
||||
this.refreshView()
|
||||
this.$message({
|
||||
message: '切换布局大小成功',
|
||||
type: 'success'
|
||||
})
|
||||
},
|
||||
refreshView() {
|
||||
// In order to make the cached page re-rendered
|
||||
this.$store.dispatch('tagsView/delAllTags')
|
||||
|
||||
const { fullPath } = this.$route
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$router.replace({
|
||||
path: '/redirect' + fullPath
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drawer-container {
|
||||
padding: 0 24px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
|
||||
.drawer-item {
|
||||
padding: 12px 0;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
|
||||
.layout {
|
||||
margin-top: 16px;
|
||||
|
||||
& > li {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 52px;
|
||||
height: 45px;
|
||||
margin-right: 10px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #409EFF;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-switch {
|
||||
float: right;
|
||||
}
|
||||
.drawer-select {
|
||||
float: right;
|
||||
width: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-picker ::v-deep .el-color-picker__trigger {
|
||||
width: 26px !important;
|
||||
height: 26px !important;
|
||||
padding: 2px;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.theme-picker-dropdown {
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
|
||||
.settings-drawer {
|
||||
width: 250px !important;
|
||||
}
|
||||
</style>
|
||||
26
src/layout/components/Sidebar/FixiOSBug.js
Normal file
26
src/layout/components/Sidebar/FixiOSBug.js
Normal file
@@ -0,0 +1,26 @@
|
||||
export default {
|
||||
computed: {
|
||||
device() {
|
||||
return this.$store.state.app.device
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// In order to fix the click on menu on the ios device will trigger the mouseleave bug
|
||||
// https://github.com/PanJiaChen/vue-element-admin/issues/1135
|
||||
this.fixBugIniOS()
|
||||
},
|
||||
methods: {
|
||||
fixBugIniOS() {
|
||||
const $subMenu = this.$refs.subMenu
|
||||
if ($subMenu) {
|
||||
const handleMouseleave = $subMenu.handleMouseleave
|
||||
$subMenu.handleMouseleave = (e) => {
|
||||
if (this.device === 'mobile') {
|
||||
return
|
||||
}
|
||||
handleMouseleave(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/layout/components/Sidebar/Item.vue
Normal file
29
src/layout/components/Sidebar/Item.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'menu-item',
|
||||
functional: true,
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
render(h, context) {
|
||||
const { icon, title } = context.props
|
||||
const vnodes = []
|
||||
|
||||
if (icon) {
|
||||
vnodes.push(<svg-icon icon-class={icon}/>)
|
||||
}
|
||||
|
||||
if (title) {
|
||||
vnodes.push(<span slot='title'>{(title)}</span>)
|
||||
}
|
||||
return vnodes
|
||||
}
|
||||
}
|
||||
</script>
|
||||
36
src/layout/components/Sidebar/Link.vue
Normal file
36
src/layout/components/Sidebar/Link.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
<template>
|
||||
<!-- eslint-disable vue/require-component-is -->
|
||||
<component v-bind="linkProps(to)">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isExternal } from '@/utils/validate'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
to: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
linkProps(url) {
|
||||
if (isExternal(url)) {
|
||||
return {
|
||||
is: 'a',
|
||||
href: url,
|
||||
target: '_blank',
|
||||
rel: 'noopener'
|
||||
}
|
||||
}
|
||||
return {
|
||||
is: 'router-link',
|
||||
to: url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
57
src/layout/components/Sidebar/SidebarItem.vue
Normal file
57
src/layout/components/Sidebar/SidebarItem.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if='!item.children || item.children.every(it=>it.hidden)'>
|
||||
<app-link :to='to(item)'>
|
||||
<el-menu-item :index='item.routePath' :class="{'submenu-title-noDropdown':!isNest}">
|
||||
<item :icon='item.icon' :title='item.title' />
|
||||
</el-menu-item>
|
||||
</app-link>
|
||||
</template>
|
||||
<el-submenu v-else ref='subMenu' :index='item.routePath' popper-append-to-body>
|
||||
<template slot='title'>
|
||||
<item :icon='item.icon' :title='item.title' />
|
||||
</template>
|
||||
<sidebar-item
|
||||
v-for='(child,idx) in item.children'
|
||||
:key='idx'
|
||||
:index='idx'
|
||||
:is-nest='true'
|
||||
:item='child'
|
||||
class='nest-menu'
|
||||
/>
|
||||
</el-submenu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Item from './Item'
|
||||
import AppLink from './Link'
|
||||
import FixiOSBug from './FixiOSBug'
|
||||
|
||||
export default {
|
||||
name: 'sidebar-item',
|
||||
components: { Item, AppLink },
|
||||
mixins: [FixiOSBug],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isNest: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
to(menuItem) {
|
||||
const visitedTags = this.$store.state.tagsView.visitedTags
|
||||
const tag = visitedTags.find(t => t.routeName === menuItem.routeName)
|
||||
return tag ? tag.routePath : menuItem.routePath
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
50
src/layout/components/Sidebar/index.vue
Normal file
50
src/layout/components/Sidebar/index.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div class="sidebar-container">
|
||||
<el-menu :default-active="activeMenu" :collapse="collapse" :collapse-transition="false" unique-opened>
|
||||
<sidebar-item v-for="(item, index) in menus" :key="index" :item="item" :index="index" />
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default {
|
||||
components: { SidebarItem },
|
||||
props: {
|
||||
menus: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
collapse() {
|
||||
setTimeout(() => {
|
||||
window.echartsArr &&
|
||||
window.echartsArr.forEach(item => {
|
||||
item.resize()
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
activeMenu() {
|
||||
if (this.$route.meta.activeMenu) {
|
||||
return this.$route.meta.activeMenu
|
||||
}
|
||||
if(this.$route.fullPath.indexOf('iframe') > -1){
|
||||
return this.$route.fullPath
|
||||
}else{
|
||||
return this.$route.path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import './sidebar.scss';
|
||||
</style>
|
||||
50
src/layout/components/Sidebar/sidebar.scss
Normal file
50
src/layout/components/Sidebar/sidebar.scss
Normal file
@@ -0,0 +1,50 @@
|
||||
.sidebar-container ::v-deep {
|
||||
.svg-icon {
|
||||
// margin-right: 16px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.el-menu {
|
||||
width: 100% !important;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
& .nest-menu {
|
||||
font-size: 0;
|
||||
}
|
||||
.el-menu>div {
|
||||
padding: 5px 0;
|
||||
}
|
||||
.el-menu-item,
|
||||
.el-submenu__title {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
.el-submenu__title {
|
||||
.el-submenu__icon-arrow {
|
||||
position: relative;
|
||||
right: 0;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.el-menu--collapse {
|
||||
.el-submenu {
|
||||
&>.el-submenu__title {
|
||||
&>span {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
.el-submenu__icon-arrow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reset element-ui css
|
||||
.horizontal-collapse-transition {
|
||||
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
|
||||
}
|
||||
100
src/layout/components/TagsView/ScrollPane.vue
Normal file
100
src/layout/components/TagsView/ScrollPane.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
|
||||
<slot />
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const tagAndTagSpacing = 4 // tagAndTagSpacing
|
||||
|
||||
export default {
|
||||
name: 'scroll-pane',
|
||||
props: {
|
||||
watchData: {}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
scrollWrapper() {
|
||||
return this.$refs.scrollContainer.$refs.wrap
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
watchData: {
|
||||
handler() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.scrollContainer.update()
|
||||
})
|
||||
},
|
||||
immediate: false,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleScroll(e) {
|
||||
const eventDelta = e.wheelDelta || -e.deltaY * 40
|
||||
const $scrollWrapper = this.scrollWrapper
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
|
||||
},
|
||||
moveToTarget(currentTag) {
|
||||
const $container = this.$refs.scrollContainer.$el
|
||||
const $containerWidth = $container.offsetWidth
|
||||
const $scrollWrapper = this.scrollWrapper
|
||||
const tagList = this.$parent.$refs.tag
|
||||
|
||||
let firstTag = null
|
||||
let lastTag = null
|
||||
|
||||
// find first tag and last tag
|
||||
if (tagList.length > 0) {
|
||||
debugger
|
||||
firstTag = tagList[0]
|
||||
lastTag = tagList[tagList.length - 1]
|
||||
}
|
||||
|
||||
if (firstTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = 0
|
||||
} else if (lastTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
|
||||
} else {
|
||||
// find preTag and nextTag
|
||||
const currentIndex = tagList.findIndex(item => item === currentTag)
|
||||
const prevTag = tagList[currentIndex - 1]
|
||||
const nextTag = tagList[currentIndex + 1]
|
||||
|
||||
// the tag's offsetLeft after of nextTag
|
||||
const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
|
||||
|
||||
// the tag's offsetLeft before of prevTag
|
||||
const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
|
||||
|
||||
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
||||
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
|
||||
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
||||
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.scroll-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
::v-deep {
|
||||
.el-scrollbar__bar {
|
||||
bottom: 0;
|
||||
}
|
||||
.el-scrollbar__wrap {
|
||||
// height: 49px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
467
src/layout/components/TagsView/index.vue
Normal file
467
src/layout/components/TagsView/index.vue
Normal file
@@ -0,0 +1,467 @@
|
||||
<template>
|
||||
<div id='tags-view-container' class='tags-view-container'>
|
||||
<scroll-pane ref='scrollPane' :watch-data='visitedTags' class='tags-view-wrapper'>
|
||||
<router-link v-for='tag in visitedTags' ref='tag' :key='tag.routeName'
|
||||
:class="isActive(tag) ? 'active' : ''"
|
||||
:to='tag.routePath' tag='span' class='tags-view-item'
|
||||
@click.middle.native="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
||||
@contextmenu.prevent.native='openMenu(tag, $event)'>
|
||||
<i v-if='tag.routeName == routeName' class='el-icon-s-home'></i>
|
||||
{{ tag.title }}
|
||||
<span v-if='tag.routeName == routeName' />
|
||||
<span v-else class='el-icon-close' @click.prevent.stop='closeSelectedTag(tag)' />
|
||||
</router-link>
|
||||
</scroll-pane>
|
||||
<ul v-if='selectedTag.routeName==routeName' v-show='visible' :style="{ left: left + 'px', top: top + 'px' }"
|
||||
class='contextmenu'>
|
||||
<li @click='refreshSelectedTag(selectedTag)'><i class='el-icon-refresh-right'></i> 刷新页面</li>
|
||||
<li @click='closeOthersTags(selectedTag)'><i class='el-icon-circle-close'></i> 关闭其他</li>
|
||||
<li @click='closeAllTags'><i class='el-icon-circle-close'></i> 关闭所有</li>
|
||||
</ul>
|
||||
<ul v-else v-show='visible' :style="{ left: left + 'px', top: top + 'px' }" class='contextmenu'>
|
||||
<li @click='refreshSelectedTag(selectedTag)'><i class='el-icon-refresh-right'></i> 刷新页面</li>
|
||||
<li @click='closeSelectedTag(selectedTag)'><i class='el-icon-close'></i> 关闭当前</li>
|
||||
<li @click='closeOthersTags(selectedTag)'><i class='el-icon-circle-close'></i> 关闭其他</li>
|
||||
<li @click='closeAllTags'><i class='el-icon-circle-close'></i> 关闭所有</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ScrollPane from './ScrollPane'
|
||||
import Sortable from 'sortablejs'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: { ScrollPane },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
top: 0,
|
||||
left: 0,
|
||||
selectedTag: {},
|
||||
affixTags: [],
|
||||
menulists: [],
|
||||
path: '',
|
||||
routeName: '',
|
||||
title: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
visitedTags() {
|
||||
// console.log("+++++++++++++++++===================++++++++++++++++++++",this.$store.state.tagsView.visitedTags)
|
||||
let arr = this.$store.state.tagsView.visitedTags
|
||||
arr.filter((item, index) => arr.indexOf(item) === index)
|
||||
//console.log("########----------",this.$store.state.user.userInfo.loginName)
|
||||
// if(this.$store.state.user.userInfo==null){
|
||||
// return arr;
|
||||
// }else{
|
||||
// if(this.$store.state.user.userInfo.roleCode[0]=='root'){
|
||||
// // arr[0]={
|
||||
// // routeName:"qualityzhilestimate",
|
||||
// // routePath: "/harmonic-boot/area/powerAssessment",
|
||||
// // title: "电能质量综合评估"
|
||||
// // };
|
||||
|
||||
// // arr[0]={
|
||||
// // routeName:"test",
|
||||
// // routePath: "/jbei/QualityOverview",
|
||||
// // title: "电能质量概览"
|
||||
// // };
|
||||
// arr[0]={
|
||||
// routeName:"dashboard",
|
||||
// routePath: "/dashboard/index",
|
||||
// title: "首页概览"
|
||||
// };
|
||||
// }
|
||||
// if(this.$store.state.user.userInfo.loginName=='njcnpqs'){
|
||||
|
||||
// arr[0]={
|
||||
// routeName:"uesrmanagement",
|
||||
// routePath: "/user-boot/user/list",
|
||||
// title: "用户管理"
|
||||
// };
|
||||
|
||||
// }else if(this.$store.state.user.userInfo.loginName=='njcnser'){
|
||||
|
||||
// arr[0]={
|
||||
// routeName:"premage",
|
||||
// routePath: "/BusinessAdministrator/TerminalManagement/FrontManagement",
|
||||
// title: "前置管理"
|
||||
// };
|
||||
|
||||
// }else if(this.$store.state.user.userInfo.loginName=='njcnsh'){
|
||||
|
||||
// arr[0]={
|
||||
// routeName:"sys-user-boot-check",
|
||||
// routePath: "/user/checkUserListview",
|
||||
// title: "审核列表"
|
||||
// };
|
||||
|
||||
// }else if(this.$store.state.user.userInfo.loginName=='njcnsj'){
|
||||
|
||||
// arr[0]={
|
||||
// routeName:"Management",
|
||||
// routePath: "/BusinessAdministrator/Audit/Operations/Management",
|
||||
// title: "审计列表管理"
|
||||
// };
|
||||
|
||||
// }else{
|
||||
|
||||
// arr[0]={
|
||||
// routeName:"qualityzhilestimate",
|
||||
// routePath: "/harmonic-boot/area/powerAssessment",
|
||||
// title: "电能质量综合评估"
|
||||
// };
|
||||
|
||||
// arr[0]={
|
||||
// routeName:"test",
|
||||
// routePath: "/jbei/QualityOverview",
|
||||
// title: "电能质量概览"
|
||||
// };
|
||||
|
||||
|
||||
// console.log("########----------",this.$store.state.user)
|
||||
// arr[0]={
|
||||
// routeName:"dashboard",
|
||||
// routePath: "/dashboard/index",
|
||||
// title: "首页概览"
|
||||
// };
|
||||
this.menulists = JSON.parse(window.sessionStorage.getItem('menus'))
|
||||
this.getmuen(this.menulists[0])
|
||||
arr[0] = {
|
||||
routeName: this.routeName,
|
||||
routePath: this.path,
|
||||
title: this.title
|
||||
}
|
||||
// }
|
||||
// }
|
||||
// console.log('++++++++++++', arr)
|
||||
return arr
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.addTags()
|
||||
//this.moveToCurrentTag();
|
||||
},
|
||||
visible(value) {
|
||||
if (value) {
|
||||
document.body.addEventListener('click', this.closeMenu)
|
||||
} else {
|
||||
document.body.removeEventListener('click', this.closeMenu)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.addTags()
|
||||
this.setSort()
|
||||
//this.filtertag()
|
||||
},
|
||||
methods: {
|
||||
getmuen(data) {
|
||||
if (data.children.length == 0) {
|
||||
this.path = data.routePath
|
||||
this.routeName = data.routeName
|
||||
this.title = data.title
|
||||
} else {
|
||||
this.getmuen(data.children[0])
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
filtertag() {
|
||||
let arr = this.$store.state.tagsView.visitedTags
|
||||
arr.filter((item, index) => arr.indexOf(item) === index)
|
||||
return arr
|
||||
},
|
||||
isActive(tag) {
|
||||
return tag.routePath === this.$route.fullPath
|
||||
},
|
||||
isAffix(tag) {
|
||||
const { affixTags } = this.$store.state.tagsView
|
||||
return affixTags.some((t) => t.routeName === tag.routeName)
|
||||
},
|
||||
addTags() {
|
||||
// alert(0)
|
||||
const { name } = this.$route
|
||||
if (name) {
|
||||
this.$store.dispatch('tagsView/addView', this.$route)
|
||||
}
|
||||
return false
|
||||
},
|
||||
//移除当前 标签
|
||||
moveToCurrentTag() {
|
||||
// alert(1)
|
||||
const tags = this.$refs.tag
|
||||
tags.splice(0, 1)
|
||||
// this.$router.push("/Statistical-analysis/indicatorClassification");
|
||||
this.$nextTick(() => {
|
||||
for (const tag of tags) {
|
||||
if (tag.to === this.$route.fullPath) {
|
||||
//if (tag.to !== "/Statistical-analysis/indicatorClassification") {
|
||||
this.$refs.scrollPane.moveToTarget(tag)
|
||||
break
|
||||
//}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
//刷新当前
|
||||
refreshSelectedTag(tag) {
|
||||
// alert(2)
|
||||
const fullPath = tag.routePath
|
||||
this.$store.commit('tagsView/DEL_CACHED_VIEW', tag.routeName)
|
||||
this.$nextTick(() => {
|
||||
this.$router.replace({
|
||||
path: '/redirect' + fullPath
|
||||
})
|
||||
})
|
||||
},
|
||||
open() {
|
||||
|
||||
},
|
||||
//关闭选择
|
||||
closeSelectedTag(tag) {
|
||||
// console.log("+++++++++++++++++++++",tag)
|
||||
if (tag.title == '算法编辑器') {
|
||||
this.$confirm('此操作将关闭删除该标签,是否已保存! 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$store.dispatch('tagsView/delTag', tag).then(({ visitedTags }) => {
|
||||
if (this.isActive(tag)) {
|
||||
this.toLastView(visitedTags, tag)
|
||||
// this.$message({
|
||||
// type: 'success',
|
||||
// message: '确认保存成功!'
|
||||
// });
|
||||
//this.$router.push("/Statistical-analysis/indicatorClassification");
|
||||
// this.$nextTick(() => {
|
||||
// this.$router.replace({
|
||||
// path: "/Statistical-analysis/indicatorClassification",
|
||||
// });
|
||||
// this.$router.push("/Statistical-analysis/indicatorClassification");
|
||||
// });
|
||||
}
|
||||
})
|
||||
|
||||
}).catch(() => {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: '已取消关闭'
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.$store.dispatch('tagsView/delTag', tag).then(({ visitedTags }) => {
|
||||
if (this.isActive(tag)) {
|
||||
this.toLastView(visitedTags, tag)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
//关闭其他
|
||||
closeOthersTags(tag) {
|
||||
// alert(4)
|
||||
this.$router.push(tag.routePath)
|
||||
const fullPath = tag.routePath
|
||||
this.$store.dispatch('tagsView/delOthersTags', tag)
|
||||
this.$nextTick(() => {
|
||||
this.$router.replace({
|
||||
path: '/redirect' + fullPath
|
||||
})
|
||||
})
|
||||
this.moveToCurrentTag()
|
||||
},
|
||||
//关闭所有
|
||||
closeAllTags() {
|
||||
// alert(5)
|
||||
this.$store.dispatch('tagsView/delAllTags')
|
||||
//this.$router.push("/dashboard/index");
|
||||
//this.$router.push("/jbei/QualityOverview");
|
||||
this.$nextTick(() => {
|
||||
|
||||
// this.$router.replace({
|
||||
// path: "/dashboard/index",
|
||||
// });
|
||||
//this.$router.push("/dashboard/index");
|
||||
// this.$router.replace({
|
||||
// path: "/jbei/QualityOverview",
|
||||
// });
|
||||
// this.$router.push("/jbei/QualityOverview");
|
||||
})
|
||||
// 关完还剩下的
|
||||
const { visitedTags } = this.$store.state.tagsView
|
||||
const lastTag = visitedTags[visitedTags.length - 1]
|
||||
if (lastTag) {
|
||||
if (this.$route.fullPath !== lastTag.routePath) {
|
||||
this.$router.push(lastTag.routePath)
|
||||
}
|
||||
} else {
|
||||
this.getmuen(this.menulists[0])
|
||||
this.$router.replace({ path: '/redirect' + this.path })
|
||||
//if (this.$route.name === "dashboard") {
|
||||
// this.$router.replace({ path: "/redirect" + this.$route.path });
|
||||
//} else {
|
||||
// this.$router.push("/");
|
||||
//}
|
||||
}
|
||||
},
|
||||
toLastView(visitedTags, view) {
|
||||
// alert(6)
|
||||
const latestTag = visitedTags.slice(-1)[0]
|
||||
if (latestTag) {
|
||||
this.$router.push(latestTag.routePath)
|
||||
} else {
|
||||
if (view.name === 'dashboard') {
|
||||
// this.$router.replace({ path: "/redirect" + view.routePath });
|
||||
} else {
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
},
|
||||
openMenu(tag, e) {
|
||||
const menuMinWidth = 105
|
||||
const windowWidth = document.documentElement.clientWidth
|
||||
const maxLeft = windowWidth - menuMinWidth // left boundary
|
||||
const left = e.clientX + 15 // 15: margin right
|
||||
|
||||
if (left > maxLeft) {
|
||||
this.left = maxLeft
|
||||
} else {
|
||||
this.left = left
|
||||
}
|
||||
|
||||
this.top = e.clientY
|
||||
this.visible = true
|
||||
this.selectedTag = tag
|
||||
},
|
||||
closeMenu() {
|
||||
this.visible = false
|
||||
},
|
||||
setSort() {
|
||||
const el = this.$refs.scrollPane.$el.querySelector('.el-scrollbar__view')
|
||||
this.sortable = Sortable.create(el, {
|
||||
ghostClass: 'tags-view-ghost', // Class name for the drop placeholder,
|
||||
setData: function(dataTransfer) {
|
||||
// to avoid Firefox bug
|
||||
// Detail see : https://github.com/RubaXa/Sortable/issues/1012
|
||||
dataTransfer.setData('Text', '')
|
||||
}
|
||||
})
|
||||
//console.log("kjkjkjkjkjkjkjkjkjkjk",this.$router)
|
||||
// this.$router.push('/Statistical-analysis/indicatorClassification')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.tags-view-container {
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
background: rgb(255, 255, 255);
|
||||
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 {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 26px;
|
||||
padding: 0 8px;
|
||||
margin-top: 4px;
|
||||
margin-left: 5px;
|
||||
font-size: 12px;
|
||||
line-height: 26px;
|
||||
color: $themeColor;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
border: 1px solid #d9ecff;
|
||||
border-radius: 3px;
|
||||
|
||||
&:first-of-type {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
background-color: $themeColor;
|
||||
border-color: $themeColor;
|
||||
|
||||
&::before {
|
||||
position: relative;
|
||||
//display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-right: 2px;
|
||||
// background: #fff;
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contextmenu {
|
||||
position: fixed;
|
||||
z-index: 3000;
|
||||
padding: 5px 0;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
list-style-type: none;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
|
||||
|
||||
li {
|
||||
padding: 7px 16px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
//reset element css of el-icon-close
|
||||
::v-deep .el-scrollbar__wrap {
|
||||
overflow: auto !important;
|
||||
height: 48px !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
::v-deep ::-webkit-scrollbar-thumb {
|
||||
background: $themeColor !important;
|
||||
height: 8px ! important
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: block;
|
||||
width: 8px !important;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
::v-deep .el-scrollbar__thumb {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
cursor: pointer !important;
|
||||
border-radius: inherit;
|
||||
background-color: $themeColor !important;
|
||||
-webkit-transition: .3s background-color;
|
||||
transition: .3s background-color;
|
||||
}
|
||||
</style>
|
||||
115
src/layout/components/alarmcenter/alarmcenter.vue
Normal file
115
src/layout/components/alarmcenter/alarmcenter.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
created(){
|
||||
// setInterval(()=>{
|
||||
// this.open1()
|
||||
// },60000)
|
||||
setInterval(()=>{
|
||||
this.ddd()
|
||||
},200000)
|
||||
},
|
||||
mounted(){
|
||||
|
||||
},
|
||||
methods:{
|
||||
// // 打开一个新的通知
|
||||
// openNotify(userNotification) {
|
||||
// // userNotification 中的userNotifyId是唯一的
|
||||
// const h = this.$createElement
|
||||
// let notify= this.$notify.success({
|
||||
// title: data.notification.notifyName,
|
||||
// dangerouslyUseHTMLString: true,
|
||||
// position: 'bottom-right',
|
||||
// message: h(
|
||||
// 'p',
|
||||
// {
|
||||
// // 相当于`v-bind:style`
|
||||
// style: {
|
||||
// color: 'red',
|
||||
// fontSize: '14px'
|
||||
// },
|
||||
// class: 'pointer',
|
||||
// on: {
|
||||
// click: () => {
|
||||
// this.closeNotification(
|
||||
// data.notification.data.message,
|
||||
// data.userNotifyId,
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// data.notification.data.message
|
||||
// ),
|
||||
// duration: 60 * 1000
|
||||
// })
|
||||
// this.notifications[data.userNotifyId] = notify
|
||||
// },
|
||||
// //关闭单个通知
|
||||
// closeNotification(message, id){
|
||||
// this.notifications[id].close();
|
||||
// delete this.notifications[id];
|
||||
// },
|
||||
// // 关闭所有通知
|
||||
// closeAllNotification(){
|
||||
// for (let key in this.notifications) {
|
||||
// this.notifications[key].close();
|
||||
// delete this.notifications[key];
|
||||
// }
|
||||
// }
|
||||
open1() {
|
||||
const h = this.$createElement
|
||||
const hrender = h('p', null, [
|
||||
h('div', [
|
||||
h('div','终端名称:pqs600-200 '), // 传变量
|
||||
h('div','告警类型:流量告警'),
|
||||
h('div','告警发生时间:2022-05-25 12:25:54'),
|
||||
], null),
|
||||
h('button', {
|
||||
class: '样式',
|
||||
}, "处理"),
|
||||
])
|
||||
|
||||
var rq = document.getElementById('ppp')
|
||||
this.$notify({
|
||||
title: '告警通知',
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: hrender,
|
||||
type: 'warning',
|
||||
// position: 'bottom-right',
|
||||
offset: 50,
|
||||
duration: 0,
|
||||
// showClose: false,
|
||||
onClick: () => {
|
||||
//this.toApproval(approvalQuery)
|
||||
this.$router.push("/BusinessAdministrator/alarManagement/AlarmInformation");
|
||||
},
|
||||
onClose: () => {
|
||||
// alert(`Notify已关闭,说明异常或已查看`)
|
||||
}
|
||||
})
|
||||
//this.yuyin()
|
||||
},
|
||||
ddd(){
|
||||
this.$notify.closeAll()
|
||||
},
|
||||
yuyin(){
|
||||
let utterThis = new window.SpeechSynthesisUtterance();
|
||||
// utterThis.text = data.toString(); //播放内容
|
||||
utterThis.text= '终端pqs9000-300空调外机启动测试发生严重告警,请尽快联系管理员处理'; //播放内容
|
||||
window.speechSynthesis.speak(utterThis);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
131
src/layout/index.vue
Normal file
131
src/layout/index.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div :class="classObj" class="layout-wrapper">
|
||||
<!-- <div id="ppp" style="right:0px;width:200px;height:90%;px;margin-top:3%;background:red;position: absolute;z-index:200 ;overflow-y: auto;"> -->
|
||||
<!-- <alarmcenter style="width:100px; z-index: " ></alarmcenter> -->
|
||||
<!-- </div> -->
|
||||
<component :is="layout">
|
||||
<div ref="appmain" id="APP_MAIN" />
|
||||
</component>
|
||||
<!-- 避免切换布局时app-main更新 -->
|
||||
<portal :target="`.${layout} #APP_MAIN`">
|
||||
<app-main />
|
||||
</portal>
|
||||
<settings />
|
||||
<MqttWs v-if="show" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import alarmcenter from '@/layout/components/alarmcenter/alarmcenter.vue'
|
||||
import Layout1 from './layout1/index.vue'
|
||||
import Layout2 from './layout2/index.vue'
|
||||
import Layout3 from './layout3/index.vue'
|
||||
import Layout4 from './layout4/index.vue'
|
||||
import AppMain from './components/AppMain.vue'
|
||||
import Settings from './components/Settings/index.vue'
|
||||
import Portal from '@/components/Portal'
|
||||
import MqttWs from '@/components/Mqtt/mqttWs.vue'
|
||||
import { getThemeColor, dictypeData, getSysConfig } from '@/api/user'
|
||||
import { addClass, removeClass } from '@/utils/className.js'
|
||||
|
||||
// 用来切换样式
|
||||
const LayoutList = ['layout1', 'layout2', 'layout3', 'layout4']
|
||||
|
||||
export default {
|
||||
name: 'layout',
|
||||
components: {
|
||||
Layout1,
|
||||
Layout2,
|
||||
Layout3,
|
||||
Layout4,
|
||||
AppMain,
|
||||
Settings,
|
||||
Portal,
|
||||
alarmcenter,
|
||||
MqttWs
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
appwidth: null,
|
||||
appheight: null,
|
||||
show: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
sidebar: state => state.app.sidebar,
|
||||
device: state => state.app.device,
|
||||
needTagsView: state => state.settings.tagsView,
|
||||
fixedHeader: state => state.settings.fixedHeader
|
||||
}),
|
||||
classObj() {
|
||||
return {
|
||||
hideSidebar: !this.sidebar.opened,
|
||||
openSidebar: this.sidebar.opened,
|
||||
withoutAnimation: this.sidebar.withoutAnimation,
|
||||
mobile: this.device === 'mobile',
|
||||
fixedHeader: this.fixedHeader,
|
||||
needTagsView: this.needTagsView
|
||||
}
|
||||
},
|
||||
layout() {
|
||||
const { layout } = this.$store.state.settings
|
||||
const body = document.body
|
||||
LayoutList.forEach(item => removeClass(body, item))
|
||||
addClass(body, layout)
|
||||
return layout
|
||||
}
|
||||
},
|
||||
created() {
|
||||
//this.getdicData()
|
||||
},
|
||||
mounted() {
|
||||
// setTimeout(() => {
|
||||
// this.appwidth = document.getElementById("app").offsetWidth -205
|
||||
// this.appheight = document.getElementById("app").offsetHeight -105
|
||||
|
||||
// window.sessionStorage.setItem("appwidth",this.appwidth);
|
||||
// window.sessionStorage.setItem("appheight", this.appheight);
|
||||
// // console.log("+++++++++++++++++++++", document.getElementById("app").offsetHeight);
|
||||
// }, 100);
|
||||
//window.sessionStorage.setItem('appheight',this.$refs.appmain.offsetHeight)
|
||||
|
||||
this.show = JSON.parse(window.sessionStorage.getItem('Info')).roleCode.includes('audit_manager')
|
||||
},
|
||||
methods: {
|
||||
getdicData() {
|
||||
dictypeData().then(response => {
|
||||
const dictType = response.data
|
||||
window.sessionStorage.setItem('dictypeData', JSON.stringify(dictType))
|
||||
// setDictype(JSON.stringify(dictType))
|
||||
})
|
||||
this.getSystemData()
|
||||
},
|
||||
getSystemData() {
|
||||
getSysConfig().then(res => {
|
||||
const sysdata = res.data
|
||||
let systype = res.data.type
|
||||
window.sessionStorage.setItem('sysdata', JSON.stringify(sysdata))
|
||||
window.sessionStorage.setItem('systype', systype)
|
||||
//setSysConfig()
|
||||
this.SysConfig()
|
||||
})
|
||||
},
|
||||
SysConfig() {
|
||||
getSysConfig().then(res => {
|
||||
if (res.code == 'A0000') {
|
||||
let systype = res.data.type
|
||||
window.sessionStorage.setItem('systype', systype)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.layout-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
206
src/layout/layout1/index.scss
Normal file
206
src/layout/layout1/index.scss
Normal file
@@ -0,0 +1,206 @@
|
||||
.layout1-aside {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
width: 100% !important;
|
||||
height: 100%;
|
||||
background-color: $themeColor;
|
||||
transition: width 0.28s;
|
||||
.sidebar-logo-container {
|
||||
.sidebar-logo-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.sidebar-title {
|
||||
display: inline-block;
|
||||
line-height: 18px;
|
||||
flex: 1;
|
||||
|
||||
@include nowrap(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout1-navbar {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
padding-right: 20px;
|
||||
overflow: hidden;
|
||||
background: $themeColor;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
.navbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
.hamburger-container {
|
||||
height: 100%;
|
||||
line-height: 60px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
}
|
||||
|
||||
.right-menu .menu-item {
|
||||
color: #f5f6f8 !important;
|
||||
background: $themeColor !important;
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.025) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//布局
|
||||
.layout1 {
|
||||
.app-container {
|
||||
//background: blue;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #F0F2F5;
|
||||
.app-aside {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
width: $sideBarWidth;
|
||||
height: 100%;
|
||||
//background: yellow;
|
||||
background: $themeColor;
|
||||
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
|
||||
transition: width 0.28s;
|
||||
|
||||
// 侧边栏滚动条
|
||||
.el-scrollbar {
|
||||
height: 100%;
|
||||
}
|
||||
.el-scrollbar__bar {
|
||||
z-index: 10;
|
||||
}
|
||||
.el-scrollbar__bar.is-vertical {
|
||||
right: 0;
|
||||
}
|
||||
.scrollbar-wrapper {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
}
|
||||
.app-main {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
margin-left: $sideBarWidth;
|
||||
overflow-y: auto;
|
||||
transition: margin-left 0.28s;
|
||||
//z-index: 2;
|
||||
.app-header-in {
|
||||
//background: green;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 60px;
|
||||
}
|
||||
.app-main-in {
|
||||
position: relative;
|
||||
height: calc(100% - 60px);
|
||||
margin-top: 60px;
|
||||
background: #F0F2F5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fixedHeader {
|
||||
.app-main-in {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
.hideSidebar {
|
||||
.app-aside {
|
||||
width: 54px !important;
|
||||
}
|
||||
.app-main {
|
||||
margin-left: 54px !important;
|
||||
}
|
||||
}
|
||||
.needTagsView {
|
||||
.app-header-in {
|
||||
height: 94px !important;
|
||||
.layout1-navbar {
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
.app-main-in {
|
||||
height: calc(100% - 94px) !important;
|
||||
margin-top: 94px !important;
|
||||
}
|
||||
}
|
||||
//.mobile {
|
||||
// .app-main {
|
||||
// margin-left: 0 !important;
|
||||
// .app-header-in {
|
||||
// left: 0 !important;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// .app-aside {
|
||||
// position: fixed;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// bottom: 0;
|
||||
// z-index: 100;
|
||||
// transition: left .28s !important;
|
||||
// width: $sideBarWidth !important;
|
||||
// }
|
||||
// &.hideSidebar {
|
||||
// .app-aside {
|
||||
// left: -$sideBarWidth;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
.withoutAnimation {
|
||||
.app-main {
|
||||
transition: none !important;
|
||||
}
|
||||
.app-aside {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//layout1 菜单颜色样式
|
||||
.layout1 .el-menu {
|
||||
background-color: $themeColor;
|
||||
.el-menu-item,
|
||||
.el-submenu__title {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
background-color: $themeColor;
|
||||
.el-submenu__icon-arrow {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: #fff;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
.el-submenu__icon-arrow {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
&.is-active {
|
||||
color: #fff;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
.is-active .el-submenu__title {
|
||||
color: #fff;
|
||||
.el-submenu__icon-arrow {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/layout/layout1/index.vue
Normal file
88
src/layout/layout1/index.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />-->
|
||||
<div class="app-aside">
|
||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||
<div class="layout1-aside">
|
||||
<logo :collapse="!sidebar.opened" />
|
||||
<sidebar :collapse="isCollapse" :menus="menus" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<div class="app-main">
|
||||
<div class="app-header-in">
|
||||
<div class="layout1-navbar">
|
||||
<div class="navbar-left">
|
||||
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
|
||||
<breadcrumb class="breadcrumb-container" />
|
||||
</div>
|
||||
<right-menu />
|
||||
</div>
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
<div class="app-main-in" id='app-main-in'>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Logo from '../components/Logo.vue'
|
||||
import Sidebar from '../components/Sidebar/index.vue'
|
||||
import TagsView from '../components/TagsView'
|
||||
import RightMenu from '../components/RightMenu.vue'
|
||||
import Breadcrumb from '../components/Breadcrumb'
|
||||
import Hamburger from '../components/Hamburger'
|
||||
import ResizeMixin from '../mixin/ResizeHandler'
|
||||
|
||||
export default {
|
||||
name: 'layout1',
|
||||
components: {
|
||||
Logo,
|
||||
RightMenu,
|
||||
Sidebar,
|
||||
Breadcrumb,
|
||||
Hamburger,
|
||||
TagsView
|
||||
},
|
||||
mixins: [ResizeMixin],
|
||||
computed: {
|
||||
sidebar() {
|
||||
return this.$store.state.app.sidebar
|
||||
},
|
||||
menus() {
|
||||
return this.$store.getters.menus
|
||||
},
|
||||
isCollapse() {
|
||||
return !this.sidebar.opened
|
||||
},
|
||||
needTagsView() {
|
||||
return this.$store.state.settings.tagsView
|
||||
},
|
||||
device() {
|
||||
return this.$store.state.app.device
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
||||
},
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch('app/toggleSideBar')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import './index.scss';
|
||||
.drawer-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 99;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
}
|
||||
</style>
|
||||
116
src/layout/layout2/index.scss
Normal file
116
src/layout/layout2/index.scss
Normal file
@@ -0,0 +1,116 @@
|
||||
//头部
|
||||
.layout2-navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 60px;
|
||||
padding: 0 20px;
|
||||
background-color: $themeColor;
|
||||
//logo
|
||||
.sidebar-logo-container {
|
||||
width: auto;
|
||||
padding-right: 16px;
|
||||
padding-left: 0;
|
||||
}
|
||||
// menu
|
||||
.navbar-menu {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
//布局
|
||||
.layout2 {
|
||||
.app-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
//background: blue;
|
||||
background: $themeColor;
|
||||
.app-header {
|
||||
height: 60px;
|
||||
background: green;
|
||||
}
|
||||
.app-main {
|
||||
position: relative;
|
||||
height: calc(100% - 60px);
|
||||
background: #F0F2F5;
|
||||
}
|
||||
}
|
||||
.fixedHeader {
|
||||
.app-main {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.app-header {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
.needTagsView {
|
||||
.app-header {
|
||||
position: relative;
|
||||
height: 94px !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.app-main {
|
||||
height: calc(100% - 94px) !important;
|
||||
}
|
||||
&.fixedHeader .app-main {
|
||||
height: calc(100% - 94px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//layout2 菜单颜色样式
|
||||
.layout2 .el-menu {
|
||||
background-color: $themeColor;
|
||||
.el-menu-item,
|
||||
.el-submenu__title {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
background-color: $themeColor;
|
||||
.el-submenu__icon-arrow {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: #fff;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
.el-submenu__icon-arrow {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
&.is-active {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.is-active .el-submenu__title {
|
||||
color: #fff;
|
||||
.el-submenu__icon-arrow {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
&.el-menu--horizontal > .el-menu-item {
|
||||
border: none;
|
||||
&.is-active {
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
//background-color: #409EFF;
|
||||
}
|
||||
}
|
||||
&.el-menu--horizontal > .el-submenu .el-submenu__title {
|
||||
border: none;
|
||||
}
|
||||
&.el-menu--horizontal > .el-submenu.is-active .el-submenu__title {
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
border: none;
|
||||
//background-color: #409EFF;
|
||||
}
|
||||
&.el-menu--popup .el-menu-item.is-active {
|
||||
//background-color: #409EFF;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
&.el-menu--popup .is-active .el-submenu__title {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
45
src/layout/layout2/index.vue
Normal file
45
src/layout/layout2/index.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="app-header">
|
||||
<div class="layout2-navbar">
|
||||
<logo :collapse="false" />
|
||||
<navbar :menus="menus" />
|
||||
<right-menu />
|
||||
</div>
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
<div class="app-main" id='app-main-in'>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Logo from '../components/Logo.vue'
|
||||
import Navbar from '../components/Navbar/index.vue'
|
||||
import RightMenu from '../components/RightMenu.vue'
|
||||
import TagsView from '../components/TagsView'
|
||||
// import ResizeMixin from '../mixin/ResizeHandler'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Logo,
|
||||
RightMenu,
|
||||
Navbar,
|
||||
TagsView
|
||||
},
|
||||
// mixins: [ResizeMixin],
|
||||
computed: {
|
||||
menus() {
|
||||
return this.$store.getters.menus
|
||||
},
|
||||
needTagsView() {
|
||||
return this.$store.state.settings.tagsView
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
217
src/layout/layout3/index.scss
Normal file
217
src/layout/layout3/index.scss
Normal file
@@ -0,0 +1,217 @@
|
||||
.layout3-navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 60px;
|
||||
padding: 0 20px;
|
||||
background-color: #fff;
|
||||
border-bottom: solid 1px #e6e6e6;
|
||||
//logo
|
||||
.sidebar-logo-container {
|
||||
width: auto;
|
||||
padding-right: 16px;
|
||||
padding-left: 0;
|
||||
.sidebar-logo {
|
||||
color: $themeColor !important;
|
||||
}
|
||||
.sidebar-title {
|
||||
color: #333 !important;
|
||||
}
|
||||
}
|
||||
// 菜单
|
||||
.navbar-menu {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
.el-menu.el-menu--horizontal {
|
||||
border-bottom: solid 1px #e6e6e6;
|
||||
}
|
||||
}
|
||||
// 右侧按钮
|
||||
.right-menu .menu-item {
|
||||
color: #303133 !important;
|
||||
background: #fff !important;
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.025) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout3-aside {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
background-color: #fff;
|
||||
border-right: solid 1px #e6e6e6;
|
||||
box-sizing: border-box;
|
||||
transition: width 0.28s;
|
||||
.subMenu-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: $sideBarWidth;
|
||||
height: 60px;
|
||||
padding-left: 20px;
|
||||
border-bottom: solid 1px #e6e6e6;
|
||||
.hamburger-container {
|
||||
height: 100%;
|
||||
line-height: 60px;
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//布局样式
|
||||
.layout3 {
|
||||
.app-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.app-header {
|
||||
height: 60px;
|
||||
//background: red;
|
||||
}
|
||||
.app-container-in {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100% - 60px);
|
||||
//background: blue;
|
||||
background: #F0F2F5;
|
||||
.app-aside {
|
||||
//background: yellow;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
width: $sideBarWidth;
|
||||
transition: width 0.28s;
|
||||
|
||||
// 侧边栏滚动条
|
||||
.el-scrollbar {
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
.el-scrollbar__bar {
|
||||
z-index: 10;
|
||||
}
|
||||
.el-scrollbar__bar.is-vertical {
|
||||
right: 0;
|
||||
}
|
||||
.scrollbar-wrapper {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
}
|
||||
.app-main {
|
||||
position: relative;
|
||||
width: calc(100% - #{$sideBarWidth});
|
||||
height: 100%;
|
||||
margin-left: $sideBarWidth;
|
||||
overflow-y: auto;
|
||||
background-color: #F0F2F5;
|
||||
transition: margin-left 0.28s, width 0.28s;
|
||||
}
|
||||
}
|
||||
}
|
||||
.hideSidebar {
|
||||
.app-aside {
|
||||
width: 54px !important;
|
||||
}
|
||||
.app-main {
|
||||
width: calc(100% - 54px) !important;
|
||||
margin-left: 54px !important;
|
||||
}
|
||||
.subMenu-title {
|
||||
padding-left: 0;
|
||||
& > span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.app-aside .el-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.nosidebar {
|
||||
.app-aside {
|
||||
width: 0 !important;
|
||||
}
|
||||
.app-main {
|
||||
width: 100% !important;
|
||||
margin-left: 0 !important;
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
.needTagsView {
|
||||
.app-header {
|
||||
position: relative;
|
||||
height: 94px !important;
|
||||
}
|
||||
.app-container-in {
|
||||
height: calc(100% - 94px) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//layout3 菜单样式
|
||||
.layout3 .app-aside .el-menu {
|
||||
background-color: #fff;
|
||||
.el-menu-item,
|
||||
.el-submenu__title {
|
||||
color: #999;
|
||||
background-color: #fff;
|
||||
.el-submenu__icon-arrow {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
color: #fff;
|
||||
.el-submenu__icon-arrow {
|
||||
color: $themeColor;
|
||||
}
|
||||
}
|
||||
&:hover,
|
||||
&.is-active {
|
||||
position: relative;
|
||||
color: #fff;
|
||||
background-color: $themeColor;
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
color: #fff;
|
||||
background-color: $themeColor;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
.is-active .el-submenu__title {
|
||||
color: $themeColor;
|
||||
.el-submenu__icon-arrow {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* stylelint-disable no-duplicate-selectors */
|
||||
.layout3 {
|
||||
.el-menu {
|
||||
&.el-menu--horizontal > .el-menu-item.is-active {
|
||||
color: #fff;
|
||||
background-color: $themeColor;
|
||||
border-bottom-color: $themeColor;
|
||||
}
|
||||
&.el-menu--horizontal > .el-submenu.is-active .el-submenu__title {
|
||||
color: $themeColor;
|
||||
background-color: $themeColor;
|
||||
border-bottom-color: $themeColor;
|
||||
}
|
||||
}
|
||||
.el-menu--popup.el-menu {
|
||||
.el-menu-item.is-active {
|
||||
color: #fff;
|
||||
background-color: $themeColor-11;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* stylelint-enable no-duplicate-selectors */
|
||||
99
src/layout/layout3/index.vue
Normal file
99
src/layout/layout3/index.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="app-header">
|
||||
<div class="layout3-navbar">
|
||||
<logo :collapse="false" />
|
||||
<navbar :custom-active-menu="activeMenu" :menus="menus" />
|
||||
<right-menu />
|
||||
</div>
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
<div class="app-container-in" :class="{nosidebar: !subMenu}">
|
||||
<div v-if="subMenu" class="app-aside">
|
||||
<div class="layout3-aside">
|
||||
<div class="subMenu-title">
|
||||
<span>{{ subMenu.title }}</span>
|
||||
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
|
||||
</div>
|
||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||
<sidebar :menus="subMenu.children" :collapse="isCollapse" />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-main" id='app-main-in'>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Logo from '../components/Logo.vue'
|
||||
import Navbar from '../components/Navbar/index.vue'
|
||||
import Sidebar from '../components/Sidebar/index.vue'
|
||||
import RightMenu from '../components/RightMenu.vue'
|
||||
import TagsView from '../components/TagsView'
|
||||
import Hamburger from '../components/Hamburger'
|
||||
import ResizeMixin from '../mixin/ResizeHandler'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Logo,
|
||||
RightMenu,
|
||||
Sidebar,
|
||||
Navbar,
|
||||
Hamburger,
|
||||
TagsView
|
||||
},
|
||||
mixins: [ResizeMixin],
|
||||
data() {
|
||||
return {
|
||||
menus: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sidebar() {
|
||||
return this.$store.state.app.sidebar
|
||||
},
|
||||
needTagsView() {
|
||||
return this.$store.state.settings.tagsView
|
||||
},
|
||||
activeMenu() {
|
||||
return this.subMenu ? this.subMenu.routePath : ''
|
||||
},
|
||||
subMenu() {
|
||||
const menus = this.$store.getters.menus
|
||||
const activeMenu = '/' + this.$route.path.split('/')[1]
|
||||
const subMenu = menus.find(item => item.routePath === activeMenu) || {}
|
||||
return subMenu.children ? subMenu : null
|
||||
},
|
||||
isCollapse() {
|
||||
return !this.sidebar.opened
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const menus = this.$store.getters.menus
|
||||
this.menus = menus.map(item => ({
|
||||
...item,
|
||||
children: null
|
||||
}))
|
||||
},
|
||||
methods: {
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
||||
},
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch('app/toggleSideBar')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index.scss';
|
||||
.layout3 .app-aside .el-menu .el-menu-item .is-active, .layout3 .app-aside .el-menu .el-submenu__title .is-active {
|
||||
position: relative;
|
||||
color: $themeColor;
|
||||
background-color: $themeColor;
|
||||
}
|
||||
</style>
|
||||
185
src/layout/layout4/index.scss
Normal file
185
src/layout/layout4/index.scss
Normal file
@@ -0,0 +1,185 @@
|
||||
.layout4-aside {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
width: 100% !important;
|
||||
height: 100%;
|
||||
background-color: $themeColor;
|
||||
transition: width 0.28s;
|
||||
}
|
||||
|
||||
// .el-menu .el-submenu__title {
|
||||
// //background-color: #003078;
|
||||
// padding-left: 10px !important;
|
||||
// }
|
||||
// .el-menu {
|
||||
// padding-left: 10px !important;
|
||||
// }
|
||||
// .el-submenu__title {
|
||||
// //background-color: #003078;
|
||||
// padding-left: 10px !important;
|
||||
// }
|
||||
// .el-submenu .el-menu-item {
|
||||
// padding-left: 20px !important;
|
||||
// }
|
||||
.layout4-navbar {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
padding-right: 20px;
|
||||
overflow: hidden;
|
||||
background: $themeColor;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
.navbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
.sidebar-logo-container {
|
||||
.sidebar-logo-link {
|
||||
.sidebar-logo {
|
||||
color: #fff;
|
||||
}
|
||||
.sidebar-title {
|
||||
color: rgb(251, 251, 251);
|
||||
}
|
||||
}
|
||||
}
|
||||
.right-menu .menu-item {
|
||||
color: #fbfbfb !important;
|
||||
background: $themeColor !important;
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.025) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//布局
|
||||
.layout4 {
|
||||
.app-container {
|
||||
//background: blue;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #F0F2F5;
|
||||
.app-header {
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
height: 60px;
|
||||
box-shadow: 0 2px 4px 0 rgba(96, 96, 96, 0.16);
|
||||
}
|
||||
.app-container-in {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
height: calc(100% - 60px);
|
||||
//background: blue;
|
||||
background: #F0F2F5;
|
||||
.app-aside {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
width: $sideBarWidth;
|
||||
height: 100%;
|
||||
//background: yellow;
|
||||
background: $themeColor;
|
||||
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
|
||||
transition: width 0.28s;
|
||||
// 侧边栏滚动条
|
||||
.el-scrollbar {
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
.el-scrollbar__bar {
|
||||
z-index: 10;
|
||||
}
|
||||
.el-scrollbar__bar.is-vertical {
|
||||
right: 0;
|
||||
}
|
||||
.scrollbar-wrapper {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
.hamburger-container {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
transition: background 0.3s;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
}
|
||||
.app-main {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
margin-left: $sideBarWidth;
|
||||
transition: margin-left 0.28s;
|
||||
.app-main-in {
|
||||
position: relative;
|
||||
height: 0%;
|
||||
//overflow: auto;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hideSidebar {
|
||||
.app-aside {
|
||||
width: 54px !important;
|
||||
}
|
||||
.app-main {
|
||||
margin-left: 54px !important;
|
||||
}
|
||||
}
|
||||
.needTagsView {
|
||||
.app-header-in {
|
||||
height: 34px;
|
||||
}
|
||||
.app-main-in {
|
||||
height: calc(100% - 34px) !important;
|
||||
}
|
||||
}
|
||||
.withoutAnimation {
|
||||
.app-main {
|
||||
transition: none !important;
|
||||
}
|
||||
.app-aside {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//layout4 菜单颜色样式
|
||||
.layout4 .el-menu {
|
||||
background-color: $themeColor;
|
||||
.el-menu-item,
|
||||
.el-submenu__title {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
background-color: $themeColor;
|
||||
.el-submenu__icon-arrow {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: #fff;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
.el-submenu__icon-arrow {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
&.is-active {
|
||||
color: rgb(0, 245, 253);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
.is-active .el-submenu__title {
|
||||
color: #fff;
|
||||
.el-submenu__icon-arrow {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
107
src/layout/layout4/index.vue
Normal file
107
src/layout/layout4/index.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!--<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />-->
|
||||
<div class="app-header">
|
||||
<div class="layout4-navbar">
|
||||
<div class="navbar-left">
|
||||
<logo :collapse="false" />
|
||||
<!--<breadcrumb class="breadcrumb-container" />-->
|
||||
</div>
|
||||
<right-menu />
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-container-in">
|
||||
<div class="app-aside">
|
||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||
<div class="layout4-aside">
|
||||
<sidebar :collapse="isCollapse" :menus="menus" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
|
||||
</div>
|
||||
<div class="app-main">
|
||||
<div v-if="needTagsView" class="app-header-in">
|
||||
<tags-view />
|
||||
</div>
|
||||
<div ref="getheight" id='app-main-in' class="app-main-in">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Logo from '../components/Logo.vue'
|
||||
import Sidebar from '../components/Sidebar/index.vue'
|
||||
import TagsView from '../components/TagsView'
|
||||
import RightMenu from '../components/RightMenu.vue'
|
||||
// import Breadcrumb from '../components/Breadcrumb'
|
||||
import Hamburger from '../components/Hamburger'
|
||||
//import ResizeMixin from '../mixin/ResizeHandler'
|
||||
import {menulist,getMenus} from '@/api/user'
|
||||
export default {
|
||||
name: 'layout4',
|
||||
components: {
|
||||
Logo,
|
||||
RightMenu,
|
||||
Sidebar,
|
||||
// Breadcrumb,
|
||||
Hamburger,
|
||||
TagsView
|
||||
},
|
||||
// mixins: [ResizeMixin],
|
||||
computed: {
|
||||
sidebar() {
|
||||
return this.$store.state.app.sidebar
|
||||
},
|
||||
menus() {
|
||||
//consloe.log(this.$store.getters.menus)
|
||||
return this.$store.getters.menus
|
||||
//return getMenus
|
||||
|
||||
},
|
||||
isCollapse() {
|
||||
return !this.sidebar.opened
|
||||
},
|
||||
needTagsView() {
|
||||
return this.$store.state.settings.tagsView
|
||||
},
|
||||
device() {
|
||||
return this.$store.state.app.device
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
// console.log(getMenus)
|
||||
// console.log(this.$refs.getheight.offsetHeight)
|
||||
const mainHeight = this.$refs.getheight.offsetHeight
|
||||
window.sessionStorage.setItem('mainHeight',mainHeight)
|
||||
},
|
||||
methods: {
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
||||
},
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch('app/toggleSideBar')
|
||||
},
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index.scss';
|
||||
.drawer-bg {
|
||||
//position: absolute;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
47
src/layout/mixin/ResizeHandler.js
Normal file
47
src/layout/mixin/ResizeHandler.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import store from '@/store'
|
||||
|
||||
const { body } = document
|
||||
//const WIDTH = 992 // refer to Bootstrap's responsive design
|
||||
|
||||
export default {
|
||||
// watch: {
|
||||
// $route(route) {
|
||||
// if (this.device === 'mobile' && this.sidebar.opened) {
|
||||
// store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
beforeMount() {
|
||||
window.addEventListener('resize', this.$_resizeHandler)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.$_resizeHandler)
|
||||
},
|
||||
mounted() {
|
||||
const isMobile = this.$_isMobile()
|
||||
if (isMobile) {
|
||||
store.dispatch('app/toggleDevice', 'mobile')
|
||||
store.dispatch('app/closeSideBar', { withoutAnimation: true })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// use $_ for mixins properties
|
||||
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
|
||||
$_isMobile() {
|
||||
const rect = body.getBoundingClientRect()
|
||||
return rect.width - 1 < WIDTH
|
||||
},
|
||||
$_resizeHandler() {
|
||||
if (!document.hidden) {
|
||||
const isMobile = this.$_isMobile()
|
||||
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
|
||||
|
||||
if (isMobile) {
|
||||
store.dispatch('app/closeSideBar', { withoutAnimation: true })
|
||||
} else {
|
||||
store.dispatch('app/openSideBar', { withoutAnimation: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user