1.组件简介
该版本开发速度较快,项目业务内容固定的场景
- 用户的角色固定
- 角色对应的权限固定
2. 设计思路
- 确定所有的角色:管理员,财务,审核员
- 确定每个角色具备的权限
2.1 前端
- 登录,选择角色登录,vuex中保存用户的角色和token
- 根据登录的角色,动态加载菜单
- 在路由中定义is_menu和role字段,来判断该路由是否为菜单和能显示的角色
-
{ path: '/front', name: 'Front', component: () => import('../views/FrontView'), meta:{ is_menu:true, roles:["manager", "admin"] } },
- 用户是否访问某一个页面
- 通过路由的导航守卫来实现
- 基于vuex中的角色。判断该角色是否能访问对应的路由
- 页面中的局部功能,如增删改查
- 按钮的展示:根据vuex中的角色判断是否展示v-if
<el-button v-if="xxx">新建</el-button>
- 功能: 角色只有有对应的权限,才允许发送ajax请求,我们也可以进行角色权限的判定
if(has_permission("admin", "manager")){ }else{ }
- 按钮的展示:根据vuex中的角色判断是否展示v-if
2.2 后端
- 用户登录,生成token返回
- 再次请求,根据认证组件,判定token,是否登陆成功
- 权限的校验,根据当前登录的角色,和token中的权限进行判断
- 我们可以在每一个视图函数中判断权限
- 也可以使用drf中的权限组件进行判断 + 配置文件中的权限配置
- api的实现
3. 项目的实现
3.1 前端
页面展示效果
部分后端数据通过模拟实现
- 登录页面的实现
<template> <div class="my_div"> <el-form label-width="100px" :model="state.form" style="max-width: 460px" > <el-form-item label="角色"> <el-select v-model="state.form.role"> <el-option :key="item.title" :value="item.value" :label="item.title" v-for="item in state.options"/> </el-select> </el-form-item> <el-form-item label="用户名"> <el-input v-model="state.form.username"/> </el-form-item> <el-form-item label="密码"> <el-input v-model="state.form.password" type="password"/> </el-form-item> <el-form-item> <el-button type="primary" @click="onSubmit" style="width: 200px;">登录</el-button> </el-form-item> </el-form> </div> </template> <script setup> import {reactive} from "vue"; import {useStore} from "vuex"; import {useRouter} from "vue-router"; const router = useRouter() const store = useStore() const state = reactive({ form: { role: "管理员", username: "cisco", password: "123" }, options: [ {title: "管理员", value: "manager"}, {title: "财务", value: "caiwu"}, {title: "运维", value: "yunwei"}, ] }) function onSubmit() { // 1. 向后端发送登录请求 // 2. 接收后端响应数据 const context = { role: state.form.role, token: "9a81a00f-fe88-4c4b-ad2d-68cd17dceb4a" } // 3. 保存在vuex中 store.commit("login", context) // 4.页面跳转 router.push({name: "basic"}) } </script> <style scoped> .my_div { width: 500px; height: 200px; border: 1px solid green; margin-top: 200px; margin-left: auto; margin-right: auto; padding: 20px; } </style>
- vuex的实现,用于保存用户登录数据
import {createStore} from 'vuex' export default createStore({ state: { token: localStorage.getItem("token") || "", role: localStorage.getItem("role") || "", }, getters: {}, mutations: { login(state, {token, role}) { state.token = token state.role = role localStorage.setItem("token", token) localStorage.setItem("role", role) } }, actions: {}, modules: {} })
- 路由的编写
- 确定路由是否为菜单
- 确定路由能够访问的角色
- 菜单名字
- 路由导航守卫的编写
import {createRouter, createWebHistory} from 'vue-router' import store from "@/store"; const routes = [ { path: '/login', name: 'login', component: () => import('../views/LoginView.vue') }, { path: '/admin', name: 'admin', component: () => import('../views/AdminView.vue'), children: [ { path: 'basic', name: 'basic', component: () => import('../views/admin/BasicView.vue'), meta: { role: ["manager", "caiwu", "yunwei"], is_menu: true, title: "基本信息", } }, { path: 'user', name: 'user', component: () => import('../views/admin/UserView.vue'), meta: { role: ["manager", "yunwei"], is_menu: true, title: "用户管理", } }, { path: 'role', name: 'role', component: () => import('../views/admin/RoleView.vue'), meta: { role: ["manager", "caiwu"], is_menu: true, title: "角色管理" } } ] } ] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) router.beforeEach((to, from, next) => { console.log(to) if (to.meta.role) { const role = store.state.role console.log(role) if (to.meta.role.indexOf(role) === -1) { router.push({name: "login"}) } else { next() } } else { next() } }) export default router
- 按钮的展示
- 添加,删除和编辑三个按钮同过角色来进行确定是否渲染在页面
- 自定义has_permission函数
<template> <el-card class="box-card"> <template #header> <div class="card-header"> <span>基本信息</span> <el-button class="button" type="success" v-if="hasPermission(['manager'])">添加</el-button> </div> </template> <div> <el-table :data="state.tableData" style="width: 100%"> <el-table-column prop="name" label="姓名" width="180"/> <el-table-column prop="age" label="年龄" width="180"/> <el-table-column prop="address" label="住址"/> <el-table-column label="操作"> <el-button class="button" type="primary" v-if="hasPermission(['manager', 'caiwu'])">编辑</el-button> <el-button class="button" type="danger" v-if="hasPermission(['manager', 'yunwe'])">删除</el-button> </el-table-column> </el-table> </div> </el-card> </template> <script setup> import {reactive} from "vue"; import store from "@/store"; const state = reactive({ tableData: [ { name: "张三", age: 16, address: "上海市" } ] }) function hasPermission(roleList) { const role = store.state.role if (roleList.indexOf(role) !== -1) { return true } } </script> <style scoped> .card-header { display: flex; justify-content: space-between; align-items: center; } </style>
前端就可以动态的显示菜单和按钮
- 管理员身份
- 运维身份
3.2 后端
假设有一个学生表,我们对学生表进行操作
- 模型类
from django.db import models # Create your models here. class User(models.Model): username = models.CharField(verbose_name="用户名", max_length=32) password = models.CharField(verbose_name="密码", max_length=32) role_choice = ((1, "admin"), (2, "caiwu"), (3, "yunwei")) role = models.SmallIntegerField(verbose_name="角色", choices=role_choice, default=1) class Student(models.Model): username = models.CharField(verbose_name="用户名", max_length=32) age = models.SmallIntegerField(verbose_name="年龄") address = models.CharField(verbose_name="住址", max_length=32)
- 路由
from django.contrib import admin from django.urls import path from rest_framework import routers from api import views router = routers.SimpleRouter() router.register("api/student", viewset=views.StudentView, basename="student") urlpatterns = [ path('admin/', admin.site.urls), ] urlpatterns += router.urls
- 认证
class User:
def __init__(self, name=None, role=None):
self.name = name
self.role = role
class MineAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get("token") if not token: raise AuthenticationFailed("认证失败") role = request.query_params.get("role") user = User("xxx", role) return (user, token) def authenticate_header(self, request): return "API" - 权限
class MinePermission(BasePermission): def has_permission(self, request, view): """ 判断角色是否有对应的权限 :param request: :param view: :return: """ role = request.user.role permission_dict = settings.PERMISSIONS.get(role) request_method = request.method request_name = request_method.reslover_match.url_name if not permission_dict: return False method_list = permission_dict[request_name] if method_list and request_method in method_list: return True return False
- 视图
class StudentView(ModelViewSet): queryset = models.Student.objects.all() serializer_class = StudentModelSerializers authentication_classes = [MineAuthentication, ] permission_classes = [MinePermission, ]
- 配置文件
PERMISSIONS = { "manager": { "student-detail": ["GET", "POST"], "student-list": ["GET", "POST", "PATCH", "DELETE", "PUT"], }, "caiwu": { "student-detail": ["GET", "POST"], }, "yunwei": { "student-detail": ["GET", "POST"], "student-list": ["GET", ] } } REST_FRAMEWORK = { "UNAUTHENTICATED_USER": None, "UNAUTHENTICATED_TOKEN": None, }
4. 扩展
上面的案例中前端是根据角色来判定用户是否有对应的权限,我们也可以按照权限来判定,修改如下即可
- 用户登陆成功之后,需要返回当前用户的所有权限,保存在vuex中
- 在需要判定是否有权限的地方,根据权限来判定
has_permission({depart-detail:["GET","POST"])