hhhhhh9980 2024-04-29 23:03 采纳率: 50%
浏览 11
已结题

vue2登录权限校验(前端)

需求:● 权限功能
    ■ 直接发送账号密码,后端自动根据账号密码获取账号等级
  ○ 如果是管理员登录
    ■ 可以授予或者收回某个账号的权限
    ■ 可以对所有的表
  ○ 如果是高级账户登录
    ■ 可以对所有的表增加、删除、修改、查询,显示在页面上就是所有接口都开放
  ○ 如果是普通账户登录
    ■ 只可以对表进行查询、修改,显示在页面上就是增加和删除按钮变灰,并且用户在点击时需要弹出“权限不足”的提示信息
**


登录功能权限校验**
获取验证码,以及验证码对应的key
通过账号密码登录系统,并且拿到accessToken
把刚生成的access_token,配置在全局参数里面,以后每次发送请求,都会携带这个参数
访问接口

接口:## 获取 accessToken


**接口地址**:`/sys/auth/token`

**请求方式**:`POST`


**请求数据类型**:`application/x-www-form-urlencoded`

## 账号密码登录


**接口地址**:`/sys/auth/login`


**请求方式**:`POST`


**请求数据类型**:`application/x-www-form-urlencoded,application/json`
## 验证码


**接口地址**:`/sys/auth/captcha`


**请求方式**:`GET`


**请求数据类型**:`application/x-www-form-urlencoded`
## 是否开启验证码


**接口地址**:`/sys/auth/captcha/enabled`


**请求方式**:`GET`


**请求数据类型**:`application/x-www-form-urlencoded`
//index.vue

<template>
  <div class="page-login">
    <div class="page-login--layer page-login--layer-area">
      <ul class="circles">
        <li v-for="n in 10" :key="n"></li>
      </ul>
    </div>
    <div
      class="page-login--layer page-login--layer-time"
      flex="main:center cross:center">
      {{time}}
    </div>
    <div class="page-login--layer">
      <div
        class="page-login--content"
        flex="dir:top main:justify cross:stretch box:justify">


        <div class="image">

          <img src="https://yayabucket.oss-cn-wulanchabu.aliyuncs.com/微信图片_20240426111346.png" style="width: 100%;height: 100%;">
        </div>


        <div
          class="page-login--content-main"
          flex="dir:top main:center cross:center">


          <div class="page-login--form"  style="margin-right: -100vh;margin-top: -50vh;">
            <el-card shadow="never"  style="margin-top:-400px;">
              <el-form ref="loginForm" label-position="top" :rules="rules" :model="formLogin" size="default">
                <el-form-item prop="username">
                  <el-input type="text" v-model="formLogin.username" placeholder="用户名" >
                    <i slot="prepend" class="fa fa-user-circle-o"></i>
                  </el-input>
                </el-form-item>
                <el-form-item prop="password">
                  <el-input type="password" v-model="formLogin.password" placeholder="密码" >
                    <i slot="prepend" class="fa fa-keyboard-o"></i>
                  </el-input>
                </el-form-item>
                <!-- <el-form-item prop="code">
                  <el-input
                    type="text"
                    v-model="formLogin.code"
                    placeholder="验证码">
                    <template slot="append">
                      <img class="login-code" src="./image/login-code.png">
                    </template>
                  </el-input>
                </el-form-item> -->



                <!-- <el-form-item v-if="captchaVisible" prop="captcha" class="login-captcha">
            <el-input v-model="formLogin.captcha"  :prefix-icon="Key"></el-input>
            <img :src="captchaBase64" @click="onCaptcha" />
        </el-form-item> -->
                <el-button
                  size="default"
                  @click="onLogin()"
                  type="primary"
                  class="button-login">
                  登录
                </el-button>

              </el-form>
            </el-card>



          </div>
        </div>

      </div>
    </div>

  </div>
</template>

<script>

import { mapActions } from 'vuex'
import localeMixin from '@/locales/mixin.js'
 import { sm2Encrypt } from '@/utils/smCrypto'
 import { mapMutations } from 'vuex';

export default {
  mixins: [
    localeMixin
  ],
  data () {
    return {
      timeInterval: null,

      // 快速选择用户
      dialogVisible: false,
      loginForm: {
      username: '',
      password: '',
      key: '',
      captcha: ''
    },
    captchaVisible: false, // 假设你有一个数据属性来控制验证码的可见性
      // 表单
      formLogin: {
        username: 'admin',
        password: 'admin',
        code: 'v9am'
      },
      // 表单校验
      rules: {
        username: [
          {
            required: true,
            message: '请输入用户名',
            trigger: 'blur'
          }
        ],
        password: [
          {
            required: true,
            message: '请输入密码',
            trigger: 'blur'
          }
        ],
        code: [
          {
            required: true,
            message: '请输入验证码',
            trigger: 'blur'
          }
        ]
      }
    }
  },
  mounted () {
    this.onCaptchaEnabled();
  },
  beforeDestroy () {

  },
  methods: {
    ...mapMutations(['changeLogin']),


    async getAccessToken() {
      try {
        const url = this.$baseURL + 'sys/auth/token';
        const formData = new FormData();
        // 假设需要传递用户名和密码来获取token
        formData.append('username', this.formLogin.username);
        formData.append('password', this.formLogin.password);
        // 还可以添加其他必要的字段
        formData.append('key', this.formLogin.key);
        formData.append('captcha', this.captcha);

        const response = await this.axios.post(url, formData, {
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
          }
        });

        // 假设响应体包含 access_token 字段
        if (response.data && response.data.access_token) {
          this.accessToken = response.data.access_token;
          // 可以在这里处理获取到token后的逻辑,比如保存到localStorage或Vuex
          console.log('成功获取 accessToken:', this.accessToken);
        } else {
          console.error('获取 accessToken 失败:', response.data);
        }
      } catch (error) {
        console.error('获取 accessToken 时发生错误:', error);
      }
    },



    async onCaptchaEnabled() {
  try {
    const url = this.$baseURL + `sys/auth/captcha/enabled`;
    const { data } = await this.axios.get(url); // 假设使用 axios 发送 GET 请求到 '/captchaEnabled' 接口
    this.captchaVisible = data; // 更新 data 中的 captchaVisible
    if (data) {
      await this.onCaptcha(); // 如果需要,调用另一个方法
    }
  } catch (error) {
    console.error('Error fetching captcha enabled status:', error);
  }
},

async onCaptcha() {
  try {
    const url = this.$baseURL + `sys/auth/captcha`;
    const { data } = await this.axios.get(url); // 假设使用 axios 发送 GET 请求到 '/captcha' 接口
    if (data.enabled) {
      this.captchaVisible = true; // 更新 captchaVisible 为 true
    }
    this.captchaKey = data.key; // 更新 captcha 的 key
    this.captchaBase64 = data.image; // 更新 captcha 的 base64 图片
  } catch (error) {
    console.error('Error fetching captcha data:', error);
  }
},

    onLogin: function() {
      let _this = this;
      this.$refs.loginForm.validate(valid => { // 假设登录表单有一个 ref 属性为 "loginForm"
        if (!valid) {
          return false;
        }

           // 重新封装登录数据
    const loginData = {
      username: this.loginForm.username,
      password: sm2Encrypt(this.loginForm.password), // 假设 sm2Encrypt 是一个可用的加密函数
      key: this.loginForm.key,
      captcha: this.loginForm.captcha
    };

    // 调用后端接口
    const url = this.$baseURL + `sys/auth/login`;
    this.$axios.post(url, loginData) // 假设使用 axios 发送 POST 请求到 '/login' 接口
      .then(response => {
        // 处理登录成功逻辑
        console.log('成功')

 _this.userToken = 'Bearer ' + response.data.data.body.token;
 // 将用户token保存到vuex中
 _this.changeLogin({ Authorization: _this.userToken });
 _this.$router.push(this.$route.query.redirect || '/');
 alert('登陆成功');
      })
      .catch(error => {
        console.log(error);
        // 处理登录失败逻辑
        if (this.captchaVisible) {
          this.onCaptcha(); // 假设 onCaptcha 是一个可用的方法,用于处理验证码逻辑
        }
      });
  });
},
    },
}




    // submit () {
    //   this.$refs.loginForm.validate((valid) => {
    //     if (valid) {
    //       // 登录
    //       // 注意 这里的演示没有传验证码
    //       // 具体需要传递的数据请自行修改代码
    //       this.login({
    //         username: this.formLogin.username,
    //         password: this.formLogin.password
    //       })
    //         .then(() => {
    //           // 重定向对象不存在则返回顶层路径
    //           this.$router.replace(this.$route.query.redirect || '/')
    //         })
    //     } else {
    //       // 登录表单校验失败
    //       this.$message.error('表单校验失败,请检查')
    //     }
    //   })
    // }


</script>

<style lang="scss">
.page-login {
  @extend %unable-select;
  $backgroundColor: rgb(255, 255, 255);
  // ---
  background-color: $backgroundColor;
  height: 100%;
  position: relative;
  // 层
  .page-login--layer {
    @extend %full;
    overflow: auto;
  }
  .page-login--layer-area {
    overflow: hidden;
  }
  // 时间
  .page-login--layer-time {
    font-size: 24em;
    font-weight: bold;
    color: rgba(0, 0, 0, 0.03);
    overflow: hidden;
  }
  // 登陆页面控件的容器
  .page-login--content {
    height: 100%;
    min-height: 500px;
  }
  // header
  .page-login--content-header {
    padding: 1em 0;
    .page-login--content-header-motto {
      margin: 0px;
      padding: 0px;
      color: $color-text-normal;
      text-align: center;
      font-size: 12px;
    }
  }
  // main
  .page-login--logo {
    width: 240px;
    // margin-bottom: 2em;
    // margin-top: -2em;
  }
  // 登录表单
  .page-login--form {
    width: 280px;
    // 卡片
    .el-card {
      margin-bottom: 15px;
    }
    // 登录按钮
    .button-login {
      width: 100%;
    }
    // 输入框左边的图表区域缩窄
    .el-input-group__prepend {
      padding: 0px 14px;
    }
    .login-code {
      height: 40px - 2px;
      display: block;
      margin: 0px -20px;
      border-top-right-radius: 2px;
      border-bottom-right-radius: 2px;
    }
    // 登陆选项
    .page-login--options {
      margin: 0px;
      padding: 0px;
      font-size: 14px;
      color: $color-primary;
      margin-bottom: 15px;
      font-weight: bold;
    }
    .page-login--quick {
      width: 100%;
    }
  }
  // 快速选择用户面板
  .page-login--quick-user {
    @extend %flex-center-col;
    padding: 10px 0px;
    border-radius: 4px;
    &:hover {
      background-color: $color-bg;
      i {
        color: $color-text-normal;
      }
      span {
        color: $color-text-normal;
      }
    }
    i {
      font-size: 36px;
      color: $color-text-sub;
    }
    span {
      font-size: 12px;
      margin-top: 10px;
      color: $color-text-sub;
    }
  }
  // footer
  .page-login--content-footer {
    padding: 1em 0;
    .page-login--content-footer-locales {
      padding: 0px;
      margin: 0px;
      margin-bottom: 15px;
      font-size: 12px;
      line-height: 12px;
      text-align: center;
      color: $color-text-normal;
      a {
        color: $color-text-normal;
        margin: 0 .5em;
        &:hover {
          color: $color-text-main;
        }
      }
    }
    .page-login--content-footer-copyright {
      padding: 0px;
      margin: 0px;
      margin-bottom: 10px;
      font-size: 12px;
      line-height: 12px;
      text-align: center;
      color: $color-text-normal;
      a {
        color: $color-text-normal;
      }
    }
    .page-login--content-footer-options {
      padding: 0px;
      margin: 0px;
      font-size: 12px;
      line-height: 12px;
      text-align: center;
      a {
        color: $color-text-normal;
        margin: 0 1em;
      }
    }
  }
  // 背景
  .circles {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    margin: 0px;
    padding: 0px;
    li {
      position: absolute;
      display: block;
      list-style: none;
      width: 20px;
      height: 20px;
      background: #FFF;
      animation: animate 25s linear infinite;
      bottom: -200px;
      @keyframes animate {
        0%{
          transform: translateY(0) rotate(0deg);
          opacity: 1;
          border-radius: 0;
        }
        100%{
          transform: translateY(-1000px) rotate(720deg);
          opacity: 0;
          border-radius: 50%;
        }
      }
      &:nth-child(1) {
        left: 15%;
        width: 80px;
        height: 80px;
        animation-delay: 0s;
      }
      &:nth-child(2) {
        left: 5%;
        width: 20px;
        height: 20px;
        animation-delay: 2s;
        animation-duration: 12s;
      }
      &:nth-child(3) {
        left: 70%;
        width: 20px;
        height: 20px;
        animation-delay: 4s;
      }
      &:nth-child(4) {
        left: 40%;
        width: 60px;
        height: 60px;
        animation-delay: 0s;
        animation-duration: 18s;
      }
      &:nth-child(5) {
        left: 65%;
        width: 20px;
        height: 20px;
        animation-delay: 0s;
      }
      &:nth-child(6) {
        left: 75%;
        width: 150px;
        height: 150px;
        animation-delay: 3s;
      }
      &:nth-child(7) {
        left: 35%;
        width: 200px;
        height: 200px;
        animation-delay: 7s;
      }
      &:nth-child(8) {
        left: 50%;
        width: 25px;
        height: 25px;
        animation-delay: 15s;
        animation-duration: 45s;
      }
      &:nth-child(9) {
        left: 20%;
        width: 15px;
        height: 15px;
        animation-delay: 2s;
        animation-duration: 35s;
      }
      &:nth-child(10) {
        left: 85%;
        width: 150px;
        height: 150px;
        animation-delay: 0s;
        animation-duration: 11s;
      }
    }
  }
}
</style>







//utils/smCrypto.js 用于密码加密
import { sm2 } from 'sm-crypto';

const publicKey = '040a302b5e4b961afb3908a4ae191266ac5866be100fc52e3b8dba9707c8620e64ae790ceffc3bfbf262dc098d293dd3e303356cb91b54861c767997799d2f0060';

/**
 * sm2加密
 * @param data 待加密数据
 * @return 加密后的数据
 */
export const sm2Encrypt = function(data) {
    return '04' + sm2.doEncrypt(data, publicKey, 1);
};


//store/index.js

import Vue from 'vue';
import Vuex from 'vuex';

import d2admin from './modules/d2admin'
Vue.use(Vuex);

const store = new Vuex.Store({

 state: {
 // 存储token
 Authorization: localStorage.getItem('Authorization') ? localStorage.getItem('Authorization') : ''
 },

 mutations: {
 // 修改token,并将token存入localStorage
 changeLogin (state, user) {
 state.Authorization = user.Authorization;
 localStorage.setItem('Authorization', user.Authorization);
 }
 },
modules: {
  d2admin
}

});

//router/index.js 路由守卫
router.beforeEach((to, from, next) => {
  if (to.path === '/login') {
  next();
  } else {
  let token = localStorage.getItem('Authorization');

  if (token === 'null' || token === '') {
  next('/login');
  } else {
  next();
  }
  }
 });


export default router



若能写出具体代码再好不过,万分感谢!

  • 写回答

21条回答 默认 最新

  • 专家-皆知 2024-04-29 23:08
    关注
    获得0.30元问题酬金

    参考免费微信小程序:皆我百晓生 作答,经由专家-皆知整理回复,如问题解决,望采纳;如仍有疑问,请评论回复。

    为了实现 Vue 2 中的登录权限校验,我们需要关注几个关键点:

    1. 登录流程:包括获取验证码、登录、获取 accessToken。
    2. 权限存储:通常将权限信息存储在 Vuex 中,以便全局访问。
    3. 权限校验:在路由守卫或全局方法中根据权限执行相应操作。
    4. 界面反馈:根据用户权限显示或禁用界面元素。

    以下是针对您提供的代码和需求的改进方案:

    1. 登录流程改进

    onLogin 方法中,您需要处理登录成功后的逻辑,包括获取 accessToken 并存储到 Vuex 中。同时,您可能需要根据后端返回的用户角色来设置不同的权限。

    // 在 methods 中添加
    onLogin() {
      // ... 省略其他代码
      .then(response => {
        // 处理登录成功逻辑
        const userToken = 'Bearer ' + response.data.data.body.token;
        // 将用户token保存到vuex中
        this.commit('changeLogin', { Authorization: userToken });
        // 根据用户角色设置权限
        const userRole = response.data.data.body.role; // 假设后端返回了用户角色
        this.setPermissions(userRole);
        this.$router.push(this.$route.query.redirect || '/');
        alert('登录成功');
      })
      .catch(error => {
        // 处理登录失败逻辑
        console.error(error);
      });
    },
    
    setPermissions(userRole) {
      // 根据用户角色设置权限
      const permissions = {
        admin: ['add', 'delete', 'update', 'query'],
        advanced: ['add', 'delete', 'update', 'query'],
        normal: ['query', 'update']
      };
      const userPermissions = permissions[userRole] || [];
      // 存储权限信息到Vuex
      this.commit('setPermissions', userPermissions);
    }
    

    2. Vuex 权限存储

    在 Vuex 中添加一个状态来存储权限,并添加相应的 mutations。

    // 在 store/index.js 中添加
    state: {
      // ... 其他状态
      permissions: [], // 存储当前用户的权限
    },
    
    mutations: {
      // ... 其他 mutations
      setPermissions(state, permissions) {
        state.permissions = permissions;
      }
    }
    

    3. 权限校验

    在路由守卫中,您可以根据权限来判断是否允许访问某个路由。

    // 在 router/index.js 中添加
    router.beforeEach((to, from, next) => {
      const publicPages = ['/login']; // 假设 '/login' 是公开的,不需要权限
      const isAuthenticated = localStorage.getItem('Authorization') !== null;
      const hasPermission = publicPages.includes(to.path) || store.state.permissions.includes('admin');
    
      if (isAuthenticated && hasPermission) {
        next();
      } else {
        next('/login');
      }
    });
    

    4. 界面反馈

    在模板中,您可以根据权限来显示或禁用按钮。

    <!-- 在 index.vue 的模板中添加 -->
    <template>
      <!-- ... 其他代码 -->
      <div v-if="permissions.includes('delete')">
        <!-- 删除按钮 -->
      </div>
      <div v-if="permissions.includes('add')">
        <!-- 添加按钮 -->
      </div>
      <!-- ... 其他代码 -->
    </template>
    

    computed 中映射权限:

    // 在 index.vue 中添加
    computed: {
      ...mapState(['permissions'])
    }
    

    这样,您就可以根据用户的角色和权限来控制界面上的按钮显示和路由访问了。请注意,这里的代码只是示例,您可能需要根据实际后端返回的数据和项目结构进行调整。

    评论 编辑记录

报告相同问题?

问题事件

  • 系统已结题 5月7日
  • 创建了问题 4月29日

悬赏问题

  • ¥15 使用sql语句实现下面两个实验(需要代码和运行结果截图)
  • ¥15 正则化逻辑回归损失函数
  • ¥20 用web解决,要给我一个完整的网页,符合上述的要求
  • ¥20 求个sql代码和结果的图 两道题
  • ¥15 银河麒麟操作系统无法使用U盘
  • ¥100 寻找:光电二极管电路设计服务
  • ¥15 YOLOv5改进后的结构图
  • ¥15 全志v3s怎么设置高速时钟,使用的荔枝派zero开发板,串口2需要921600的波特率
  • ¥15 关于#单片机#的问题:Lora通讯模块hc-14电路图求内部原理图
  • ¥50 esp32 wroom 32e 芯片解锁