【二开】CRMEB开源版按钮权限控制
使用方法
v-unique_auth="'order-refund'"
<el-dropdown-item
v-unique_auth="'order-refund'"
>立即退款</el-dropdown-item
>
或者 满足其中一个即可
v-unique_auth="['order-delete','order-dels']"
通过管理端权限维护按钮即可
1. 存储权限
template/admin/src/pages/account/login/index.vue
登录成功后存储
Local.set('unique_auth', data.unique_auth);
完整代码
<template>
<div class="page-account">
<div class="container" :class="[fullWidth > 768 ? 'containerSamll' : 'containerBig']">
<swiper :options="swiperOption" class="swiperPross" v-if="fullWidth > 768">
<swiper-slide class="swiperPic" v-for="(item, index) in swiperList" :key="index">
<img :src="item.slide" alt="" />
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
<div class="index_from page-account-container from-wh">
<div class="page-account-top">
<div class="page-account-top-logo">
<img :src="login_logo" alt="logo" style="width: 100%; height: 74px" />
</div>
</div>
<el-form ref="formInline" :model="formInline" :rules="ruleInline" @keyup.enter="handleSubmit('formInline')">
<el-form-item prop="username">
<el-input
type="text"
v-model="formInline.username"
prefix="ios-contact-outline"
placeholder="请输入用户名"
size="large"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
v-model="formInline.password"
prefix="ios-lock-outline"
placeholder="请输入密码"
size="large"
show-password
/>
</el-form-item>
<!-- <el-form-item prop="code">
<div class="code">
<el-input
type="text"
v-model="formInline.code"
prefix="ios-keypad-outline"
placeholder="请输入验证码"
size="large"
/>
<img :src="imgcode" class="pictrue" v-db-click @click="captchas" />
</div>
</el-form-item> -->
<el-form-item class="pt10">
<el-button
type="primary"
:loading="loading"
size="large"
v-db-click
@click="handleSubmit('formInline')"
class="btn"
>登录</el-button
>
</el-form-item>
</el-form>
</div>
</div>
<Verify
@success="success"
captchaType="blockPuzzle"
:imgSize="{ width: '330px', height: '155px' }"
ref="verify"
></Verify>
<div class="footer">
<div class="pull-right" v-if="copyright">{{ copyright }}</div>
<div class="pull-right" v-else>
Copyright © 2014-2025 <a href="https://www.crmeb.com" target="_blank">{{ version }}</a>
</div>
</div>
</div>
</template>
<script>
import { AccountLogin, loginInfoApi } from '@/api/account';
import { getWorkermanUrl } from '@/api/kefu';
import { setCookies } from '@/libs/util';
import '@/assets/js/canvas-nest.min';
import Verify from '@/components/verifition/Verify';
import { PrevLoading } from '@/utils/loading.js';
import { formatFlatteningRoutes, findFirstNonNullChildren } from '@/libs/system';
import { Local } from '@/utils/storage.js';
export default {
components: {
Verify,
},
data() {
return {
fullWidth: document.documentElement.clientWidth,
swiperOption: {
pagination: '.swiper-pagination',
autoplay: true,
},
loading: false,
isShow: false,
imgcode: '',
formInline: {
username: '',
password: '',
},
ruleInline: {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
},
login_captcha: 0,
login_logo: '',
swiperList: [],
defaultSwiperList: require('@/assets/images/sw.png'),
key: '',
copyright: '',
version: '',
timer: null,
};
},
created() {
document.onkeydown = (e) => {
if (this.$route.name === 'login' && (e.keyCode === 13 || e.which === 13)) {
this.handleSubmit('formInline');
}
};
window.addEventListener('resize', this.handleResize);
},
mounted() {
this.$nextTick(() => {
this.handleResize();
this.swiperData();
});
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
document.onkeydown = null;
const canvas = document.getElementsByTagName('canvas')[0];
if (canvas) canvas.removeAttribute('class', 'index_bg');
},
methods: {
swiperData() {
loginInfoApi()
.then((res) => {
const data = res.data || {};
document.title = `${data.site_name} - 登录`;
localStorage.setItem('ADMIN_TITLE', data.site_name || '');
this.$store.commit('setAdminTitle', data.site_name);
this.login_logo = data.login_logo || require('@/assets/images/logo.png');
this.swiperList = data.slide && data.slide.length ? data.slide : [{ slide: this.defaultSwiperList }];
this.key = data.key;
this.copyright = data.copyright;
this.version = data.version;
this.login_captcha = data.login_captcha;
})
.catch((err) => {
this.$message.error(err);
this.login_logo = require('@/assets/images/logo.png');
this.swiperList = [{ slide: this.defaultSwiperList }];
});
},
success(params) {
this.closeModel(params);
},
closeModel(params) {
this.isShow = false;
this.loading = true;
AccountLogin({
account: this.formInline.username,
pwd: this.formInline.password,
key: this.key,
captchaType: 'blockPuzzle',
captchaVerification: params ? params.captchaVerification : '',
})
.then(async (res) => {
const data = res.data;
const expires = this.getExpiresTime(data.expires_time);
setCookies('uuid', data.user_info.id, expires);
setCookies('token', data.token, expires);
setCookies('expires_time', data.expires_time, expires);
Local.set('PERMISSIONS', data.site_func);
Local.set('unique_auth', data.unique_auth);
this.$store.commit('userInfo/uniqueAuth', data.unique_auth);
this.$store.commit('userInfo/userInfo', data.user_info);
this.$store.commit('menus/setopenMenus', []);
this.$store.commit('menus/getmenusNav', data.menus);
this.$store.dispatch('routesList/setRoutesList', data.menus);
const arr = formatFlatteningRoutes(this.$router.options.routes);
this.formatTwoStageRoutes(arr);
this.$store.commit('menus/setOneLvMenus', arr);
const routes = formatFlatteningRoutes(data.menus);
this.$store.commit('menus/setOneLvRoute', routes);
this.$store.commit('userInfo/name', data.user_info.account);
this.$store.commit('userInfo/avatar', data.user_info.head_pic);
this.$store.commit('userInfo/access', data.unique_auth);
this.$store.commit('userInfo/logo', data.logo);
this.$store.commit('userInfo/logoSmall', data.logo_square);
this.$store.commit('userInfo/version', data.version);
this.$store.commit('userInfo/newOrderAudioLink', data.newOrderAudioLink);
this.login_captcha = 0;
try {
if (data.queue === false) {
this.$notify.warning({
title: '温馨提示',
dangerouslyUseHTMLString: true,
message:
'您的【消息队列】未开启,没有开启会导致异步任务无法执行。请尽快执行命令开启!!<a href="https://doc.crmeb.com/single/v54/13667" target="_blank">点击查看开启方法</a>',
duration: 30000,
});
}
if (data.timer === false) {
setTimeout(() => {
this.$notify.warning({
title: '温馨提示',
dangerouslyUseHTMLString: true,
message:
'您的【定时任务】未开启,没有开启会导致自动收货、未支付自动取消订单、订单自动好评、拼团到期退款等任务无法正常执行。请尽快执行命令开启!!<a href="https://doc.crmeb.com/single/v54/13667" target="_blank">点击查看开启方法</a>',
duration: 30000,
});
}, 0);
}
this.checkSocket();
} catch (e) {}
PrevLoading.start();
this.$router.push({
path: data.menus.length ? findFirstNonNullChildren(data.menus).path : this.$routeProStr + '/',
});
})
.catch((res) => {
const data = res || {};
this.$message.error(data.msg || '登录失败');
if (res && res.data) this.login_captcha = res.data.login_captcha;
})
.finally(() => {
setTimeout(() => {
this.loading = false;
}, 1000);
});
},
formatTwoStageRoutes(arr) {
if (!arr.length) return false;
const cacheList = [];
arr.forEach((v) => {
if (v && v.meta && v.meta.keepAlive) {
cacheList.push(v.name);
}
});
if (cacheList.length) {
this.$store.dispatch('keepAliveNames/setCacheKeepAlive', cacheList);
}
},
checkSocket() {
getWorkermanUrl().then((res) => {
const url = res.data.admin;
let isNotice = false;
const socket = new window.WebSocket(url);
socket.onopen = () => {
isNotice = true;
socket.close();
};
socket.onerror = socket.onclose = () => {
if (!isNotice) {
isNotice = true;
this.$notify.warning({
title: '温馨提示',
message:
'您的【长连接】未开启,没有开启会导致系统默认客服无法使用,后台订单通知无法收到。请尽快执行命令开启!!<a href="https://doc.crmeb.com/single/v54/13667" target="_blank">点击查看开启方法</a>',
dangerouslyUseHTMLString: true,
duration: 30000,
});
}
};
});
},
getExpiresTime(expiresTime) {
const nowTimeNum = Math.round(Date.now() / 1000);
const expiresTimeNum = expiresTime - nowTimeNum;
return parseFloat(expiresTimeNum / 60 / 60 / 24);
},
closefail() {
this.$message.error('校验错误');
},
handleResize() {
this.fullWidth = document.documentElement.clientWidth;
const canvas = document.getElementsByTagName('canvas')[0];
if (canvas) {
if (this.fullWidth < 768) {
canvas.removeAttribute('class', 'index_bg');
} else {
canvas.className = 'index_bg';
}
}
},
handleSubmit(name) {
this.$refs[name].validate((valid) => {
if (valid) {
if (this.login_captcha === 1) {
this.$refs.verify.show();
} else {
this.closeModel();
}
}
});
},
},
};
</script>
<style lang="scss" scoped>
.page-account {
display: flex;
width: 100%;
background-image: url('../../../assets/images/bg.jpg');
background-size: cover;
background-position: center;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
overflow: auto;
}
.page-account .code {
display: flex;
align-items: center;
justify-content: center;
}
.page-account .code .pictrue {
height: 40px;
}
.swiperPross {
border-radius: 12px 0px 0px 12px;
}
.swiperPross,
.swiperPic,
.swiperPic img {
width: 510px;
height: 100%;
}
.swiperPic img {
width: 100%;
height: 100%;
}
.container {
height: 400px !important;
padding: 0 !important;
border-radius: 12px;
z-index: 1;
display: flex;
}
.containerSamll {
/* width: 56% !important; */
background: #fff !important;
}
.containerBig {
width: auto !important;
background: #f7f7f7 !important;
}
.index_from {
padding: 32px 40px 32px 40px;
height: 400px;
box-sizing: border-box;
}
.page-account-top {
padding: 20px 0 24px 0 !important;
box-sizing: border-box !important;
display: flex;
justify-content: center;
}
.page-account-container {
border-radius: 0px 6px 6px 0px;
}
.btn {
width: 100%;
background: linear-gradient(90deg, rgba(25, 180, 241, 1) 0%, rgba(14, 115, 232, 1) 100%) !important;
}
.captchaBox {
width: 310px;
}
input {
display: block;
width: 290px;
line-height: 40px;
margin: 10px 0;
padding: 0 10px;
outline: none;
border: 1px solid #c8cccf;
border-radius: 4px;
color: #6a6f77;
}
#msg {
width: 100%;
line-height: 40px;
font-size: 14px;
text-align: center;
}
a:link,
a:visited,
a:hover,
a:active {
margin-left: 100px;
color: #0366d6;
}
.index_from ::v-deep .ivu-input-large {
font-size: 14px !important;
}
.from-wh {
width: 400px;
}
.pull-right {
float: right !important;
}
::v-deep .el-button--primary {
border: none;
}
::v-deep .el-button {
padding: 13px 20px !important;
}
.pull-right {
float: right !important;
color: #666;
}
.pull-right a {
margin-left: 0;
color: #666;
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
left: 0;
margin: 0;
background: rgba(255, 255, 255, 0.8);
border-top: 1px solid #e7eaec;
overflow: hidden;
padding: 10px 20px;
height: 36px;
line-height: 18px;
z-index: 999;
}
</style>
2. 创建权限验证 js
template/admin/src/directive/module/unique_auth.js
import { Local } from '@/utils/storage.js';
/**
* 判断传入的 key 是否在数组 arr 中存在
* @param {string} key - 待判断的字符串
* @returns {boolean} - 返回布尔值,表示是否有权限
*/
function checkArray(key) {
// seckill 秒杀 bargain 砍价 combination 拼团
let arr = Local.get('unique_auth'); // 定义一个数组,包含三种类型
if(!key){
return false;
}
if(key instanceof Array){
for (let i = 0; i < key.length; i++){
let keytmp = key[i]
if(!keytmp){
continue
}
let index = arr.indexOf(keytmp); // 获取 key 在数组中的索引
console.info('所有权限', index, keytmp, arr)
if (index > -1) {
// 如果索引大于 -1,说明 key 存在于数组中
return true; // 有权限
} else {
return false; // 无权限
}
}
return false
}
let index = arr.indexOf(key); // 获取 key 在数组中的索引
console.info('所有权限', index, key, arr)
if (index > -1) {
// 如果索引大于 -1,说明 key 存在于数组中
return true; // 有权限
} else {
return false; // 无权限
}
}
/**
* @description 一个Vue指令,用于控制组件的显示和隐藏
* @param {Object} el - 指令绑定的DOM元素
* @param {Object} binding - 指令绑定的对象
*/
const unique_auth = {
inserted: function (el, binding) {
console.info('权限验证', binding.value, el)
let permission = binding.value; // 获取到 v-permission的值
if (permission) {
let hasPermission = checkArray(permission); // 调用checkArray函数判断是否有权限
if (!hasPermission) {
console.info('无权限', permission)
// 没有权限 移除Dom元素
el.parentNode && el.parentNode.removeChild(el);
}
}
},
};
export default unique_auth;
3. 配置directives
import unique_auth from './module/unique_auth';
const directives = {
draggable,
clipboard,
auth,
permission, unique_auth,
dbClick,
};
template/admin/src/directive/directives.js
import draggable from './module/draggable';
import clipboard from './module/clipboard';
import auth from './module/auth';
import permission from './module/permission';
import dbClick from './module/dbClick';
import unique_auth from './module/unique_auth';
const directives = {
draggable,
clipboard,
auth,
permission, unique_auth,
dbClick,
};
export default directives;
4.配置指令
Vue.directive('unique_auth', directive.unique_auth);
template/admin/src/directive/index.js
import directive from './directives';
const importDirective = (Vue) => {
/**
* 拖拽指令 v-draggable="options"
* options = {
* trigger: /这里传入作为拖拽触发器的CSS选择器/,
* body: /这里传入需要移动容器的CSS选择器/,
* recover: /拖动结束之后是否恢复到原来的位置/
* }
*/
Vue.directive('draggable', directive.draggable);
/**
* clipboard指令 v-draggable="options"
* options = {
* value: /在输入框中使用v-model绑定的值/,
* success: /复制成功后的回调/,
* error: /复制失败后的回调/
* }
*/
Vue.directive('clipboard', directive.clipboard);
/**
* v-auth="['string-string']"
* */
Vue.directive('auth', directive.auth);
Vue.directive('permission', directive.permission);
Vue.directive('unique_auth', directive.unique_auth);
Vue.directive('dbClick', directive.dbClick);
};
export default importDirective;