什么!你还不会写Vue组件,编写《功能级权限》匹配公式组件

说明

该文章是属于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

关注公众号:发送【权限】,获取源码

有兴趣的朋友,请关注我微信公众号吧(*^▽^*)。

关注我:一个全栈多端的宝藏博主,定时分享技术文章,不定时分享开源项目。关注我,带你认识不一样的程序世界