说明
该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发)。
该系统文章,我会尽量说的非常详细,做到不管新手、老手都能看懂。
说明:OverallAuth2.0 是一个简单、易懂、功能强大的权限+可视化流程管理系统。
友情提醒:本篇文章是属于系列文章,看该文章前,建议先看之前文章,可以更好理解项目结构。
qq群:801913255,进群有什么不懂的尽管问,群主都会耐心解答。
有兴趣的朋友,请关注我吧(*^▽^*)。
关注我,学不会你来打我
前言
该篇文章是实现【功能级权限】的开篇文章,其主要实现内容如下图↓
该图为功能级权限匹配插件
创建模型和数据源
在实现组件前,先要使用TS把模型和数据源创建好
我的文档目录如:Src->model->match->index.ts 依托于开源项目OverallAuth2.0统一权限分发中心的系统架构
创建匹配条件的关系

//组条件 export const matchingGroup = [ { label: '且', value: 'And', disabled: false }, { label: '或', value: 'Or', disabled: false } ]
View Code
创建匹配组件模型

//公式匹配模型 export interface matchingData { id: string; // 父级id pid: string; //匹配组(and,or) matchGroup: string; //层级 level: number; //匹配条件 matchingWhere: matchingWhereData[]; //子集 children: matchingData[]; } //匹配条件模型 export interface matchingWhereData { //主键 id: string; //字段key(选中的字段) fieldKey: string; //等式符号key(选中的符号) matchEquationKey: string; //匹配数据key(选中的匹配值) matchDataKey: string; }
View Code
创建生成随机id的方法

/* 生成随机不重复id */ export const randamId = function () { let n = 1; let arr = []; for (let i = 0; i < n; i++) { arr[i] = parseInt((Math.random() * 10000000000).toString()); } for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (arr[i] === arr[j]) { randamId(); return false; } } } return ("Item-" + arr).toString(); };
View Code
编写组件
我的页面目录:Src->views->match->index.vue Src->views->match->match.vue
编写match.vue页面代码

<template> <div class="plandiv"> <div v-for="item in data" :key="item.id" class="forDiv"> <div class="groupDiv"> <div class="groupBackColor"> <div style="width: 20%"> <el-select v-model="item.matchGroup" placeholder="请选择" style=" float: left; margin-right: 10px; margin-left: 10px; min-width: 100px; " > <el-option v-for="group in matchingGroup" :key="group.value" :label="group.label" :value="group.value" /> </el-select> </div> <div style="width: 80%"> <div class="buttonStyle">这里放操作按钮</div> </div> </div> </div> </div> </div> </template> <script lang="ts" > import { matchingData, matchingGroup, matchingWhere, matchingEquation, positionList, } from "@/model/match"; import { defineComponent, PropType } from "vue"; export default defineComponent({ name: "xc-match", props: { data: { type: Object as PropType<matchingData[]>, required: true, }, }, setup() { return { matchingGroup, matchingWhere, matchingEquation, positionList, }; }, components: {}, }); </script> <style scoped> /* 最外层样式 */ .plandiv { background-color: white; height: auto; } /* 循环层样式 */ .forDiv { overflow-y: auto; } /* 分组样式 */ .groupDiv { border: 1px solid #919aa3; width: auto; height: auto; margin-top: 5px; margin-right: 20px; margin-bottom: 10px; margin-left: 20px; } /* 组条件背景色 */ .groupBackColor { background-color: #919aa3; height: 50px; line-height: 50px; display: flex; width: 100%; justify-content: center; align-items: center; } /* 按钮样式 */ .buttonStyle { text-align: left; margin-left: 20px; } </style>
View Code
编写index.vue页面代码

<template> <match :data="pageList"></match> </template> <script lang="ts" setup> import { matchingData, randamId } from "@/model/match"; import { ref } from "vue"; import match from "../match/match.vue"; const pageList = ref<matchingData[]>([ { id: "Group-1", pid: "0", matchGroup: "And", level: 1, matchingWhere: [ { id: randamId().toString(), fieldKey: "", matchEquationKey: "", matchDataKey: "", }, ], children: [], }, ]); </script>
View Code
index.vue页面中,我们添加了一条分组的默认值。查看下效果
添加分组按钮
在class='buttonStyle' div中添加如下代码
<el-button icon="CirclePlus" plain @click="addGroup(item)" >新增分组</el-button > <el-button icon="Plus" plain @click="addItem(item)" >新增条件</el-button > <el-button v-if="item.level !== 1" type="danger" icon="Delete" @click="deleteGroup(item)" >删除分组</el-button >
添加按钮事件
添加前,我们必须先安装一个插件:npm install number-precision
在setup(props)中添加如下代码,并retrun事件

//最多组 const maxGroup = ref<number>(5); //最多层级 const maxLevel = ref<number>(3); //最多条件 const maxWhere = ref<number>(10); // 添加组事件 const addGroup = function (item: matchingData) { //获取当前组的长度 var listGroupLength = item.children.length; //添加前验证最多添加多少层级 if (item.level >= maxLevel.value) { ElMessage({ message: "最多添加" + maxLevel.value + "级", type: "warning", }); return; } //添加前验证能添加多少组 if (listGroupLength >= maxGroup.value) { ElMessage({ message: "每层下最多添加" + maxGroup.value + "个组", type: "warning", }); return; } //当前组必须要有条件才能添加下级组 if (item.matchingWhere.length == 0) { ElMessage({ message: "当前组下无任何条件,不能添加分组!", type: "warning", }); return; } //组织要添加节点的数据 var groupId = item.id + "-" + (listGroupLength + 1); var groupPid = item.id; var groupLevel = item.level + 1; //找到对应的下标 const index = props.data.findIndex((s) => { if (s.id === item.id) { return true; } }); //精确插入当前节点及插入位置 var indexLength = listGroupLength + 1; item.children.splice(plus(...[index, indexLength]), 0, { id: groupId, pid: groupPid, matchGroup: "Or", level: groupLevel, matchingWhere: [], children: [], }); }; // 删除组 const deleteGroup = function (item: matchingData) { GetGroupSpliceIndex(item.id, props.data); }; //递归删除组 const GetGroupSpliceIndex = (id: string, list: matchingData[]) => { //找到删除数据下标 const index = list.findIndex((p: { id: string }) => { if (p.id === id) { return true; } }); if (index === -1) GetGroupSpliceIndex(id, list[0].children); list.forEach((f: { id: string }) => { if (f.id == id) { list.splice(index, 1); } }); };
View Code
这个时候,我们点击按钮,不会出现下级。因为递归的重要一步,并没有完成。
在match.vue 页面中找到有class="groupDiv" 的div,在div中的末尾添加如下代码
<xc-match v-if="item.children && item.children.length" :data="item.children" />
以上代码是实现递归的关键,位置一定要准。
说明一点xc-match一定要和页面导出的名称一样。
看效果图
添加条件及条件按钮
添加条件项
在match.vue页面xc-match元素前,添加如下代码

<div class="itemBackColor" v-for="whereItem in item.matchingWhere" :key="whereItem.id" > <!-- 匹配条件 --> <el-select v-model="whereItem.fieldKey" placeholder="请选择匹配条件" style="width: 240px" > <el-option v-for="where in matchingWhere" :key="where.value" :label="where.label" :value="where.value" /> </el-select> <!-- 匹配等式 --> <el-select v-model="whereItem.matchEquationKey" placeholder="请选择等式" style="width: 240px" > <el-option v-for="equation in matchingEquation" :key="equation.value" :label="equation.label" :value="equation.value" /> </el-select> <!-- 匹配值 --> <el-input-number v-model="whereItem.matchDataKey" :step="1" min="1" max="200" step-strictly style="width: 240px" v-if="whereItem.fieldKey === 'Age'" /> <el-select v-else-if="whereItem.fieldKey === 'Position'" v-model="whereItem.matchDataKey" placeholder="请选择职位" style="width: 240px" > <el-option v-for="position in positionList" :key="position.value" :label="position.label" :value="position.value" /> </el-select> <el-date-picker v-else-if="whereItem.fieldKey === 'CreateTime'" v-model="whereItem.matchDataKey" type="date" style="width: 240px" placeholder="请选择时间" /> <el-input v-else v-model="whereItem.matchDataKey" style="width: 240px" placeholder="请输入" clearable /> <el-button type="danger" icon="Delete" plain size="small" style="margin-left: 10px" @click="deleteItem(whereItem, item)" >删除条件</el-button > <!-- 当前项id:{{ whereItem.id }} --> </div>
View Code
css如下
/* 项背景色 */ .itemBackColor { height: 46px; display: -webkit-box; margin-left: 20px; margin-right: 20px; display: flex; align-items: center; } .itemBackColor > *:not(:first-child) { margin-left: 10px; }
添加条件按钮事件

//添加项事件 const addItem = function (item: matchingData) { if (item.matchingWhere.length > maxWhere.value) { ElMessage({ message: "每层下最多添加" + maxWhere.value + "组条件", type: "warning", }); return; } item.matchingWhere.push({ id: randamId().toString(), fieldKey: "", matchEquationKey: "", matchDataKey: "", }); }; // 删除项 const deleteItem = function (item: matchingWhereData, data: matchingData) { GetItemSpliceIndex(item.id, data); }; //递归删除项 const GetItemSpliceIndex = (id: string, list: any) => { //找到删除数据下标 const index = list.matchingWhere.findIndex((p: { id: string }) => { if (p.id === id) { return true; } }); if (index === -1) GetItemSpliceIndex(id, list.children); list.matchingWhere.forEach((f: { id: string }) => { if (f.id == id) { //删除当前项 list.matchingWhere.splice(index, 1); if (list.matchingWhere.length == 0) { var parentGroup = props.data.filter((s) => s.id == list.pid); //当前组下无任何项并且层级不等于1,删除当前组 if (parentGroup.length == 0 && list.level !== 1) { GetGroupSpliceIndex(list.id, props.data); } } } }); };
View Code
查看效果,如下图↓
验证条件是否完整
编写验证方法

//验证条件是否为空 const VerifyWhereEmpty = function () { const isTrueArray = ref<boolean[]>([]); VerifyFunction(props.data, isTrueArray.value); const trueArray = isTrueArray.value?.filter((f) => f === true); if (trueArray.length === 0) { ElMessage({ message: "成功", type: "warning", }); } else { ElMessage({ message: "匹配条件未填写完整", type: "warning", }); } }; //递归验证 const VerifyFunction = function ( list: matchingData[], isTrueArray: boolean[] ) { list.forEach((element) => { element.matchingWhere.forEach((w) => { if ( w.matchEquationKey.length == 0 || w.matchDataKey.length == 0 || w.fieldKey.length == 0 ) { isTrueArray.push(true); return; } }); if (element.children.length > 0) { VerifyFunction(element.children, isTrueArray); } }); };
View Code
在index.vue 页面调用

<template> <div> <el-button type="success" icon="Check" @click="submitForm"> 保存 </el-button> <match :data="pageList" ref="childRef"></match> </div> </template> <script lang="ts" setup> import { matchingData, randamId } from "@/model/match"; import { ref } from "vue"; import match from "../match/match.vue"; //样式 const emit = defineEmits(["validate"]); const pageList = ref<matchingData[]>([ { id: "Group-1", pid: "0", matchGroup: "And", level: 1, matchingWhere: [ { id: randamId().toString(), fieldKey: "", matchEquationKey: "", matchDataKey: "", }, ], children: [], }, ]); //保存 const childRef = ref(); const submitForm = function () { if (childRef.value != null) { childRef.value.VerifyWhereEmpty(); } }; </script>
View Code
做完这些就能达到最终效果
需要源码的,关注公众号,发送【权限】获取源码
以上就是本篇文章的全部内容,感谢耐心观看
后端WebApi 预览地址:http://139.155.137.144:8880/swagger/index.html
前端vue 预览地址:http://139.155.137.144:8881
关注公众号:发送【权限】,获取源码
有兴趣的朋友,请关注我微信公众号吧(*^▽^*)。
关注我:一个全栈多端的宝藏博主,定时分享技术文章,不定时分享开源项目。关注我,带你认识不一样的程序世界