Procházet zdrojové kódy

权限管理页面及api对接

lfeish před 1 rokem
rodič
revize
cc17991a44

+ 44 - 0
src/api/system/permission.ts

@@ -0,0 +1,44 @@
+import { http } from '@/utils/http/axios';
+import { PermissionTree, PermissionItem } from '@/types/permission/type';
+
+/**
+ * 获取整个权限树
+ */
+export function getPermissionTree() {
+  return http.request<PermissionTree>({
+    url: '/admin/perm/queryAllPermissionTree',
+    method: 'post'
+  });
+};
+
+/**
+ * 添加权限
+ */
+export function addPermission(data: PermissionItem) {
+  return http.request({
+    url: '/admin/perm/savePermission',
+    method: 'POST',
+    data,
+  });
+}
+
+/**
+ * 修改权限
+ */
+export function updatePermission(data: PermissionItem) {
+  return http.request({
+    url: '/admin/perm/updatePermission',
+    method: 'POST',
+    data,
+  });
+}
+
+/**
+ * 删除权限
+ */
+export function deletePermission(permissionId: number) {
+  return http.request({
+    url: `/admin/perm/deletePermission?permId=${permissionId}`,
+    method: 'POST',
+  });
+}

+ 44 - 0
src/types/permission/type.ts

@@ -0,0 +1,44 @@
+/**
+ * 权限项 详细信息
+ */
+export interface PermissionItem {
+  // 权限ID
+  id: number | null;
+  // 上级ID
+  parentId: number | null;
+  // 权限编码
+  permissionCode: string;
+  // 权限名字
+  permissionName: string;
+  // 排序
+  orderNum: number;
+  // 是否禁用, 0 - 禁用, 1 - 启用
+  isDisabled: 0 | 1;
+  // 是否删除  
+  isDeleted?: number;
+  // 创建日期
+  createdAt?: string;
+  // 创建人
+  createdBy?: string;
+  // 更新日期
+  updatedAt?: string;
+  // 更新人
+  updatedBy?: string;
+}
+
+// 权限是可以嵌套的
+export type Permission = PermissionItem & { children?: PermissionItem[] | null };
+// 权限树
+export type PermissionTree = Permission[];
+
+/**
+ * 权限显示项目,用于生成 el-tree
+ */
+export interface PermissionViewItem {
+  label: string;
+  value: number | string;
+}
+
+export type PermissionView = PermissionViewItem & { children?: PermissionViewItem[] | null };
+export type PermissionViewTree = PermissionView[];
+

+ 63 - 0
src/views/system/permission/CreateDrawer.vue

@@ -0,0 +1,63 @@
+<template>
+  <el-drawer v-model="drawerVisible" :size="width" :title="title" @close="handleReset">
+    <PermissionForm ref="formInstance" :permissionList="props.permissionList" @change="handleFormChange" />
+
+    <template #footer>
+      <el-space>
+        <el-button type="primary" :loading="subLoading" @click="formSubmit">提交</el-button>
+        <el-button @click="handleReset">重置</el-button>
+      </el-space>
+    </template>
+  </el-drawer>
+</template>
+
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import PermissionForm from './PermissionForm.vue';
+
+  const props = defineProps({
+    title: {
+      type: String,
+      default: '添加权限',
+    },
+    width: {
+      type: Number,
+      default: 580,
+    },
+    permissionList: {
+      type: Array,
+    },
+  })
+
+  const emit = defineEmits(['change']);
+
+  const formInstance = ref<InstanceType<typeof PermissionForm>>();
+  const drawerVisible = ref(false);
+  const subLoading = ref(false);
+
+  function openDrawer() {
+    drawerVisible.value = true;
+  }
+
+  function closeDrawer() {
+    drawerVisible.value = false;
+  }
+
+  function formSubmit() {
+    formInstance.value?.formSubmit();
+  }
+
+  function handleReset() {
+    formInstance.value?.handleReset();
+  }
+
+  function handleFormChange() {
+    closeDrawer();
+    emit('change');
+  }
+
+  defineExpose({
+    openDrawer,
+    closeDrawer,
+  });
+</script>

+ 238 - 0
src/views/system/permission/PagePermission.vue

@@ -0,0 +1,238 @@
+<template>
+  <div class="page-permission">
+    <el-row :gutter="12">
+      <!-- 左侧权限树 -->
+      <el-col :span="6">
+        <el-card shadow="hover">
+          <template #header>
+            <el-space>
+              <el-button type="primary" icon-placement="left" @click="openCreateDrawer">
+                添加权限
+                <template #icon>
+                  <div class="flex items-center">
+                    <el-icon size="14">
+                      <FileAddOutlined />
+                    </el-icon>
+                  </div>
+                </template>
+              </el-button>
+
+              <el-button type="primary" plain icon-placement="left" @click="packHandle">
+                全部{{ expandedKeys.length ? '收起' : '展开' }}
+                <template #icon>
+                  <div class="flex items-center">
+                    <el-icon size="14">
+                      <AlignLeftOutlined />
+                    </el-icon>
+                  </div>
+                </template>
+              </el-button>
+            </el-space>
+          </template>
+
+          <div class="w-full">
+            <div v-loading="loading">
+              <el-scrollbar height="620px">
+                <el-tree
+                  ref="treeRef"
+                  :data="permissionViewTree"
+                  nodeKey="value"
+                  highlight-current
+                  check-strictly
+                  @current-change="onSelectTreeNode"
+                  @update:expanded-keys="onExpandedKeys"
+                />
+              </el-scrollbar>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+
+      <!-- 右侧编辑权限 -->
+      <el-col :span="18">
+        <el-card shadow="hover">
+          <template #header>
+            <div class="flex justify-between">
+              <el-space>
+                <span>编辑权限: </span>
+                <span>{{ treeItemTitle }}</span>
+              </el-space>
+              <el-popconfirm v-if="isEditing" :title="`确定要删除${treeItemTitle}吗?`" width="200" @confirm="handleDeletePermission">
+                <template #reference>
+                  <el-button type="danger" size="small">删除权限</el-button>
+                </template>
+              </el-popconfirm>
+            </div>
+          </template>
+          <!-- 表单 -->
+          <div class="pt-6">
+            <PermissionForm
+              v-show="selectedPermissionId != null"
+              ref="formInstance"
+              :permissionList="permissionViewTree"
+              @change="handleChangePermssion"
+              isShowSubmit
+              class="w-2/3 ml-10"
+            />
+            <el-empty v-show="selectedPermissionId == null" description="从左侧列表选择一项后,进行编辑" />
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 添加新权限 -->
+    <CreateDrawer
+      ref="drawerInstance"
+      :permissionList="permissionViewTree"
+      @change="handleChangePermssion"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, shallowRef, onMounted } from 'vue';
+  import PermissionForm from './PermissionForm.vue';
+  import CreateDrawer from './CreateDrawer.vue';
+  import { AlignLeftOutlined, FileAddOutlined } from '@vicons/antd';
+  import { PermissionTree, PermissionView, PermissionViewItem, PermissionViewTree, Permission } from '@/types/permission/type';
+  import { getPermissionTree, deletePermission } from '@/api/system/permission';
+  import { getTreeItem } from '@/utils';
+  import { cloneDeep } from 'lodash-es';
+import { ElMessage } from 'element-plus';
+  
+
+  // 左侧权限树相关
+  const expandedKeys = ref<PermissionViewItem['value'][]>([]);
+  const loading = ref(true);
+  const treeRef = ref();
+  const permissionTree = shallowRef<PermissionTree>([]);
+  const permissionViewTree = shallowRef<PermissionViewTree>([]);
+
+  // 右侧编辑菜单相关
+  const treeItemTitle = ref('');
+  const isEditing = ref(false);
+  const selectedPermissionId = ref<number | null>(null);
+  const formInstance = ref<InstanceType<typeof PermissionForm>>();
+
+  // 抽屉相关
+  const drawerInstance = ref<InstanceType<typeof CreateDrawer>>();
+
+  const queryPermissionTree = async () => {
+    loading.value = true;
+    try {
+      permissionTree.value = await getPermissionTree();
+      permissionViewTree.value = transformToViewTree(permissionTree.value);
+    } catch (e) {
+      console.error(e);
+    } finally {
+      loading.value = false;
+    }
+  };
+
+  const transformToViewTree = (tree: PermissionTree | null): PermissionViewTree => {
+    if (tree == null) return [];
+
+    const viewTree: PermissionViewTree = [];
+    for (const item of tree) {
+      const viewItem: PermissionView = {
+        value: item.id!,
+        label: item.permissionName,
+      };
+
+      if (Array.isArray(item.children) && item.children.length > 0) {
+        viewItem.children = transformToViewTree(item.children);
+        viewTree.push(viewItem);
+      } else {
+        viewTree.push(viewItem);
+      }
+    }
+
+    return viewTree;
+  };
+
+  const onSelectTreeNode = (data: PermissionView, node: any) => {
+    console.log(node);
+
+    const permissionId = data.value as number;
+    selectedPermissionId.value = permissionId;
+    isEditing.value = true;
+    treeItemTitle.value = data.label;
+      
+    const treeItem: Permission = getTreeItem(permissionTree.value, permissionId, 'id');
+    const permission = cloneDeep(treeItem);
+    delete permission.children;
+    formInstance.value?.setData(permission);
+  };
+
+  /**
+   * 左侧权限树 展开 / 收起
+   */
+  const packHandle = () => {
+    if (!expandedKeys.value.length) {
+      treeNodeExpand(true);
+      expandedKeys.value = permissionViewTree.value?.map((item) => item.value);
+    } else {
+      expandedKeys.value = [];
+      treeNodeExpand(false);
+    }
+  };
+
+  const treeNodeExpand = (status) => {
+    for (var i = 0; i < treeRef.value.store._getAllNodes().length; i++) {
+      treeRef.value.store._getAllNodes()[i].expanded = status;
+    }
+  };
+
+  const onExpandedKeys = (keys) => {
+    expandedKeys.value = keys;
+  };
+
+
+  function clearEditing() {
+    isEditing.value = false;
+    treeItemTitle.value = '';
+    selectedPermissionId.value = null;
+  }
+
+  /**
+   * 编辑权限,或者 新建权限 后的 change 事件处理
+   */
+  const handleChangePermssion = () => {
+    clearEditing();
+    queryPermissionTree();
+  };
+
+
+  /**
+   * 不允许删除父节点
+   */
+  const handleDeletePermission = async () => {
+    const treeItem: Permission = getTreeItem(permissionTree.value, selectedPermissionId.value!, 'id');
+    if (treeItem.children && treeItem.children.length > 0) {
+      ElMessage.error('当前权限含有子权限,无法删除');
+      return;
+    }
+
+    try {
+      await deletePermission(selectedPermissionId.value!);
+      clearEditing();
+      queryPermissionTree();
+    } catch(e) {
+      console.error(e);
+    }
+  }
+
+  function openCreateDrawer() {
+    drawerInstance.value?.openDrawer();
+  }
+
+  onMounted(() => {
+    queryPermissionTree();
+  });
+</script>
+
+<style scoped>
+  .page-permission {
+    height: calc(100vh - 100px);
+  }
+</style>

+ 209 - 0
src/views/system/permission/PermissionForm.vue

@@ -0,0 +1,209 @@
+<template>
+  <el-form
+    :model="formParams"
+    :rules="rules"
+    ref="formRef"
+    label-placement="left"
+    :label-width="100"
+    require-mark-placement="left"
+  >
+    <el-divider border-style="dashed" class="mt-0 mb-10">基本设置</el-divider>
+
+    <el-form-item label="上级目录" prop="parentId">
+      <el-tree-select
+        rowKey="key"
+        :data="permissionTree"
+        clearable
+        check-strictly
+        v-model="formParams.parentId"
+      />
+    </el-form-item>
+
+    <el-row :gutter="24">
+      <!-- 权限名称 -->
+      <el-col :span="12">
+        <el-form-item prop="permissionName">
+          <template #label>
+            <div class="flex items-center">
+              <el-tooltip trigger="hover">
+                <el-icon :size="18" class="mr-1 text-gray-400 cursor-pointer">
+                  <QuestionCircleOutlined />
+                </el-icon>
+                <template #content>
+                  权限名称 对应 权限标识 `中文名称`
+                </template>
+              </el-tooltip>
+              <span>权限名称</span>
+            </div>
+          </template>
+          <el-input placeholder="权限名称" v-model="formParams.permissionName" />
+        </el-form-item>
+      </el-col>
+
+      <!-- 权限标识 -->
+      <el-col :span="12">
+        <el-form-item prop="permissionCode">
+          <template #label>
+            <div class="flex items-center">
+              <el-tooltip trigger="hover">
+                <el-icon :size="18" class="mr-1 text-gray-400 cursor-pointer">
+                  <QuestionCircleOutlined />
+                </el-icon>
+                <template #content>
+                  权限标识,也是权限字符,比如 `system:menu:list`
+                </template>
+              </el-tooltip>
+              <span>权限标识</span>
+            </div>
+          </template>
+          <el-input placeholder="权限标识" v-model="formParams.permissionCode" />
+        </el-form-item>
+      </el-col>
+    </el-row>
+
+    <el-row :gutter="24">
+      <el-col :span="12">
+        <el-form-item label="显示排序" prop="orderNum">
+          <el-input-number placeholder="显示排序" v-model="formParams.orderNum" class="w-full" />
+        </el-form-item>
+      </el-col>
+    </el-row>
+
+    <el-divider border-style="dashed" class="mb-10">功能设置</el-divider>
+
+    <el-row :gutter="24">
+      <el-col :span="12">
+        <el-form-item label="权限状态" prop="isDisabled">
+          <el-radio-group v-model="formParams.isDisabled">
+            <el-radio-button label="正常" :value="0" />
+            <el-radio-button label="停用" :value="1" />
+          </el-radio-group>
+        </el-form-item>
+      </el-col>
+    </el-row>
+
+    <el-form-item v-if="isShowSubmit">
+      <el-button type="primary" :loading="subLoading" @click="formSubmit">保存修改</el-button>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+  import { ref, computed } from 'vue';
+  import { ElMessage, FormInstance, FormRules } from 'element-plus';
+  import { addPermission, updatePermission } from '@/api/system/permission';
+  import { QuestionCircleOutlined } from '@vicons/antd';
+  import { PermissionItem } from '@/types/permission/type';
+
+  const emit = defineEmits(['change']);
+
+  const props = defineProps({
+    permissionList: {
+      type: Array,
+      defalut: () => [],
+    },
+    isShowSubmit: {
+      type: Boolean,
+      defalut: false,
+    },
+  });
+
+  const formRef = ref<FormInstance>();
+  const subLoading = ref(false);
+
+  const defaultFormParams: PermissionItem = {
+    id: null,
+    parentId: null,
+    permissionCode: '',
+    permissionName: '',
+    orderNum: 0,
+    isDisabled: 0,
+  };
+
+  const formParams = ref({ ...defaultFormParams });
+
+  const permissionTree = computed(() => {
+    // 可根据需要是否需要嵌套这个 避免出现选择器出现 0 的情况
+    return [
+      {
+        label: '顶级目录',
+        value: 0,
+        children: props.permissionList || [],
+      },
+    ];
+  });
+
+  const rules: FormRules = {
+    parentId: {
+      required: true,
+      message: '上级目录',
+      trigger: 'change',
+    },
+    permissionName: {
+      required: true,
+      message: '权限名称',
+      trigger: 'blur',
+    },
+    permissionCode: {
+      required: true,
+      message: '权限标识',
+      trigger: 'blur',
+    },
+  };
+
+  function setData(data: PermissionItem) {
+    formParams.value = data;
+    formRef.value?.resetFields();
+  }
+
+  function formSubmit() {
+    subLoading.value = true;
+    formRef.value?.validate((valid) => {
+      if (valid) {
+        if (!formParams.value.id) {
+          addPermission(formParams.value)
+            .then(() => {
+              subLoading.value = false;
+              ElMessage.success('添加成功');
+              handleReset();
+              emit('change');
+            })
+            .catch(() => {
+              subLoading.value = false;
+            });
+        } else {
+          updatePermission(formParams.value)
+            .then(() => {
+              subLoading.value = false;
+              ElMessage.success('修改成功');
+              handleReset();
+              emit('change');
+            })
+            .catch(() => {
+              subLoading.value = false;
+            });
+        }
+      } else {
+        subLoading.value = false;
+        ElMessage.error('请填写完整信息');
+      }
+    });
+  }
+
+  function handleReset() {
+    formRef.value?.resetFields();
+    formParams.value = Object.assign(formParams.value, defaultFormParams);
+  }
+
+  defineExpose({
+    formSubmit,
+    handleReset,
+    setData,
+  });
+</script>
+
+<style lang="scss" sctep>
+  .submit-form-item {
+    margin-left: 100px;
+  }
+</style>