Эх сурвалжийг харах

菜单管理,角色管理部分

zouzs 4 долоо хоног өмнө
parent
commit
05d662668a

+ 27 - 0
src/api/system/role.ts

@@ -0,0 +1,27 @@
+import {http} from "@/utils/http";
+import {baseUrlApi} from "../utils";
+
+export interface BasicResponseModel<T = any> {
+    code: number;
+    msg: string;
+    data: T;
+}
+
+export interface BasicPageResponseModel<T = any> {
+    code: number;
+    msg: string;
+    rows: T;
+    total: number;
+}
+
+export const getSystemRoleList = (query?: object) => {
+    return http.request<BasicPageResponseModel>("post", baseUrlApi("/system/role/list"), {params: query});
+};
+
+export const changeRoleStatus = (roleId: number, status: string) => {
+    const data = {
+        roleId,
+        status
+    };
+    return http.request<BasicResponseModel>("put", baseUrlApi("/system/role/changeStatus"), {data});
+}

+ 9 - 2
src/utils/http/index.ts

@@ -137,8 +137,8 @@ class PureHttp {
                     PureHttp.initConfig.beforeResponseCallback(response);
                     return response.data;
                 }
-                console.log('111111', response.data);
-                if (response.data.code === -10) {
+                console.log('111111', response);
+                if (response.data.code === 401) {
                     console.log('用户未登录')
                     // 用户未登录
                     useUserStoreHook().logOut();
@@ -149,6 +149,13 @@ class PureHttp {
             (error: PureHttpError) => {
                 const $error = error;
                 $error.isCancelRequest = Axios.isCancel($error);
+                console.log('111111', $error);
+                if ($error.status === 401) {
+                    console.log('登录状态已过期')
+                    // 用户未登录
+                    useUserStoreHook().logOut();
+                    return Promise.reject(new Error("登录状态已过期"));
+                }
                 // 关闭进度条动画
                 NProgress.done();
                 // 所有的响应异常 区分来源为取消请求/非取消请求

+ 8 - 7
src/views/system/menu/index.vue

@@ -7,7 +7,8 @@
         :before-search-submit="handleBeforeSearch"
         :is-card="true"
         :search="{
-          labelWidth: 100
+          labelWidth: 100,
+          showNumber: 3
         }"
         :table="{
           actionBar: { buttons, type: 'link', width: 140 },
@@ -20,7 +21,7 @@
     >
       <template #table-title>
         <el-row class="button-row">
-          <el-button size="default" type="success" @click="handleCreate"> 添加</el-button>
+          <el-button size="default" type="success" @click="handleCreate">新增</el-button>
         </el-row>
       </template>
     </PlusPage>
@@ -52,7 +53,6 @@ import {
 } from "plus-pro-components";
 import {cloneDeep} from "lodash-es";
 import {ElMessage} from "element-plus";
-import ExportView from "@/components/ExportView/index.vue";
 import {
   addMenu,
   deleteMenu,
@@ -146,7 +146,6 @@ const systemList = ref([]);
 const tableConfig: PlusColumn[] = [
   {
     label: "菜单名称",
-    tooltip: "名称最多显示6个字符",
     prop: "menuName",
     width: 180,
     tableColumnProps: {
@@ -205,6 +204,7 @@ const tableConfig: PlusColumn[] = [
     prop: "systemCode",
     valueType: "select",
     options: computed(() => systemList.value),
+    hideInTable: true,
     fieldProps: {
       clearable: true,
       placeholder: '请选择系统',
@@ -216,6 +216,7 @@ const tableConfig: PlusColumn[] = [
     prop: "status",
     valueType: "select",
     options: [{label: '正常', value: '0'}, {label: '停用', value: '1'}],
+    hideInTable: true,
     fieldProps: {
       clearable: true,
       placeholder: '请选择菜单状态',
@@ -408,7 +409,7 @@ const columns: PlusColumn[] = [
       placeholder: '请输入路由地址',
       clearable: true
     },
-    tooltip: "访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头",
+    tooltip: "访问的路由地址,如:`role`,如外网地址需内链访问则以`http(s)://`开头",
     hideInForm: computed(() => form.value.menuType === 'F')
   },
   {
@@ -419,7 +420,7 @@ const columns: PlusColumn[] = [
       placeholder: '请输入组件路径',
       clearable: true
     },
-    tooltip: "访问的组件路径,如:`system/user/index`,默认在`views`目录下",
+    tooltip: "访问的组件路径,如:`system/role/index`,默认在`views`目录下",
     hideInForm: computed(() => form.value.menuType !== 'C')
   },
   {
@@ -430,7 +431,7 @@ const columns: PlusColumn[] = [
       placeholder: '请输入权限标识',
       clearable: true
     },
-    tooltip: "控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasPermi('system:user:list')`)",
+    tooltip: "控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasPermi('system:role:list')`)",
     hideInForm: computed(() => form.value.menuType === 'M')
   },
   {

+ 572 - 0
src/views/system/role/index.vue

@@ -0,0 +1,572 @@
+<template>
+  <div>
+    <PlusPage
+        ref="plusPageInstance"
+        :columns="tableConfig"
+        :request="getList"
+        :before-search-submit="handleBeforeSearch"
+        :is-card="true"
+        :search="{
+          labelWidth: 100,
+          showNumber: 3
+        }"
+        :table="{
+          actionBar: { buttons, type: 'link', width: 180, showNumber: 2 },
+          adaptive: { offsetBottom: 50 },
+          onSelectionChange: handleSelect,
+          onFormChange: handleTableChange
+        }"
+    >
+      <template #table-title>
+        <el-row class="button-row">
+          <el-button size="default" type="success" @click="handleCreate">新增</el-button>
+        </el-row>
+      </template>
+    </PlusPage>
+    <!-- 弹窗编辑 -->
+    <PlusDialogForm
+        ref="dialogForm"
+        v-model="form"
+        v-model:visible="dialogVisible"
+        @confirm="handleSubmit"
+        @submit-error="handleSubmitError"
+        @close="handleClose"
+        :form="{ columns, labelPosition: 'right',labelWidth: 100, rules, rowProps: {gutter: 20}, colProps: {span: 12} }"
+        :dialog="{ title: dialogTitle + '菜单', width: 800, confirmLoading }"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {computed, defineOptions, nextTick, reactive, ref, toRefs} from "vue";
+import type {FormRules} from 'element-plus'
+import {ElMessage, ElMessageBox} from "element-plus";
+import {
+  type FieldValues,
+  type PlusColumn,
+  PlusDialogForm,
+  PlusPage,
+  PlusPageInstance,
+  useTable
+} from "plus-pro-components";
+import {cloneDeep} from "lodash-es";
+import {changeRoleStatus, getSystemRoleList} from "@/api/system/role";
+
+defineOptions({
+  name: "PageTable"
+});
+
+interface TableRow {
+  id: number;
+  name: string;
+  status: string;
+  tag: string;
+  time: Date;
+}
+
+// 菜单列表
+const menuList = ref([]);
+const plusPageInstance = ref<PlusPageInstance | null>(null)
+
+const getList = async (query: Record<string, any>) => {
+  let res = await getSystemRoleList(query);
+  return {
+    data: res.rows,
+    total: res.total
+  }
+};
+
+// 重新请求列表接口
+const refresh = () => {
+  plusPageInstance.value?.getList()
+}
+
+// 搜索之前函数
+const handleBeforeSearch = (values: any) => {
+  // 返回新的参数
+  return cloneDeep(values);
+};
+
+const dialogTitle = computed(() => (state.isCreate ? '新增' : '编辑'))
+const {buttons} = useTable<TableRow[]>();
+
+const systemList = ref([]);
+
+// 表格数据
+const tableConfig: PlusColumn[] = [
+  {
+    label: "角色编号",
+    prop: "roleId",
+    hideInSearch: true,
+  },
+  {
+    label: "角色名称",
+    prop: "roleName",
+  },
+  {
+    label: "权限字符",
+    prop: "roleKey"
+  },
+  {
+    label: "显示顺序",
+    prop: "roleSort",
+    hideInSearch: true,
+  },
+  {
+    label: "组织ID",
+    prop: "deptId",
+    hideInTable: true,
+    valueType: "tree-select",
+    fieldProps: {
+
+    }
+  },
+  {
+    label: "状态",
+    prop: "status",
+    valueType: "select",
+    fieldProps: {
+      options: [
+        {label: '正常', value: '0'},
+        {label: '停用', value: '1'}
+      ]
+    },
+    hideInTable: true
+  },
+  {
+    label: "状态",
+    prop: "status",
+    valueType: 'switch',
+    hideInSearch: true,
+    fieldProps: {
+      placeholder: '角色状态',
+      activeValue: "0",
+      inactiveValue: "1",
+    },
+    editable: true,
+    colProps: {span: 8}
+  },
+  {
+    label: "创建时间",
+    prop: "createTime",
+    valueType: 'date-picker',
+    fieldProps: {
+      type: 'daterange',
+      startPlaceholder: '开始日期',
+      endPlaceholder: '结束日期',
+      style: {width: '100%'},
+      unlinkPanels: true,
+      format: 'YYYY-MM-DD'
+    },
+    hideInTable: true,
+    colProps: {span: 16}
+  }
+];
+
+/*--------------------表单--------------------*/
+
+const dialogForm = ref(null);
+
+interface State {
+  dialogVisible: boolean;
+  detailsVisible: boolean;
+  confirmLoading: boolean;
+  selectedIds: number[];
+  isCreate: boolean;
+  form: {
+    component: string, // 组件路径
+    icon: string, // 菜单图标
+    isCache: string, // 是否缓存 (0缓存 1不缓存)
+    isFrame: string, // 是否外链 (0是 1否)
+    menuName: string,// 菜单名称
+    menuType: string, // 菜单类型(M目录 C菜单 F按钮)
+    orderNum: string,// 显示排序
+    parentId: number,// 上级菜单
+    path: string, // 路由地址
+    perms: string, // 权限标识
+    query: string, // 路由参数
+    routeName: string, // 路由名称
+    status: string, // 菜单状态(0正常 1停用)
+    systemCode: string, // 系统编码
+    url: string, // 网关URL
+    urlmatch: string, // 网关权限
+    visible: string, // 显示状态(0显示 1隐藏)
+  };
+  rules: FormRules;
+}
+
+const state = reactive<State>({
+  dialogVisible: false,
+  detailsVisible: false,
+  confirmLoading: false,
+  selectedIds: [],
+  isCreate: false,
+  form: {
+    component: null, // 组件路径
+    icon: null, // 菜单图标
+    isCache: '0', // 是否缓存 (0缓存 1不缓存)
+    isFrame: '1', // 是否外链 (0是 1否)
+    menuName: null,// 菜单名称
+    menuType: 'M', // 菜单类型(M目录 C菜单 F按钮)
+    orderNum: null,// 显示排序
+    parentId: 0,// 上级菜单
+    path: null, // 路由地址
+    perms: null, // 权限标识
+    query: null, // 路由参数
+    routeName: null, // 路由名称
+    status: '0', // 菜单状态(0正常 1停用)
+    systemCode: null, // 系统编码
+    url: null, // 网关URL
+    urlmatch: null, // 网关权限
+    visible: '0', // 显示状态(0显示 1隐藏)
+  },
+  rules: {
+    parentId: [{required: true, message: '请选择上级菜单', trigger: 'blur'}],
+    orderNum: [{required: true, message: '请输入排序', trigger: 'blur'}],
+    menuName: [{required: true, message: '请输入菜单名称', trigger: 'blur'}],
+    path: [{required: true, message: '请输入路由地址', trigger: 'blur'}],
+  }
+})
+
+const columns: PlusColumn[] = [
+  {
+    label: '上级菜单',
+    prop: 'parentId',
+    colProps: {span: 24},
+    valueType: 'tree-select',
+    fieldProps: {
+      placeholder: '请选择上级菜单',
+      clearable: true,
+      showSearch: true,
+      checkStrictly: true,
+      defaultExpandAll: false,
+      style: {width: '100%'},
+      data: computed(() => {
+        return [{menuId: 0, menuName: '主类目', children: [...menuList.value]},]
+      }),
+      // el-tree-select 组件属性
+      props: {
+        label: 'menuName',
+        value: 'menuId',
+        children: 'children'
+      }
+    }
+  },
+  {
+    label: "系统编码",
+    prop: "systemCode",
+    valueType: 'input',
+    colProps: {span: 24},
+    fieldProps: {
+      placeholder: '请输入系统编码',
+      clearable: true
+    }
+  },
+  {
+    label: "菜单类型",
+    prop: "menuType",
+    valueType: 'radio',
+    colProps: {span: 24},
+    fieldProps: {
+      placeholder: '请选择菜单类型',
+      clearable: true,
+      options: [
+        {label: '目录', value: 'M'},
+        {label: '菜单', value: 'C'},
+        {label: '按钮', value: 'F'}
+      ],
+    },
+  },
+  {
+    label: "菜单图标",
+    prop: "icon",
+    valueType: 'input',
+    colProps: {span: 12},
+    fieldProps: {
+      placeholder: '请输入菜单图标',
+      clearable: true,
+    },
+    hideInForm: computed(() => form.value.menuType === 'F')
+  },
+  {
+    label: "显示排序",
+    prop: "orderNum",
+    valueType: 'input-number',
+    colProps: {span: 12},
+    fieldProps: {
+      placeholder: '请输入显示排序',
+      style: {width: '100%'},
+      min: 0
+    }
+  },
+  {
+    label: "菜单名称",
+    prop: "menuName",
+    colProps: {span: 12},
+    fieldProps: {
+      placeholder: '请输入菜单名称',
+      clearable: true
+    }
+  },
+  {
+    label: "路由名称",
+    prop: "routeName",
+    colProps: {span: 12},
+    fieldProps: {
+      placeholder: '请输入路由名称',
+      clearable: true
+    },
+    hideInForm: computed(() => form.value.menuType !== 'C')
+  },
+  {
+    label: "是否外链",
+    prop: "isFrame",
+    valueType: 'radio',
+    colProps: {span: 12},
+    fieldProps: {
+      options: [
+        {label: '是', value: '0'},
+        {label: '否', value: '1'}
+      ]
+    },
+    tooltip: '选择是外链则路由地址需要以`http(s)://`开头',
+    hideInForm: computed(() => form.value.menuType === 'F')
+  },
+  {
+    label: "路由地址",
+    prop: "path",
+    colProps: {span: 12},
+    fieldProps: {
+      placeholder: '请输入路由地址',
+      clearable: true
+    },
+    tooltip: "访问的路由地址,如:`role`,如外网地址需内链访问则以`http(s)://`开头",
+    hideInForm: computed(() => form.value.menuType === 'F')
+  },
+  {
+    label: "组件路径",
+    prop: "component",
+    colProps: {span: 12},
+    fieldProps: {
+      placeholder: '请输入组件路径',
+      clearable: true
+    },
+    tooltip: "访问的组件路径,如:`system/role/index`,默认在`views`目录下",
+    hideInForm: computed(() => form.value.menuType !== 'C')
+  },
+  {
+    label: "权限字符",
+    prop: "perms",
+    colProps: {span: 12},
+    fieldProps: {
+      placeholder: '请输入权限标识',
+      clearable: true
+    },
+    tooltip: "控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasPermi('system:role:list')`)",
+    hideInForm: computed(() => form.value.menuType === 'M')
+  },
+  {
+    label: "网关URL",
+    prop: "url",
+    colProps: {span: 12},
+    fieldProps: {
+      placeholder: '请输入URL',
+      clearable: true
+    },
+  },
+  {
+    label: "网关权限",
+    prop: "urlmatch",
+    colProps: {span: 12},
+    fieldProps: {
+      placeholder: '请输入网关权限标识',
+      clearable: true
+    },
+    tooltip: "控制器中定义的权限字符",
+  },
+  {
+    label: "路由参数",
+    prop: "query",
+    colProps: {span: 12},
+    fieldProps: {
+      placeholder: '请输入路由参数',
+      clearable: true
+    },
+    tooltip: "访问路由的默认传递参数,如:`{\"id\": 1, \"name\": \"ry\"}`",
+    hideInForm: computed(() => form.value.menuType !== 'C')
+  },
+  {
+    label: "是否缓存",
+    prop: "isCache",
+    valueType: 'radio',
+    colProps: {span: 12},
+    fieldProps: {
+      options: [
+        {label: '是', value: '0'},
+        {label: '否', value: '1'}
+      ]
+    },
+    tooltip: "选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致",
+    hideInForm: computed(() => form.value.menuType !== 'C')
+  },
+  {
+    label: "显示状态",
+    prop: "visible",
+    valueType: 'radio',
+    colProps: {span: 12},
+    fieldProps: {
+      options: [
+        {label: '显示', value: '0'},
+        {label: '隐藏', value: '1'}
+      ]
+    },
+    tooltip: "选择隐藏则路由将不会出现在侧边栏,但仍然可以访问",
+    hideInForm: computed(() => form.value.menuType === 'F')
+  },
+  {
+    label: "菜单状态",
+    prop: "status",
+    valueType: 'radio',
+    colProps: {span: 12},
+    fieldProps: {
+      options: [
+        {label: '正常', value: '0'},
+        {label: '停用', value: '1'}
+      ]
+    },
+    tooltip: "选择停用则路由将不会出现在侧边栏,也不能被访问"
+  },
+]
+
+// 创建
+const handleCreate = (): void => {
+  form.value = {
+    component: null, // 组件路径
+    icon: null, // 菜单图标
+    isCache: '0', // 是否缓存 (0缓存 1不缓存)
+    isFrame: '1', // 是否外链 (0是 1否)
+    menuName: null,// 菜单名称
+    menuType: 'M', // 菜单类型(M目录 C菜单 F按钮)
+    orderNum: null,// 显示排序
+    parentId: 0,// 上级菜单
+    path: null, // 路由地址
+    perms: null, // 权限标识
+    query: null, // 路由参数
+    routeName: null, // 路由名称
+    status: '0', // 菜单状态(0正常 1停用)
+    systemCode: null, // 系统编码
+    url: null, // 网关URL
+    urlmatch: null, // 网关权限
+    visible: '0', // 显示状态(0显示 1隐藏)
+  }
+  state.isCreate = true
+  state.dialogVisible = true
+}
+
+// 选择
+const handleSelect = (data: any) => {
+  state.selectedIds = [...data].map(item => item.id)
+}
+
+const handleTableChange = (values: FieldValues) => {
+  console.log(values)
+  ElMessageBox.confirm("确定修改此数据吗?", "提示", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning",
+  })
+    .then(async () => {
+      try {
+        let res = await changeRoleStatus(values.row.roleId, values.row.status)
+        if (res.code === 200) {
+          ElMessage.success('修改成功')
+          refresh()
+        } else {
+          ElMessage.error(res.msg)
+        }
+      } catch (e) {
+        ElMessage.error('修改失败')
+      }
+    })
+    .finally(() => {
+      refresh()
+    });
+}
+
+const handleSubmit = async (values: FieldValues) => {
+  console.log(values, 'Submit')
+  confirmLoading.value = true
+  if (state.isCreate) {
+    try {
+      let params = form.value
+      let res = await addMenu(params)
+      if (res.code === 200) {
+        ElMessage.success('新增成功')
+        confirmLoading.value = false
+        dialogVisible.value = false
+        refresh()
+      } else {
+        ElMessage.error(res.msg)
+      }
+    } finally {
+      confirmLoading.value = false
+    }
+  } else {
+    // 编辑
+    try {
+      let params = form.value
+      let res = await updateMenu(params)
+      if (res.code === 200) {
+        ElMessage.success('修改成功')
+        confirmLoading.value = false
+        dialogVisible.value = false
+        refresh()
+      } else {
+        ElMessage.error(res.msg)
+      }
+    } finally {
+      confirmLoading.value = false
+    }
+  }
+}
+const handleSubmitError = (err: any) => {
+  console.log(err, 'err')
+}
+const handleClose = () => {
+  // 重置表单
+  nextTick(() => {
+    if (dialogForm.value) {
+      dialogForm.value.formInstance.resetFields()
+    }
+  })
+  console.log(dialogForm.value.formInstance)
+}
+
+buttons.value = [
+  {
+    // 修改
+    text: "修改",
+    code: "edit",
+    // props v0.1.16 版本新增函数类型
+    props: {
+      type: "primary"
+    }
+  },
+  {
+    // 删除
+    text: "删除",
+    code: "delete",
+    props: {type: "danger"},
+  },
+  {
+    text: "数据权限",
+    code: "dataPermission",
+  },
+  {
+    text: "分配用户",
+    code: "assignUser",
+  }
+];
+
+const {form, confirmLoading, rules, dialogVisible} = toRefs(state)
+</script>