一、使用vue+elementUI搭登录框架,主要就是1、2、3、4
配置:
①vue.config.js
'use strict'
const path = require('path')
function resolve(dir) {
return path.join(__dirname, dir)
}
// All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = {
publicPath: '/',
outputDir: 'dist',
assetsDir: 'static',
lintOnSave: false, // 是否校验语法
productionSourceMap: false,
devServer: {
port: 8888,
open: true,
},
configureWebpack: {
resolve: {
alias: {
'@': resolve('src')
}
}
}
}
②main.js
import Vue from "vue"
import App from "./App.vue"
import router from "./router"
import store from "./store"
import ElementUI from "element-ui"
import 'element-ui/lib/theme-chalk/index.css'
import "./router/router-config" // 路由守卫,做动态路由的地方
Vue.config.productionTip = false
Vue.use(ElementUI)
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app")
二、代码部分
- layout目录:
- layout/index.vue
<template>
<el-container>
<el-header>
<header-temp></header-temp>
</el-header>
<el-container>
<el-aside width="200px"><sidebar class="sidebar-container"></sidebar></el-aside>
<el-main><app-main></app-main></el-main>
</el-container>
</el-container>
</template>
<script>
import AppMain from './appMain' // 页面布局的右侧区域
import sidebar from './sideBar' // 页面布局的左侧菜单
import headerTemp from "./headerTemp" // 页面布局的header菜单
export default {
name: 'layout',
components: { sidebar, AppMain, headerTemp }
}
</script>
<style>
.el-header{padding: 0!important;margin-left: 180px;}
</style>
①appMain/index.vue
<template>
<section class="app-main">
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
</section>
</template>
<script>
export default { name: 'AppMain' }
</script>
②headerTemp/index.vue
<template>
<div class="header-temp-container">
<div class="userInfo">
<el-image :src="userInfo.avatar" class="eImage"></el-image>
<el-dropdown @command="handleLogout">
<div class="detail user-link">
<span>{{ userInfo.name }}</span>
<span>{{ userInfo.desc }}</span>
<i class="el-icon--right"></i>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="logout">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script>
import { Message } from "element-ui"
export default {
name: "header-temp-container",
data() {
return {
userInfo: JSON.parse(window.localStorage.getItem("userInfo"))
}
},
methods: {
// 退出登录
handleLogout(key) {
if(key == "logout") {
window.localStorage.removeItem("userInfo")
Message({ type: 'success', message: "退出登录", showClose: true, duration: 3000 })
this.$router.replace({ path: "/login" })
location.reload()
}
}
}
}
</script>
<style scoped>
.header-temp-container{border-bottom: 1px solid #ddd; width: 100%;height: 60px;}
.userInfo{display: flex;flex-direction: row;align-items: center;justify-content: flex-end;height: 100%;margin-right: 20px;}
.eImage{width: 40px;height: 40px;border-radius: 50%;margin-right: 10px;}
.detail{display: flex;flex-direction: column;align-items: flex-start;justify-content: space-around;}
</style>
③sideBar/index.vue
<template>
<el-menu
mode="vertical"
unique-opened
:default-active="$route.path"
background-color="#304156"
text-color="#fff"
active-text-color="#409EFF"
>
<sidebar-item :routes="routes"></sidebar-item>
</el-menu>
</template>
<script>
import sidebarItem from "./sidebarItem";
export default {
components: { sidebarItem },
computed: {
routes() {
return this.$router.options.routes;
},
},
};
</script>
<style scoped>
.sidebar-container {
transition: width 0.28s;
width: 180px !important;
height: 100%;
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
}
.sidebar-container a {
display: inline-block;
width: 100%;
}
.sidebar-container .svg-icon {
margin-right: 16px;
}
.sidebar-container .el-menu {
border: none;
width: 100%;
}
</style>
<style>
a{text-decoration: none;}
</style>
④sideBar/sidebarItem.vue
<template>
<div class="menu-wrapper">
<template v-for="item in routes" v-if="!item.hidden && item.children">
<router-link
v-if="
item.children.length === 1 &&
!item.children[0].children &&
!item.alwaysShow
"
:to="item.children[0].path"
:key="item.children[0].name"
>
<el-menu-item
:index="item.children[0].path"
:class="{ 'submenu-title-noDropdown': !isNest }"
>
<span v-if="item.children[0].meta && item.children[0].meta.title">{{
item.children[0].meta.title
}}</span>
</el-menu-item>
</router-link>
<el-submenu v-else :index="item.name || item.path" :key="item.name">
<template slot="title">
<span v-if="item.meta && item.meta.title">{{ item.meta.title }}</span>
</template>
<template v-for="child in item.children" v-if="!child.hidden">
<sidebar-item
:is-nest="true"
class="nest-menu"
v-if="child.children && child.children.length > 0"
:routes="[child]"
:key="child.path"
>
</sidebar-item>
<router-link v-else :to="child.path" :key="child.name">
<el-menu-item :index="child.path">
<span v-if="child.meta && child.meta.title">{{
child.meta.title
}}</span>
</el-menu-item>
</router-link>
</template>
</el-submenu>
</template>
</div>
</template>
<script>
export default {
name: "sidebarItem",
props: {
routes: { type: Array },
isNest: {
type: Boolean,
default: false,
},
},
};
</script>
<style scoped>
.nest-menu .el-submenu > .el-submenu__title,
.el-submenu .el-menu-item {
min-width: 180px !important;
background-color: #1f2d3d !important;
}
.nest-menu .el-submenu > .el-submenu__title,
.el-submenu .el-menu-item :hover {
background-color: #001528 !important;
}
.el-menu--collapse .el-menu .el-submenu {
min-width: 180px !important;
}
</style>
- 路由配置
①router/index.js
import Vue from "vue"
import VueRouter from "vue-router"
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch(err => err)
}
Vue.use(VueRouter)
const routes = [
{ name: "login",
path: "/login",
meta: { title: "login" },
component: () => import("../views/login/index"),
hidden: true }
]
const router = new VueRouter({ routes })
export default router
②router/router-config.js
import router from "./index"
import Layout from "../layout/index"
import NProgress from 'nprogress' // progress bar
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const filterRoutes = ["/login"] // 需要过滤掉的路由
router.beforeEach((to, from, next) => {
// start progress bar
NProgress.start()
// 获取路由 meta 中的title,并设置给页面标题
document.title = "动态路由(" + to.meta.title + ")"
// 判断路由指向是否在需要过滤的路由地址数组里
// 如果在,则直接跳进页面,无需判断
if(filterRoutes.indexOf(to.path) !== -1) {
next()
return false
}
if(router.options.routes.length == 1) {
// 获取token和原始路由数组
const userInfo = JSON.parse(window.localStorage.getItem('userInfo')) ?? ""
// 当token和原始路由都存在的时候
if(userInfo.token && userInfo.routes) {
onFilterRoutes(to, next, userInfo.routes) // 执行路由过滤和跳转
}
else {
next({ path: "/login", replace: true })
}
} else next()
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
// 路由拼接
function loadView(view) {
return () => import(`@/views/${ view }`)
}
// 路由过滤和跳转
async function onFilterRoutes(to, next, e) {
const routes = await filterASyncRoutes(e) // 路由过滤
routes.sort((a, b) => a['id'] - b['id'])
routes.forEach(item => {
router.options.routes.push(item)
router.addRoute(item)
})
next({ ...to, replace: true })
}
// 路由过滤 遍历路由 转换为组件对象和路径
function filterASyncRoutes(data) {
const routes = data.filter(item => {
if(item["component"] === "Layout") item.component = Layout
else item["component"] = loadView(item["component"])
// 路由递归,转换组件对象和路径
if(item["children"] && item["children"].length > 0)
item["children"] = filterASyncRoutes(item.children)
return true
})
return routes
}
- 登录(views/login/index.vue)
<template>
<div class="login-wrapper">
<div class="modal">
<el-form :model="user" status-icon :rules="rules" ref="userForm">
<div class="title">动态路由</div>
<el-form-item prop="username">
<el-input type="text" prefix-icon="el-icon-user" placeholder="请输入用户名" v-model="user.username" />
</el-form-item>
<el-form-item prop="password">
<el-input type="password" prefix-icon="el-icon-view" placeholder="请输入密码" v-model="user.password" />
</el-form-item>
<el-form-item>
<el-button type="primary" class="btn-login" @click="login">登录</el-button>
</el-form-item>
<div class="toast">
<span>管理员账号:admin </span>
<span>密码:654321</span>
</div>
<div class="toast">
<span>普通人员账号:people</span>
<span>密码:123456</span>
</div>
</el-form>
</div>
</div>
</template>
<script>
import dynamicUser from "../../mock"
import { Message } from "element-ui"
export default {
name: 'login',
data() {
return {
user: {
username: "",
password: ""
},
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' }
]
}
}
},
methods: {
login() {
this.$refs.userForm.validate(( valid ) => {
if(valid) {
let flag = !1
window.localStorage.removeItem("userInfo")
dynamicUser.forEach(item => {
if(item["username"] == this.user['username'] && item["password"] == this.user['password']) {
flag = !0
Message({ type: 'success', message: "登录成功", showClose: true, duration: 3000 })
window.localStorage.setItem("userInfo", JSON.stringify(item))
this.$router.replace({ path: "/" })
}
})
if(!flag) Message({ type: 'warning', message: "账号密码错误,请重试!", showClose: true, duration: 3000 })
} else return false
})
}
}
}
</script>
<style scoped>
.login-wrapper {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: #fff;
width: 100vw;
height: 100vh;
}
.modal {
width: 360px;
height: 380px;
box-shadow: 0 0 10px 5px #ddd;
padding: 50px;
border-radius: 5px;
}
.title {
width: 100%;
text-align: center;
line-height: 1.5;
font-size: 50px;
margin-bottom: 30px;
}
.btn-login {
width: 100%;
}
.toast{
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 50px;
}
</style>
- 动态返回路由
使用mock.js造了一条路由,后端返回格式类似于下列这种样式:
const dynamicUser = [
{
name: "管理员",
avatar: "https://sf3-ttcdn-tos.pstatp.com/img/user-avatar/ccb565eca95535ab2caac9f6129b8b7a~300x300.image",
desc: "管理员 - admin",
username: "admin",
password: "654321",
token: "rtVrM4PhiFK8PNopqWuSjsc1n02oKc3f",
routes: [
{ id: 1, name: "/", path: "/", component: "Layout", redirect: "/index", hidden: false, children: [
{ name: "index", path: "/index", meta: { title: "index" }, component: "index/index" },
]},
{ id: 2, name: "/form", path: "/form", component: "Layout", redirect: "/form/index", hidden: false, children: [
{ name: "/form/index", path: "/form/index", meta: { title: "form" }, component: "form/index" }
]},
{ id: 3, name: "/example", path: "/example", component: "Layout", redirect: "/example/tree", meta: { title: "example" }, hidden: false, children: [
{ name: "/tree", path: "/example/tree", meta: { title: "tree" }, component: "tree/index" },
{ name: "/copy", path: "/example/copy", meta: { title: "copy" }, component: "tree/copy" }
] },
{ id: 4, name: "/table", path: "/table", component: "Layout", redirect: "/table/index", hidden: false, children: [
{ name: "/table/index", path: "/table/index", meta: { title: "table" }, component: "table/index" }
] },
{ id: 5, name: "/admin", path: "/admin", component: "Layout", redirect: "/admin/index", hidden: false, children: [
{ name: "/admin/index", path: "/admin/index", meta: { title: "admin" }, component: "admin/index" }
] },
{ id: 6, name: "/people", path: "/people", component: "Layout", redirect: "/people/index", hidden: false, children: [
{ name: "/people/index", path: "/people/index", meta: { title: "people" }, component: "people/index" }
] }
]
},
{
name: "普通用户",
avatar: "https://img0.baidu.com/it/u=2097980764,1024880469&fm=253&fmt=auto&app=138&f=JPEG?w=300&h=300",
desc: "普通用户 - people",
username: "people",
password: "123456",
token: "4es8eyDwznXrCX3b3439EmTFnIkrBYWh",
routes: [
{ id: 1, name: "/", path: "/", component: "Layout", redirect: "/index", hidden: false, children: [
{ name: "index", path: "/index", meta: { title: "index" }, component: "index/index" },
]},
{ id: 2, name: "/form", path: "/form", component: "Layout", redirect: "/form/index", hidden: false, children: [
{ name: "/form/index", path: "/form/index", meta: { title: "form" }, component: "form/index" }
]},
{ id: 6, name: "/people", path: "/people", component: "Layout", redirect: "/people/index", hidden: false, children: [
{ name: "/people/index", path: "/people/index", meta: { title: "people" }, component: "people/index" }
] }
]
}
]
export default dynamicUser