feat: init+修改node版本
This commit is contained in:
67
src/views/layout/Layout.vue
Normal file
67
src/views/layout/Layout.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="app-wrapper" :class="classObj">
|
||||
|
||||
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"></div>
|
||||
<sidebar class="sidebar-container"></sidebar>
|
||||
<div class="main-container">
|
||||
<navbar></navbar>
|
||||
<!--<tags-view></tags-view>-->
|
||||
<app-main></app-main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Navbar, Sidebar, AppMain, TagsView } from './components'
|
||||
import ResizeMixin from './mixin/ResizeHandler'
|
||||
|
||||
export default {
|
||||
name: 'layout',
|
||||
components: {
|
||||
Navbar,
|
||||
Sidebar,
|
||||
AppMain,
|
||||
TagsView
|
||||
},
|
||||
mixins: [ResizeMixin],
|
||||
computed: {
|
||||
sidebar() {
|
||||
return this.$store.state.app.sidebar
|
||||
},
|
||||
device() {
|
||||
return this.$store.state.app.device
|
||||
},
|
||||
classObj() {
|
||||
return {
|
||||
hideSidebar: !this.sidebar.opened,
|
||||
withoutAnimation: this.sidebar.withoutAnimation,
|
||||
mobile: this.device === 'mobile'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClickOutside() {
|
||||
this.$store.dispatch('closeSideBar', { withoutAnimation: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
@import "src/styles/mixin.scss";
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
23
src/views/layout/components/AppMain.vue
Normal file
23
src/views/layout/components/AppMain.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<section class="app-main" style="min-height: 100%">
|
||||
<transition name="fade" mode="out-in">
|
||||
<keep-alive :include="cachedViews">
|
||||
<router-view></router-view>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AppMain',
|
||||
computed: {
|
||||
cachedViews() {
|
||||
return this.$store.state.tagsView.cachedViews
|
||||
}
|
||||
// key() {
|
||||
// return this.$route.name !== undefined ? this.$route.name + +new Date() : this.$route + +new Date()
|
||||
// }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
212
src/views/layout/components/Navbar.vue
Normal file
212
src/views/layout/components/Navbar.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-menu
|
||||
:default-active="activeIndex"
|
||||
class="el-menu-demo"
|
||||
mode="horizontal"
|
||||
background-color="#2E4155"
|
||||
text-color="#fff"
|
||||
active-text-color="#ffd04b"
|
||||
:router="true"
|
||||
>
|
||||
<el-menu-item
|
||||
:index="item.path"
|
||||
:key="item.path"
|
||||
v-for="item in topMenu"
|
||||
v-if="ids.indexOf(item.id)!==-1"
|
||||
@click="handleTopMenuClick(item)">
|
||||
{{item.meta.title}}
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
|
||||
<el-menu class="navbar" mode="horizontal">
|
||||
<hamburger class="hamburger-container" :toggleClick="toggleSideBar"
|
||||
:isActive="sidebar.opened"></hamburger>
|
||||
|
||||
<breadcrumb class="breadcrumb-container"></breadcrumb>
|
||||
|
||||
|
||||
<div class="right-menu">
|
||||
<error-log class="errLog-container right-menu-item"></error-log>
|
||||
|
||||
<el-tooltip effect="dark" :content="$t('navbar.screenfull')" placement="bottom">
|
||||
<screenfull class="screenfull right-menu-item"></screenfull>
|
||||
</el-tooltip>
|
||||
|
||||
<!--<lang-select class="international right-menu-item"></lang-select>-->
|
||||
|
||||
<el-tooltip effect="dark" :content="$t('navbar.theme')" placement="bottom">
|
||||
<theme-picker class="theme-switch right-menu-item"></theme-picker>
|
||||
</el-tooltip>
|
||||
|
||||
<el-dropdown class="avatar-container right-menu-item" trigger="click">
|
||||
<div class="avatar-wrapper">
|
||||
<img class="user-avatar" :src="avatar+'?imageView2/1/w/80/h/80'">
|
||||
<i class="el-icon-caret-bottom"></i>
|
||||
</div>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<router-link to="/">
|
||||
<el-dropdown-item>
|
||||
{{$t('navbar.dashboard')}}
|
||||
</el-dropdown-item>
|
||||
</router-link>
|
||||
|
||||
<el-dropdown-item divided>
|
||||
<span @click="logout" style="display:block;">{{$t('navbar.logOut')}}</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {mapGetters} from 'vuex'
|
||||
import Breadcrumb from '@/components/Breadcrumb'
|
||||
import Hamburger from '@/components/Hamburger'
|
||||
import ErrorLog from '@/components/ErrorLog'
|
||||
import Screenfull from '@/components/Screenfull'
|
||||
import LangSelect from '@/components/LangSelect'
|
||||
import ThemePicker from '@/components/ThemePicker'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Breadcrumb,
|
||||
Hamburger,
|
||||
ErrorLog,
|
||||
Screenfull,
|
||||
LangSelect,
|
||||
ThemePicker
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'sidebar',
|
||||
'name',
|
||||
'avatar',
|
||||
'topMenu',
|
||||
"ids"
|
||||
])
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeIndex: "",
|
||||
// topMenu:[{name:"网站运营",path:"/website-operation"},
|
||||
// {name:"统计分析",path:"/statistic"},
|
||||
// {name:"用户中心",path:"/usercenter"},
|
||||
// {name:"财务管理",path:"/finance"},
|
||||
// {name:"资金明细",path:"/assets"},
|
||||
// {name:"币币交易参数",path:"/trade-config"},
|
||||
// {name:"参数配置",path:"/normal-config"},
|
||||
// {name:"创新交易参数",path:"/forex-config"},
|
||||
// {name:"系统配置",path:"/system-config"},
|
||||
// ]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const setSideMenu = this.setSideMenu;
|
||||
if (setSideMenu) {
|
||||
return;
|
||||
}
|
||||
const currentPath = this.$route.path;
|
||||
const pathArray = currentPath.split('/').filter(item => !!item);
|
||||
if (!pathArray || pathArray.length < 1) {
|
||||
return;
|
||||
}
|
||||
const menuItem = this.topMenu.find(item => item.path === `/${pathArray[0]}`);
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
this.activeIndex = menuItem.path;
|
||||
this.$store.dispatch('setSideMenu', menuItem)
|
||||
},
|
||||
methods: {
|
||||
handleTopMenuClick(item) {
|
||||
for (let i = 0; i < this.topMenu.length; i++) {
|
||||
let menuItem = this.topMenu[i];
|
||||
if (menuItem.id === item.id) {
|
||||
console.log(menuItem)
|
||||
this.$store.dispatch('setSideMenu', menuItem).then(() => {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch('toggleSideBar')
|
||||
},
|
||||
logout() {
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
location.reload()// In order to re-instantiate the vue-router object to avoid bugs
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.center-menu {
|
||||
border: 1px solid #f00;
|
||||
display: flex;
|
||||
|
||||
}
|
||||
|
||||
.navbar {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
border-radius: 0px !important;
|
||||
.hamburger-container {
|
||||
line-height: 58px;
|
||||
height: 50px;
|
||||
float: left;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.breadcrumb-container {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.errLog-container {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
.right-menu {
|
||||
float: right;
|
||||
height: 100%;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
.right-menu-item {
|
||||
display: inline-block;
|
||||
margin: 0 8px;
|
||||
}
|
||||
.screenfull {
|
||||
height: 20px;
|
||||
}
|
||||
.international {
|
||||
vertical-align: top;
|
||||
}
|
||||
.theme-switch {
|
||||
vertical-align: 15px;
|
||||
}
|
||||
.avatar-container {
|
||||
height: 50px;
|
||||
margin-right: 30px;
|
||||
.avatar-wrapper {
|
||||
cursor: pointer;
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
.user-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.el-icon-caret-bottom {
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
top: 25px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
57
src/views/layout/components/Sidebar/SidebarItem.vue
Normal file
57
src/views/layout/components/Sidebar/SidebarItem.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div class="menu-wrapper">
|
||||
<template v-if="sideMenu&&!sideMenu.hidden&&sideMenu.children">
|
||||
<el-submenu :index="sideMenu.name||sideMenu.path" :key="sideMenu.name">
|
||||
<template slot="title">
|
||||
<svg-icon v-if="sideMenu.meta&&sideMenu.meta.icon" :icon-class="sideMenu.meta.icon"></svg-icon>
|
||||
<span v-if="sideMenu.meta&&sideMenu.meta.title" slot="title">{{sideMenu.meta.title}}</span>
|
||||
</template>
|
||||
|
||||
<template v-for="child in sideMenu.children" v-if="!child.hidden &&ids.indexOf(child.id)>-1" >
|
||||
<sidebar-item class="nest-menu" v-if="child.children&&child.children.length>0 " :sideMenu="child" :key="child.path" :ids="ids"></sidebar-item>
|
||||
<router-link v-else :to="sideMenu.path+'/'+child.path" :key="child.name">
|
||||
<el-menu-item :index="sideMenu.path+'/'+child.path">
|
||||
<svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
|
||||
<span v-if="child.meta&&child.meta.title " slot="title">{{child.meta.title}}</span>
|
||||
</el-menu-item>
|
||||
</router-link>
|
||||
</template>
|
||||
</el-submenu>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { generateTitle } from '@/utils/i18n'
|
||||
|
||||
export default {
|
||||
name: 'SidebarItem',
|
||||
props: {
|
||||
routes: {
|
||||
type: Array
|
||||
},
|
||||
// 当前路由
|
||||
sideMenu:{
|
||||
type: Object,
|
||||
default:function () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
ids: {
|
||||
type: Array,
|
||||
default:function () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
isNest: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateTitle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
49
src/views/layout/components/Sidebar/index.vue
Normal file
49
src/views/layout/components/Sidebar/index.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<el-scrollbar wrapClass="scrollbar-wrapper">
|
||||
<el-menu
|
||||
mode="vertical"
|
||||
:show-timeout="200"
|
||||
:default-active="$route.path"
|
||||
:collapse="isCollapse"
|
||||
background-color="#304156"
|
||||
text-color="#bfcbd9"
|
||||
active-text-color="#409EFF"
|
||||
:default-openeds="defaultOpends"
|
||||
>
|
||||
<sidebar-item :routes="permission_routers" :ids="ids" :sideMenu="sideMenu"></sidebar-item>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default {
|
||||
components: { SidebarItem },
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'permission_routers',
|
||||
'sidebar',
|
||||
"ids",
|
||||
"sideMenu",
|
||||
"topMenu"
|
||||
]),
|
||||
isCollapse() {
|
||||
return !this.sidebar.opened
|
||||
}
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
defaultOpends:''
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.defaultOpends = this.topMenu.map((item)=>{
|
||||
|
||||
return item.name
|
||||
})
|
||||
console.log(this.defaultOpends)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
204
src/views/layout/components/TagsView.vue
Normal file
204
src/views/layout/components/TagsView.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<div class="tags-view-container">
|
||||
<scroll-pane class='tags-view-wrapper' ref='scrollPane'>
|
||||
<router-link ref='tag' class="tags-view-item" :class="isActive(tag)?'active':''" v-for="tag in Array.from(visitedViews)"
|
||||
:to="tag.path" :key="tag.path" @contextmenu.prevent.native="openMenu(tag,$event)">
|
||||
{{generateTitle(tag.title)}}
|
||||
<span class='el-icon-close' @click.prevent.stop='closeSelectedTag(tag)'></span>
|
||||
</router-link>
|
||||
</scroll-pane>
|
||||
<ul class='contextmenu' v-show="visible" :style="{left:left+'px',top:top+'px'}">
|
||||
<li @click="closeSelectedTag(selectedTag)">{{$t('tagsView.close')}}</li>
|
||||
<li @click="closeOthersTags">{{$t('tagsView.closeOthers')}}</li>
|
||||
<li @click="closeAllTags">{{$t('tagsView.closeAll')}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ScrollPane from '@/components/ScrollPane'
|
||||
import { generateTitle } from '@/utils/i18n'
|
||||
|
||||
export default {
|
||||
components: { ScrollPane },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
top: 0,
|
||||
left: 0,
|
||||
selectedTag: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
visitedViews() {
|
||||
return this.$store.state.tagsView.visitedViews
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.addViewTags()
|
||||
this.moveToCurrentTag()
|
||||
},
|
||||
visible(value) {
|
||||
if (value) {
|
||||
document.body.addEventListener('click', this.closeMenu)
|
||||
} else {
|
||||
document.body.removeEventListener('click', this.closeMenu)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.addViewTags()
|
||||
},
|
||||
methods: {
|
||||
generateTitle, // generateTitle by vue-i18n
|
||||
generateRoute() {
|
||||
if (this.$route.name) {
|
||||
return this.$route
|
||||
}
|
||||
return false
|
||||
},
|
||||
isActive(route) {
|
||||
return route.path === this.$route.path || route.name === this.$route.name
|
||||
},
|
||||
addViewTags() {
|
||||
const route = this.generateRoute()
|
||||
if (!route) {
|
||||
return false
|
||||
}
|
||||
this.$store.dispatch('addVisitedViews', route)
|
||||
},
|
||||
moveToCurrentTag() {
|
||||
const tags = this.$refs.tag
|
||||
this.$nextTick(() => {
|
||||
for (const tag of tags) {
|
||||
if (tag.to === this.$route.path) {
|
||||
this.$refs.scrollPane.moveToTarget(tag.$el)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
closeSelectedTag(view) {
|
||||
this.$store.dispatch('delVisitedViews', view).then((views) => {
|
||||
if (this.isActive(view)) {
|
||||
const latestView = views.slice(-1)[0]
|
||||
if (latestView) {
|
||||
this.$router.push(latestView.path)
|
||||
} else {
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
closeOthersTags() {
|
||||
this.$router.push(this.selectedTag.path)
|
||||
this.$store.dispatch('delOthersViews', this.selectedTag).then(() => {
|
||||
this.moveToCurrentTag()
|
||||
})
|
||||
},
|
||||
closeAllTags() {
|
||||
this.$store.dispatch('delAllViews')
|
||||
this.$router.push('/')
|
||||
},
|
||||
openMenu(tag, e) {
|
||||
this.visible = true
|
||||
this.selectedTag = tag
|
||||
this.left = e.clientX
|
||||
this.top = e.clientY
|
||||
},
|
||||
closeMenu() {
|
||||
this.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.tags-view-container {
|
||||
.tags-view-wrapper {
|
||||
background: #fff;
|
||||
height: 34px;
|
||||
border-bottom: 1px solid #d8dce5;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
|
||||
.tags-view-item {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
border: 1px solid #d8dce5;
|
||||
color: #495060;
|
||||
background: #fff;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
margin-left: 5px;
|
||||
margin-top: 4px;
|
||||
&:first-of-type {
|
||||
margin-left: 15px;
|
||||
}
|
||||
&.active {
|
||||
background-color: #42b983;
|
||||
color: #fff;
|
||||
border-color: #42b983;
|
||||
&::before {
|
||||
content: '';
|
||||
background: #fff;
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 7px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss">
|
||||
//reset element css of el-icon-close
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
.el-icon-close {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: 2px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
transition: all .3s cubic-bezier(.645, .045, .355, 1);
|
||||
transform-origin: 100% 50%;
|
||||
&:before {
|
||||
transform: scale(.6);
|
||||
display: inline-block;
|
||||
vertical-align: -3px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #b4bccc;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
4
src/views/layout/components/index.js
Normal file
4
src/views/layout/components/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as Navbar } from './Navbar'
|
||||
export { default as Sidebar } from './Sidebar/index.vue'
|
||||
export { default as TagsView } from './TagsView'
|
||||
export { default as AppMain } from './AppMain'
|
||||
41
src/views/layout/mixin/ResizeHandler.js
Normal file
41
src/views/layout/mixin/ResizeHandler.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import store from '@/store'
|
||||
|
||||
const { body } = document
|
||||
const WIDTH = 1024
|
||||
const RATIO = 3
|
||||
|
||||
export default {
|
||||
watch: {
|
||||
$route(route) {
|
||||
if (this.device === 'mobile' && this.sidebar.opened) {
|
||||
store.dispatch('closeSideBar', { withoutAnimation: false })
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
window.addEventListener('resize', this.resizeHandler)
|
||||
},
|
||||
mounted() {
|
||||
const isMobile = this.isMobile()
|
||||
if (isMobile) {
|
||||
store.dispatch('toggleDevice', 'mobile')
|
||||
store.dispatch('closeSideBar', { withoutAnimation: true })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isMobile() {
|
||||
const rect = body.getBoundingClientRect()
|
||||
return rect.width - RATIO < WIDTH
|
||||
},
|
||||
resizeHandler() {
|
||||
if (!document.hidden) {
|
||||
const isMobile = this.isMobile()
|
||||
store.dispatch('toggleDevice', isMobile ? 'mobile' : 'desktop')
|
||||
|
||||
if (isMobile) {
|
||||
store.dispatch('closeSideBar', { withoutAnimation: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user