【二开】CRMEB开源版按钮权限控制

【二开】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>
  1. 创建权限验证 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;
  1. 配置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;
相关推荐
周杰伦_Jay8 小时前
【大模型数据标注】核心技术与优秀开源框架
人工智能·机器学习·eureka·开源·github
玄魂8 小时前
如何查看、生成 github 开源项目star 图表
前端·开源·echarts
隐语SecretFlow9 小时前
【隐语Secreflow】如何配置 Kuscia 对请求进行 Path Rewrit
架构·开源
hh.h.9 小时前
开源鸿蒙生态下Flutter的发展前景分析
flutter·开源·harmonyos
一RTOS一14 小时前
光亚鸿道携手AGIROS开源社区,共筑中国具身智能机器人操作系统新生态
机器人·开源·鸿道实时操作系统·国产嵌入式操作系统选型·具身智能操作系统选型
刘发财14 小时前
前端一行代码生成数千页PDF,dompdf.js新增分页功能
前端·typescript·开源
CoderJia程序员甲14 小时前
GitHub 热榜项目 - 日榜(2025-12-15)
git·ai·开源·llm·github
wei_shuo15 小时前
华为盘古 Ultra-MoE-718B-V1.1 正式开源:Pangu 超大稀疏架构的价值解码与技术剖析
开源·openpangu·华为盘古
知行力16 小时前
【GitHub每日速递 20251215】微软开源12周26课机器学习入门课程,多语言支持还能离线学!
机器学习·开源·github