项目初始

This commit is contained in:
liupopo
2023-11-21 10:59:40 +08:00
parent 03bac664dc
commit e5b9c0d7ad
351 changed files with 22021 additions and 113 deletions

View File

@@ -0,0 +1,48 @@
<template>
<section class="app-main" style="height: calc(100vh - 140px);">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
</transition>
</section>
</template>
<script>
export default {
name: 'AppMain',
computed: {
cachedViews() {
return this.$store.state.tagsView.cachedViews
},
key() {
return this.$route.path
}
}
}
</script>
<style scoped>
.app-main {
/*50 = navbar */
height: calc(100vh - 140px);
position: relative;
background-color: #FFFFFF;
/* margin-left:10px; */
/*padding: 0 20px;*/
overflow-x: hidden;
overflow-y: hidden;
}
.fixed-header+.app-main {
padding-top: 50px;
}
</style>
<!-- <style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 15px;
}
}
</style> -->

View File

@@ -0,0 +1,168 @@
<template>
<div>
<div class="navbar">
<p class="breadcrumb-container"><img src="@/assets/loginImg/logo.png" style="width: 45px;height: 45px;margin-right: 10px;"/>山海光伏监管平台</p>
<!-- <h3 class="breadcrumb-container">汇融银行供应链贷后监管平台</h3> -->
<div class="right-menu">
<!-- <p>{{Orgname}} {{departmentName}} 欢迎, {{name}}</p>-->
<p>欢迎您{{name}}</p>
<p class="dy" @click="logout()"><img src="@/assets/images/dy.png"></p>
</div>
</div>
<el-footer class="footer" height="40px">Copyright © {{ year }} 山海光伏监管平台 All Rights Reserved</el-footer>
</div>
</template>
<script>
export default {
data() {
return {
name: '',
year:'',
}
},
mounted() {
this.name = window.sessionStorage.getItem('userName')
var nowDate = new Date()
this.year = nowDate.getFullYear()
},
methods: {
logout() {
this.$confirm('确定要退出吗, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// this.$store.commit('user/SET_UESRINFO', '')
this.$router.push({
path: '/login'
})
})
},
}
}
</script>
<style lang="scss" scoped>
@import "~@/styles/variables.scss";
.navbar {
height: 60px;
overflow: hidden;
position: relative;
background: $header-bg;
padding: 0 30px;
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background .3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, .025)
}
}
.breadcrumb-container {
display: flex;
align-items: center;
color: #fff;
font-size: 26px;
line-height: 60px;
margin: 0;
float: left;
}
.right-menu {
float: right;
height: 100%;
line-height: 60px;
color: #fff;
&:focus {
outline: none;
}
p {
display: inline-block;
margin: 0;
padding: 0;
padding: 0 15px;
}
img {
vertical-align: middle;
margin: 0 10px;
cursor: pointer;
}
.backToHome {
display: inline-block;
vertical-align: middle;
padding: 0 15px;
cursor: pointer;
}
.backToHome:hover {
background: #087dba;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background .3s;
&:hover {
background: rgba(0, 0, 0, .025)
}
}
}
.avatar-container {
margin-right: 30px;
.avatar-wrapper {
margin-top: 5px;
position: relative;
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
}
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
line-height: 40px;
background: #E9F1F7;
text-align: center;
font-size: 14px;
color: #666;
}
</style>

View 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)
}
}
}
}
}

View 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('el-icon')) {
vnodes.push(<i class={[icon, 'sub-el-icon']} />)
} else {
vnodes.push(<svg-icon icon-class={icon}/>)
}
}
if (title) {
vnodes.push(<span slot='title'>{(title)}</span>)
}
return vnodes
}
}
</script>
<style scoped>
.sub-el-icon {
color: currentColor;
width: 1em;
height: 1em;
}
</style>

View File

@@ -0,0 +1,43 @@
<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { isExternal } from '@/utils/validate'
export default {
props: {
to: {
type: String,
required: true
}
},
computed: {
isExternal() {
return isExternal(this.to)
},
type() {
if (this.isExternal) {
return 'a'
}
return 'router-link'
}
},
methods: {
linkProps(to) {
if (this.isExternal) {
return {
href: to,
target: '_blank',
rel: 'noopener'
}
}
return {
to: to
}
}
}
}
</script>

View File

@@ -0,0 +1,82 @@
<template>
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo">
<h1 v-else class="sidebar-title">{{ title }} </h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo">
<h1 class="sidebar-title">{{ title }} </h1>
</router-link>
</transition>
</div>
</template>
<script>
export default {
name: 'SidebarLogo',
props: {
collapse: {
type: Boolean,
required: true
}
},
data() {
return {
title: 'Vue Admin Template',
logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
}
}
}
</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: 50px;
line-height: 50px;
background: #2b2f3a;
text-align: center;
overflow: hidden;
& .sidebar-logo-link {
height: 100%;
width: 100%;
& .sidebar-logo {
width: 32px;
height: 32px;
vertical-align: middle;
margin-right: 12px;
}
& .sidebar-title {
display: inline-block;
margin: 0;
color: #fff;
font-weight: 600;
line-height: 50px;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
}
}
&.collapse {
.sidebar-logo {
margin-right: 0px;
}
}
}
</style>

View File

@@ -0,0 +1,117 @@
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children,item)">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
</el-menu-item>
</app-link>
</template>
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
<template slot="title">
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-submenu>
</div>
</template>
<script>
import path from 'path'
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: ''
}
},
data() {
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
// TODO: refactor with render function
this.onlyOneChild = null
return {
// onlyOneChild:null
}
},
methods: {
hasOneShowingChild(children = [], parent) {
// debugger
// var tempItem=null
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
this.onlyOneChild = item
return true
}
})
// When there is only one child
// if (showingChildren.length === 1) {
// this.onlyOneChild=item()
// return true
// }
// 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)
}
}
}
</script>
<style scoped>
/deep/ .el-submenu__title {
height: 40px !important;
line-height: 40px !important;
font-size: 15px!important;
}
/deep/ .el-submenu .el-menu-item {
height: 40px !important;
line-height: 40px !important;
}
/deep/ .el-menu-item {
height: 40px !important;
line-height: 40px !important;
font-size: 15px!important;
}
</style>

View File

@@ -0,0 +1,282 @@
<template>
<div :class="{ 'has-logo': showLogo }">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="$route.path"
: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 routes"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
</el-scrollbar>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import Logo from "./Logo";
import SidebarItem from "./SidebarItem";
import variables from "@/styles/variables.scss";
import { getrolemenus, loginDetails } from "@/api/system/Role/role.js";
import { getStorage } from "@/utils/auth";
export default {
components: {
SidebarItem,
Logo,
},
data() {
return {
YongHuid: [],
// routes: [],
params: {
sourceSid: "9d048c07-7677-4774-9177-aa02b049ff06",
userSid: "",
},
routes: [
{
alwaysShow: true,
component: "supervise",
meta: {
icon: "el-icon-menu",
title: "进销存管理",
},
name: "supervise",
path: "/supervise",
children: [
{
alwaysShow: true,
component: "recordOilTank",
meta: {
icon: "el-icon-help",
title: "出库申请",
},
name: "oilTypeInBound",
path: "/oilTypeInBound",
children: [
{
alwaysShow: true,
component: "oilTypeInBound",
meta: {
icon: "el-icon-help",
title: "出库申请",
},
name: "/supervise/oilTypeInBound",
path: "/supervise/oilTypeInBound",
},
{
alwaysShow: true,
component: "oilTypeOutBound",
meta: {
icon: "el-icon-help",
title: "出库记录列表",
},
name: "/supervise/oilTypeOutBound",
path: "/supervise/oilTypeOutBound",
}
],
},
{
alwaysShow: true,
component: "index",
meta: {
icon: "el-icon-help",
title: "入库申请",
},
name: "recordOilTank",
path: "/recordOilTank",
children: [
{
alwaysShow: true,
component: "recordOilTank",
meta: {
icon: "el-icon-help",
title: "入库申请",
},
name: "/supervise/recordOilTank",
path: "/supervise/recordOilTank",
},
{
alwaysShow: true,
component: "rukujilu",
meta: {
icon: "el-icon-help",
title: "入库记录列表",
},
name: "/supervise/rukujilu",
path: "/supervise/rukujilu",
}
],
},
{
alwaysShow: true,
component: "churuku",
meta: {
icon: "el-icon-help",
title: "出入库记录",
},
name: "/supervise/churuku",
path: "/supervise/churuku",
},
{
alwaysShow: true,
component: "inventory",
meta: {
icon: "el-icon-help",
title: "库存管理",
},
name: "/supervise/inventory",
path: "/supervise/inventory",
},
{
alwaysShow: true,
component: "churukuHT",
meta: {
icon: "el-icon-help",
title: "出入库合同管理",
},
name: "/supervise/churukuHT",
path: "/supervise/churukuHT",
}
],
},
{
alwaysShow: true,
component: "MaterialBrand",
meta: {
icon: "el-icon-menu",
title: "物料品牌",
},
name: "/MaterialBrand/index",
path: "/MaterialBrand/index"
},
{
alwaysShow: true,
component: "materialManagement",
meta: {
icon: "el-icon-menu",
title: "物料管理",
},
name: "/materialManagement/index",
path: "/materialManagement/index"
},
{
alwaysShow: true,
component: "storeManagement",
meta: {
icon: "el-icon-menu",
title: "仓库管理",
},
name: "/storeManagement/index",
path: "/storeManagement/index"
},
{
alwaysShow: true,
component: "supplierManagement",
meta: {
icon: "el-icon-menu",
title: "供应商管理",
},
name: "/supplierManagement/index",
path: "/supplierManagement/index"
},
],
};
},
computed: {
...mapGetters(["sidebar"]),
// routes() {
// f4d2e507-c4ed-451c-b364-04c08f962045
// console.log('78979789', this.$router.options.routes)
// return this.$router.options.routes
// },
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 "/index";
},
showLogo() {
return this.$store.state.settings.sidebarLogo;
},
variables() {
return variables;
},
isCollapse() {
return !this.sidebar.opened;
},
},
created() {
this.postHuoquyonghu();
},
methods: {
// 获取用户信息
postHuoquyonghu() {
// var token = getStorage()
// loginDetails(token).then((response) => {
// console.log('resss', response)
// if (response.code === '200') {
// this.YongHuid = response.data
// this.params.userSid = this.YongHuid.sid
// getrolemenus(this.params).then((res) => {
// const userRoles = this.resRouter(res.data)
this.routes.push({
path: "*",
redirect: "/404",
hidden: true,
});
console.log("左侧菜单", this.routes);
return this.routes;
// })
// }
// })
},
resRouter(menus) {
// 递归,将后台传来数组
for (var i = 0; i < menus.length; i++) {
if (menus[i].children && menus[i].children.length != 0) {
this.resRouter(menus[i].children);
}
if (menus[i].children.length == 0) {
delete menus[i].children;
delete menus[i].redirect;
}
if (menus[i].component == "") {
console.log("55555", menus[i]);
menus[i] = {
path: menus[i].path,
component: "",
redirect: menus[i].path,
children: [menus[i]],
};
} else {
// menus[i] = {
// path: menus[i].path,
// component: '',
// redirect: menus[i].path,
// children: [menus[i]],
// }
}
}
this.routes = menus;
console.log("左侧菜单", this.routes);
return menus;
},
},
};
</script>

View File

@@ -0,0 +1,87 @@
<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: 59px;
}
}
}
</style>

View File

@@ -0,0 +1,333 @@
<template>
<div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
<router-link
v-for="(tag,index) 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)"
>
{{ tag.title }}
<span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
</router-link>
</scroll-pane>
<!--<div class="tags-close-box">
<el-dropdown @command="handleTags">
<el-button size="mini" type="primary"> 标签选项<i class="el-icon-arrow-down el-icon--right"></i> </el-button>
<el-dropdown-menu size="small" slot="dropdown">
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>-->
<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 ScrollPane from './ScrollPane'
import path from 'path'
export default {
components: { ScrollPane },
data() {
return {
visible: false,
top: 0,
left: 0,
selectedTag: {},
affixTags: []
}
},
computed: {
visitedViews() {
return this.$store.state.tagsView.visitedViews
},
routes() {
return this.$store.state.permission.routes
}
},
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: {
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(tag) {
this.$store.dispatch('tagsView/delView', tag).then(({ visitedViews }) => {
if (this.isActive(tag)) {
this.toLastView(visitedViews, tag)
}
})
},
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('/')
}
}
},
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 - 32
this.visible = true
this.selectedTag = tag
},
closeMenu() {
this.visible = false
},
handleScroll() {
this.closeMenu()
},
handleTags(command) {
command === 'other' ? this.closeOther() : this.closeAll()
},
// 关闭全部标签
closeAll() {
// this.visitedViews = [];
this.visitedViews.splice(0, this.visitedViews.length)
this.$router.push('/index')
},
// 关闭其他标签
closeOther() {
return this.visitedViews.filter((item) => {
return item.path === this.$route.fullPath
})
// this.visitedViews = curItem;
}
}
}
</script>
<style lang="scss" scoped>
@import "~@/styles/variables.scss";
.tags-view-container {
position:relative;
height: 40px;
width: 100%;
// background: $menuHover;
background: #FFFFFF;
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-wrapper {
height: 40px;
.tags-view-item {
display: inline-block;
position: relative;
cursor: pointer;
height: 30px;
line-height: 30px;
border-radius: 10px 10px 0 0;
color: #495060;
border: 1px solid #d8dce5;
background: #FFFFFF;
padding: 0 10px;
font-size: 14px;
margin-left: 5px;
margin-top: 9px;
&:first-of-type {
margin-left: 15px;
}
&:last-of-type {
margin-right: 15px;
}
&.active {
background-color: $menuHover;
color: #FFFFFF;
&::before {
content: '';
background: #FFFFFF;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 2px;
}
}
}
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
}
</style>
<style lang="scss">
//reset element css of el-icon-close
.tags-view-wrapper {
.tags-view-item {
.el-icon-close {
width: 16px;
height: 16px;
vertical-align: 1px;
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: -1px;
}
&:hover {
background-color: #b4bccc;
color: #fff;
}
}
}
}
.tags-close-box {
position: absolute;
right: 0;
top: 0;
box-sizing: border-box;
padding-top: 1px;
text-align: center;
width: 110px;
height: 30px;
background: #fff;
box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
z-index: 10;
}
</style>

View File

@@ -0,0 +1,4 @@
export { default as Navbar } from './Navbar'
export { default as Sidebar } from './Sidebar'
export { default as AppMain } from './AppMain'
export { default as TagsView } from './TagsView/index.vue'

114
src/layout/index.vue Normal file
View File

@@ -0,0 +1,114 @@
<template>
<div :class="classObj" class="app-wrapper">
<div class="fixed-header">
<navbar />
</div>
<div
v-if="device === 'mobile' && sidebar.opened"
class="drawer-bg"
@click="handleClickOutside"
/>
<div class="main-container">
<div class="home-box">
<a href="javascript:window.opener=null;window.open('','_self');window.close();" class="text-center">平台首页</a>
<a href="javascript:void(0);" class="text-center">光伏</a>
</div>
<sidebar class="sidebar-container" />
<!--菜单-->
<div class="TagsView">
<tags-view />
<!--横向标签-->
</div>
<app-main />
<!--<el-footer height="40px">Copyright © 2021 技术支持集团 All Rights Reserved</el-footer>-->
</div>
</div>
</template>
<script>
import { AppMain, Navbar, Sidebar, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
export default {
name: 'Layout',
components: {
AppMain,
Navbar,
Sidebar,
TagsView
},
mixins: [ResizeMixin],
computed: {
...mapState({
sidebar: (state) => state.app.sidebar,
device: (state) => state.app.device
}),
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile'
}
}
},
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%;
&.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: 0;
right: 0;
z-index: 9;
width: 100%;
transition: width 0.28s;
}
// .hideSidebar .fixed-header {
// width: calc(100% - 54px)
// }
.mobile .fixed-header {
width: 100%;
}
.el-footer {
line-height: 40px;
text-align: center;
background-color: #f7f9fc;
}
</style>

View File

@@ -0,0 +1,45 @@
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 })
}
}
}
}
}