初次提交
This commit is contained in:
70
src/layout/components/AppMain.vue
Normal file
70
src/layout/components/AppMain.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<section id="app-main" class="app-main">
|
||||
<div class="main-content">
|
||||
<transition name="fade-transform" mode="out-in">
|
||||
<keep-alive :include="cachedViews">
|
||||
<router-view :key="key" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AppMain',
|
||||
components: {
|
||||
},
|
||||
computed: {
|
||||
cachedViews() {
|
||||
return this.$store.state.tagsView.cachedViews
|
||||
},
|
||||
key() {
|
||||
return this.$route.path
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-main {
|
||||
/* 50= navbar 50 */
|
||||
height: calc(100vh - #{$nav-bar-height});
|
||||
width: 100%;
|
||||
min-width: (100% - #{$sideBarWidth});
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: $content-padding;
|
||||
.main-content{
|
||||
// height: calc(100vh - #{$nav-bar-height} - #{$content-padding}*2);
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
// background: #091a31;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-header+.app-main {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.hasTagsView {
|
||||
.app-main {
|
||||
/* 84 = navbar + tags-view = 50 + 34 */
|
||||
min-height: calc(100vh - #{$nav-bar-height});
|
||||
}
|
||||
|
||||
.fixed-header+.app-main {
|
||||
padding-top: 68px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
// fix css style bug in open el-dialog
|
||||
.el-popup-parent--hidden {
|
||||
.fixed-header {
|
||||
padding-right: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1346
src/layout/components/Navbar.vue
Normal file
1346
src/layout/components/Navbar.vue
Normal file
File diff suppressed because it is too large
Load Diff
133
src/layout/components/PasswordMessage/index.vue
Normal file
133
src/layout/components/PasswordMessage/index.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div class="upgrade-dialog">
|
||||
<el-dialog
|
||||
:append-to-body="false"
|
||||
title="温馨提示"
|
||||
center
|
||||
:visible.sync="isUpgradeShow"
|
||||
width="304px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
:show-close="false"
|
||||
>
|
||||
<div class="upgrade-content">
|
||||
为了安全考虑,智慧管控平台将清除密码为666666的账户,请于2023-09-21 18:00前修改密码,如果登录不上请及时联系管理员!</div>
|
||||
<div slot="footer" class="upgrade-btn">
|
||||
<el-button class="close" @click="close">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
isUpgradeShow: false,
|
||||
version: '',
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (+this.$store.getters.password === +666666) {
|
||||
this.isUpgradeShow = false
|
||||
}
|
||||
// console.log(this.$store.getters.password)
|
||||
},
|
||||
methods: {
|
||||
|
||||
close() {
|
||||
this.isUpgradeShow = false
|
||||
this.timer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .el-dialog{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin:0 !important;
|
||||
position:absolute;
|
||||
top:50%;
|
||||
left:50%;
|
||||
transform:translate(-50%,-50%);
|
||||
max-height:calc(100% - 30px);
|
||||
max-width:calc(100% - 30px);
|
||||
}
|
||||
::v-deep .el-dialog .el-dialog__body{
|
||||
flex:1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__header{
|
||||
height: 30px;
|
||||
line-height: -20px!important;
|
||||
padding: 0!important;
|
||||
border-bottom: 0!important;
|
||||
.el-dialog__title{
|
||||
display: inline-block!important;
|
||||
height:30px;
|
||||
|
||||
line-height: -20px!important;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
.upgrade-btn{
|
||||
width: 100%;
|
||||
.close {
|
||||
border: 1px solid #0094FF;
|
||||
box-shadow: 4px 4px 24px 0px #0094FF80 inset;
|
||||
background: #11304f;
|
||||
color: #fff;
|
||||
border-radius: 6px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.sure {
|
||||
border: 1px solid #00FFF6;
|
||||
box-shadow: 4px 4px 24px 0px #00FFF6 inset;
|
||||
background: #11304f;
|
||||
color: #fff;
|
||||
border-radius: 6px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
.upgrade-content{
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
letter-spacing:2px;
|
||||
text-indent:28px!important;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
.upgrade-item{
|
||||
width: 100%;
|
||||
height: 42px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.version{
|
||||
color: #0094FF;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
::v-deep .el-dialog{
|
||||
//margin-top: 20vh!important;
|
||||
width: 584px;
|
||||
height: 230px;
|
||||
border: none;
|
||||
background: url(../../../assets/images/upgrade-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
.el-dialog__header{
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
145
src/layout/components/Settings/index.vue
Normal file
145
src/layout/components/Settings/index.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="drawer-container">
|
||||
<div>
|
||||
<h3 class="drawer-title">系统布局配置</h3>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>换肤</span>
|
||||
<theme-picker style="float: right;height: 26px;margin: -3px 8px 0 0;" @change="themeChange" />
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>开启 Tags-View</span>
|
||||
<el-switch v-model="tagsView" class="drawer-switch" />
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>固定 Header</span>
|
||||
<el-switch v-model="fixedHeader" class="drawer-switch" />
|
||||
</div>
|
||||
|
||||
<div class="drawer-item">
|
||||
<span>侧边栏 Logo</span>
|
||||
<el-switch v-model="sidebarLogo" class="drawer-switch" />
|
||||
</div>
|
||||
<a v-if="isShowJob" href="https://panjiachen.github.io/vue-element-admin-site/zh/job/" target="_blank" class="job-link">
|
||||
<el-alert
|
||||
title="部门目前非常缺人!有兴趣的可以点击了解详情。坐标: 字节跳动"
|
||||
type="success"
|
||||
:closable="false"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<div v-if="lang === 'zh'" class="drawer-item">
|
||||
<span>菜单支持拼音搜索</span>
|
||||
<el-switch v-model="supportPinyinSearch" class="drawer-switch" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ThemePicker from '@/components/ThemePicker'
|
||||
|
||||
export default {
|
||||
components: { ThemePicker },
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
isShowJob() {
|
||||
return this.$store.getters.language === 'zh'
|
||||
},
|
||||
fixedHeader: {
|
||||
get() {
|
||||
return this.$store.state.settings.fixedHeader
|
||||
},
|
||||
set(val) {
|
||||
this.$store.dispatch('settings/changeSetting', {
|
||||
key: 'fixedHeader',
|
||||
value: val
|
||||
})
|
||||
}
|
||||
},
|
||||
tagsView: {
|
||||
get() {
|
||||
return this.$store.state.settings.tagsView
|
||||
},
|
||||
set(val) {
|
||||
this.$store.dispatch('settings/changeSetting', {
|
||||
key: 'tagsView',
|
||||
value: val
|
||||
})
|
||||
}
|
||||
},
|
||||
sidebarLogo: {
|
||||
get() {
|
||||
return this.$store.state.settings.sidebarLogo
|
||||
},
|
||||
set(val) {
|
||||
this.$store.dispatch('settings/changeSetting', {
|
||||
key: 'sidebarLogo',
|
||||
value: val
|
||||
})
|
||||
}
|
||||
},
|
||||
supportPinyinSearch: {
|
||||
get() {
|
||||
return this.$store.state.settings.supportPinyinSearch
|
||||
},
|
||||
set(val) {
|
||||
this.$store.dispatch('settings/changeSetting', {
|
||||
key: 'supportPinyinSearch',
|
||||
value: val
|
||||
})
|
||||
}
|
||||
},
|
||||
lang() {
|
||||
return this.$store.getters.language
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
themeChange(val) {
|
||||
this.$store.dispatch('settings/changeSetting', {
|
||||
key: 'theme',
|
||||
value: val
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drawer-container {
|
||||
padding: 24px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
|
||||
.drawer-title {
|
||||
margin-bottom: 12px;
|
||||
color: rgba(0, 0, 0, .85);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.drawer-item {
|
||||
color: rgba(0, 0, 0, .65);
|
||||
font-size: 14px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.drawer-switch {
|
||||
float: right
|
||||
}
|
||||
|
||||
.job-link{
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
</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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/layout/components/Sidebar/Item.vue
Normal file
41
src/layout/components/Sidebar/Item.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'MenuItem',
|
||||
functional: true,
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
render(h, context) {
|
||||
const { icon, title } = context.props
|
||||
const vnodes = []
|
||||
|
||||
if (icon) {
|
||||
if (icon.includes('iconfont')) {
|
||||
vnodes.push(<i class={[icon, 'sub-el-icon']} />)
|
||||
} else {
|
||||
vnodes.push(<svg-icon icon-class={icon}/>)
|
||||
}
|
||||
}
|
||||
|
||||
if (title) {
|
||||
vnodes.push(<span slot='title' style=' display:inline-block; white-space:nowrap;text-overflow:ellipsis;overflow:hidden;width: 160px;' >{(title)}</span>)
|
||||
}
|
||||
return vnodes
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sub-el-icon {
|
||||
color: currentColor;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
</style>
|
||||
77
src/layout/components/Sidebar/Link.vue
Normal file
77
src/layout/components/Sidebar/Link.vue
Normal file
@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<component :is="type" v-bind="linkProps(to)">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import path from 'path'
|
||||
import { isExternal } from '@/utils/validate'
|
||||
import { checkPlatToken, GetApplyLargeScreenToken } from '@/api/user'
|
||||
import { getToken } from '@/utils/auth'
|
||||
export default {
|
||||
props: {
|
||||
to: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isExternal() {
|
||||
return isExternal(this.to)
|
||||
},
|
||||
type() {
|
||||
if (this.isExternal) {
|
||||
return 'a'
|
||||
}
|
||||
return 'router-link'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resolvePath(routePath) {
|
||||
if (isExternal(routePath)) {
|
||||
return routePath
|
||||
}
|
||||
if (isExternal(this.basePath)) {
|
||||
return this.basePath
|
||||
}
|
||||
return path.resolve(this.basePath, routePath)
|
||||
},
|
||||
handleMenuClick(path) {
|
||||
if (this.type === 'div') {
|
||||
checkPlatToken().then(res => {
|
||||
if (res.data) {
|
||||
GetApplyLargeScreenToken().then(response => {
|
||||
const href = path.href + '?token=' + response.data.accessToken
|
||||
window.open(href, '_blank')
|
||||
})
|
||||
} else {
|
||||
this.$store.dispatch('user/logout')
|
||||
this.$router.push(localStorage.getItem('loginPage'))
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
linkProps(to) {
|
||||
if (this.isExternal) {
|
||||
const token = getToken(token)
|
||||
const params = to.split('?')
|
||||
let path = ''
|
||||
if (params.length > 1) {
|
||||
path = to + '&token=' + token
|
||||
} else {
|
||||
path = to + '?token=' + token
|
||||
}
|
||||
return {
|
||||
href: path,
|
||||
target: '_blank',
|
||||
rel: 'noopener'
|
||||
}
|
||||
}
|
||||
return {
|
||||
to: to
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
132
src/layout/components/Sidebar/Logo.vue
Normal file
132
src/layout/components/Sidebar/Logo.vue
Normal file
@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="sidebar-logo-container" :class="{ collapse: collapse }">
|
||||
<transition name="sidebarLogoFade">
|
||||
<div v-if="collapse" class="sidebar-logo-link">
|
||||
<img v-if="isDefaultLogo && logo" :src="logo" class="sidebar-logo" :style="logoStyle">
|
||||
<img v-else-if="!isDefaultLogo && logo" :src="logoUrl ? logoUrl : logo" class="sidebar-logo">
|
||||
</div>
|
||||
<div v-else class="sidebar-logo-link">
|
||||
<img v-if="isDefaultLogo && logo" :src="logo" class="sidebar-logo" :style="logoStyle">
|
||||
<img v-else-if="!isDefaultLogo && logo" :src="logoUrl ? logoUrl : logo" class="sidebar-logo">
|
||||
<h1 class="sidebar-title" :style="textStyle">{{ title }}</h1>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import logo from '@/assets/images/logo.png'
|
||||
export default {
|
||||
name: 'SidebarLogo',
|
||||
props: {
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: this.$t('dashboard.controlPlatform'),
|
||||
logo: null,
|
||||
isDefaultLogo: false,
|
||||
stationId: null,
|
||||
logoStyle: {},
|
||||
textStyle: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentStation() {
|
||||
return this.$store.getters.currentStation || undefined
|
||||
},
|
||||
logoUrl() {
|
||||
return this.$store.getters.logoUrl
|
||||
},
|
||||
userId() {
|
||||
return this.$store.getters.userinfo.userId
|
||||
},
|
||||
language() {
|
||||
return this.$store.getters.language || undefined
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentStation: {
|
||||
handler(val) {
|
||||
if (val && val.id) {
|
||||
this.stationId = val.id
|
||||
this.logo = logo
|
||||
this.isDefaultLogo = true
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
language: {
|
||||
handler(val) {
|
||||
this.title = this.$t('dashboard.controlPlatform')
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sidebarLogoFade-enter-active {
|
||||
transition: opacity 1.5s;
|
||||
}
|
||||
|
||||
.sidebarLogoFade-enter,
|
||||
.sidebarLogoFade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sidebar-logo-container {
|
||||
// position: relative;
|
||||
width: 100%;
|
||||
height: $nav-bar-height;
|
||||
line-height: $nav-bar-height;
|
||||
background: linear-gradient(180deg, #024a7b 0%, rgba(0, 148, 255, 0) 94%);
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
& .sidebar-logo-link {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
// flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 2px;
|
||||
|
||||
& .sidebar-logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
& .sidebar-title {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
// display: inline-block;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
width: 200px;
|
||||
font-size: 19px;
|
||||
font-weight: normal;
|
||||
letter-spacing: 0.1em;
|
||||
color: #edf8ff;
|
||||
opacity: 0.9;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&.collapse {
|
||||
.sidebar-logo {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
108
src/layout/components/Sidebar/SidebarItem.vue
Normal file
108
src/layout/components/Sidebar/SidebarItem.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div v-if="!item.hidden">
|
||||
<template v-if="hasOneShowingChild(item.children,item)">
|
||||
<app-link :to="resolvePath(onlyOneChild.url)">
|
||||
<el-menu-item :index="resolvePath(onlyOneChild.url)" :class="{'submenu-title-noDropdown':!isNest,'activeCalss':resolvePath(onlyOneChild.path) === activePath , commonClass:resolvePath(onlyOneChild.path) !== activePath}">
|
||||
<!-- <el-tooltip class="item" effect="dark" :content="generateTitle(onlyOneChild.name)" placement="right-start">
|
||||
<span> <item :icon="'iconfont ' + item.icon " :title="generateTitle(onlyOneChild.name)" /></span>
|
||||
</el-tooltip> -->
|
||||
<item :icon="'iconfont ' + item.icon " :title="generateTitle(onlyOneChild.name)" />
|
||||
</el-menu-item>
|
||||
</app-link>
|
||||
</template>
|
||||
|
||||
<el-submenu v-else ref="subMenu" :index="resolvePath(item.url)" popper-:append-to-body="false">
|
||||
<template slot="title">
|
||||
<item :icon="'iconfont ' + item.icon " :title="generateTitle(item.name)" />
|
||||
</template>
|
||||
<sidebar-item
|
||||
v-for="child in item.children"
|
||||
:key="child.url"
|
||||
:is-nest="true"
|
||||
:item="child"
|
||||
:base-path="resolvePath(child.url)"
|
||||
class="nest-menu"
|
||||
:active-path="activePath"
|
||||
/>
|
||||
</el-submenu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import path from 'path'
|
||||
import { generateTitle } from '@/utils/i18n'
|
||||
import { isExternal } from '@/utils/validate'
|
||||
import Item from './Item'
|
||||
import AppLink from './Link'
|
||||
import FixiOSBug from './FixiOSBug'
|
||||
|
||||
export default {
|
||||
name: 'SidebarItem',
|
||||
components: { Item, AppLink },
|
||||
mixins: [FixiOSBug],
|
||||
props: {
|
||||
// route object
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isNest: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
basePath: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
activePath: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
|
||||
// TODO: refactor with render function
|
||||
this.onlyOneChild = null
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
hasOneShowingChild(children = [], parent) {
|
||||
const showingChildren = children
|
||||
|
||||
// When there is only one child router, the child router is displayed by default
|
||||
if (showingChildren.length > 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Show parent if there are no child router to display
|
||||
if (showingChildren.length === 0) {
|
||||
this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
resolvePath(routePath) {
|
||||
if (isExternal(routePath)) {
|
||||
return routePath
|
||||
}
|
||||
if (isExternal(this.basePath)) {
|
||||
return this.basePath
|
||||
}
|
||||
return path.resolve(this.basePath, routePath)
|
||||
},
|
||||
|
||||
generateTitle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.activeCalss{
|
||||
background-color: $menuActiveBg !important;
|
||||
color: $subMenuActiveText !important;
|
||||
}
|
||||
.commonClass{
|
||||
background-color: $menuBg !important;
|
||||
color: $menuText !important;
|
||||
}
|
||||
</style>
|
||||
282
src/layout/components/Sidebar/index.vue
Normal file
282
src/layout/components/Sidebar/index.vue
Normal file
@ -0,0 +1,282 @@
|
||||
<template>
|
||||
<div :class="{ 'has-logo': showLogo }">
|
||||
<logo :collapse="isCollapse" />
|
||||
|
||||
<div v-show="!isCollapse" style="padding: 10px">
|
||||
|
||||
<el-cascader
|
||||
:key="showKey"
|
||||
v-model="selectData"
|
||||
:options="stations"
|
||||
filterable
|
||||
:props="{ label: 'name', children: 'list', value: 'id', emitPath: false, }"
|
||||
:show-all-levels="false"
|
||||
@change="handleToSelect"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
:collapse="isCollapse"
|
||||
:background-color="variables.menuBg"
|
||||
:text-color="variables.menuText"
|
||||
:unique-opened="false"
|
||||
:active-text-color="variables.menuActiveText"
|
||||
:collapse-transition="false"
|
||||
mode="vertical"
|
||||
>
|
||||
<sidebar-item
|
||||
v-for="route in menuList"
|
||||
:key="route.id"
|
||||
:item="route"
|
||||
:base-path="route.url"
|
||||
:active-path="activePath"
|
||||
/>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
<div class="open-btn" @click="handleClickOutside">
|
||||
<i class="iconfont icon-celanyincang" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { windowResize } from '@/utils/index'
|
||||
|
||||
import Logo from './Logo'
|
||||
import SidebarItem from './SidebarItem'
|
||||
import variables from '@/styles/variables.scss'
|
||||
|
||||
// import { GetExclusiveMenu } from '@/api/user'
|
||||
import {
|
||||
getProvinceStation,
|
||||
GetLogo
|
||||
} from '@/api/station/maintain'
|
||||
import { findItemsWithSameId } from '@/utils/index'
|
||||
|
||||
export default {
|
||||
components: { SidebarItem, Logo },
|
||||
data() {
|
||||
return {
|
||||
stationId: undefined,
|
||||
index: '',
|
||||
stations: [],
|
||||
showKey: 0,
|
||||
selectData: [],
|
||||
taitanStationids: [579, 576, 578, 577, 384, 481, 512],
|
||||
rundaStationIds: [591, 721, 422],
|
||||
qingtaoStationIds: [996, 997, 999, 1000, 1001]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['sidebar']),
|
||||
username() {
|
||||
return this.$store.getters.userinfo.username
|
||||
},
|
||||
menuList() {
|
||||
return this.$store.getters.menuList
|
||||
},
|
||||
activeMenu() {
|
||||
const route = this.$route
|
||||
const { meta, path } = route
|
||||
// if set path, the sidebar will highlight the path you set
|
||||
if (meta.activeMenu) {
|
||||
return meta.activeMenu
|
||||
}
|
||||
return path
|
||||
},
|
||||
activePath() {
|
||||
const route = this.$route
|
||||
const { meta, path } = route
|
||||
// if set path, the sidebar will highlight the path you set
|
||||
if (meta.activeMenu) {
|
||||
return meta.activeMenu
|
||||
}
|
||||
return path
|
||||
},
|
||||
showLogo() {
|
||||
return this.$store.state.settings.sidebarLogo
|
||||
},
|
||||
variables() {
|
||||
return variables
|
||||
},
|
||||
isCollapse() {
|
||||
return !this.sidebar.opened
|
||||
},
|
||||
activeMenuName() {
|
||||
return this.$store.getters.activeMenuName
|
||||
},
|
||||
isScreenEnter() {
|
||||
return this.$store.state.user.isScreenEnter
|
||||
},
|
||||
language() {
|
||||
return this.$store.getters.language || undefined
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
language: {
|
||||
async handler() {
|
||||
if (this.stationId) {
|
||||
this.$store.dispatch('user/getNewMenu', {
|
||||
id: this.stationId,
|
||||
path: this.$route.path
|
||||
})
|
||||
await this.$store.dispatch('user/getInfo')
|
||||
this.$store.dispatch('user/getAllDict')
|
||||
this.getProvinceStation()
|
||||
this.$store.dispatch('user/getStationByUser')
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
activeMenu: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.changeIco()
|
||||
if (val === '/task/all-task-detail') {
|
||||
localStorage.setItem(this.activeMenuName, '/task/all-task')
|
||||
} else {
|
||||
localStorage.setItem(this.activeMenuName, val)
|
||||
}
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
|
||||
$route: {
|
||||
handler: function(route) {
|
||||
setTimeout(() => {
|
||||
windowResize()
|
||||
}, 300)
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
isScreenEnter: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
// this.selectData = ['浙江', 349]
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getProvinceStation()
|
||||
},
|
||||
|
||||
methods: {
|
||||
async getProvinceStation() {
|
||||
const res = await getProvinceStation()
|
||||
this.stations = res.data.list
|
||||
|
||||
this.stations.map((i) => {
|
||||
i.id = i.name
|
||||
return i
|
||||
})
|
||||
if (localStorage.getItem('currentStationId')) {
|
||||
this.selectData = +localStorage.getItem('currentStationId')
|
||||
this.handleToSelect(+localStorage.getItem('currentStationId'))
|
||||
} else {
|
||||
this.selectData = this.stations[0].list[0].list[0].id
|
||||
this.handleToSelect(this.selectData)
|
||||
}
|
||||
},
|
||||
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch('app/toggleSideBar')
|
||||
},
|
||||
// 修改Favicon的方法
|
||||
changeFavicon(link) {
|
||||
let $favicon = document.querySelector('link[rel="icon"]')
|
||||
if ($favicon !== null) {
|
||||
$favicon.href = link
|
||||
} else {
|
||||
$favicon = document.createElement('link')
|
||||
$favicon.rel = 'icon'
|
||||
$favicon.href = link
|
||||
document.head.appendChild($favicon)
|
||||
}
|
||||
},
|
||||
// 根据传递的参数修改Favicon
|
||||
changeIco(id) {
|
||||
|
||||
},
|
||||
async getLogo() {
|
||||
const res = await GetLogo(this.stationId)
|
||||
if (res.data) {
|
||||
this.$store.commit('user/SET_LOGOURL', res.data.url)
|
||||
} else {
|
||||
this.$store.commit('user/SET_LOGOURL', null)
|
||||
}
|
||||
},
|
||||
async handleToSelect(val) {
|
||||
const item = findItemsWithSameId(this.stations, val)
|
||||
this.stationId = val
|
||||
localStorage.setItem('stationAddress', item[0].address)
|
||||
this.changeIco(val)
|
||||
this.$store.commit(
|
||||
'user/SET_SELECT_ID',
|
||||
val
|
||||
)
|
||||
this.$store.commit(
|
||||
'user/SET_CURRENT_STATION',
|
||||
item[0]
|
||||
)
|
||||
this.$store.dispatch('user/getNewMenu', {
|
||||
id: val,
|
||||
path: this.$route.path
|
||||
})
|
||||
this.getLogo()
|
||||
this.showKey++
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.company-wrapper {
|
||||
width: $sideBarWidth;
|
||||
height: $nav-bar-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
.name {
|
||||
font-size: 22px;
|
||||
font-weight: normal;
|
||||
letter-spacing: 0.1em;
|
||||
color: #edf8ff;
|
||||
opacity: 0.9;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.mobileName {
|
||||
font-size: 20px !important;
|
||||
color: #fff;
|
||||
}
|
||||
.mobileLogo {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
.enName {
|
||||
font-size: 24px;
|
||||
max-width: 400px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
/deep/.el-select-dropdown__item {
|
||||
width: 50px !important;
|
||||
}
|
||||
::v-deep .el-input--small .el-input__inner {
|
||||
width: calc(#{$sideBarWidth} - 20px) !important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
94
src/layout/components/TagsView/ScrollPane.vue
Normal file
94
src/layout/components/TagsView/ScrollPane.vue
Normal file
@ -0,0 +1,94 @@
|
||||
<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: 'ScrollPane',
|
||||
data() {
|
||||
return {
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
scrollWrapper() {
|
||||
return this.$refs.scrollContainer.$refs.wrap
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.scrollWrapper.addEventListener('scroll', this.emitScroll, true)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.scrollWrapper.removeEventListener('scroll', this.emitScroll)
|
||||
},
|
||||
methods: {
|
||||
handleScroll(e) {
|
||||
const eventDelta = e.wheelDelta || -e.deltaY * 40
|
||||
const $scrollWrapper = this.scrollWrapper
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
|
||||
},
|
||||
emitScroll() {
|
||||
this.$emit('scroll')
|
||||
},
|
||||
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) {
|
||||
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 {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
::v-deep {
|
||||
.el-scrollbar__bar {
|
||||
bottom: 0px;
|
||||
}
|
||||
.el-scrollbar__wrap {
|
||||
height: $nav-bar-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
303
src/layout/components/TagsView/index.vue
Normal file
303
src/layout/components/TagsView/index.vue
Normal file
@ -0,0 +1,303 @@
|
||||
<template>
|
||||
<div id="tags-view-container" class="tags-view-container">
|
||||
<scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
|
||||
<img :src="tagview_colspan" alt="" class="tagview_colspan" @click.prevent.stop="toggleSideBar">
|
||||
<router-link
|
||||
v-for="tag in visitedViews"
|
||||
ref="tag"
|
||||
:key="tag.path"
|
||||
:class="isActive(tag)?'active':''"
|
||||
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
||||
tag="span"
|
||||
class="tags-view-item"
|
||||
@click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
|
||||
@contextmenu.prevent.native="openMenu(tag,$event)"
|
||||
>
|
||||
<img v-if="!isActive(tag)" :src="tagview" class="tags-view-icon-close">
|
||||
<img v-if="isActive(tag)" :src="tagview_active" class="tags-view-icon-close">
|
||||
<span class="route-name">{{ generateTitle(tag.title) }}</span>
|
||||
<img v-if="!isAffix(tag) && !isActive(tag)" :src="tagview_close" class="tags-view-icon-close" @click.prevent.stop="closeSelectedTag(tag)">
|
||||
<img v-if="!isAffix(tag) && isActive(tag)" :src="tagview_close_active" class="tags-view-icon-close" @click.prevent.stop="closeSelectedTag(tag)">
|
||||
</router-link>
|
||||
</scroll-pane>
|
||||
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
|
||||
<li @click="refreshSelectedTag(selectedTag)">刷新</li>
|
||||
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭</li>
|
||||
<li @click="closeOthersTags">关闭其它</li>
|
||||
<li @click="closeAllTags(selectedTag)">关闭所有</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import ScrollPane from './ScrollPane'
|
||||
import { generateTitle } from '@/utils/i18n'
|
||||
import tagview_close from '@/assets/icon/tagview_close.png'
|
||||
import tagview_close_active from '@/assets/icon/tagview_close_active.png'
|
||||
import tagview_colspan from '@/assets/icon/tagview_colspan.png'
|
||||
import tagview from '@/assets/icon/tagview.png'
|
||||
import tagview_active from '@/assets/icon/tagview_active.png'
|
||||
import path from 'path'
|
||||
|
||||
export default {
|
||||
components: { ScrollPane },
|
||||
data() {
|
||||
return {
|
||||
tagview_close,
|
||||
tagview_close_active,
|
||||
tagview_colspan,
|
||||
tagview,
|
||||
tagview_active,
|
||||
visible: false,
|
||||
top: 0,
|
||||
left: 0,
|
||||
selectedTag: {},
|
||||
affixTags: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['sidebar', 'device']),
|
||||
visitedViews() {
|
||||
return this.$store.state.tagsView.visitedViews
|
||||
},
|
||||
routes() {
|
||||
return [{
|
||||
fullPath: '/dashboard',
|
||||
path: 'dashboard',
|
||||
name: 'Dashboard',
|
||||
meta: { title: 'dashboard', icon: 'dashboard', affix: true }
|
||||
}]
|
||||
}
|
||||
},
|
||||
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.initTags()
|
||||
this.addTags()
|
||||
},
|
||||
methods: {
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch('app/toggleSideBar')
|
||||
},
|
||||
generateTitle, // generateTitle by vue-i18n
|
||||
isActive(route) {
|
||||
return route.path === this.$route.path
|
||||
},
|
||||
isAffix(tag) {
|
||||
return tag.meta && tag.meta.affix
|
||||
},
|
||||
filterAffixTags(routes, basePath = '/') {
|
||||
let tags = []
|
||||
routes.forEach(route => {
|
||||
if (route.meta && route.meta.affix) {
|
||||
const tagPath = path.resolve(basePath, route.path)
|
||||
tags.push({
|
||||
fullPath: tagPath,
|
||||
path: tagPath,
|
||||
name: route.name,
|
||||
meta: { ...route.meta }
|
||||
})
|
||||
}
|
||||
if (route.children) {
|
||||
const tempTags = this.filterAffixTags(route.children, route.path)
|
||||
if (tempTags.length >= 1) {
|
||||
tags = [...tags, ...tempTags]
|
||||
}
|
||||
}
|
||||
})
|
||||
return tags
|
||||
},
|
||||
initTags() {
|
||||
const affixTags = this.affixTags = this.filterAffixTags(this.routes)
|
||||
|
||||
for (const tag of affixTags) {
|
||||
// Must have tag name
|
||||
if (tag.name) {
|
||||
this.$store.dispatch('tagsView/addVisitedView', tag)
|
||||
}
|
||||
}
|
||||
},
|
||||
addTags() {
|
||||
const { name } = this.$route
|
||||
if (name) {
|
||||
this.$store.dispatch('tagsView/addView', this.$route)
|
||||
}
|
||||
return false
|
||||
},
|
||||
moveToCurrentTag() {
|
||||
const tags = this.$refs.tag
|
||||
this.$nextTick(() => {
|
||||
for (const tag of tags) {
|
||||
if (tag.to.path === this.$route.path) {
|
||||
this.$refs.scrollPane.moveToTarget(tag)
|
||||
// when query is different then update
|
||||
if (tag.to.fullPath !== this.$route.fullPath) {
|
||||
this.$store.dispatch('tagsView/updateVisitedView', this.$route)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
refreshSelectedTag(view) {
|
||||
this.$store.dispatch('tagsView/delCachedView', view).then(() => {
|
||||
const { fullPath } = view
|
||||
this.$nextTick(() => {
|
||||
this.$router.replace({
|
||||
path: '/redirect' + fullPath
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
closeSelectedTag(view) {
|
||||
this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
|
||||
if (this.isActive(view)) {
|
||||
this.toLastView(visitedViews, view)
|
||||
}
|
||||
})
|
||||
},
|
||||
closeOthersTags() {
|
||||
this.$router.push(this.selectedTag)
|
||||
this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
|
||||
this.moveToCurrentTag()
|
||||
})
|
||||
},
|
||||
closeAllTags(view) {
|
||||
this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
|
||||
if (this.affixTags.some(tag => tag.path === view.path)) {
|
||||
return
|
||||
}
|
||||
this.toLastView(visitedViews, view)
|
||||
})
|
||||
},
|
||||
toLastView(visitedViews, view) {
|
||||
const latestView = visitedViews.slice(-1)[0]
|
||||
if (latestView) {
|
||||
this.$router.push(latestView.fullPath)
|
||||
} else {
|
||||
// now the default is to redirect to the home page if there is no tags-view,
|
||||
// you can adjust it according to your needs.
|
||||
if (view.name === 'Dashboard') {
|
||||
// to reload home page
|
||||
this.$router.replace({ path: '/redirect' + view.fullPath })
|
||||
} else {
|
||||
this.$router.push(localStorage.getItem('loginPage'))
|
||||
}
|
||||
}
|
||||
},
|
||||
openMenu(tag, e) {
|
||||
const menuMinWidth = 105
|
||||
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
|
||||
const offsetWidth = this.$el.offsetWidth // container width
|
||||
const maxLeft = offsetWidth - menuMinWidth // left boundary
|
||||
const left = e.clientX - offsetLeft + 15 // 15: margin right
|
||||
|
||||
if (left > maxLeft) {
|
||||
this.left = maxLeft
|
||||
} else {
|
||||
this.left = left
|
||||
}
|
||||
this.top = e.clientY - 20
|
||||
this.visible = true
|
||||
this.selectedTag = tag
|
||||
},
|
||||
closeMenu() {
|
||||
this.visible = false
|
||||
},
|
||||
handleScroll() {
|
||||
this.closeMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tags-view-container {
|
||||
height: $tag-view-height;
|
||||
width: 100%;
|
||||
background: $tagview-background;
|
||||
border-bottom: $parting-line;
|
||||
position: relative;
|
||||
|
||||
.tags-view-wrapper {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left :0;
|
||||
overflow: hidden;
|
||||
.tagview_colspan{
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.tags-view-item {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
height: $tag-view-height;
|
||||
line-height: $tag-view-height;
|
||||
color: $text-color;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
margin-left: 5px;
|
||||
&:first-of-type {
|
||||
margin-left: 15px;
|
||||
}
|
||||
&:last-of-type {
|
||||
margin-right: 15px;
|
||||
}
|
||||
&.active {
|
||||
background-color: $content-background;
|
||||
color: $menuActiveText;
|
||||
border-left: $parting-line;
|
||||
border-right: $parting-line;
|
||||
border-bottom: none;
|
||||
}
|
||||
.tags-view-icon-close{
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
right: 0;
|
||||
}
|
||||
.route-name{
|
||||
display: inline-block;
|
||||
padding-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
background: $content-background;
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 7px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: $parting-line-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
153
src/layout/components/Upgrade/index.vue
Normal file
153
src/layout/components/Upgrade/index.vue
Normal file
@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div class="upgrade-dialog">
|
||||
<el-dialog
|
||||
:append-to-body="false"
|
||||
title="检测到版本更新"
|
||||
center
|
||||
:visible.sync="isUpgradeShow"
|
||||
width="584px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
:show-close="false"
|
||||
>
|
||||
<div class="upgrade-content">
|
||||
<div class="upgrade-item version">V {{ version }}</div>
|
||||
<div class="upgrade-item desc">
|
||||
<div class="circle" />
|
||||
<div class="name">新的模块</div>
|
||||
</div>
|
||||
<div class="upgrade-item desc">
|
||||
<div class="circle" />
|
||||
<div class="name">安全保障</div>
|
||||
</div>
|
||||
<div class="upgrade-item desc">
|
||||
<div class="circle" />
|
||||
<div class="name">更多服务</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div slot="footer" class="upgrade-btn">
|
||||
<el-button class="close" @click="close">暂不更新</el-button>
|
||||
<el-button type="primary" class="sure" @click="sure">马上更新</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from '../../../../package.json'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
isUpgradeShow: false,
|
||||
version: '',
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!localStorage.getItem('hoenergy_control_version')) {
|
||||
localStorage.setItem('hoenergy_control_version', process.env.__NEXT_VERSION__)
|
||||
}
|
||||
|
||||
this.checkedVersion()
|
||||
self.timer = setInterval(() => {
|
||||
this.checkedVersion()
|
||||
}, 1000 * 60 * 30)
|
||||
},
|
||||
methods: {
|
||||
checkedVersion() {
|
||||
const self = this
|
||||
if (config.version !== localStorage.getItem('hoenergy_control_version')) {
|
||||
self.isUpgradeShow = true
|
||||
this.version = config.version
|
||||
} else {
|
||||
self.isUpgradeShow = false
|
||||
}
|
||||
},
|
||||
sure() {
|
||||
this.isUpgradeShow = false
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
localStorage.setItem('hoenergy_control_version', config.version)
|
||||
window.location.reload()
|
||||
},
|
||||
close() {
|
||||
this.isUpgradeShow = false
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .el-dialog__header{
|
||||
border-bottom: 0!important;
|
||||
}
|
||||
.upgrade-btn{
|
||||
width: 100%;
|
||||
.close {
|
||||
border: 1px solid #0094FF;
|
||||
box-shadow: 4px 4px 24px 0px #0094FF80 inset;
|
||||
background: #11304f;
|
||||
color: #fff;
|
||||
border-radius: 6px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.sure {
|
||||
border: 1px solid #00FFF6;
|
||||
box-shadow: 4px 4px 24px 0px #00FFF6 inset;
|
||||
background: #11304f;
|
||||
color: #fff;
|
||||
border-radius: 6px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
.upgrade-content{
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.upgrade-item{
|
||||
width: 100%;
|
||||
height: 42px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.version{
|
||||
color: #0094FF;
|
||||
font-size: 32px;
|
||||
}
|
||||
.desc{
|
||||
.circle{
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
.name{
|
||||
color: #eee;
|
||||
font-size: 20px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-dialog{
|
||||
//margin-top: 20vh!important;
|
||||
width: 584px;
|
||||
height: 347px;
|
||||
border: none;
|
||||
background: url(../../../assets/images/upgrade-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
.el-dialog__header{
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
7
src/layout/components/index.js
Normal file
7
src/layout/components/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
export { default as AppMain } from './AppMain'
|
||||
export { default as Navbar } from './Navbar'
|
||||
export { default as Settings } from './Settings'
|
||||
export { default as Sidebar } from './Sidebar/index.vue'
|
||||
export { default as TagsView } from './TagsView/index.vue'
|
||||
export { default as Upgrade } from './Upgrade/index.vue'
|
||||
export { default as PasswordMessage } from './PasswordMessage/index.vue'
|
||||
139
src/layout/index.vue
Normal file
139
src/layout/index.vue
Normal file
@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div :class="classObj" class="app-wrapper">
|
||||
<!-- <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" /> -->
|
||||
<!-- <div v-if="device==='mobile'&&sidebar.opened" @click="handleClickOutside" /> -->
|
||||
|
||||
<navbar />
|
||||
<sidebar class="sidebar-container" />
|
||||
<div :class="{ hasTagsView: needTagsView }" class="main-container">
|
||||
<div :class="{ 'fixed-header': fixedHeader }">
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
<app-main />
|
||||
<right-panel v-if="showSettings">
|
||||
<settings />
|
||||
</right-panel>
|
||||
</div>
|
||||
<!-- <Upgrade /> -->
|
||||
<PasswordMessage />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RightPanel from '@/components/RightPanel'
|
||||
import { AppMain, Navbar, Settings, Sidebar, TagsView, PasswordMessage } from './components'
|
||||
|
||||
import ResizeMixin from './mixin/ResizeHandler'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Layout',
|
||||
components: {
|
||||
AppMain,
|
||||
Navbar,
|
||||
RightPanel,
|
||||
Settings,
|
||||
Sidebar,
|
||||
TagsView,
|
||||
PasswordMessage
|
||||
},
|
||||
mixins: [ResizeMixin],
|
||||
data() {
|
||||
return {
|
||||
theme: {
|
||||
// 'url(../assets/images/dashboardBg.png)',
|
||||
'--main-bg-img': 'url(' + require('../assets/images/dashboardBg.png') + ')',
|
||||
'--table-bg': 'rgba(4, 48, 80, 0.6)',
|
||||
'--topNav-bg': 'linear-gradient(91deg,rgba(2, 74, 123, 0.6) 0%,rgba(2, 61, 100, 0) 94%)',
|
||||
'--item-header-bg': 'url(' + require('../assets/images/home-item-header.png') + ')',
|
||||
'--font-family': 'PangMenZhengDao'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
sidebar: state => state.app.sidebar,
|
||||
device: state => state.app.device,
|
||||
showSettings: state => state.settings.showSettings,
|
||||
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'
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.$store.getters.config?.title === 'th') {
|
||||
const result = JSON.parse(this.$store.getters.config.theme)
|
||||
for (const [key, value] of Object.entries(result)) {
|
||||
document.getElementsByTagName('body')[0].style.setProperty(key, value)
|
||||
}
|
||||
document.getElementsByTagName('body')[0].style.setProperty('--item-header-bg', 'url(' + require('../assets/images/home-item-header-dark.png') + ')')
|
||||
document.getElementsByTagName('body')[0].style.setProperty('--font-family', 'HYQY')
|
||||
} else {
|
||||
for (const [key, value] of Object.entries(this.theme)) {
|
||||
document.getElementsByTagName('body')[0].style.setProperty(key, value)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "~@/styles/mixin.scss";
|
||||
@import "~@/styles/variables.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: var(--main-bg-img);
|
||||
overflow: hidden;
|
||||
background-size: 100% 100%;
|
||||
background-attachment: fixed;
|
||||
|
||||
// &.mobile.openSidebar {
|
||||
// position: fixed;
|
||||
// top: 0;
|
||||
// }
|
||||
}
|
||||
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.fixed-header {
|
||||
position: fixed;
|
||||
top: $nav-bar-height;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
width: calc(100% - #{$sideBarWidth});
|
||||
transition: width 0.28s;
|
||||
}
|
||||
|
||||
// .hideSidebar .fixed-header {
|
||||
// width: calc(100% - #{$nav-bar-height})
|
||||
// }
|
||||
|
||||
.mobile .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
42
src/layout/mixin/ResizeHandler.js
Normal file
42
src/layout/mixin/ResizeHandler.js
Normal file
@ -0,0 +1,42 @@
|
||||
const { body } = document
|
||||
const WIDTH = 1024 // 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 || this.$store.getters.menuList.length === 0) {
|
||||
// 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() || this.$store.getters.menuList.length === 0
|
||||
// store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'mobile')
|
||||
if (isMobile) {
|
||||
// store.dispatch('app/closeSideBar', { withoutAnimation: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user