vue实现用户动态权限登录

news2024/10/7 12:28:06

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/554219.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

想自学写个操作系统,有哪些推荐看的书籍?

前言 哈喽&#xff0c;我是子牙&#xff0c;一个很卷的硬核男人。喜欢研究底层&#xff0c;聚焦做那些大家想学没地方学的课程&#xff1a;手写操作系统、手写虚拟机、手写编程语言… 今天我们将站在一个自学者的角度来聊聊如何实现自己的操作系统。并为大家推荐几本能够帮助你…

Ubuntu20.04配置静态IP地址,开启远程连接

本文操作演示为windows系统使用虚拟机安装的ubuntu系统进行&#xff1a;操作系统为ubuntu20.04&#xff0c;VMware15.5.0 build-14665864&#xff0c;内容分为两部分&#xff0c;第一部分为配置ubuntu系统的静态ip地址&#xff0c;第二部分内容为修改配置开启远程连接功能 一、…

第16章_变量、流程控制与游标

第16章_变量、流程控制与游标 1. 变量 在MySQL数据库的存储过程和函数中&#xff0c;可以使用变量来存储查询或计算的中间结果数据&#xff0c;或者输出最终的结果数据。 在 MySQL 数据库中&#xff0c;变量分为系统变量以及用户自定义变量。 1.1 系统变量 1.1.1 系统变量分…

Ansys Lumerical | 对铁电波导调制器进行仿真应用

说明 在本例中&#xff0c;我们仿真了使用BaTiO2的铁电波导调制器&#xff0c;BaTiO2是一种折射率因外加电场而发生变化的材料。该器件的结构基于文献[1]。我们模拟并分析了给定工作频率下波导调制器的有效折射率与电压的关系。 背景 铁电波导由硅层和玻璃衬底上的BiTiO3&#…

《实战大数据》书评

前言 首先感谢 CSDN 社区图书馆 举办的“图书活动第四期”&#xff0c;让我获得了“《实战大数据——分布式大数据分析处理系统开发与应用》”这本书。收到此书之后&#xff0c;对里面的内容非常感兴趣&#xff0c;同时也充满的未知的期待。 当今时代&#xff0c;物联网、大数据…

第四章 数学知识

第四章 数学知识 初等数论二&#xff1a;https://www.acwing.com/blog/content/26394/ 质数 试除法判定质数 bool is_prime(int x) {if (x < 2) return false;for (int i 2; i < x / i; i )if (x % i 0)return false;return true; }试除法分解质因数 void divide…

第四十八天学习记录:工作相关:Qt resizeEvent 的诡异问题

今天&#xff0c;在做一个新项目时&#xff0c;发现一个诡异的问题。 在软件初次打开的时候&#xff0c;会调用一次resizeEvent(QResizeEvent *sizechangeevent)函数来对主界面控件大小以及位置进行一次调整。 但由于窗口在设计的时候用的一个大小&#xff0c;而在打开软件后…

基于全志D1-H的Tina Linux SPI主从通信验证实录

本文转载自&#xff1a;https://bbs.aw-ol.com/topic/3031 作者 whycan矿工-小叶 背景 主控: D1H板卡: 两块哪吒开发板(以下简称为主机, 从机)操作系统: Tina Linux 2.0 问题 验证D1H芯片SPI主从机通信. 硬件接线 主机SPI从机SPI19SPI1_MOSISPI1_MOSI1921SPI1_MISOSPI1_M…

Git切换用户;Git提交之后是别人的用户怎么办?

Git切换用户 前言解决方法1.删除计算机凭证2.Idea控制台切换 前言 前几天在入职之后&#xff0c;公司的电脑上仍然储存了之前用户的信息&#xff0c;比如git。 我在创建分支、提交代码的时候会遇到这样的问题&#xff1a; 登录的是我自己的账号&#xff0c;但是在git动态里缺…

借助TeeChart图表控件,创建本地静态、实时浏览器图表

Steema是全球领先的图表类控件公司&#xff0c;总部设在西班牙的巴塞罗那附近&#xff0c;Steema公司的VCL图表报表控件在全球拥有极高知名度。TeeChart可以在微软的Visual Studio、Office和.NET以及Java和PHP开发平台中使用&#xff0c;也可以作为本地Javascript-HTML5使用。 …

Python新手怎么兼职,用Python在家兼职赚钱的4个方法

随着人工智能技术的发展&#xff0c;各行各业都在发生着变化&#xff0c;每天AI&#xff08;人工智能&#xff09;新技术都在冲击着各行各业&#xff0c;比如WPS的智能设计、阿里的鲁班等等&#xff0c;总有一种干死干活不如早点掌握新技能的感觉&#xff0c;避免心中的小慌张。…

DAY 65 mysql的高可用之MHA集群

MHA概述 什么是 MHA MHA&#xff08;MasterHigh Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。 MHA 的出现就是解决MySQL 单点故障的问题。 MySQL故障切换过程中&#xff0c;MHA能做到0-30秒内自动完成故障切换操作。 MHA能在故障切换…

基于原子化思想的 Vue3 组件库 UnoCSS UI

UnoCSS UI 项目地址前言Monorepo 项目架构UnoCSS UI 的模块设计PNPM Monorepo 常用操作: --filter, -wMonorepo 中的依赖管理项目整体结构 基于原子化 CSS 的组件封装方式原子化 CSS 基础原子化 VS 内联样式原子化 VS class 原子化对组件封装的影响unocss-ui/components 项目结…

HashTable 在蚂蚁转化归因中的极致运用

作者&#xff1a;开七 蚂蚁集团数据技术专家 本文围绕 hash cluster 表运用及 Shuffle 过程原理进行讨论&#xff0c;欢迎各位开发者加入大数据计算 MaxCompute 社区&#xff1a;https://developer.aliyun.com/group/maxcompute 概述 蚂蚁的转化归因在初期运行两个多小时的情况…

Facebook拆分的深度思考:社交媒体真的是必需品吗?

在当今数字化时代&#xff0c;社交媒体已经成为我们日常生活中不可或缺的一部分。而Facebook作为其中的巨头之一&#xff0c;不可否认地对人们的社交行为和信息传播产生了巨大的影响。 然而&#xff0c;随着越来越多的争议和讨论浮出水面&#xff0c;我们有必要进行深入思考&a…

文档处理新探究成果——前沿技术CCIG文档图像智能分析论坛分享

目录 前言 一、文档分析与识别最新研究 二、视觉-语言预训练模型及迁移学习 三、篡改文本图像的生成与检测技术 四、智能文档处理技术在工业界的应用与挑战 总结 前言 图文智能处理前沿技术一直是我所关注的技术&#xff0c;尤其在现在集成多态大模型的基础之上&#xff0…

关于PCBA元器件布局的重要性

SMT贴片加工逐步往高密度、细间距的设计发展&#xff0c;元器件的最小间距设计&#xff0c;需考虑SMT厂家的经验和工艺完善程度。元器件最小间距的设计&#xff0c;除了保证SMT焊盘间安全距离外&#xff0c;还应考虑元器件的可维护性。 器件布局时保证安全间距 1、安全距离跟…

设计模式-简单例子理解适配器模式、装饰器模式

文章目录 一、适配器模式1. 要点2. Demo 二、装饰器模式1. 要点2. Demo 三、区别 本文参考&#xff1a; 基本原理&#xff1a;装饰器模式 | 菜鸟教程 (runoob.com) 基本原理&#xff1a;适配器模式 | 菜鸟教程 (runoob.com) 优缺点和区别&#xff0c;装饰模式&#xff1a;适配器…

微服务流量控制组件Sentinel

1 简介 Sentinel是阿里开源的项目&#xff0c;是一款面向分布式服务架构的轻量级流量控制组件&#xff0c;主要以流量为切入点&#xff0c;从流量控制、熔断降级、系统自适应保护等多个维度来保障服务的稳定性。 核心思想是&#xff1a;根据对应资源配置的规则来为资源执行相…

西米支付:“中止”支付牌照,汇卡支付机构“失联”

近日&#xff0c;又一家支付公司因“失联”被列入了经营异常名录。 工商信息显示&#xff0c;目前被“中止”中的持牌支付机构广东汇卡商务服务有限公司&#xff08;简称“汇卡支付”&#xff09;因“通过登记的住所或者经营场所无法联系” 被广州市市场监督管理局列入经营异常…