您现在的位置是:首页 >技术杂谈 >vue实现用户动态权限登录网站首页技术杂谈

vue实现用户动态权限登录

极端~ 2024-06-17 11:28:33
简介vue实现用户动态权限登录

一、使用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")

二、代码部分

  1. 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>
  1. 路由配置
    ①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
}
  1. 登录(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>
  1. 动态返回路由
    使用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
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。