Selaa lähdekoodia

Merge branch 'dev-home' into 'dev'

feat: 字典管理

See merge request product-group-fe/sfy-safety-group/sfy-safety!10
楼航飞 11 kuukautta sitten
vanhempi
commit
918687778c

+ 292 - 0
src/api/dict/index.ts

@@ -0,0 +1,292 @@
+import { http } from '@/utils/http/axios';
+import { InteractionFilled } from '@vicons/antd';
+
+export interface QueryDictPageParams {
+  /*页号 */
+  pageNumber: number;
+
+  /*每页数量 */
+  pageSize: number;
+
+  /*查询参数 */
+  queryParam?: Record<string, unknown>;
+}
+
+export interface DictPageItem {
+  /*字典主键 */
+  dictId: number;
+
+  /*字典code */
+  dictCode: string;
+
+  /*字典名称 */
+  dictName: string;
+
+  /*字典分类 */
+  dictType: number;
+
+  /*字典描述 */
+  description: string;
+
+  /*状态:0-禁用 1启用 */
+  status: number;
+
+  /*创建者 */
+  createBy: number;
+
+  /*更新者 */
+  updateBy: number;
+
+  /*创建时间 */
+  createAt: Record<string, unknown>;
+
+  /*更新时间 */
+  updateAt: Record<string, unknown>;
+
+  /*租户id */
+  tenantId: number;
+
+  /*0-未删除,大于0(时间戳)-已删除 */
+  isDeleted: number;
+}
+
+export interface QueryDictPages {
+  records: DictPageItem[];
+
+  /* */
+  pageNumber: number;
+
+  /* */
+  pageSize: number;
+
+  /* */
+  totalPage: number;
+
+  /* */
+  totalRow: number;
+}
+
+/** 分页查询字典 */
+export const queryDictPageApi = (data: QueryDictPageParams) => {
+  return http.request<QueryDictPages>({
+    url: '/admin/dict/queryDictPage',
+    method: 'post',
+    data,
+  });
+};
+
+/** 字典项详情 */
+export interface SysDictDataDetail {
+  /*字典项主键 */
+  id: number;
+
+  /*字典id */
+  dictId: number;
+
+  /*字典code */
+  dictCode: number;
+
+  /*字典项排序 */
+  itemSort: number;
+
+  /*字典项值 */
+  itemValue: string;
+
+  /*字典项编码 */
+  itemCode: string;
+
+  /*是否默认:0-否 1-是 */
+  isDefault: number;
+
+  /*状态:0-禁用 1-启用 */
+  status: number;
+
+  /*拓展属性 */
+  imageUrl: string;
+}
+
+/** 字典详情 */
+export interface DictDetailRes {
+  /*字典主键 */
+  dictId: number;
+
+  /*字典code */
+  dictCode: string;
+
+  /*字典名称 */
+  dictName: string;
+
+  /*字典分类 */
+  dictType: number;
+
+  /*字典描述 */
+  description: string;
+
+  /*状态:0-禁用 1启用 */
+  status: number;
+
+  /*创建者 */
+  createBy: number;
+
+  /*更新者 */
+  updateBy: number;
+
+  /*创建时间 */
+  createAt: Record<string, unknown>;
+
+  /*更新时间 */
+  updateAt: Record<string, unknown>;
+
+  /*租户id */
+  tenantId: number;
+
+  /*字典项 */
+  sysDictDataList: SysDictDataDetail[];
+}
+
+/**
+ * 查询字典详情
+ * @param {string} dictCode 字典code
+ * @returns
+ */
+export function queryDictTypeDetail(dictCode: string) {
+  return http.request<DictDetailRes>({ url: `/admin/dict/queryDictTypeDetail?dictCode=${dictCode}`, method: 'GET' });
+}
+
+/** 删除字典 */
+export function deleteDict(dictId: number) {
+  return http.request<null>({ url: `/admin/dict/deleteDict?dictId=${dictId}`, method: 'Delete' });
+}
+
+// Response interface
+export interface SaveDictRes {
+  /*200-为请求成功 401-没有登录 403-没有权限 400-业务异常 500-系统错误 */
+  code: number;
+
+  /*异常描述 */
+  message: string;
+
+  /*数据 */
+  data: Record<string, unknown>;
+}
+
+// Parameter interface
+export interface SaveDictParams {
+  dictId?: number;
+  /*字典code */
+  dictCode: string;
+
+  /*字典名称 */
+  dictName: string;
+
+  /*字典分类 */
+  dictType: number;
+
+  /*字典描述 */
+  description?: string;
+
+  /*状态:0-禁用 1启用 */
+  status: number;
+
+  /*字典项 */
+  sysDictDataList?: {
+    /*字典项主键 */
+    id?: number;
+
+    /*字典id */
+    dictId?: number;
+
+    /*字典code */
+    dictCode?: number;
+
+    /*字典项排序 */
+    itemSort?: number;
+
+    /*字典项值 */
+    itemValue?: string;
+
+    /*字典项编码 */
+    itemCode?: string;
+
+    /*是否默认:0-否 1-是 */
+    isDefault?: number;
+
+    /*状态:0-禁用 1-启用 */
+    status?: number;
+
+    /*拓展属性 */
+    imageUrl?: string;
+  }[];
+}
+
+/** 添加字典 */
+export function createDictApi(params: SaveDictParams): Promise<SaveDictRes> {
+  return http.request({ url: `/admin/dict/saveDict`, data: params, method: 'POST' });
+}
+
+// Parameter interface
+export interface UpdateDictParams {
+  /*字典id */
+  dictId?: number;
+
+  /*字典code */
+  dictCode: string;
+
+  /*字典名称 */
+  dictName: string;
+
+  /*字典分类 */
+  dictType: number;
+
+  /*字典描述 */
+  description?: string;
+
+  /*状态:0-禁用 1启用 */
+  status: number;
+
+  /*字典项 */
+  sysDictDataList?: {
+    /*字典项主键 */
+    id?: number;
+
+    /*字典id */
+    dictId?: number;
+
+    /*字典code */
+    dictCode?: number;
+
+    /*字典项排序 */
+    itemSort?: number;
+
+    /*字典项值 */
+    itemValue?: string;
+
+    /*字典项编码 */
+    itemCode?: string;
+
+    /*是否默认:0-否 1-是 */
+    isDefault?: number;
+
+    /*状态:0-禁用 1-启用 */
+    status?: number;
+
+    /*拓展属性 */
+    imageUrl?: string;
+  }[];
+}
+
+export function updateDict(params: UpdateDictParams): Promise<null> {
+  return http.request({ url: `/admin/dict/updateDict`, data: params, method: 'POST' });
+}
+
+export const uploadPresetImageApi = (file: Blob, bizType: string) => {
+  const formData = new FormData();
+  formData.append('bizType', bizType);
+  formData.append('file', file);
+  return http.request({
+    url: `/admin/minio/uploadFile`,
+    method: 'post',
+    data: formData,
+    headers: { 'Content-Type': 'multipart/form-data' },
+  });
+};

+ 3 - 5
src/layout/MenuLayout.vue

@@ -10,7 +10,9 @@
               <span class="menu-title">{{ item.meta?.title }}</span>
             </template>
             <el-menu-item v-for="child in item.children" :key="child.name" :index="child.path">
-              {{ child.meta?.title }}
+              <div style="margin-left: 12px">
+                {{ child.meta?.title }}
+              </div>
             </el-menu-item>
           </el-sub-menu>
           <el-menu-item v-else :index="item.path">
@@ -124,8 +126,4 @@
       }
     }
   }
-
-  .menu-title {
-    margin-left: 10px;
-  }
 </style>

+ 4 - 1
src/main.ts

@@ -4,7 +4,7 @@ import { setupStore } from '@/store';
 import './main.scss';
 import 'element-plus/dist/index.css';
 import App from './App.vue';
-import { setupElement } from '@/plugins';
+import { setupElement, setupDirectives } from '@/plugins';
 import dayjs from 'dayjs';
 import 'dayjs/locale/zh-cn';
 
@@ -18,6 +18,9 @@ async function bootstrap() {
   // 全局完整引入 element 组件
   setupElement(app);
 
+  // 注册全局自定义指令,如:v-permission权限指令
+  setupDirectives(app);
+
   // 挂载状态管理
   setupStore(app);
   //优先挂载一下 Provider 解决路由守卫,Axios中可使用,Dialog,Message 等之类组件

+ 10 - 0
src/router/full-routes.ts

@@ -526,6 +526,16 @@ export const platformRoutes = {
             title: '账号管理',
           },
         },
+        {
+          // 账号管理
+          path: 'person',
+          name: 'Person',
+          component: '/todo/todo',
+          meta: {
+            icon: '',
+            title: '人员分组',
+          },
+        },
       ],
     },
     {

+ 2 - 1
src/router/generator-routers.ts

@@ -43,7 +43,8 @@ export const routerGenerator = (routerMap, parent?): any[] => {
     // 是否有子菜单,并递归处理
     if (item.children && item.children.length > 0) {
       //如果未定义 redirect 默认第一个子路由为 redirect
-      !item.redirect && (currentRouter.redirect = `${item.path}/${item.children[0].path}`);
+      !item.redirect &&
+        (currentRouter.redirect = `${parent?.path ? parent?.path + '/' : ''}${item.path}/${item.children[0].path}`);
       // Recursion
       currentRouter.children = routerGenerator(item.children, currentRouter);
     }

+ 3 - 11
src/views/auth/dept/dept.vue

@@ -3,7 +3,7 @@
     <el-card :bordered="false" class="proCard">
       <template #header>
         <el-space align="center">
-          <el-button type="primary" @click="openCreateDrawer" v-permission="{ action: [PERM_USER.DEPT_ADD] }">
+          <el-button type="primary" @click="openCreateDrawer">
             <template #icon>
               <el-icon>
                 <FileAddOutlined />
@@ -23,18 +23,10 @@
           <template #default="scope">
             <el-space>
               <div class="el-space el-space--horizontal">
-                <div
-                  class="el-space__item"
-                  @click="handleEdit(scope.row)"
-                  v-permission="{ action: [PERM_USER.DEPT_EDIT] }"
-                >
+                <div class="el-space__item" @click="handleEdit(scope.row)">
                   <div><img :src="editIcon" class="el-tooltip__trigger" /></div>
                 </div>
-                <div
-                  class="el-space__item"
-                  @click="handleDelete(scope.row)"
-                  v-permission="{ action: [PERM_USER.DEPT_DELETE] }"
-                >
+                <div class="el-space__item" @click="handleDelete(scope.row)">
                   <div><img :src="deleteIcon" class="el-tooltip__trigger" /></div>
                 </div>
               </div>

+ 0 - 23
src/views/system/dictionary/columns.ts

@@ -1,23 +0,0 @@
-import { BasicColumn } from '@/components/Table';
-
-export const columns: BasicColumn[] = [
-  {
-    type: 'selection',
-  },
-  {
-    label: '字典名称',
-    prop: 'label',
-  },
-  {
-    label: '字典值',
-    prop: 'value',
-  },
-  {
-    label: '排序',
-    prop: 'order',
-  },
-  {
-    label: '创建时间',
-    prop: 'create_date',
-  },
-];

+ 434 - 0
src/views/system/dictionary/components/AddDict.vue

@@ -0,0 +1,434 @@
+<template>
+  <el-drawer :model-value="true" title="新建字典" @close="handleClose" :size="600">
+    <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" class="add-dict-form">
+      <el-form-item label="字典名称" prop="dictName">
+        <el-input v-model="formData.dictName" />
+      </el-form-item>
+
+      <el-form-item label="字典编码" prop="dictCode">
+        <el-input v-model="formData.dictCode" />
+      </el-form-item>
+
+      <el-form-item label="字典分类" prop="dictType">
+        <el-select v-model="formData.dictType" placeholder="请选择">
+          <el-option v-for="(item, index) in dictionaryTypeOptions" :key="index" :label="item.label"
+            :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <div class="subDictWrapper">
+        <div class="el-form-item__label" style="width: 100px">字典项</div>
+        <div class="subDictList">
+          <div>
+            <div v-for="(item, index) in formData.sysDictDataList" :key="index" class="dict-item-group">
+              <!-- 移动按钮 -->
+              <div class="dict-item-header">
+                <div class="move-buttons">
+                  <el-button v-if="index > 0" type="primary" link @click="moveDictItem(index, 'up')">
+                    <el-icon>
+                      <Top />
+                    </el-icon>
+                  </el-button>
+                  <el-button v-if="index < formData.sysDictDataList.length - 1" type="primary" link
+                    @click="moveDictItem(index, 'down')">
+                    <el-icon>
+                      <Bottom />
+                    </el-icon>
+                  </el-button>
+                  <el-button v-if="formData.sysDictDataList.length > 1" type="danger" link
+                    @click="removeDictItem(index)">
+                    <el-icon>
+                      <Delete />
+                    </el-icon>删除
+                  </el-button>
+                </div>
+              </div>
+
+              <el-form-item :label="`字典项值`" :prop="`sysDictDataList.${index}.itemValue`"
+                :rules="[{ required: true, message: '请输入字典项值', trigger: 'blur' }]">
+                <el-input v-model="item.itemValue" placeholder="请输入" />
+              </el-form-item>
+              <el-form-item :label="`字典项编码`" :prop="`sysDictDataList.${index}.itemCode`"
+                :rules="[{ required: true, message: '请输入字典项编码', trigger: 'blur' }]">
+                <el-input v-model="item.itemCode" placeholder="请输入" />
+              </el-form-item>
+              <el-form-item label="图标">
+                <el-upload 
+                  auto-upload="false"
+                  :action="actionUrl" 
+                  list-type="picture-card" 
+                  :limit="1"
+                  :on-preview="(file) => handlePictureCardPreview(file, index)" :on-remove="() => handleRemove(index)"
+                  :on-change="(file, fileList) => handleChange(file, fileList, index)"
+
+                  >
+                  <el-icon>
+                    <Plus />
+                  </el-icon>
+                </el-upload>
+              </el-form-item>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <el-form-item>
+        <el-button type="primary" link @click="addDictItem">
+          <el-icon>
+            <CirclePlus />
+          </el-icon> 新增字典分类
+        </el-button>
+      </el-form-item>
+
+      <el-form-item label="字典描述" prop="description">
+        <el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请描述灾害处置过程, 不超过1000字"
+          maxlength="1000" show-word-limit />
+      </el-form-item>
+
+      <el-form-item label="状态" prop="status">
+        <el-radio-group v-model="formData.status">
+          <el-radio-button v-for="item in dictionaryStatusOptions" :key="item.value" :value="item.value">{{ item.label
+            }}</el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+
+      <el-form-item class="form-footer">
+        <el-button @click="handleCancel">取消</el-button>
+        <el-button type="primary" @click="handleSubmit">提交</el-button>
+      </el-form-item>
+    </el-form>
+  </el-drawer>
+
+  <el-dialog v-model="dialogVisible">
+    <div class="dialog-content">
+      <img :src="dialogImageUrl" alt="Preview Image" />
+    </div>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, PropType, watch, computed } from 'vue';
+import {
+  ElForm,
+  ElFormItem,
+  ElInput,
+  ElSelect,
+  ElOption,
+  ElButton,
+  ElRadioGroup,
+  ElRadioButton,
+  ElInputNumber,
+  ElUpload,
+  ElIcon,
+  ElDialog,
+  ElDivider,
+  FormInstance,
+  FormRules,
+  UploadProps,
+  UploadUserFile,
+} from 'element-plus';
+import { Plus, Delete, CirclePlus, Minus, Picture, Top, Bottom } from '@element-plus/icons-vue';
+import { dictionaryTypeOptions, DictionaryStatus, dictionaryStatusOptions } from '../constants';
+import { queryDictTypeDetail, uploadPresetImageApi } from '@/api/dict'
+import { getHeaders } from '@/utils/http/axios';
+import { useGlobSetting } from '@/hooks/setting';
+import urlJoin from 'url-join';
+
+interface SysDictDataItem {
+  id?: string | number; // 可选,用于编辑时
+  dictId: string | number | undefined; // 关联的字典ID
+  dictCode: string;
+  itemValue: string;
+  itemCode: string;
+  itemSort: number | undefined;
+  isDefault: 0 | 1,
+  imageUrl?: string; // 用于显示已上传的图片
+  status: DictionaryStatus.disabled | DictionaryStatus.enabled;
+}
+
+interface FormData {
+  dictId?: string | number; // 可选,用于编辑时
+  dictName: string;
+  dictCode: string;
+  dictType: string;
+  sysDictDataList: SysDictDataItem[];
+  description: string;
+  status: DictionaryStatus.disabled | DictionaryStatus.enabled;
+}
+
+const props = defineProps({
+  dictCode: {
+    type: String,
+    default: '',
+  }
+});
+
+const { urlPrefix } = useGlobSetting();
+const actionUrl = computed(() => {
+  return urlJoin(urlPrefix!, `/admin/minio/uploadFile`);
+});
+
+const emit = defineEmits(['submit', 'close']);
+const handleClose = () => {
+  emit('close');
+};
+
+const formRef = ref<FormInstance>();
+const formData = reactive<FormData>({
+  dictName: '',
+  dictCode: '',
+  dictType: '',
+  sysDictDataList: [
+    {
+      id: undefined,
+      dictId: undefined,
+      dictCode: '',
+      itemCode: '',
+      itemValue: '',
+      itemSort: 1, // 默认排序值
+      // iconFile: null,
+      isDefault: 0,
+      status: DictionaryStatus.disabled,
+      imageUrl: '',
+    },
+  ],
+  description: '',
+  status: DictionaryStatus.disabled,
+  ...props.initialData, // 使用 initialData 初始化表单
+});
+
+watch(() => props.dictCode, (newData) => {
+ if (newData) {
+   queryDictTypeDetail(newData).then((res) => {
+    Object.assign(formData, {
+      ...res,
+      // 单独处理字典项数组保持响应式更新
+      sysDictDataList: res.sysDictDataList || []
+    });
+  })
+ }
+}, { deep: true, immediate: true })
+
+const formRules = reactive<FormRules<FormData>>({
+  dictName: [{ required: true, message: '请选择字典名称', trigger: 'change' }],
+  dictCode: [{ required: true, message: '请选择字典编码', trigger: 'change' }],
+  dictType: [{ required: true, message: '请选择字典分类', trigger: 'change' }],
+  description: [{ max: 1000, message: '描述不超过1000字', trigger: 'blur' }],
+  status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+});
+
+const addDictItem = () => {
+  formData.sysDictDataList.push({
+    id: undefined,
+    dictId: formData.dictId,
+    dictCode: formData.dictCode,
+    itemCode: '',
+    itemValue: '',
+    itemSort: 1, // 默认排序值
+    isDefault: 0,
+    status: DictionaryStatus.disabled,
+    imageUrl: '',
+  },
+  );
+};
+
+const removeDictItem = (index: number) => {
+  formData.sysDictDataList.splice(index, 1);
+};
+
+// 图片上传相关
+const dialogImageUrl = ref('');
+const dialogVisible = ref(false);
+
+const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
+  dialogImageUrl.value = uploadFile.url!;
+  dialogVisible.value = true;
+};
+
+const handleRemove = (itemIndex: number) => {
+  if (formData.sysDictDataList[itemIndex]) {
+    // formData.sysDictDataList[itemIndex].iconFile = null;
+    formData.sysDictDataList[itemIndex].imageUrl = ''; // 如果有单独的 URL 字段
+  }
+};
+
+const handleChange = (uploadFile: UploadUserFile, uploadFiles: UploadUserFile[], itemIndex: number) => {
+  // Element Plus 的 onChange 会在文件状态改变时触发多次,通常在 ready 状态时文件已可选
+  // 这里简单地将文件对象存起来,实际上传应在 handleSubmit 中处理
+  if (formData.sysDictDataList[itemIndex]) {
+    // formData.sysDictDataList[itemIndex].iconFile = uploadFile;
+    // console.log('uploadFile:', uploadFile);
+    // uploadPresetImageApi(uploadFile.raw, 'CAMERA_IMAGE').then((res) => {
+    //   // formData.sysDictDataList[itemIndex].imageUrl = res.data;
+    // })
+  }
+};
+
+const handleUpload = (res: any) => {
+  console.log('res:', res)
+};
+
+const handleSubmit = async () => {
+  if (!formRef.value) return;
+
+  await formRef.value.validate((valid) => {
+    if (valid) {
+      // 在这里处理实际的文件上传逻辑,例如使用 FormData
+      // const submissionData = new FormData();
+      // Object.keys(formData).forEach(key => {
+      //   if (key === 'sysDictDataList') {
+      //     formData.sysDictDataList.forEach((item, index) => {
+      //       submissionData.append(`sysDictDataList[${index}][value]`, item.value);
+      //       submissionData.append(`sysDictDataList[${index}][code]`, item.code);
+      //       submissionData.append(`sysDictDataList[${index}][sortOrder]`, String(item.sortOrder));
+      //       if (item.iconFile && item.iconFile.raw) {
+      //         submissionData.append(`sysDictDataList[${index}][icon]`, item.iconFile.raw);
+      //       }
+      //     });
+      //   } else {
+      //     submissionData.append(key, formData[key as keyof Omit<FormData, 'sysDictDataList'>]);
+      //   }
+      // });
+      // emit('submit', submissionData);
+      formData.sysDictDataList.forEach(item => {
+        item.dictCode = formData.dictCode;
+      });
+      
+      emit('submit', JSON.parse(JSON.stringify(formData))); // 暂时发送原始数据,上传需单独处理
+    } else {
+
+      return false;
+    }
+  });
+};
+
+// 移动方法
+const moveDictItem = (index: number, direction: 'up' | 'down') => {
+  const items = formData.sysDictDataList;
+  if (direction === 'up' && index > 0) {
+    // 交换位置
+    [items[index - 1], items[index]] = [items[index], items[index - 1]];
+  } else if (direction === 'down' && index < items.length - 1) {
+    // 交换位置
+    [items[index + 1], items[index]] = [items[index], items[index + 1]];
+  }
+  
+  // 根据最新位置重新设置排序值
+  items.forEach((item, idx) => {
+    item.itemSort = idx + 1;
+  });
+};
+              
+const handleCancel = () => {
+  emit('close');
+};
+
+// 暴露方法给父组件,例如重置表单
+defineExpose({
+  resetForm: () => {
+    formRef.value?.resetFields();
+    formData.sysDictDataList = [
+      {
+        id: undefined,
+        dictId: undefined,
+        dictCode: '',
+        itemCode: '',
+        itemValue: '',
+        itemSort: 1, // 默认排序值
+        isDefault: 0,
+        status: DictionaryStatus.disabled,
+        imageUrl: '',
+      },
+    ];
+    
+  },
+  getFormData: () => formData, // 允许父组件获取当前表单数据
+});
+</script>
+
+<style lang="scss" scoped>
+.add-dict-form {
+
+  .el-select,
+  .el-input-number {
+    width: 100%;
+  }
+}
+
+.dict-item-group {
+  padding: 15px;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  margin-bottom: 20px;
+  position: relative;
+
+  .dict-item-header {
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+    margin-bottom: 10px;
+
+    h4 {
+      margin: 0;
+      font-size: 16px;
+    }
+  }
+}
+
+.form-footer {
+  text-align: right;
+  margin-top: 20px;
+}
+
+// 覆盖 el-upload 的样式,使其适应小图标场景
+:deep(.el-upload--picture-card) {
+  width: 100px;
+  height: 100px;
+  line-height: 110px;
+}
+
+:deep(.el-upload-list--picture-card .el-upload-list__item) {
+  width: 100px;
+  height: 100px;
+}
+
+.subDictWrapper {
+  display: flex;
+
+  .dictItemsLabel {
+    width: 30%;
+    font-size: 14px;
+    color: #606266;
+    text-align: right;
+    margin-right: 26px;
+  }
+
+  .subDictList {
+    flex: 1;
+  }
+}
+
+.dict-item-header {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  margin-bottom: 10px;
+  
+  .move-buttons {
+    margin-right: auto;
+    .el-button {
+      padding: 5px;
+      margin-right: 8px;
+    }
+  }
+}
+
+.dialog-content {
+  width: 100%;
+  height: 100%;
+
+  img {
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>

+ 0 - 166
src/views/system/dictionary/components/AddDictData.vue

@@ -1,166 +0,0 @@
-<template>
-  <basicModal @register="modalRegister" ref="modalRef" @ok="okModal">
-    <template #default>
-      <BasicForm @register="registerForm" class="pt-6 basicForm" />
-    </template>
-  </basicModal>
-</template>
-
-<script lang="ts" setup>
-  import { ref, computed } from 'vue';
-  import { BasicForm, useForm } from '@/components/Form/index';
-  import { basicModal, useModal } from '@/components/Modal';
-  import { addDictData, editDictData } from '@/api/system/dictionary';
-  import type { FormSchema } from '@/components/Form/index';
-  import { ElMessage } from 'element-plus';
-
-  const emit = defineEmits(['change', 'register']);
-
-  const message = ElMessage;
-
-  const props = defineProps({
-    title: {
-      type: String,
-      default: '添加字典项',
-    },
-  });
-
-  //表单项配置
-  const schemas: FormSchema[] = [
-    {
-      field: 'dictCode',
-      hidden: true,
-    },
-    {
-      field: 'dictType',
-      hidden: true,
-    },
-    {
-      field: 'dictLabel',
-      component: 'Input',
-      label: `字典项名称`,
-      componentProps: {
-        placeholder: `请输入字典项名称`,
-      },
-      rules: [
-        {
-          required: true,
-          message: `字典项名称不能为空`,
-          trigger: ['blur'],
-        },
-      ],
-    },
-    {
-      field: 'dictValue',
-      component: 'Input',
-      label: `字典项值`,
-      componentProps: {
-        placeholder: `请输入字典项值`,
-      },
-      rules: [
-        {
-          required: true,
-          message: `字典项值不能为空`,
-          trigger: ['blur'],
-        },
-      ],
-    },
-    {
-      field: 'dictSort',
-      component: 'InputNumber',
-      label: '排序',
-      componentProps: {
-        placeholder: '请输入排序',
-      },
-    },
-    {
-      field: 'status',
-      component: 'RadioGroup',
-      label: '状态',
-      defaultValue: '0',
-      componentProps: {
-        options: [
-          {
-            label: '正常',
-            value: '0',
-          },
-          {
-            label: '停用',
-            value: '1',
-          },
-        ],
-      },
-    },
-    {
-      field: 'remark',
-      component: 'Input',
-      label: '备注',
-      componentProps: {
-        placeholder: '请输入备注',
-        type: 'textarea',
-      },
-    },
-  ];
-
-  const title = ref(props.title);
-
-  //创建form
-  const [registerForm, { submit, setFieldsValue, getFieldsValue }] = useForm({
-    collapsed: false,
-    labelWidth: 100,
-    layout: 'horizontal',
-    submitButtonText: '确定',
-    showActionButtonGroup: false,
-    schemas,
-  });
-
-  //弹窗
-  const [modalRegister, { openModal, closeModal, setSubLoading, setProps }] = useModal({
-    title,
-    subBtuText: '确定',
-    width: 450,
-  });
-
-  //提交
-  async function okModal() {
-    const formRes = await submit();
-    if (formRes) {
-      const formData = getFieldsValue();
-      const params = {
-        ...formData,
-      };
-      if (formData.dictCode) {
-        editDictData(params)
-          .then(() => {
-            message.success('修改成功');
-            emit('change');
-            closeModal();
-          })
-          .catch(() => {
-            setSubLoading(false);
-          });
-      } else {
-        addDictData(params)
-          .then(() => {
-            message.success('添加成功');
-            emit('change');
-            closeModal();
-          })
-          .catch(() => {
-            setSubLoading(false);
-          });
-      }
-    } else {
-      message.error('验证失败,请填写完整信息');
-      setSubLoading(false);
-    }
-  }
-
-  //导出方法
-  defineExpose({
-    openModal,
-    closeModal,
-    setFieldsValue,
-    setProps,
-  });
-</script>

+ 0 - 154
src/views/system/dictionary/components/AddDictType.vue

@@ -1,154 +0,0 @@
-<template>
-  <basicModal @register="modalRegister" ref="modalRef" @ok="okModal">
-    <template #default>
-      <BasicForm @register="registerForm" class="pt-6 basicForm" />
-    </template>
-  </basicModal>
-</template>
-
-<script lang="ts" setup>
-  import { ref } from 'vue';
-  import { BasicForm, useForm } from '@/components/Form/index';
-  import { basicModal, useModal } from '@/components/Modal';
-  import { addDictType, editDictType } from '@/api/system/dictionary';
-  import type { FormSchema } from '@/components/Form/index';
-  import { ElMessage } from 'element-plus';
-
-  const emit = defineEmits(['change', 'register']);
-
-  const message = ElMessage;
-
-  const props = defineProps({
-    title: {
-      type: String,
-      default: '添加字典',
-    },
-  });
-
-  //表单项配置
-  const schemas: FormSchema[] = [
-    {
-      field: 'dictId',
-      hidden: true,
-    },
-    {
-      field: 'dictName',
-      component: 'Input',
-      label: `字典名称`,
-      componentProps: {
-        placeholder: `请输入字典名称`,
-      },
-      rules: [
-        {
-          required: true,
-          message: `字典名称不能为空`,
-          trigger: ['blur'],
-        },
-      ],
-    },
-    {
-      field: 'dictType',
-      component: 'Input',
-      label: '字典类型',
-      labelMessage: '如已经用到此类型,修改此处时候,相关代码也需要一并修改',
-      componentProps: {
-        placeholder: `请输入字典类型`,
-      },
-      rules: [
-        {
-          required: true,
-          message: `字典类型不能为空`,
-          trigger: ['blur'],
-        },
-      ],
-    },
-    {
-      field: 'status',
-      component: 'RadioGroup',
-      label: '状态',
-      componentProps: {
-        options: [
-          {
-            label: '正常',
-            value: '0',
-          },
-          {
-            label: '停用',
-            value: '1',
-          },
-        ],
-      },
-    },
-    {
-      field: 'remark',
-      component: 'Input',
-      label: '备注',
-      componentProps: {
-        placeholder: '请输入备注',
-        type: 'textarea',
-      },
-    },
-  ];
-
-  const title = ref(props.title);
-
-  //创建form
-  const [registerForm, { submit, setFieldsValue, getFieldsValue }] = useForm({
-    collapsed: false,
-    labelWidth: 100,
-    layout: 'horizontal',
-    submitButtonText: '确定',
-    showActionButtonGroup: false,
-    schemas,
-  });
-
-  //弹窗
-  const [modalRegister, { openModal, closeModal, setSubLoading, setProps }] = useModal({
-    title,
-    subBtuText: '确定',
-    width: 450,
-  });
-
-  //提交
-  async function okModal() {
-    const formRes = await submit();
-    if (formRes) {
-      const formData = getFieldsValue();
-      const params = {
-        ...formData,
-      };
-      if (formData.dictId) {
-        editDictType(params)
-          .then(() => {
-            message.success('修改成功');
-            emit('change');
-            closeModal();
-          })
-          .catch(() => {
-            setSubLoading(false);
-          });
-      } else {
-        addDictType(params)
-          .then(() => {
-            message.success('添加成功');
-            emit('change');
-            closeModal();
-          })
-          .catch(() => {
-            setSubLoading(false);
-          });
-      }
-    } else {
-      message.error('验证失败,请填写完整信息');
-      setSubLoading(false);
-    }
-  }
-
-  //导出方法
-  defineExpose({
-    openModal,
-    closeModal,
-    setFieldsValue,
-    setProps,
-  });
-</script>

+ 38 - 0
src/views/system/dictionary/constants.ts

@@ -0,0 +1,38 @@
+export const dictionaryTypeOptions = [
+  {
+    value: 1,
+    label: '灾害防护'
+  },
+  {
+    value: 2,
+    label: '应急管理'
+  },
+  {
+    value: 3,
+    label: '交通安全'
+  },
+  {
+    value: 4,
+    label: '保卫保密'
+  },
+  {
+    value: 5,
+    label: '生产安全'
+  },
+] 
+
+export enum DictionaryStatus {
+  disabled = 0,
+  enabled = 1,
+}
+
+export const dictionaryStatusOptions = [
+  {
+    value: DictionaryStatus.disabled,
+    label: '禁用'
+  },
+  {
+    value: DictionaryStatus.enabled,
+    label: '启用'
+  },
+]

+ 153 - 318
src/views/system/dictionary/dictionary.vue

@@ -1,347 +1,182 @@
 <template>
-  <PageWrapper>
-    <el-row :gutter="24">
-      <el-col :span="8" class="mb-4">
-        <el-card title="字典类型" :bordered="false">
-          <template #header>
-            <div class="flex justify-between">
-              <h4>字典类型</h4>
-              <div class="flex">
-                <el-input
-                  clearable
-                  :style="{ width: '80%' }"
-                  v-model="leftParams.dictName"
-                  @keyup.enter="leftReloadTable"
-                  placeholder="请输入字典名称"
-                />
-                <el-button type="primary" @click="leftReloadTable" class="ml-2">
-                  <template #icon>
-                    <el-icon>
-                      <SearchOutlined />
-                    </el-icon>
-                  </template>
-                  查询
-                </el-button>
-              </div>
-            </div>
-          </template>
-          <BasicTable
-            :columns="leftColumns"
-            :request="leftLoadDataTable"
-            :row-key="(row) => row.dictId"
-            ref="leftTableRef"
-            :actionColumn="leftActionColumn"
-            @update:checked-row-keys="onCheckedRow"
-          >
-            <template #tableTitle>
-              <el-space>
-                <el-button type="primary" @click="addDictType">
-                  <template #icon>
-                    <el-icon>
-                      <FileAddOutlined />
-                    </el-icon>
-                  </template>
-                  添加
-                </el-button>
-              </el-space>
-            </template>
-          </BasicTable>
-        </el-card>
-      </el-col>
-      <el-col :span="16">
-        <el-card :bordered="false" class="mb-4 proCard">
-          <BasicTable
-            :columns="rightColumn"
-            :request="loadDataTable"
-            :row-key="(row) => row.dictCode"
-            ref="rightTableRef"
-            :actionColumn="actionColumn"
-          >
-            <template #tableTitle>
-              <el-space>
-                <el-input
-                  clearable
-                  :style="{ width: '80%' }"
-                  v-model="rightParams.dictLabel"
-                  @keyup.enter="rightReloadTable"
-                  placeholder="请输入字典名称"
-                />
-                <el-button type="primary" @click="rightReloadTable">
-                  <template #icon>
-                    <el-icon>
-                      <SearchOutlined />
-                    </el-icon>
-                  </template>
-                  查询
-                </el-button>
-                <el-button type="primary" @click="addDictData">
-                  <template #icon>
-                    <el-icon>
-                      <FileAddOutlined />
-                    </el-icon>
-                  </template>
-                  添加
-                </el-button>
-              </el-space>
-            </template>
-          </BasicTable>
-        </el-card>
-      </el-col>
-    </el-row>
-    <!-- 添加/编辑 字典 -->
-    <AddDictType ref="addDictTypeRef" :title="addDictTypeTitle" @change="leftReloadTable()" />
-    <!-- 添加/编辑 字典项 -->
-    <AddDictData ref="addDictDataRef" :title="addDictDataTitle" @change="rightReloadTable()" />
-  </PageWrapper>
+  <div class="dictionary-container">
+    <div class="table-operations">
+      <el-button type="primary" @click="handleAddDialogShow">新增字典</el-button>
+    </div>
+
+    <el-table v-loading="loading" height="calc(100vh - 220px)" :data="dataSource" style="width: 100%" border>
+      <!-- <el-table-column prop="id" label="序号" /> -->
+      <el-table-column prop="dictName" label="字典名称" />
+      <el-table-column prop="dictCode" label="字典编码" />
+      <el-table-column prop="description" label="字典描述" show-overflow-tooltip />
+      <el-table-column prop="dictType" label="分类" width="180">
+        <template #default="{ row }">
+          {{ typeLabelMap[row.dictType] || '' }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="status" label="状态" width="180">
+        <template #default="{ row }">
+          {{ statusLabelMap[row.status] || '' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="200" fixed="right">
+        <template #default="{ row }">
+          <el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
+          <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div class="pagination-container">
+      <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[10, 20, 50, 100]"
+        :total="totalPage" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
+        @current-change="handleCurrentChange" />
+    </div>
+
+    <AddDict v-if="dialogVisible" ref="addDictRef" @close="closeAddDialog" @submit="handleSubmit"
+      :dictCode="currentDictCode" />
+  </div>
 </template>
 
 <script lang="ts" setup>
-  import { ref, reactive, h, onMounted, nextTick, unref } from 'vue';
-  import { BasicTable, TableAction } from '@/components/Table';
-  import { dictTypeList, dictInfo, delDictType, delDictData } from '@/api/system/dictionary';
-  import AddDictType from './components/AddDictType.vue';
-  import AddDictData from './components/AddDictData.vue';
-  import { PageWrapper } from '@/components/Page';
-
-  import { ElMessage, ElCheckbox, ElTag } from 'element-plus';
-  import { FileAddOutlined, SearchOutlined } from '@vicons/antd';
-
-  const message = ElMessage;
-  const leftTableRef = ref();
-  const rightTableRef = ref();
-  const dictTableList = ref<any[]>([]);
-  const rowKeys = ref<any[]>([]);
-  const rowKeysName = ref('');
-  const isEdit = ref(false);
-  const dictTypeTableList = ref();
-  const dictType = ref('');
-  const addDictTypeRef = ref();
-  const addDictDataRef = ref();
-  const addDictTypeTitle = ref('添加字典');
-  const addDictDataTitle = ref('添加字典项');
-
-  const leftParams = ref({
-    dictName: '',
-  });
-
-  const rightParams = ref({
-    dictLabel: '',
+  import { ref, reactive, onMounted, computed } from 'vue';
+  import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
+  import AddDict from './components/AddDict.vue';
+  import { queryDictPageApi, createDictApi, SaveDictParams, updateDict, deleteDict } from '@/api/dict';
+  import { useDataSource } from './useDataSource';
+  import { dictionaryStatusOptions, dictionaryTypeOptions } from './constants'
+  
+  // 表格数据
+  const loading = ref(false);
+
+  const { dataSource, getDataSource, pageSize, currentPage, totalPage } = useDataSource();
+
+  onMounted(() => {
+    getDataSource();
   });
+  // 搜索
+  function handleSearch() {
+    currentPage.value = 1;
+    getDataSource();
+  }
 
-  const leftColumns = [
-    {
-      fixed: 'left',
-      width: 60,
-      render(record) {
-        return h(ElCheckbox, {
-          modelValue: dictType.value === record.row.dictType,
-          onChange() {
-            dictType.value = record.row.dictType;
-            rightTableRef.value.reload();
-          },
-        });
-      },
-    },
-    {
-      label: '字典名称',
-      prop: 'dictName',
-    },
-    {
-      label: '字典类型',
-      prop: 'dictType',
-    },
-  ];
-
-  const leftActionColumn = reactive({
-    width: 140,
-    label: '操作',
-    prop: 'action',
-    fixed: 'right',
-    align: 'center',
-    render(record) {
-      return h(TableAction as any, {
-        actions: [
-          {
-            label: '删除',
-            isConfirm: true,
-            popConfirm: {
-              onConfirm: removeDictType.bind(null, record.row),
-              title: `您确定要删除【${record.row.dictName}】吗?`,
-              width: 300,
-              confirmButtonText: '确定',
-              cancelButtonText: '取消',
-            },
-          },
-          {
-            label: '编辑',
-            onClick: editDictType.bind(null, record.row),
-          },
-        ],
-      });
-    },
-  });
+  // 分页相关
+  function handleSizeChange(val: number) {
+    pageSize.value = val;
+    getDataSource();
+  }
 
-  const rightColumn = [
-    {
-      label: '字典项名称',
-      prop: 'dictLabel',
-    },
-    {
-      label: '字典项值',
-      prop: 'dictValue',
-    },
-    {
-      label: '排序',
-      prop: 'dictSort',
-    },
-    {
-      label: '状态',
-      prop: 'status',
-      render(record) {
-        return h(
-          ElTag,
-          {
-            type: record.row.status === '0' ? 'success' : 'danger',
-          },
-          {
-            default: () => (record.row.status === '0' ? '正常' : '停用'),
-          },
-        );
-      },
-    },
-    {
-      label: '创建时间',
-      prop: 'createTime',
-    },
-  ];
+  function handleCurrentChange(val: number) {
+    currentPage.value = val;
+    getDataSource();
+  }
 
-  const actionColumn = reactive({
-    width: 140,
-    label: '操作',
-    prop: 'action',
-    fixed: 'right',
-    align: 'center',
-    render(record) {
-      return h(TableAction as any, {
-        actions: [
-          {
-            label: '删除',
-            isConfirm: true,
-            popConfirm: {
-              onConfirm: removeDictData.bind(null, record.row),
-              title: `您确定要删除【${record.row.dictLabel}】吗?`,
-              width: 300,
-              confirmButtonText: '确定',
-              cancelButtonText: '取消',
-            },
-          },
-          {
-            label: '编辑',
-            onClick: editDictData.bind(null, record.row),
-          },
-        ],
-      });
-    },
-  });
+  // 表单相关
+  const dialogVisible = ref(false);
+  const dialogTitle = ref('新增字典');
+  const addDictRef = ref<InstanceType<typeof AddDict>>();
+  const currentDictCode = ref('');
+
+  const formRules = {
+    name: [
+      { required: true, message: '请输入字典名称', trigger: 'blur' },
+      { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' },
+    ],
+    code: [
+      { required: true, message: '请输入字典编码', trigger: 'blur' },
+      { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' },
+    ],
+  };
 
-  //添加字典类型
-  function addDictType() {
-    addDictTypeRef.value.openModal();
-    nextTick(() => {
-      isEdit.value = false;
-      addDictTypeRef.value.setProps({ label: '添加字典类型' });
-    });
+  // 新增字典
+  function handleAddDialogShow() {
+    dialogVisible.value = true;
+    currentDictCode.value = '';
   }
 
-  //删除字典类型
-  function removeDictType(record) {
-    rowKeysName.value = record.dictName;
-    delDictType({ dictId: record.dictId }).then(() => {
-      message.success('删除成功');
-      leftReloadTable();
-    });
+  const closeAddDialog = () => {
+    dialogVisible.value = false;
+    if (addDictRef.value) {
+      addDictRef.value.resetForm();
+    }
+  };
+
+  // 编辑字典
+  function handleEdit(row: any) {
+    dialogTitle.value = '编辑字典';
+    dialogVisible.value = true;
+    currentDictCode.value = row.dictCode;
   }
 
-  //编辑字段类型
-  function editDictType(record) {
-    addDictTypeRef.value.openModal();
-    nextTick(() => {
-      isEdit.value = true;
-      addDictTypeRef.value.setProps({ title: '编辑字典类型' });
-      addDictTypeRef.value.setFieldsValue({
-        ...unref(record),
+  // 删除字典
+  async function handleDelete(row: any) {
+    try {
+      await ElMessageBox.confirm('确定要删除这条字典记录吗?', '提示', {
+        type: 'warning',
       });
-    });
+      await deleteDictionary(row.dictId)
+    } catch {
+      // 用户取消删除
+    }
   }
 
-  //删除字典
-  function removeDictData(record) {
-    rowKeysName.value = record.dictName;
-    delDictData({ dictCode: record.dictCode }).then(() => {
-      message.success('删除成功');
-      rightReloadTable();
-    });
+  // 提交表单
+  async function handleSubmit(formData: SaveDictParams) {
+    if (!addDictRef.value) return;
+    if (formData.dictId) {
+      await updateDictionary(formData)
+    } else {
+      await createDictionary(formData)
+    }
+    dialogVisible.value = false;
+    getDataSource();
   }
 
-  //添加字典
-  function addDictData() {
-    addDictDataRef.value.openModal();
-    nextTick(() => {
-      isEdit.value = false;
-      addDictDataRef.value.setProps({ title: '添加字典项' });
-      addDictDataRef.value.setFieldsValue({
-        dictType: dictType.value,
-      });
+  const createDictionary = (formData: SaveDictParams) => {
+    createDictApi(formData).then((res) => {
+      ElMessage.success('保存成功');
     });
-  }
+  };
 
-  //编辑字典
-  function editDictData(record) {
-    addDictDataRef.value.openModal();
-    nextTick(() => {
-      isEdit.value = true;
-      addDictDataRef.value.setProps({ title: '编辑字典项' });
-      addDictDataRef.value.setFieldsValue({
-        ...unref(record),
-      });
-    });
+  const updateDictionary = (formData: SaveDictParams) => {
+    updateDict(formData).then((res) => {
+      ElMessage.success('编辑成功');
+    })
   }
 
-  //选择字典项
-  function onCheckedRow(keys) {
-    rowKeys.value = keys;
+  const deleteDictionary = (id: number) => {
+    deleteDict(id).then((res) => {
+      ElMessage.success('删除成功');
+      getDataSource();
+    })
   }
 
-  //刷新字典类型
-  function leftReloadTable() {
-    leftTableRef.value.restReload();
-  }
+  const typeLabelMap = computed(() => 
+    Object.fromEntries(dictionaryTypeOptions.map(item => [item.value, item.label]))
+  );
 
-  //刷新字典列表
-  function rightReloadTable() {
-    rightTableRef.value.restReload();
-  }
+  const statusLabelMap = computed(() =>
+    Object.fromEntries(dictionaryStatusOptions.map(item => [item.value, item.label]))
+  );
+    
+</script>
 
-  //加载字典项列表
-  const leftLoadDataTable = async (res) => {
-    const result = await dictTypeList({ ...leftParams.value, ...res });
-    dictTypeTableList.value = result.list;
-    if (result.list.length) {
-      dictType.value = result.list[0].dictType;
-      rightReloadTable();
-    }
-    return result;
-  };
+<style lang="scss" scoped>
+  .dictionary-container {
+    padding: 16px;
 
-  //加载字典项值列表
-  const loadDataTable = async (res) => {
-    if (!dictType.value) return;
-    const result = await dictInfo({ dictType: dictType.value, ...rightParams.value, ...res });
-    dictTableList.value = result.list;
-    return result;
-  };
+    .search-form {
+      margin-bottom: 16px;
+    }
 
-  onMounted(() => {});
-</script>
+    .table-operations {
+      margin-bottom: 16px;
+    }
 
-<style lang="scss" setup></style>
+    .pagination-container {
+      margin-top: 16px;
+      display: flex;
+      justify-content: flex-end;
+    }
+  }
+</style>

+ 31 - 0
src/views/system/dictionary/useDataSource.ts

@@ -0,0 +1,31 @@
+import { queryDictPageApi } from '@/api/dict';
+import { onMounted, ref } from 'vue';
+
+export const useDataSource = () => {
+  const dataSource = ref<Record<string, any>[]>([]);
+  const currentPage = ref<number>(1);
+  const pageSize = ref<number>(10);
+  const totalPage = ref<number>(1);
+  const loading = ref<boolean>(false);
+
+  const getDataSource = (param?: { currentPage?: number; pageSize?: number }) => {
+    loading.value = true;
+    if (param?.currentPage) {
+      currentPage.value = param.currentPage;
+    }
+    if (param?.pageSize) {
+      pageSize.value = param.pageSize;
+    }
+    queryDictPageApi({ pageNumber: currentPage.value, pageSize: pageSize.value })
+      .then((res) => {
+        dataSource.value = res.records;
+        totalPage.value = res.totalPage;
+        loading.value = false;
+      })
+      .finally(() => {
+        loading.value = false;
+      });
+  };
+
+  return { dataSource, currentPage, pageSize, totalPage, loading, getDataSource };
+};

+ 1 - 0
src/views/system/person-group/PersonGroup.vue

@@ -0,0 +1 @@
+<template>人员分组 </template>

+ 4 - 6
src/views/system/role/role.vue

@@ -15,9 +15,7 @@
 
     <el-card>
       <template #header>
-        <el-button type="primary" @click="openDrawer()" v-permission="{ action: [PERM_USER.ROLE_ADD] }">
-          添加角色
-        </el-button>
+        <el-button type="primary" @click="openDrawer()"> 添加角色 </el-button>
       </template>
 
       <el-table height="calc(100vh - 340px)" :data="roleList">
@@ -28,8 +26,8 @@
         <el-table-column label="操作" width="160">
           <template #default="{ row }">
             <section class="actions">
-              <img src="@/assets/icons/edit.png" @click="openDrawer(row)" v-permission="{ action: [PERM_USER.ROLE_EDIT] }" />
-              <img src="@/assets/icons/delete.png" @click="deleteRole(row.id)" v-permission="{ action: [PERM_USER.ROLE_DELETE] }" />
+              <img src="@/assets/icons/edit.png" @click="openDrawer(row)" />
+              <img src="@/assets/icons/delete.png" @click="deleteRole(row.id)" />
             </section>
           </template>
         </el-table-column>
@@ -91,7 +89,7 @@
     } catch (e) {
       console.error(e);
     }
-  }
+  };
 
   onMounted(() => {
     queryRolesPage();

+ 12 - 8
src/views/system/user/user.vue

@@ -10,7 +10,7 @@
     <el-card>
       <template #header>
         <el-space align="center">
-          <el-button @click="openAddAdminDrawer" v-if="!isSysTenant && isCanAddTenant && hasAddPermission()">
+          <el-button @click="openAddAdminDrawer" v-if="true || (!isSysTenant && isCanAddTenant && hasAddPermission())">
             <template #icon>
               <el-icon>
                 <Plus />
@@ -18,7 +18,8 @@
             </template>
             添加租户管理员
           </el-button>
-          <el-button type="primary" @click="openAddSingleDrawer" v-permission="{ action: [PERM_USER.ACCOUNT_ADD] }">
+          <!-- <el-button type="primary" @click="openAddSingleDrawer" v-permission="{ action: [PERM_USER.ACCOUNT_ADD] }"> -->
+          <el-button type="primary" @click="openAddSingleDrawer">
             <template #icon>
               <el-icon>
                 <Plus />
@@ -26,13 +27,14 @@
             </template>
             添加用户
           </el-button>
-          <el-button
+          <!-- <el-button
             color="#1890FF"
             @click="openAddMultipleDrawer"
             style="margin-left: 18px"
             plain
             v-permission="{ action: [PERM_USER.ACCOUNT_ADD] }"
-          >
+          > -->
+          <el-button color="#1890FF" @click="openAddMultipleDrawer" style="margin-left: 18px" plain>
             <template #icon>
               <el-icon>
                 <DocumentAdd />
@@ -100,18 +102,20 @@
           <template #default="scope">
             <el-space v-if="scope.row.roleType !== RoleTypeEnum.SUPER_ADMIN">
               <div class="el-space el-space--horizontal">
-                <div
+                <!-- <div
                   class="el-space__item"
                   @click="handleEdit(scope.row)"
                   v-permission="{ action: [PERM_USER.ACCOUNT_EDIT] }"
-                >
+                > -->
+                <div class="el-space__item" @click="handleEdit(scope.row)">
                   <div><img :src="editIcon" class="el-tooltip__trigger" /></div>
                 </div>
-                <div
+                <!-- <div
                   class="el-space__item"
                   @click="handleDelete(scope.row)"
                   v-permission="{ action: [PERM_USER.ACCOUNT_DELETE] }"
-                >
+                > -->
+                <div class="el-space__item" @click="handleDelete(scope.row)">
                   <div><img :src="deleteIcon" class="el-tooltip__trigger" /></div>
                 </div>
                 <div

+ 2 - 1
src/views/todo/todo.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="flex flex-col justify-center page-container">
+  <div class="page-container">
     <div class="text-center">
       <img src="~@/assets/images/exception/403.svg" alt="" />
     </div>
@@ -23,6 +23,7 @@
     width: 100%;
     border-radius: 4px;
     padding-top: 100px;
+    text-align: center;
 
     .text-center {
       h1 {