Bladeren bron

feat: 开发字典管理一半功能

louhangfei 11 maanden geleden
bovenliggende
commit
3a13bbf336

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

@@ -0,0 +1,279 @@
+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: 'Post' });
+}
+
+/** 删除字典 */
+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 {
+  /*字典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' });
+}

+ 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',
-  },
-];

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

@@ -0,0 +1,348 @@
+<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="dictCategoryType">
+        <el-select v-model="formData.dictCategoryType" placeholder="请选择">
+          <!-- <el-option label="分类1" value="category1"></el-option> -->
+          <!-- <el-option label="分类2" value="category2"></el-option> -->
+        </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.dictItems" :key="index" class="dict-item-group">
+              <div class="dict-item-header">
+                <el-button v-if="formData.dictItems.length > 1" type="danger" link @click="removeDictItem(index)">
+                  <el-icon><Delete /></el-icon> 删除
+                </el-button>
+              </div>
+              <el-form-item
+                :label="`字典项值`"
+                :prop="`dictItems.${index}.value`"
+                :rules="[{ required: true, message: '请输入字典项值', trigger: 'blur' }]"
+              >
+                <el-input v-model="item.value" placeholder="请输入" />
+              </el-form-item>
+              <el-form-item
+                :label="`字典项编码`"
+                :prop="`dictItems.${index}.code`"
+                :rules="[{ required: true, message: '请输入字典项编码', trigger: 'blur' }]"
+              >
+                <el-input v-model="item.code" placeholder="请输入" />
+              </el-form-item>
+              <el-form-item
+                :label="`显示排序`"
+                :prop="`dictItems.${index}.sortOrder`"
+                :rules="[{ required: true, message: '请输入显示排序', trigger: 'blur' }]"
+              >
+                <el-input-number v-model="item.sortOrder" :min="1" placeholder="请输入" />
+              </el-form-item>
+              <el-form-item label="图标">
+                <el-upload
+                  action="#"
+                  list-type="picture-card"
+                  :auto-upload="false"
+                  :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 label="enabled">启用</el-radio-button>
+          <el-radio-button label="disabled">停用</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">
+    <img w-full :src="dialogImageUrl" alt="Preview Image" />
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+  import { ref, reactive, PropType, watch } 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 } from '@element-plus/icons-vue';
+
+  interface DictItem {
+    id?: string | number; // 可选,用于编辑时
+    value: string;
+    code: string;
+    sortOrder: number | undefined;
+    iconFile?: UploadUserFile | null; // 用于存储上传的文件对象
+    iconUrl?: string; // 用于显示已上传的图片
+  }
+
+  interface FormData {
+    id?: string | number; // 可选,用于编辑时
+    dictName: string;
+    dictCode: string;
+    dictCategoryType: string;
+    dictItems: DictItem[];
+    description: string;
+    status: 'enabled' | 'disabled';
+  }
+
+  const props = defineProps({
+    initialData: {
+      type: Object as PropType<Partial<FormData>>,
+      default: () => ({}),
+    },
+    // 可根据需要添加其他 props,例如区分是新增还是编辑模式
+    isEditMode: {
+      type: Boolean,
+      default: false,
+    },
+  });
+
+  const emit = defineEmits(['submit', 'close']);
+
+  const handleClose = () => {
+    emit('close');
+  };
+
+  const formRef = ref<FormInstance>();
+  const formData = reactive<FormData>({
+    dictName: '',
+    dictCode: '',
+    dictCategoryType: '',
+    dictItems: [
+      {
+        value: '',
+        code: '',
+        sortOrder: 5, // 默认排序值
+        iconFile: null,
+      },
+    ],
+    description: '',
+    status: 'enabled',
+    ...props.initialData, // 使用 initialData 初始化表单
+  });
+
+  // 监听 initialData 的变化,用于编辑时填充表单
+  watch(
+    () => props.initialData,
+    (newData) => {
+      if (newData) {
+        Object.assign(formData, {
+          ...formData, // 保留现有响应式链接
+          ...newData,
+          dictItems: newData.dictItems
+            ? newData.dictItems.map((item) => ({ ...item }))
+            : [{ value: '', code: '', sortOrder: 5, iconFile: null }],
+        });
+      }
+    },
+    { deep: true, immediate: true },
+  );
+
+  const formRules = reactive<FormRules<FormData>>({
+    dictName: [{ required: true, message: '请选择字典名称', trigger: 'change' }],
+    dictCode: [{ required: true, message: '请选择字典编码', trigger: 'change' }],
+    dictCategoryType: [{ required: true, message: '请选择字典分类', trigger: 'change' }],
+    description: [{ max: 1000, message: '描述不超过1000字', trigger: 'blur' }],
+    status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+  });
+
+  const addDictItem = () => {
+    formData.dictItems.push({
+      value: '',
+      code: '',
+      sortOrder: 5,
+      iconFile: null,
+    });
+  };
+
+  const removeDictItem = (index: number) => {
+    formData.dictItems.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.dictItems[itemIndex]) {
+      formData.dictItems[itemIndex].iconFile = null;
+      formData.dictItems[itemIndex].iconUrl = ''; // 如果有单独的 URL 字段
+    }
+  };
+
+  const handleChange = (uploadFile: UploadUserFile, uploadFiles: UploadUserFile[], itemIndex: number) => {
+    // Element Plus 的 onChange 会在文件状态改变时触发多次,通常在 ready 状态时文件已可选
+    // 这里简单地将文件对象存起来,实际上传应在 handleSubmit 中处理
+    if (formData.dictItems[itemIndex]) {
+      formData.dictItems[itemIndex].iconFile = uploadFile;
+    }
+  };
+
+  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 === 'dictItems') {
+        //     formData.dictItems.forEach((item, index) => {
+        //       submissionData.append(`dictItems[${index}][value]`, item.value);
+        //       submissionData.append(`dictItems[${index}][code]`, item.code);
+        //       submissionData.append(`dictItems[${index}][sortOrder]`, String(item.sortOrder));
+        //       if (item.iconFile && item.iconFile.raw) {
+        //         submissionData.append(`dictItems[${index}][icon]`, item.iconFile.raw);
+        //       }
+        //     });
+        //   } else {
+        //     submissionData.append(key, formData[key as keyof Omit<FormData, 'dictItems'>]);
+        //   }
+        // });
+        // emit('submit', submissionData);
+        emit('submit', JSON.parse(JSON.stringify(formData))); // 暂时发送原始数据,上传需单独处理
+      } else {
+        console.log('error submit!');
+        return false;
+      }
+    });
+  };
+
+  const handleCancel = () => {
+    emit('close');
+  };
+
+  // 暴露方法给父组件,例如重置表单
+  defineExpose({
+    resetForm: () => {
+      formRef.value?.resetFields();
+      formData.dictItems = [
+        {
+          value: '',
+          code: '',
+          sortOrder: 5,
+          iconFile: null,
+        },
+      ];
+    },
+    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;
+    }
+  }
+</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>

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


+ 140 - 320
src/views/system/dictionary/dictionary.vue

@@ -1,347 +1,167 @@
 <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" :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" />
+      <el-table-column prop="status" label="状态" width="180" />
+      <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" @close="closeAddDialog" />
+    <EditDict />
+  </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 } from 'vue';
+  import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
+  import AddDict from './components/AddDict.vue';
+  import { queryDictPageApi } from '@/api/dict';
+  import { useDataSource } from './useDataSource';
+  // 表格数据
+  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 formRef = ref<FormInstance>();
+  const formData = reactive({
+    name: '',
+    code: '',
+    description: '',
   });
 
-  //添加字典类型
-  function addDictType() {
-    addDictTypeRef.value.openModal();
-    nextTick(() => {
-      isEdit.value = false;
-      addDictTypeRef.value.setProps({ label: '添加字典类型' });
-    });
-  }
+  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 removeDictType(record) {
-    rowKeysName.value = record.dictName;
-    delDictType({ dictId: record.dictId }).then(() => {
-      message.success('删除成功');
-      leftReloadTable();
-    });
+  // 新增字典
+  function handleAddDialogShow() {
+    dialogVisible.value = true;
   }
 
-  //编辑字段类型
-  function editDictType(record) {
-    addDictTypeRef.value.openModal();
-    nextTick(() => {
-      isEdit.value = true;
-      addDictTypeRef.value.setProps({ title: '编辑字典类型' });
-      addDictTypeRef.value.setFieldsValue({
-        ...unref(record),
-      });
-    });
-  }
+  const closeAddDialog = () => {
+    dialogVisible.value = false;
+  };
 
-  //删除字典
-  function removeDictData(record) {
-    rowKeysName.value = record.dictName;
-    delDictData({ dictCode: record.dictCode }).then(() => {
-      message.success('删除成功');
-      rightReloadTable();
+  // 编辑字典
+  function handleEdit(row: any) {
+    dialogTitle.value = '编辑字典';
+    dialogVisible.value = true;
+    // 填充表单数据
+    Object.assign(formData, {
+      ...row,
     });
   }
 
-  //添加字典
-  function addDictData() {
-    addDictDataRef.value.openModal();
-    nextTick(() => {
-      isEdit.value = false;
-      addDictDataRef.value.setProps({ title: '添加字典项' });
-      addDictDataRef.value.setFieldsValue({
-        dictType: dictType.value,
+  // 删除字典
+  async function handleDelete(row: any) {
+    try {
+      await ElMessageBox.confirm('确定要删除这条字典记录吗?', '提示', {
+        type: 'warning',
       });
-    });
+      // TODO: 实现删除API调用
+      // await deleteDictionary(row.id)
+      ElMessage.success('删除成功');
+      loadTableData();
+    } catch {
+      // 用户取消删除
+    }
   }
 
-  //编辑字典
-  function editDictData(record) {
-    addDictDataRef.value.openModal();
-    nextTick(() => {
-      isEdit.value = true;
-      addDictDataRef.value.setProps({ title: '编辑字典项' });
-      addDictDataRef.value.setFieldsValue({
-        ...unref(record),
-      });
+  // 提交表单
+  async function handleSubmit() {
+    if (!formRef.value) return;
+
+    await formRef.value.validate(async (valid) => {
+      if (valid) {
+        try {
+          // TODO: 实现保存API调用
+          // if (formData.id) {
+          //   await updateDictionary(formData)
+          // } else {
+          //   await createDictionary(formData)
+          // }
+          ElMessage.success('保存成功');
+          dialogVisible.value = false;
+          loadTableData();
+        } catch (error) {
+          ElMessage.error('保存失败');
+        }
+      }
     });
   }
+</script>
 
-  //选择字典项
-  function onCheckedRow(keys) {
-    rowKeys.value = keys;
-  }
-
-  //刷新字典类型
-  function leftReloadTable() {
-    leftTableRef.value.restReload();
-  }
-
-  //刷新字典列表
-  function rightReloadTable() {
-    rightTableRef.value.restReload();
-  }
+<style lang="scss" scoped>
+  .dictionary-container {
+    padding: 16px;
 
-  //加载字典项列表
-  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();
+    .search-form {
+      margin-bottom: 16px;
     }
-    return result;
-  };
-
-  //加载字典项值列表
-  const loadDataTable = async (res) => {
-    if (!dictType.value) return;
-    const result = await dictInfo({ dictType: dictType.value, ...rightParams.value, ...res });
-    dictTableList.value = result.list;
-    return result;
-  };
 
-  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 };
+};