需求:● 权限功能
■ 直接发送账号密码,后端自动根据账号密码获取账号等级
○ 如果是管理员登录
■ 可以授予或者收回某个账号的权限
■ 可以对所有的表
○ 如果是高级账户登录
■ 可以对所有的表增加、删除、修改、查询,显示在页面上就是所有接口都开放
○ 如果是普通账户登录
■ 只可以对表进行查询、修改,显示在页面上就是增加和删除按钮变灰,并且用户在点击时需要弹出“权限不足”的提示信息
**
登录功能权限校验**
获取验证码,以及验证码对应的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
若能写出具体代码再好不过,万分感谢!