Ver Fonte

feat(文件模块): 添加文件管理模块

shizhongming há 2 anos atrás
pai
commit
8012e17946

+ 2 - 1
src/components/registerGlobComp.ts

@@ -1,6 +1,6 @@
 import type { App } from 'vue';
 import { Button } from './Button';
-import { Input, Layout, Radio, Tag, Select, Tooltip, Tree, Tabs } from 'ant-design-vue';
+import { Input, Layout, Radio, Tag, Select, Tooltip, Tree, Tabs, Switch } from 'ant-design-vue';
 import VXETable from 'vxe-table';
 
 import { i18n } from '@/locales/setupI18n';
@@ -31,5 +31,6 @@ export function registerGlobComp(app: App) {
     .use(Tooltip)
     .use(Tree)
     .use(Tabs)
+    .use(Switch)
     .use(VXETable);
 }

+ 60 - 0
src/modules/fileManager/views/SmartFile/SmartFileListView.api.ts

@@ -0,0 +1,60 @@
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+import { applyTempToken } from '@/utils/auth';
+
+enum Api {
+  list = 'smart/file/list',
+  getById = 'smart/file/getById',
+  delete = 'smart/file/batchDeleteFile',
+  uploadFile = 'smart/file/upload',
+  download = '/public/file/download/',
+}
+
+export const listApi = (params) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_FILE,
+    url: Api.list,
+    data: {
+      ...params,
+    },
+  });
+};
+
+export const uploadFileApi = (data, file: File) => {
+  return defHttp.uploadFile(
+    {
+      service: ApiServiceEnum.SMART_FILE,
+      url: Api.uploadFile,
+    },
+    {
+      data: data,
+      file: {
+        file: file,
+      },
+    },
+  );
+};
+
+export const deleteApi = (removeRecords: Recordable[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_FILE,
+    url: Api.delete,
+    data: removeRecords.map((item) => item.fileId),
+  });
+};
+
+export const getByIdApi = (model: Recordable) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_FILE,
+    url: Api.getById,
+    data: model.fileId,
+  });
+};
+
+export const downloadApi = async (id) => {
+  let url = `${defHttp.getApiUrlByService(ApiServiceEnum.SMART_FILE)}/${Api.download}${id}`;
+
+  // 申请临时token
+  const tempToken = await applyTempToken('smart:file:download');
+  url = url + '?access-token=' + tempToken;
+  window.open(url);
+};

+ 180 - 0
src/modules/fileManager/views/SmartFile/SmartFileListView.config.ts

@@ -0,0 +1,180 @@
+import type { SmartColumn, SmartSearchFormSchema } from '@/components/SmartTable';
+import type { FormSchema } from '@/components/Form';
+
+/**
+ * 表格列表
+ */
+export const getTableColumns = (): SmartColumn[] => {
+  return [
+    {
+      type: 'checkbox',
+      width: 60,
+      align: 'center',
+      fixed: 'left',
+    },
+    {
+      field: 'fileId',
+      visible: false,
+      title: '{system.views.file.title.fileId}',
+      width: 120,
+    },
+    {
+      field: 'fileStorageId',
+      title: '{system.views.file.title.fileStorageId}',
+      width: 160,
+      fixed: 'left',
+      formatter: ({ row }) => {
+        return row.fileStorage?.storageName;
+      },
+      sortable: true,
+    },
+    {
+      field: 'filename',
+      title: '{system.views.file.title.fileName}',
+      width: 160,
+      fixed: 'left',
+    },
+    {
+      field: 'type',
+      title: '{system.views.file.title.type}',
+      width: 120,
+      sortable: true,
+    },
+    {
+      field: 'contentType',
+      title: '{system.views.file.title.contentType}',
+      width: 120,
+    },
+    {
+      field: 'fileSize',
+      title: '{system.views.file.title.fileSize}',
+      width: 120,
+      sortable: true,
+    },
+    {
+      field: 'seq',
+      title: '{common.table.seq}',
+      width: 120,
+      sortable: true,
+    },
+    {
+      field: 'createTime',
+      title: '{common.table.createTime}',
+      width: 160,
+      sortable: true,
+    },
+    {
+      field: 'createBy',
+      title: '{common.table.createUser}',
+      width: 120,
+    },
+    {
+      title: '{common.table.operation}',
+      field: 'operation',
+      width: 120,
+      fixed: 'right',
+      slots: {
+        default: 'table-operation',
+      },
+    },
+  ];
+};
+
+/**
+ * 添加修改表单
+ */
+export const getFormSchemas = (t: Function): FormSchema[] => {
+  return [
+    {
+      field: 'fileId',
+      show: false,
+      label: t('system.views.file.title.fileId'),
+      component: 'Input',
+      componentProps: {},
+    },
+    {
+      field: 'fileStorageId',
+      label: t('system.views.file.title.fileStorageId'),
+      component: 'SmartApiSelectTable',
+      componentProps: {
+        modelClassName: 'com.smart.file.manager.model.SmartFileStoragePO',
+        valueFieldName: 'id',
+        labelFieldName: 'storageName',
+        params: {
+          sortName: 'seq',
+          parameter: {
+            'deleteYn@<>': true,
+            'useYn@=': true,
+          },
+        },
+      },
+      required: true,
+    },
+    {
+      field: 'fileName',
+      label: t('system.views.file.title.fileName'),
+      component: 'Input',
+      componentProps: {},
+    },
+    {
+      field: 'type',
+      label: t('system.views.file.title.type'),
+      component: 'SmartApiSelectDict',
+      componentProps: {
+        dictCode: 'FILE_TYPE',
+      },
+    },
+    {
+      field: 'seq',
+      label: t('common.table.seq'),
+      component: 'InputNumber',
+      defaultValue: 1,
+      componentProps: {},
+      required: true,
+    },
+    {
+      field: 'fileList',
+      label: '文件',
+      slot: 'form-upload',
+      required: true,
+    },
+  ];
+};
+
+export const getSearchFormSchemas = (t: Function): SmartSearchFormSchema[] => {
+  return [
+    {
+      field: 'fileName',
+      label: t('system.views.file.title.fileName'),
+      component: 'Input',
+      searchSymbol: '=',
+    },
+    {
+      field: 'type',
+      label: t('system.views.file.title.type'),
+      component: 'Input',
+      searchSymbol: '=',
+    },
+    {
+      field: 'fileStorageId',
+      label: t('system.views.file.title.fileStorageId'),
+      component: 'SmartApiSelectTable',
+      componentProps: {
+        style: {
+          width: '150px',
+        },
+        modelClassName: 'com.smart.file.manager.model.SmartFileStoragePO',
+        valueFieldName: 'id',
+        labelFieldName: 'storageName',
+        params: {
+          sortName: 'seq',
+          parameter: {
+            'deleteYn@<>': true,
+            'useYn@=': true,
+          },
+        },
+      },
+      searchSymbol: '=',
+    },
+  ];
+};

+ 134 - 0
src/modules/fileManager/views/SmartFile/SmartFileListView.vue

@@ -0,0 +1,134 @@
+<template>
+  <div class="full-height page-container">
+    <SmartTable @register="registerTable" :size="getTableSize">
+      <template #table-operation="{ row }">
+        <SmartVxeTableAction :actions="getActions(row)" />
+      </template>
+      <template #form-upload="{ model }">
+        <Upload v-model:fileList="model.fileList" :max-count="1" :beforeUpload="() => false">
+          <a-button>Upload</a-button>
+        </Upload>
+      </template>
+    </SmartTable>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+  import { Upload } from 'ant-design-vue';
+
+  import {
+    ActionItem,
+    SmartTable,
+    SmartVxeTableAction,
+    useSmartTable,
+  } from '@/components/SmartTable';
+
+  import {
+    getTableColumns,
+    getFormSchemas,
+    getSearchFormSchemas,
+  } from './SmartFileListView.config';
+  import {
+    listApi,
+    deleteApi,
+    getByIdApi,
+    uploadFileApi,
+    downloadApi,
+  } from './SmartFileListView.api';
+
+  const { t } = useI18n();
+  const { getTableSize } = useSizeSetting();
+
+  const getActions = (row: Recordable): ActionItem[] => {
+    return [
+      {
+        label: t('common.button.delete'),
+        onClick: () => deleteByRow(row),
+        danger: true,
+      },
+      {
+        label: t('common.button.download'),
+        preIcon: 'ant-design:download-outlined',
+        auth: 'smart:file:download',
+        onClick: async () => {
+          await downloadApi(row.fileId);
+        },
+      },
+    ];
+  };
+
+  const [registerTable, { deleteByRow }] = useSmartTable({
+    id: 'smart-file-fileList',
+    columns: getTableColumns(),
+    height: 'auto',
+    border: true,
+    pagerConfig: true,
+    useSearchForm: true,
+    rowConfig: {
+      keyField: 'fileId',
+      isCurrent: true,
+    },
+    customConfig: {
+      storage: true,
+    },
+    columnConfig: {
+      resizable: true,
+    },
+    searchFormConfig: {
+      schemas: getSearchFormSchemas(t),
+      searchWithSymbol: true,
+      colon: true,
+      layout: 'inline',
+      actionColOptions: {
+        span: undefined,
+      },
+      compact: true,
+    },
+    addEditConfig: {
+      formConfig: {
+        schemas: getFormSchemas(t),
+        baseColProps: { span: 24 },
+        labelCol: { span: 6 },
+        wrapperCol: { span: 17 },
+      },
+    },
+    proxyConfig: {
+      ajax: {
+        query: (params) => listApi(params.ajaxParameter),
+        save: ({ body: { insertRecords } }) => {
+          const dataList = [...insertRecords];
+          if (dataList.length === 0) {
+            return Promise.resolve();
+          }
+          const { fileStorageId, fileName, type, seq, fileList } = dataList[0];
+          return uploadFileApi(
+            {
+              fileStorageId,
+              fileName,
+              type,
+              seq,
+            },
+            fileList[0].originFileObj,
+          );
+        },
+        delete: ({ body: { removeRecords } }) => deleteApi(removeRecords),
+        getById: (params) => getByIdApi(params.id),
+      },
+    },
+    toolbarConfig: {
+      zoom: true,
+      refresh: true,
+      column: { columnOrder: true },
+      buttons: [
+        {
+          code: 'ModalAdd',
+        },
+        {
+          code: 'delete',
+        },
+      ],
+    },
+  });
+</script>

+ 29 - 0
src/modules/fileManager/views/SmartFile/lang/en_US.ts

@@ -0,0 +1,29 @@
+/**
+ * 文件表 国际化信息
+ */
+export default {
+  trans: true,
+  key: 'system.views.file',
+  data: {
+    title: {
+      fileId: '文件ID',
+      fileStorageId: '文件存储器',
+      fileName: '文件名',
+      type: '类型',
+      contentType: '文件类型',
+      fileSize: '文件大小',
+    },
+    validate: {
+      fileId: '请输入文件ID',
+      fileStorageId: '请输入文件存储器',
+      fileName: '请输入文件名',
+      type: '请输入类型',
+    },
+    rules: {},
+    search: {
+      fileName: '请输入文件名',
+      type: '请输入类型',
+      fileStorageId: '请输入文件存储器',
+    },
+  },
+};

+ 29 - 0
src/modules/fileManager/views/SmartFile/lang/zh_CN.ts

@@ -0,0 +1,29 @@
+/**
+ * 文件表 国际化信息
+ */
+export default {
+  trans: true,
+  key: 'system.views.file',
+  data: {
+    title: {
+      fileId: '文件ID',
+      fileStorageId: '文件存储器',
+      fileName: '文件名',
+      type: '类型',
+      contentType: '文件类型',
+      fileSize: '文件大小',
+    },
+    validate: {
+      fileId: '请输入文件ID',
+      fileStorageId: '请输入文件存储器',
+      fileName: '请输入文件名',
+      type: '请输入类型',
+    },
+    rules: {},
+    search: {
+      fileName: '请输入文件名',
+      type: '请输入类型',
+      fileStorageId: '请输入文件存储器',
+    },
+  },
+};

+ 53 - 0
src/modules/fileManager/views/SmartFileStorage/SmartFileStorageListView.api.ts

@@ -0,0 +1,53 @@
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+enum Api {
+  list = '/smart/fileStorage/list',
+  getById = '/smart/fileStorage/getById',
+  batchSaveUpdate = '/smart/fileStorage/saveUpdateBatch',
+  delete = '/smart/fileStorage/batchDeleteById',
+  setDefault = '/smart/fileStorage/setDefault',
+}
+
+export const listApi = (params) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_FILE,
+    url: Api.list,
+    data: {
+      ...params,
+    },
+  });
+};
+
+export const batchSaveUpdateApi = (modelList: any[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_FILE,
+    url: Api.batchSaveUpdate,
+    data: modelList,
+  });
+};
+
+export const deleteApi = (removeRecords: Recordable[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_FILE,
+    url: Api.delete,
+    data: removeRecords.map((item) => item.id),
+  });
+};
+
+export const getByIdApi = (model: Recordable) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_FILE,
+    url: Api.getById,
+    data: model.id,
+  });
+};
+
+export const setDefaultApi = (id) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_FILE,
+    url: Api.setDefault,
+    data: {
+      id,
+    },
+  });
+};

+ 459 - 0
src/modules/fileManager/views/SmartFileStorage/SmartFileStorageListView.config.ts

@@ -0,0 +1,459 @@
+import type { SmartColumn, SmartSearchFormSchema } from '@/components/SmartTable';
+import type { FormSchema } from '@/components/Form';
+
+/**
+ * 表格列表
+ */
+export const getTableColumns = (): SmartColumn[] => {
+  return [
+    {
+      type: 'checkbox',
+      width: 60,
+      align: 'center',
+      fixed: 'left',
+    },
+    {
+      field: 'id',
+      visible: false,
+      title: '{smart.file.storage.title.id}',
+      width: 120,
+    },
+    {
+      field: 'storageCode',
+      title: '{smart.file.storage.title.storageCode}',
+      width: 160,
+      fixed: 'left',
+    },
+    {
+      field: 'storageName',
+      title: '{smart.file.storage.title.storageName}',
+      width: 160,
+      fixed: 'left',
+    },
+    {
+      field: 'storageType',
+      sortable: true,
+      title: '{smart.file.storage.title.storageType}',
+      width: 120,
+      slots: {
+        default: 'table-storageType',
+      },
+    },
+    {
+      field: 'seq',
+      sortable: true,
+      title: '{common.table.seq}',
+      width: 120,
+    },
+    {
+      field: 'remark',
+      title: '{common.table.remark}',
+      width: 120,
+    },
+    {
+      field: 'defaultStorage',
+      title: '{smart.file.storage.title.defaultStorage}',
+      width: 140,
+      component: 'switch',
+      componentProps: {
+        disabled: true,
+      },
+    },
+    // {
+    //   field: 'storageConfig',
+    //   title: '{smart.file.storage.title.storageConfig}',
+    //   width: 120,
+    // },
+    {
+      field: 'createTime',
+      title: '{common.table.createTime}',
+      width: 160,
+      sortable: true,
+    },
+    {
+      field: 'createBy',
+      title: '{common.table.createUser}',
+      width: 120,
+    },
+    {
+      field: 'updateTime',
+      title: '{common.table.updateTime}',
+      width: 160,
+      sortable: true,
+    },
+    {
+      field: 'updateBy',
+      title: '{common.table.updateUser}',
+      width: 120,
+    },
+    {
+      title: '{common.table.useYn}',
+      field: 'useYn',
+      width: 100,
+      component: 'booleanTag',
+    },
+    {
+      title: '{common.table.operation}',
+      field: 'operation',
+      width: 150,
+      fixed: 'right',
+      slots: {
+        default: 'table-operation',
+      },
+    },
+  ];
+};
+
+/**
+ * 添加修改表单
+ */
+export const getFormSchemas = (t: Function): FormSchema[] => {
+  return [
+    {
+      field: 'id',
+      show: false,
+      label: t('smart.file.storage.title.id'),
+      component: 'Input',
+      componentProps: {},
+    },
+    {
+      field: 'storageCode',
+      label: t('smart.file.storage.title.storageCode'),
+      component: 'Input',
+      componentProps: {},
+      required: true,
+    },
+    {
+      field: 'storageName',
+      label: t('smart.file.storage.title.storageName'),
+      component: 'Input',
+      componentProps: {},
+      required: true,
+    },
+    // {
+    //   field: 'defaultStorage',
+    //   label: t('smart.file.storage.title.defaultStorage'),
+    //   component: 'Switch',
+    //   componentProps: {},
+    //   colProps: {
+    //     span: 12,
+    //   },
+    // },
+    {
+      field: 'useYn',
+      label: t('common.table.useYn'),
+      component: 'Switch',
+      componentProps: {},
+      defaultValue: true,
+    },
+    {
+      field: 'seq',
+      label: t('common.table.seq'),
+      component: 'InputNumber',
+      componentProps: {},
+      required: true,
+      defaultValue: 1,
+    },
+    {
+      field: 'remark',
+      label: t('common.table.remark'),
+      component: 'InputTextArea',
+      componentProps: {},
+    },
+    {
+      field: 'storageType',
+      label: t('smart.file.storage.title.storageType'),
+      component: 'SmartApiSelectDict',
+      componentProps: {
+        dictCode: 'FILE_STORAGE_TYPE',
+      },
+      required: true,
+    },
+    // --------------自定义配置信息
+    // 磁盘存储配置
+    {
+      field: 'storageConfig.DISK.basePath',
+      component: 'Input',
+      label: t('smart.file.storage.title.basePath'),
+      show: ({ model }) => {
+        return model.storageType === 'DISK';
+      },
+      required: ({ model }) => model.storageType === 'DISK',
+    },
+    // ---------- minio配置
+    {
+      field: 'storageConfig.MINIO.endpoint',
+      component: 'Input',
+      label: t('smart.file.storage.title.endpoint'),
+      show: ({ model }) => {
+        return model.storageType === 'MINIO';
+      },
+      required: ({ model }) => model.storageType === 'MINIO',
+    },
+    {
+      field: 'storageConfig.MINIO.accessKey',
+      component: 'Input',
+      label: t('smart.file.storage.title.accessKey'),
+      show: ({ model }) => {
+        return model.storageType === 'MINIO';
+      },
+      required: ({ model }) => model.storageType === 'MINIO',
+    },
+    {
+      field: 'storageConfig.MINIO.secretKey',
+      component: 'Input',
+      label: t('smart.file.storage.title.secretKey'),
+      show: ({ model }) => {
+        return model.storageType === 'MINIO';
+      },
+      required: ({ model }) => model.storageType === 'MINIO',
+    },
+    {
+      field: 'storageConfig.MINIO.bucketName',
+      component: 'Input',
+      label: t('smart.file.storage.title.bucketName'),
+      show: ({ model }) => {
+        return model.storageType === 'MINIO';
+      },
+      required: ({ model }) => model.storageType === 'MINIO',
+    },
+    // ------------- sftp
+    {
+      field: 'storageConfig.SFTP.host',
+      component: 'Input',
+      label: t('smart.file.storage.title.host'),
+      show: ({ model }) => {
+        return model.storageType === 'SFTP';
+      },
+      required: ({ model }) => model.storageType === 'SFTP',
+    },
+    {
+      field: 'storageConfig.SFTP.port',
+      component: 'InputNumber',
+      label: t('smart.file.storage.title.port'),
+      show: ({ model }) => {
+        return model.storageType === 'SFTP';
+      },
+      required: ({ model }) => model.storageType === 'SFTP',
+    },
+    {
+      field: 'storageConfig.SFTP.basePath',
+      component: 'Input',
+      label: t('smart.file.storage.title.basePath'),
+      show: ({ model }) => {
+        return model.storageType === 'SFTP';
+      },
+      required: ({ model }) => model.storageType === 'SFTP',
+    },
+    {
+      field: 'storageConfig.SFTP.username',
+      component: 'Input',
+      label: t('smart.file.storage.title.username'),
+      show: ({ model }) => {
+        return model.storageType === 'SFTP';
+      },
+      required: ({ model }) => model.storageType === 'SFTP',
+    },
+    {
+      field: 'storageConfig.SFTP.password',
+      component: 'Input',
+      label: t('smart.file.storage.title.password'),
+      show: ({ model }) => {
+        return model.storageType === 'SFTP';
+      },
+      required: ({ model }) => model.storageType === 'SFTP',
+    },
+    {
+      field: 'storageConfig.SFTP.privateKey',
+      component: 'Input',
+      label: t('smart.file.storage.title.privateKey'),
+      show: ({ model }) => {
+        return model.storageType === 'SFTP';
+      },
+    },
+    // --------------- 阿里云OSS
+    ...getAliyunOssFormSchemas(t),
+    // --------------- 七牛云
+    ...getQiniuFormSchemas(t),
+    ...getFtpFormSchemas(t),
+  ];
+};
+
+export const getSearchFormSchemas = (t: Function): SmartSearchFormSchema[] => {
+  return [
+    {
+      field: 'storageCode',
+      label: t('smart.file.storage.title.storageCode'),
+      component: 'Input',
+      searchSymbol: 'like',
+    },
+    {
+      field: 'storageName',
+      label: t('smart.file.storage.title.storageName'),
+      component: 'Input',
+      searchSymbol: 'like',
+    },
+    {
+      field: 'storageType',
+      label: t('smart.file.storage.title.storageType'),
+      component: 'SmartApiSelectDict',
+      componentProps: {
+        dictCode: 'FILE_STORAGE_TYPE',
+        style: { width: '140px' },
+      },
+      searchSymbol: '=',
+    },
+  ];
+};
+
+/**
+ * 获取阿里云OSS表单配置
+ * @param t
+ */
+const getAliyunOssFormSchemas = (t: Function): FormSchema[] => {
+  return [
+    {
+      field: 'storageConfig.ALIYUN_OSS.endpoint',
+      component: 'Input',
+      label: t('smart.file.storage.title.endpoint'),
+      show: ({ model }) => {
+        return model.storageType === 'ALIYUN_OSS';
+      },
+      required: ({ model }) => model.storageType === 'ALIYUN_OSS',
+    },
+    {
+      field: 'storageConfig.ALIYUN_OSS.accessKey',
+      component: 'Input',
+      label: t('smart.file.storage.title.accessKey'),
+      show: ({ model }) => {
+        return model.storageType === 'ALIYUN_OSS';
+      },
+      required: ({ model }) => model.storageType === 'ALIYUN_OSS',
+    },
+    {
+      field: 'storageConfig.ALIYUN_OSS.secretKey',
+      component: 'Input',
+      label: t('smart.file.storage.title.secretKey'),
+      show: ({ model }) => {
+        return model.storageType === 'ALIYUN_OSS';
+      },
+      required: ({ model }) => model.storageType === 'ALIYUN_OSS',
+    },
+    {
+      field: 'storageConfig.ALIYUN_OSS.bucketName',
+      component: 'Input',
+      label: t('smart.file.storage.title.bucketName'),
+      show: ({ model }) => {
+        return model.storageType === 'ALIYUN_OSS';
+      },
+      required: ({ model }) => model.storageType === 'ALIYUN_OSS',
+    },
+  ];
+};
+
+const getQiniuFormSchemas = (t: Function): FormSchema[] => {
+  return [
+    {
+      field: 'storageConfig.QINIU.accessKey',
+      component: 'Input',
+      label: t('smart.file.storage.title.accessKey'),
+      show: ({ model }) => {
+        return model.storageType === 'QINIU';
+      },
+      required: ({ model }) => model.storageType === 'QINIU',
+    },
+    {
+      field: 'storageConfig.QINIU.secretKey',
+      component: 'InputPassword',
+      label: t('smart.file.storage.title.secretKey'),
+      show: ({ model }) => {
+        return model.storageType === 'QINIU';
+      },
+      required: ({ model }) => model.storageType === 'QINIU',
+    },
+    {
+      field: 'storageConfig.QINIU.bucketName',
+      component: 'Input',
+      label: t('smart.file.storage.title.bucketName'),
+      show: ({ model }) => {
+        return model.storageType === 'QINIU';
+      },
+      required: ({ model }) => model.storageType === 'QINIU',
+    },
+    {
+      field: 'storageConfig.QINIU.region',
+      component: 'Input',
+      label: t('smart.file.storage.title.region'),
+      show: ({ model }) => {
+        return model.storageType === 'QINIU';
+      },
+    },
+    {
+      field: 'storageConfig.QINIU.url',
+      component: 'Input',
+      label: t('smart.file.storage.title.url'),
+      show: ({ model }) => {
+        return model.storageType === 'QINIU';
+      },
+      required: ({ model }) => model.storageType === 'QINIU',
+    },
+    {
+      field: 'storageConfig.QINIU.useHttps',
+      component: 'Switch',
+      label: t('smart.file.storage.title.useHttps'),
+      show: ({ model }) => {
+        return model.storageType === 'QINIU';
+      },
+    },
+  ];
+};
+
+const getFtpFormSchemas = (t: Function): FormSchema[] => {
+  return [
+    {
+      field: 'storageConfig.FTP.host',
+      component: 'Input',
+      label: t('smart.file.storage.title.host'),
+      show: ({ model }) => {
+        return model.storageType === 'FTP';
+      },
+      required: ({ model }) => model.storageType === 'FTP',
+    },
+    {
+      field: 'storageConfig.FTP.port',
+      component: 'InputNumber',
+      label: t('smart.file.storage.title.port'),
+      show: ({ model }) => {
+        return model.storageType === 'FTP';
+      },
+      required: ({ model }) => model.storageType === 'FTP',
+    },
+    {
+      field: 'storageConfig.FTP.basePath',
+      component: 'Input',
+      label: t('smart.file.storage.title.basePath'),
+      show: ({ model }) => {
+        return model.storageType === 'FTP';
+      },
+      required: ({ model }) => model.storageType === 'FTP',
+    },
+    {
+      field: 'storageConfig.FTP.username',
+      component: 'Input',
+      label: t('smart.file.storage.title.username'),
+      show: ({ model }) => {
+        return model.storageType === 'FTP';
+      },
+      required: ({ model }) => model.storageType === 'FTP',
+    },
+    {
+      field: 'storageConfig.FTP.password',
+      component: 'Input',
+      label: t('smart.file.storage.title.password'),
+      show: ({ model }) => {
+        return model.storageType === 'FTP';
+      },
+      required: ({ model }) => model.storageType === 'FTP',
+    },
+  ];
+};

+ 193 - 0
src/modules/fileManager/views/SmartFileStorage/SmartFileStorageListView.vue

@@ -0,0 +1,193 @@
+<template>
+  <div class="full-height page-container">
+    <SmartTable @register="registerTable" :size="getTableSize">
+      <template #table-operation="{ row }">
+        <SmartVxeTableAction
+          :actions="getActions(row)"
+          :drop-down-actions="getDropDownActions(row)"
+        />
+      </template>
+      <template #table-storageType="{ row }">
+        <span>{{ getDictItemMap[row.storageType] }}</span>
+      </template>
+    </SmartTable>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+  import { replace } from 'lodash-es';
+  import { Modal } from 'ant-design-vue';
+  import { hasPermission } from '@/utils/auth';
+
+  import {
+    ActionItem,
+    SmartTable,
+    SmartVxeTableAction,
+    useSmartTable,
+  } from '@/components/SmartTable';
+  import { useLoadDictItem } from '@/modules/system/hooks/SysDictHooks';
+
+  import {
+    getTableColumns,
+    getFormSchemas,
+    getSearchFormSchemas,
+  } from './SmartFileStorageListView.config';
+  import {
+    listApi,
+    deleteApi,
+    getByIdApi,
+    batchSaveUpdateApi,
+    setDefaultApi,
+  } from './SmartFileStorageListView.api';
+  import { createVNode } from 'vue';
+  import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
+
+  const { t } = useI18n();
+  const { getTableSize } = useSizeSetting();
+
+  const storageConfigPrefix = 'storageConfig';
+  const { getDictItemMap } = useLoadDictItem('FILE_STORAGE_TYPE');
+
+  const getActions = (row: Recordable): ActionItem[] => {
+    return [
+      {
+        label: t('common.button.edit'),
+        auth: 'smart:fileStorage:edit',
+        onClick: () => editByRowModal(row),
+      },
+      {
+        label: t('common.button.delete'),
+        onClick: () => deleteByRow(row),
+        auth: 'smart:fileStorage:delete',
+        danger: true,
+      },
+    ];
+  };
+
+  const getDropDownActions = (row: Recordable): ActionItem[] => {
+    return [
+      {
+        label: t('smart.file.storage.button.setDefault'),
+        preIcon: 'ant-design:check-outlined',
+        disabled: row.defaultStorage === true,
+        auth: 'smart:fileStorage:setDefault',
+        onClick: () => {
+          Modal.confirm({
+            title: t('common.notice.confirm'),
+            icon: createVNode(ExclamationCircleOutlined),
+            content: t('smart.file.storage.message.setDefault'),
+            onOk: async () => {
+              await setDefaultApi(row.id);
+              query();
+            },
+          });
+        },
+      },
+    ];
+  };
+
+  const [registerTable, { editByRowModal, deleteByRow, query }] = useSmartTable({
+    columns: getTableColumns(),
+    height: 'auto',
+    pagerConfig: true,
+    useSearchForm: true,
+    searchFormConfig: {
+      schemas: getSearchFormSchemas(t),
+      searchWithSymbol: true,
+      colon: true,
+      compact: true,
+      layout: 'inline',
+      actionColOptions: {
+        span: undefined,
+      },
+    },
+    addEditConfig: {
+      modalConfig: {
+        // width: '800px',
+        height: 800,
+      },
+      formConfig: {
+        colon: true,
+        schemas: getFormSchemas(t),
+        baseColProps: { span: 24 },
+        labelCol: { style: { width: '120px' } },
+        wrapperCol: { span: 17 },
+      },
+    },
+    sortConfig: {
+      remote: true,
+    },
+    columnConfig: {
+      resizable: true,
+    },
+    border: true,
+    proxyConfig: {
+      ajax: {
+        query: ({ ajaxParameter }) => {
+          const params = {
+            sortName: 'seq',
+            ...ajaxParameter,
+          };
+          return listApi(params);
+        },
+        save: ({ body: { insertRecords, updateRecords } }) => {
+          const saveDatList = [...insertRecords, ...updateRecords];
+          const formatDataList = saveDatList.map((item) => {
+            const result: any = {};
+            const storageConfig: Recordable = {};
+            const configKeyPrefix = storageConfigPrefix + '.' + item.storageType + '.';
+            Object.keys(item).forEach((key) => {
+              if (!key.startsWith(storageConfigPrefix)) {
+                result[key] = item[key];
+              } else if (key.startsWith(configKeyPrefix)) {
+                storageConfig[replace(key, configKeyPrefix, '')] = item[key];
+              }
+            });
+            result.storageConfig = JSON.stringify(storageConfig);
+            return result;
+          });
+          return batchSaveUpdateApi(formatDataList);
+        },
+        delete: ({ body: { removeRecords } }) => deleteApi(removeRecords),
+        getById: async (params) => {
+          const result = await getByIdApi(params);
+          if (result.storageConfig) {
+            const storageConfig = JSON.parse(result.storageConfig);
+            const storageType = result.storageType;
+            const formatData: Recordable = {};
+            Object.keys(storageConfig).forEach((item) => {
+              formatData[`${storageConfigPrefix}.${storageType}.${item}`] = storageConfig[item];
+            });
+            return {
+              ...result,
+              ...formatData,
+            };
+          }
+          return result;
+        },
+      },
+    },
+    authConfig: {
+      authHandler: hasPermission,
+      toolbar: {
+        ModalAdd: 'smart:fileStorage:save',
+        delete: 'smart:fileStorage:delete',
+      },
+    },
+    toolbarConfig: {
+      zoom: true,
+      refresh: true,
+      column: { columnOrder: true },
+      buttons: [
+        {
+          code: 'ModalAdd',
+        },
+        {
+          code: 'delete',
+        },
+      ],
+    },
+  });
+</script>

+ 50 - 0
src/modules/fileManager/views/SmartFileStorage/lang/en_US.ts

@@ -0,0 +1,50 @@
+/**
+ * 文件存储器配置 国际化信息
+ */
+export default {
+  trans: true,
+  key: 'smart.file.storage',
+  data: {
+    title: {
+      id: 'id',
+      storageCode: 'Storage code',
+      storageName: 'Storage name',
+      storageType: 'Storage type',
+      defaultStorage: 'Default',
+      storageConfig: 'config',
+      basePath: 'Bash path',
+      endpoint: 'End point',
+      accessKey: 'Access key',
+      secretKey: 'Secret key',
+      bucketName: 'Bucket name',
+      host: 'Host',
+      port: 'Port',
+      username: 'Username',
+      password: 'Password',
+      privateKey: 'Private key',
+      region: 'Region',
+      url: 'URL',
+      useHttps: 'Use https',
+    },
+    validate: {
+      storageCode: 'Please enter storage code',
+      storageName: 'Please enter storage name',
+      storageType: 'Please select storage type',
+      defaultStorage: 'Please enter storage default',
+      storageConfig: 'Please enter storage config',
+    },
+    message: {
+      setDefault:
+        'Only one default memory can be set. Are you sure you want to set it as the default memory?',
+    },
+    rules: {},
+    search: {
+      storageCode: 'Please enter storage code',
+      storageName: 'Please enter storage name',
+      storageType: 'Please select storage type',
+    },
+    button: {
+      setDefault: 'Set default',
+    },
+  },
+};

+ 49 - 0
src/modules/fileManager/views/SmartFileStorage/lang/zh_CN.ts

@@ -0,0 +1,49 @@
+/**
+ * 文件存储器配置 国际化信息
+ */
+export default {
+  trans: true,
+  key: 'smart.file.storage',
+  data: {
+    title: {
+      id: 'id',
+      storageCode: '存储器编码',
+      storageName: '存储器名称',
+      storageType: '存储器类型',
+      defaultStorage: '默认存储器',
+      storageConfig: '存储器配置信息',
+      basePath: '基础路径',
+      endpoint: 'Endpoint',
+      accessKey: 'AccessKey',
+      secretKey: 'SecretKey',
+      bucketName: 'BucketName',
+      host: '主机地址',
+      port: '端口',
+      username: '用户名',
+      password: '密码',
+      privateKey: '私钥',
+      region: '区域',
+      url: 'URL',
+      useHttps: '启用https',
+    },
+    validate: {
+      storageCode: '请输入存储器编码',
+      storageName: '请输入存储器名称',
+      storageType: '请输入存储器类型',
+      defaultStorage: '请输入是否是默认存储器',
+      storageConfig: '请输入存储器配置信息',
+    },
+    message: {
+      setDefault: '只能设置一个默认存储器,确定要设为默认存储器吗?',
+    },
+    rules: {},
+    search: {
+      storageCode: '请输入存储器编码',
+      storageName: '请输入存储器名称',
+      storageType: '请输入存储器类型',
+    },
+    button: {
+      setDefault: '设为默认',
+    },
+  },
+};