Vue + ElementPlus 实现权限管理系统(七): 实现菜单管理功能

本篇文章开始我们便进入了真正的页面开发阶段,首先要做的是一个菜单管理的页面,所实现的功能其实就是对菜单的增删改查功能。效果图如下

Vue + ElementPlus 实现权限管理系统(七): 实现菜单管理功能
image.png

其中条件查询部分使用elementplusel-form组件,先定义一下查询的参数

const queryParams = reactive({
  title"",
  status"",
});
const dickStatus = [
  {
    label"全部",
    value"",
  },
  {
    label"启用",
    value1,
  },
  {
    label"禁用",
    value0,
  },
];

然后模板中使用el-form组件,并绑定相关参数,同时为搜索按钮加上权限system:menu:list,如果没有权限则隐藏该按钮。

<el-form :model="queryParams" ref="queryRef" :inline="true">
  <el-form-item label="菜单名称" prop="menuName">
    <el-input
      v-model="queryParams.title"
      placeholder="请输入菜单名称"
      clearable
    />

  </el-form-item>
  <el-form-item label="状态" prop="status" class="w-[150px]">
    <el-select v-model="queryParams.status" placeholder="菜单状态" clearable>
      <el-option
        v-for="dict in dickStatus"
        :key="dict.value"
        :label="dict.label"
        :value="dict.value"
      />

    </el-select>
  </el-form-item>
  <el-form-item>
    <el-button
      type="primary"
      v-hasPerm="['system:menu:list']"
      icon="Search"
      @click="handleQuery"
      >
搜索</el-button
    >

  </el-form-item>
</el-form>

同样的,表格使用el-table组件,它是支持树类型的数据的显示, 当 row 中包含 children 字段时,被视为树形数据。

<el-table
  :data="tableData"
  style="width: 100%; margin-bottom: 20px"
  row-key="id"
  :default-expand-all="isExpandAll"
>

  <el-table-column prop="title" label="菜单名" />
  <el-table-column prop="order_num" label="排序" />
  <el-table-column prop="path" label="路由" />
  <el-table-column prop="permission" label="权限字段" />
  <el-table-column prop="component" label="组件路径" />
  <el-table-column label="图标" width="80">
    <template #default="scope">
      <component
        v-if="scope.row.icon"
        class="w-[20px] mr-2 ml-1"
        :is="scope.row.icon"
      />

    </template>
  </el-table-column>
  <el-table-column prop="create_time" label="创建时间" />
  <el-table-column prop="status" label="状态" width="80">
    <template #default="scope">
      <el-switch
        @change="changeStatus(scope.row)"
        :model-value="!!scope.row.status"
      >
</el-switch>
    </template>
  </el-table-column>
  <el-table-column
    label="操作"
    align="center"
    width="200"
    class-name="small-padding fixed-width"
  >

    <template #default="scope">
      <el-link
        type="primary"
        icon="Plus"
        class="mr-1"
        @click="handleAdd(scope.row)"
        v-hasPerm="['system:menu:add']"
        >
新增</el-link
      >

      <el-link
        type="primary"
        icon="Edit"
        class="mr-1"
        @click="handleUpdate(scope.row)"
        v-hasPerm="['system:menu:edit']"
        >
修改</el-link
      >

      <el-link
        type="danger"
        icon="Delete"
        @click="handleDelete(scope.row)"
        v-hasPerm="['system:menu:delete']"
        >
删除</el-link
      >

    </template>
  </el-table-column>
</el-table>

同时为新增,修改,删除按钮加上权限,如果没有权限则隐藏该按钮。然后我们需要在api/menu/index.ts定义一下这些接口

//新增菜单
export const addMenu = (data: any) => {
  return request({
    url"/menu/createMenu",
    data,
    method"post",
  });
};

//获取菜单列表
export const getMenuList = (query: any) => {
  return request({
    url"/menu/list",
    method"get",
    params: query,
  });
};

//删除菜单
export const deleteMenu = (menuId: number | number[]) => {
  return request({
    url`/menu/deleteMenu/${menuId}`,
    method"delete",
  });
};
//更新菜单
export const updateMenu = (data: any) => {
  return request({
    url`/menu/updateMenu`,
    method"put",
    data,
  });
};

接下来我们就来实现这些增删改查功能。

查询菜单

查询功能很简单直接调用查询接口然后给表格数据赋值即可

const tableData = ref<MenuList[]>([]);
const getList = async () => {
  const { data } = await getMenuList(queryParams);
  tableData.value = data;
};

这里查询条件为空默认是查询所有的菜单,如果有条件则查询符合条件的菜单。其中菜单名的查询是模糊查询。我们用管理员账户登录测试一下。注意测试之前需要将按钮的权限添加到菜单表中,或者先先把指令去掉。如system:menu:list按钮权限,这里后面会实现

Vue + ElementPlus 实现权限管理系统(七): 实现菜单管理功能
image.png

然后点击输入一个菜单名称然后点击搜索按钮,可以看到查询到了符合条件的菜单。

Vue + ElementPlus 实现权限管理系统(七): 实现菜单管理功能
image.png

新增和修改菜单

新增和修改菜单需要用弹出一个对话框,这里使用Dialog组件,同时根据菜单类型来显示不同的字段。并且使用rules规定了其中一些字段的规则

<el-dialog
  v-model="dialogVisible"
  :title="isUpdate ? '修改菜单' : '新增菜单'"
  width="670px"
>

  <el-form :model="form" ref="ruleFormRef" label-width="120px" :rules="rules">
    <el-row>
      <el-col :span="24">
        <el-form-item label="上级菜单">
          <el-cascader
            v-model="form.parent_id"
            :show-all-levels="false"
            @change="handleMenuChange"
            :options="tableData"
            placeholder="请选择菜单"
            checkStrictly
            :props="defaultProps"
            clearable
          />

        </el-form-item>
      </el-col>
      <el-col :span="24">
        <el-form-item label="菜单类型" prop="menu_type">
          <el-radio-group v-model="form.menu_type">
            <el-radio :value="1">目录</el-radio>
            <el-radio :value="2">菜单</el-radio>
            <el-radio :value="3">按钮</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="菜单名称" prop="title">
          <el-input v-model="form.title" placeholder="请输入菜单名称" />
        </el-form-item>
      </el-col>
      <el-col :span="12" v-show="form.menu_type === 2">
        <el-form-item label="组件路径">
          <el-input v-model="form.component" placeholder="请输入组件路径" />
        </el-form-item>
      </el-col>
      <el-col :span="24" v-show="form.menu_type !== 3">
        <el-form-item class="relative" label="图标">
          <el-input
            :prefix-icon="form.icon"
            @click="showIconView = true"
            v-model="form.icon"
            readonly
            placeholder="请选择图标"
          />

          <div
            v-if="showIconView"
            class="border absolute top-10 bg-[#fff] z-10 border-solid p-4 h-[200px] overflow-y-scroll"
          >

            <selectIcon @change="getMenuIconName" />
          </div>
        </el-form-item>
      </el-col>
      <el-col :span="12" v-show="form.menu_type !== 1">
        <el-form-item>
          <el-input
            v-model="form.permission"
            placeholder="请输入权限标识"
            maxlength="100"
          />

          <template #label>
            <span>
              <el-tooltip
                content="控制器中定义的权限字符,如: @Permissions('system:menu:list')"
              >

                <component class="w-[18px] inline" is="Warning" />
              </el-tooltip>
              权限字符
            </span>
          </template>
        </el-form-item>
      </el-col>
      <el-col :span="12" v-if="form.menu_type !== 3">
        <el-form-item label="路由地址" prop="path">
          <el-input v-model="form.path" placeholder="请输入路由地址" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="排序">
          <el-input v-model="form.order_num" type="number" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="显示状态">
          <el-radio-group v-model="form.status">
            <el-radio :value="1">启用</el-radio>
            <el-radio :value="0">不启用</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-col>
      <el-col :span="12" v-show="form.menu_type === 2">
        <el-form-item label="是否缓存">
          <el-radio-group v-model="form.catch">
            <el-radio :value="1">缓存</el-radio>
            <el-radio :value="0">不缓存</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-col>
    </el-row>
  </el-form>
  <template #footer>
    <span class="dialog-footer">
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" @click="handelMenuList(ruleFormRef)">
        确定
      </el-button>
    </span>
  </template>
</el-dialog>

图标选择的时候需要使用selectIcon组件,这是一个自定义的组件如下

<template>
<div class="flex flex-wrap">
<div
v-for="item in ElementPlusIconsVue"
@click="emits('change', item.name)"
class="p-3 border border-solid cursor-pointer"
:key="item.name"
>
<component class="w-5" :is="item.name" />
</div>
</div>
</template>

<script lang="ts" setup>
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
type Emits = {
(e: "change", val: string): void;
};
const emits = defineEmits<Emits>();
</script>

其中每次新增和修改菜单的时候需要将form表单重置一下,然后根据isUpdate的值来判断是新增还是修改。

const rules = ref({
  title: [{ requiredtruemessage"菜单名称不能为空"trigger"blur" }],
  path: [{ requiredtruemessage"路由地址不能为空"trigger"blur" }],
});
//是否显示图标
const showIconView = ref(false);
const isUpdate = ref(false);
interface MenuForm {
  title: string;
  path: string;
  parent_id: any;
  component: string;
  order_num: number;
  icon: string;
  id: number | null;
  menu_type: number;
  permission: string;
  status: number;
  catch: number;
}
const form = ref<MenuForm>({} as MenuForm);
//新增
const handleAdd = (row?: MenuForm) => {
  resetForm();
  if (row?.id) {
      //如果选择从列表里新增,赋值父级菜单id
    form.value.parent_id = row.id;
  }
  dialogVisible.value = true;
  isUpdate.value = false;
};
//编辑
const handleUpdate = (row: MenuForm) => {
  resetForm();
  isUpdate.value = true;
  form.value = deepClone(row);
  dialogVisible.value = true;
};
//重置表单
const resetForm = () => {
  form.value = {
    title"",
    path"",
    parent_idnull,
    component"",
    order_num0,
    icon"",
    idnull,
    menu_type2,
    permission"",
    status1,
    catch0,
  };
};

const handelMenuList = async (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  await formEl.validate(async (valid, fields) => {
    if (valid) {
      dialogVisible.value = false;
      if (isUpdate.value) {
        await updateMenu(form.value);
        ElMessage({
          type"success",
          message"修改成功",
        });
        handleQuery();
        return;
      }

      await addMenu(form.value);
      ElMessage({
        type"success",
        message"新增成功",
      });
      handleQuery();
    } else {
      console.log("error submit!", fields);
    }
  });

  //获取选择菜单图标名称
const getMenuIconName = (menuName: string) => {
  form.value.icon = menuName;
  showIconView.value = false;
};
};
//选择父级菜单
const handleMenuChange = (val: any) => {
  form.value.parent_id = val[val.length - 1];
};

//更新状态
const changeStatus = async (row: MenuForm) => {
  const uptateRow: MenuForm = {} as MenuForm;
  uptateRow.status = row.status === 1 ? 0 : 1;
  uptateRow.id = row.id;

  await updateMenu(uptateRow);
  ElMessage({
    type"success",
    message"状态更新成功",
  });
  getList();
};

最后看一下效果

Vue + ElementPlus 实现权限管理系统(七): 实现菜单管理功能
image.png

删除菜单

最后看一下删除菜单,这里实现的逻辑是:如果删除的菜单有子菜单则会将其下的所有子菜单删除,所以我们传入的是一些 id 的数组。这时候当我们删除的菜单有子菜单的时候就需要将子菜单的 id 和自身的 id 都传入删除接口。因此我们用递归的方式先实现一个获取自身及子菜单 id 的函数

//获取自身及子菜单id
const getTreesId = (row: MenuList): number[] => {
  const ids: number[] = [];
  ids.push(row.id);
  if (row.children) {
    row.children.forEach((item: MenuList) => {
      if (item.children) {
        const childIds = getTreesId(item);
        ids.push(...childIds);
      } else {
        ids.push(item.id);
      }
    });
  }
  return ids;
};

然后删除的时候用这个函数处理一下即可

//删除
const handleDelete = async (row: MenuList) => {
  await ElMessageBox.confirm("确认删除此条菜单及子菜单吗?""提示", {
    confirmButtonText"确定",
    cancelButtonText"取消",
  });
  const ids = getTreesId(row);
  await deleteMenu(ids);
  ElMessage({
    type"success",
    message"删除成功",
  });
  handleQuery();
};

到这里我们便完成了菜单管理的所有功能

源码地址( https://github.com/qddidi/fs-admin) 各位看官们可以去下载源码,喜欢的话可以给个 star 哦!



原文始发于微信公众号(web前端进阶):Vue + ElementPlus 实现权限管理系统(七): 实现菜单管理功能

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/305612.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!