说明
该文章是属于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
关注公众号:发送【权限】,获取源码
有兴趣的朋友,请关注我微信公众号吧(*^▽^*)。
关注我:一个全栈多端的宝藏博主,定时分享技术文章,不定时分享开源项目。关注我,带你认识不一样的程序世界