本章目的:UI整体框架搭起来 1、安装并引用element-ui需注意,vue-cli@4+的版本,在创建项目时,选择vue2的版本,如果选择vue3的版本就不能这样引入element-ui了npm i element-ui -Smain.js 引入element-uiimport...
本章目的:UI整体框架搭起来
1、安装并引用element-ui
需注意,vue-cli@4+的版本,在创建项目时,选择vue2的版本,如果选择vue3的版本就不能这样引入element-ui了
npm i element-ui -S
main.js 引入element-ui
import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
2、router/index.js 设置路由
菜单先写死,然后根据菜单集合动态配置路由

import Vue from 'vue'
import Router from 'vue-router'
import Home from '../views/Home.vue'
//import User from '../views/User/Users.vue'
//import Role from '../views/User/Roles.vue'
//import Menu from '../views/Permission/Menu.vue'
//import RoleMenu from '../views/Permission/RoleMenu.vue'
//import Action from '../views/Permission/Action.vue'
import Layout from "../views/Layout/Layout";
const _import = require('@/router/_importview.js')//获取组件的方法
Vue.use(Router)
//菜单集合
const routesList = [
{
id: "1",
path: '/',
name: '首页',
iconCls: 'el-icon-s-home',
IsHide: false,
meta: {
title: '首页'
}
},
{
id: "2",
path: '-',
name: '用户角色管理',
iconCls: 'el-icon-user-solid',
IsHide: false,
children: [
{
id: "3",
path: '/User/Users',
name: '用户管理',
IsHide: false,
meta: {
title: '用户管理'
}
},
{
id: "4",
path: '/User/Roles',
name: '角色管理',
IsHide: false,
meta: {
title: '角色管理'
}
}
]
},
{
id: "5",
path: '-',
name: '授权管理',
iconCls: 'el-icon-menu',
IsHide: false,
children: [
{
id: "6",
path: '/Permission/Menu',
name: '菜单管理',
IsHide: false,
meta: {
title: '菜单管理'
}
},
{
id: "7",
path: '/Permission/Action',
name: '接口管理',
IsHide: false,
meta: {
title: '接口管理'
}
},
{
id: "8",
path: '/Permission/RoleMenu',
name: '菜单授权',
IsHide: false,
meta: {
title: '菜单授权'
}
}
]
}
]
const createRouter = () => new Router({
mode: 'history',
routes: [{
id: "1",
path: '/',
name: '首页',
component: Home,
iconCls: 'el-icon-s-home',
IsHide: false,
meta: {
title: '首页'
}
}]
})
const router = createRouter()
/*--------------------------根据菜单集合动态配置路由----------------------*/
export function filterAsyncRouter(asyncRouterMap) {
const accessedRouters = asyncRouterMap.filter(route => {
if (route.path) {
if (route.path === '-') {//一级菜单
route.component = Layout
} else {
try {
route.component = _import(route.path.replace('/:id', ''))
} catch (e) {
try {
route.component = () => import('@/views' + route.path.replace('/:id', '') + '.vue');
} catch (error) {
}
}
}
}
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children)
}
return true
})
return accessedRouters
}
router.$addRoutes = (params) => {
var f = item => {
if (item['children']) {
item['children'] = item['children'].filter(f);
return true;
}else {
return true;
}
}
var params = params.filter(f);
router.addRoutes(params)
}
let getRouter = filterAsyncRouter(routesList); //过滤路由
router.$addRoutes(getRouter) //动态添加路由
/*--------------------------根据菜单集合动态配置路由----------------------*/
window.localStorage.router = (JSON.stringify(routesList));
export default router
View Code
3、模板页 App.vue

<template>
<div id="app">
<transition v-if="!$route.meta.NoNeedHome" name="fade" mode="out-in">
<el-row class="container">
<el-col :span="24" class="header">
<el-col :span="10" class="logo collapsedLogo" :class="collapsed?'logo-collapse-width':'logo-width'">
<div @click="toindex"> {{collapsed?sysNameShort:sysName}}</div>
</el-col>
<el-col :span="10" class="logoban">
<div :class=" collapsed?'tools collapsed':'tools'" @click="collapse">
<i class="fa el-icon-s-operation"></i>
</div>
<el-breadcrumb separator="/" class="breadcrumb-inner collapsedLogo">
<el-breadcrumb-item v-for="item in $route.matched" :key="item.path">
<span style=""> {{ item.name }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</el-col>
<el-col :span="4" class="userinfo">
<el-dropdown trigger="hover">
<span class="el-dropdown-link userinfo-inner">
{{sysUserName}}
<img src="./assets/logo.png" height="128" width="128" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item divided @click.native="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-col>
</el-col>
<el-col :span="24" class="main">
<aside :class="collapsedClass ">
<el-scrollbar style="height:100%;background: #2f3e52;" class="scrollbar-handle">
<el-menu :default-active="$route.path"
class="el-menu-vertical-demo"
unique-opened router :collapse="isCollapse"
background-color="#2f3e52"
style="border-right: none;"
text-color="#fff"
active-text-color="#ffd04b">
<sidebar v-for="(menu,index) in routes" @collaFa="collapseFa" :key="index"
:item="menu" />
</el-menu>
</el-scrollbar>
</aside>
<el-col :span="24" class="content-wrapper"
:class="collapsed?'content-collapsed':'content-expanded'">
<div class="tags" v-if="showTags">
<div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPane" class="tags-view-wrapper">
<router-link v-for="(tag,index) in tagsList"
ref="tag"
:key="tag.path"
:class="{'active': isActive(tag.path)}"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
tag="span"
@click.middle.native="closeTags(index)"
class="tags-view-item">
{{ tag.title }}
<span class="el-icon-close" @click.prevent.stop="closeTags(index)" />
</router-link>
</scroll-pane>
</div>
<!-- 其他操作按钮 -->
<div class="tags-close-box">
<el-dropdown @command="handleTags">
<el-button size="mini">
<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>
</div>
<transition name="fade" mode="out-in">
<div class="content-az router-view-withly">
<!-- 含有母版的路由页面 -->
<router-view></router-view>
</div>
</transition>
</el-col>
</el-col>
</el-row>
</transition>
<transition v-else name="fade" mode="out-in">
<div class="content-az router-view-noly">
<!-- 单独的页面 -->
<router-view></router-view>
</div>
</transition>
<div class="v-modal " @click="collapse" v-show="SidebarVisible" tabindex="0" style="z-index: 2917;"></div>
</div>
</template>
<style lang="css">
@import "./style/home.css";
.el-menu-vertical-demo {
/*width: 230px;*/
}
.el-breadcrumb {
line-height: 60px !important;
}
</style>
<script>
import Sidebar from './components/Sidebar'
import ScrollPane from './components/ScrollPane'
export default {
components: { Sidebar, ScrollPane },
data() {
return {
sysName: '后台管理系统',
sysNameShort: 'ET',
SidebarVisible: false,//左侧是否可见
collapsed: false,//左侧菜单是否折叠
collapsedClass: 'menu-expanded',//折叠样式
ispc: false,
sysUserName: '管理员',
isCollapse: false,
tagsList: [],//标签页
routes: []
}
},
methods: {
//首页
toindex() {
this.$router.replace({
path: "/",
});
},
//折叠导航栏
collapse: function () {
this.collapsed = !this.collapsed;
if (this.ispc) {
if (this.collapsed) {
this.collapsedClass = 'menu-collapsed';
} else {
this.collapsedClass = 'menu-expanded';
}
} else { // mobile
if (this.collapsed) {
this.SidebarVisible = true;
this.collapsedClass = 'menu-collapsed-mobile';
} else {
this.SidebarVisible = false;
this.collapsedClass = 'menu-expanded-mobile';
}
this.collapsedClass += ' mobile-ex ';
}
this.isCollapse = !this.isCollapse;
},
collapseFa: function () {
if (!this.ispc) {
this.collapse();
}
},
//退出登录
logout: function () {
var _this = this;
this.$confirm('确认退出吗?', '提示', {
}).then(() => {
this.tagsList = [];
this.routes = [];
}).catch(() => {
});
},
isActive(path) {
return path === this.$route.fullPath;
},
// 关闭单个标签
closeTags(index) {
const delItem = this.tagsList.splice(index, 1)[0];
const item = this.tagsList[index] ? this.tagsList[index] : this.tagsList[index - 1];
if (item) {
delItem.path === this.$route.fullPath && this.$router.push(item.path);
this.$store.commit("saveTagsData", JSON.stringify(this.tagsList));
} else {
this.$router.push('/');
}
},
// 设置标签
setTags(route) {
if (!route.meta.NoTabPage) {
const isExist = this.tagsList.some(item => {
return item.path === route.fullPath;
})
!isExist && this.tagsList.push({
title: route.meta.title,
path: route.fullPath,
})
}
},
// 关闭全部标签
closeAll() {
this.tagsList = [];
this.$router.push('/');
sessionStorage.removeItem("Tags");
},
// 关闭其他标签
closeOther() {
const curItem = this.tagsList.filter(item => {
return item.path === this.$route.fullPath;
})
this.tagsList = curItem;
sessionStorage.setItem("Tags", JSON.stringify(this.tagsList))
},
// 当关闭所有页面时隐藏
handleTags(command) {
command === 'other' ? this.closeOther() : this.closeAll();
}
},
mounted() {
console.log(this.$route)
var tags = sessionStorage.getItem('Tags') ? JSON.parse(sessionStorage.getItem('Tags')) : [];
if (tags && tags.length > 0) {
this.tagsList = tags;
}
var NavigationBar = JSON.parse(window.localStorage.router ? window.localStorage.router : null);
if (this.routes.length <= 0 && NavigationBar && NavigationBar.length >= 0) {
this.routes = NavigationBar;
}
// 折叠菜单栏
this.collapse();
},
updated() {
var user = JSON.parse(window.localStorage.user ? window.localStorage.user : null);
if (user) {
this.sysUserName = user.uRealName || '未登录';
this.sysUserAvatar = user.avatar || '../assets/user.png';
}
var NavigationBar = JSON.parse(window.localStorage.router ? window.localStorage.router : null);
if (NavigationBar && NavigationBar.length >= 0) {
if (this.routes.length <= 0 || (JSON.stringify(this.routes) != JSON.stringify((NavigationBar)))) {
this.routes = NavigationBar;
}
}
},
computed: {
showTags() {
if (this.tagsList.length > 1) {
this.$store.commit("saveTagsData", JSON.stringify(this.tagsList));
}
return this.tagsList.length > 0;
}
},
watch: {
// 对router进行监听,每当访问router时,对tags的进行修改
$route: async function (newValue, from) {
if (global.IS_IDS4) {
await this.refreshUserInfo();
}
this.setTags(newValue);
const tags = this.$refs.tag
this.$nextTick(() => {
if (tags) {
for (const tag of tags) {
if (tag.to.path === this.$route.path) {
this.$refs.scrollPane.moveToTarget(tag, tags)
break
}
}
}
})
}
},
created() {
this.setTags(this.$route);
this.ispc = window.screen.width > 680;
if (this.ispc) {
this.collapsedClass = 'menu-expanded';
} else {
this.collapsedClass = 'menu-expanded-mobile mobile-ex';
this.collapsed = true;
this.collapse();
}
}
}
</script>
<style lang="css">
@import "./style/home.css";
.el-menu-vertical-demo {
/*width: 230px;*/
}
.el-breadcrumb {
line-height: 60px !important;
}
</style>
<style>
.menu-collapsed .el-icon-arrow-right:before {
display: none;
}
.tags {
position: relative;
overflow: hidden;
border: 1px solid #f0f0f0;
background: #f0f0f0;
}
.tags ul {
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
display: none;
}
.tags-li {
float: left;
margin: 3px 5px 2px 3px;
border-radius: 3px;
font-size: 13px;
overflow: hidden;
height: 23px;
line-height: 23px;
border: 1px solid #e9eaec;
background: #fff;
padding: 3px 5px 4px 12px;
vertical-align: middle;
color: #666;
-webkit-transition: all .3s ease-in;
transition: all .3s ease-in;
}
.tags-li-icon {
cursor: pointer;
}
.tags-li:not(.active):hover {
background: #f8f8f8;
}
.tags-li-title {
float: left;
max-width: 80px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-right: 5px;
color: #666;
text-decoration: none;
}
.tags-li.active {
/*color: #fff;*/
/*border: 1px solid #10B9D3;*/
/*background-color: #10B9D3;*/
}
.tags-li.active .tags-li-title {
/*color: #fff;*/
}
.tags-close-box {
box-sizing: border-box;
text-align: center;
z-index: 10;
float: right;
margin-right: 1px;
line-height: 2;
}
</style>
<style>
/*.logoban{*/
/*width: auto !important;*/
/*}*/
.news-dialog {
background: #fff;
z-index: 3000 !important;
position: fixed;
height: 100vh;
width: 100%;
max-width: 260px;
top: 60px !important;
left: 0 !important;
;
-webkit-box-shadow: 0 0 15px 0 rgba(0, 0, 0, .05);
box-shadow: 0 0 15px 0 rgba(0, 0, 0, .05);
-webkit-transition: all .25s cubic-bezier(.7, .3, .1, 1);
transition: all .25s cubic-bezier(.7, .3, .1, 1);
-webkit-transform: translate(100%);
z-index: 40000;
left: auto !important;
;
right: 0 !important;
;
transform: translate(0);
}
.news-dialog .el-dialog {
margin: auto !important;
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
box-shadow: none;
width: 100%;
}
.news-dialog.show {
transform: translate(0);
}
.tag-new {
width: 100%;
margin: 10px 0;
}
@media screen and (max-width: 680px) {
.collapsedLogo {
display: none;
}
.el-dialog {
width: 90% !important;
}
.content-expanded {
max-width: 100% !important;
max-height: calc(100% - 60px);
}
.mobile-ex {
background: #fff;
z-index: 3000;
position: fixed;
height: 100vh;
width: 100%;
max-width: 260px;
top: 0;
left: 0;
-webkit-box-shadow: 0 0 15px 0 rgba(0, 0, 0, .05);
box-shadow: 0 0 15px 0 rgba(0, 0, 0, .05);
-webkit-transition: all .25s cubic-bezier(.7, .3, .1, 1);
transition: all .25s cubic-bezier(.7, .3, .1, 1);
-webkit-transform: translate(100%);
z-index: 40000;
left: auto;
right: 0;
transform: translate(100%);
}
.mobile-ex.menu-collapsed-mobile {
transform: translate(0);
}
.el-menu--collapse {
width: 100% !important;
}
.el-date-editor.el-input, .el-date-editor.el-input__inner, .el-cascader.el-cascader--medium {
width: 100% !important;
}
.toolbar.roles {
width: 100% !important;
}
.toolbar.perms {
width: 800px !important;
}
.toolbar.perms .box-card {
width: 100% !important;
}
.login-container {
width: 300px !important;
}
.count-test label {
}
.content-wrapper .tags {
margin: 0px;
padding: 0px;
}
.activeuser {
display: none !important;
}
}
</style>
<style>
.tags-view-container {
height: 34px;
width: calc(100% - 60px);
/*background: #fff;*/
/*border-bottom: 1px solid #d8dce5;*/
/*box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);*/
float: left;
}
.tags-view-container .tags-view-wrapper .tags-view-item {
display: inline-block;
position: relative;
cursor: pointer;
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;
}
.tags-view-container .tags-view-wrapper .tags-view-item:first-of-type {
margin-left: 15px;
}
.tags-view-container .tags-view-wrapper .tags-view-item:last-of-type {
margin-right: 15px;
}
.tags-view-container .tags-view-wrapper .tags-view-item.active {
/*background-color: #42b983;*/
/*color: #fff;*/
/*border-color: #42b983;*/
}
.tags-view-container .tags-view-wrapper .tags-view-item.active::before {
content: "";
background: #2d8cf0;
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
position: relative;
margin-right: 2px;
}
.tags-view-container .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, 0.3);
}
.tags-view-container .contextmenu li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
}
.tags-view-container .contextmenu li:hover {
background: #eee;
}
</style>
<style>
.tags-view-wrapper .tags-view-item .el-icon-close {
width: 16px;
height: 16px;
vertical-align: 2px;
border-radius: 50%;
text-align: center;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transform-origin: 100% 50%;
}
.tags-view-wrapper .tags-view-item .el-icon-close:before {
transform: scale(0.6);
display: inline-block;
vertical-align: -3px;
}
.tags-view-wrapper .tags-view-item .el-icon-close:hover {
background-color: #ef2b74;
color: #fff;
}
</style>
View Code
4、菜单组件、标签页组件

<template>
<div>
<!-- if 有子节点,渲染节点递归 -->
<template v-if="item.children">
<!--一级菜单-->
<el-submenu
v-if="!(item.path!=''&&item.path!=' '&&item.path!='-')"
:index="item.id+'index'"
:key="item.path"
>
<template slot="title">
<i
v-if="item.children&&item.children.length>0&&item.iconCls"
class="fa"
:class="item.iconCls"
></i>
<span class="title-name" slot="title">{{item.name}}</span>
</template>
<template v-for="child in item.children">
<!-- 递归嵌套子菜单 -->
<template v-if="!child.IsHide">
<sidebar
v-if="child.children&&child.children.length>0"
:item="child"
:index="child.id"
:key="child.path"
/>
<app-link :to="child.path" v-else :key="child.path" :data-link="child.path">
<el-menu-item :key="child.path"
:index="isExternalLink(child.path)? '':child.path"
@click="cop">
<i class="fa" :class="child.iconCls"></i>
<template slot="title">
<span class="title-name" slot="title">{{child.name}}</span>
</template>
</el-menu-item>
</app-link>
</template>
</template>
</el-submenu>
<template v-else>
<!--一级菜单path不等于空或- -->
<app-link :to="item.path" :key="item.path+'d'" :data-link="item.path">
<el-menu-item
:index="isExternalLink(item.path)? '':item.path"
:key="item.path+'d'">
<i class="fa" :class="item.iconCls"></i>
<template slot="title">
<span class="title-name" slot="title">{{item.name}}33</span>
</template>
</el-menu-item>
</app-link>
</template>
</template>
<!-- else 没有子节点,直接输出:首页 -->
<template v-else>
<app-link :to="item.path" :key="item.path+'d'">
<el-menu-item
:index="isExternalLink(item.path)? '':item.path"
:key="item.path+'d'"
@click="cop"
>
<i class="fa" :class="item.iconCls"></i>
<template slot="title">
<span class="title-name" slot="title">{{item.name}}</span>
</template>
</el-menu-item>
</app-link>
</template>
</div>
</template>
<script>
import AppLink from "./AppLink";
import { isExternal } from "../js/validate";
export default {
name: "Sidebar",
components: { AppLink },
props: {
item: {
type: Object,
required: true
}
},
methods: {
isExternalLink(to) {
return isExternal(to);
},
cop: function() {
// 子组件中触发父组件方法collaFa并传值123
this.$emit("collaFa", "123");
}
}
};
</script>
View Code

<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
}
},
methods: {
handleScroll(e) {
const eventDelta = e.wheelDelta || -e.deltaY * 40
const $scrollWrapper = this.scrollWrapper
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
},
moveToTarget(currentTag,tagList) {
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 >
.scroll-container {
white-space: nowrap;
position: relative !important;
overflow: hidden !important;
width: 100%;
}
.scroll-container .el-scrollbar__bar {
bottom: 0px;
}
.scroll-container .el-scrollbar__wrap {
height: 49px;
}
</style>
View Code

<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { isExternal } from '../js/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',
style:'color:#fff;'
}
}
return {
to: to
}
}
}
}
</script>
View Code
5、运行效果

沃梦达教程
本文标题为:(十).netcore+vue vue-cli@4+element-ui+router+vuex
基础教程推荐
猜你喜欢
- C#集合查询Linq在项目中使用详解 2023-06-09
- C# Winform实现石头剪刀布游戏 2023-01-11
- C#中类与接口的区别讲解 2023-06-04
- C#通过GET/POST方式发送Http请求 2023-04-28
- Unity shader实现多光源漫反射以及阴影 2023-03-04
- 京东联盟C#接口测试示例分享 2022-12-02
- c#读取XML多级子节点 2022-11-05
- c#中利用Tu Share获取股票交易信息 2023-03-03
- C# – NetUseAdd来自Windows Server 2008和IIS7上的NetApi32.dll 2023-09-20
- 使用c#从分隔文本文件中插入SQL Server表中的批量数据 2023-11-24
