diff --git a/packages/core/client/src/schema-component/antd/association-field/FileManager.tsx b/packages/core/client/src/schema-component/antd/association-field/FileManager.tsx index 004d1055df..01af7d681f 100644 --- a/packages/core/client/src/schema-component/antd/association-field/FileManager.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/FileManager.tsx @@ -85,9 +85,10 @@ const useTableSelectorProps = () => { function FileSelector(props) { const { disabled, multiple, value, onChange, action, onSelect, quickUpload, selectFile, ...other } = props; const { wrapSSR, hashId, componentCls: prefixCls } = useStyles(); - const { useFileCollectionStorageRules } = useExpressionScope(); + const { useFileCollectionStorageRules, useAttachmentFieldProps } = useExpressionScope(); const { t } = useTranslation(); const rules = useFileCollectionStorageRules(); + const attachmentFieldProps = useAttachmentFieldProps(); // 兼容旧版本 const showSelectButton = selectFile === undefined && quickUpload === undefined; return wrapSSR( @@ -116,6 +117,7 @@ function FileSelector(props) { ) : null} {quickUpload ? ( (props: T) { const api = useAPIClient(); return { - ...props, // in customRequest method can't modify form's status(e.g: form.disabled=true ) // that will be trigger Upload component(actual Underlying is AjaxUploader component )'s componentWillUnmount method // which will cause multiple files upload fail @@ -180,6 +179,7 @@ export function useUploadProps(props: T) { }, }; }, + ...props, }; } diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageRules.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageRules.ts index e40f1cb927..ca53c92a2f 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageRules.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageRules.ts @@ -10,6 +10,7 @@ import { useEffect } from 'react'; import { useField } from '@formily/react'; import { useAPIClient, useCollectionField, useCollectionManager, useRequest } from '@nocobase/client'; +import { useStorageUploadProps } from './useStorageUploadProps'; export function useStorageRules(storage) { const name = storage ?? ''; @@ -17,7 +18,7 @@ export function useStorageRules(storage) { const field = useField(); const { loading, data, run } = useRequest( { - url: `storages:getRules/${name}`, + url: `storages:getBasicInfo/${name}`, }, { manual: true, @@ -31,17 +32,16 @@ export function useStorageRules(storage) { } run(); }, [field.pattern, run]); - return (!loading && data?.data) || null; + return (!loading && data?.data?.rules) || null; } export function useAttachmentFieldProps() { const field = useCollectionField(); - const rules = useStorageRules(field?.storage); - - return { - rules, - action: `${field.target}:create${field.storage ? `?attachmentField=${field.collectionName}.${field.name}` : ''}`, - }; + const action = `${field.target}:create${ + field.storage ? `?attachmentField=${field.collectionName}.${field.name}` : '' + }`; + const storageUploadProps = useStorageUploadProps({ action }); + return { action, ...storageUploadProps }; } export function useFileCollectionStorageRules() { diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageUploadProps.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageUploadProps.ts new file mode 100644 index 0000000000..75a1d27071 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageUploadProps.ts @@ -0,0 +1,67 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { + Input, + Upload, + useCollection, + useCollectionField, + useCollectionManager, + useCollectionRecordData, + usePlugin, + useRequest, + withDynamicSchemaProps, +} from '@nocobase/client'; +import React, { useEffect } from 'react'; +import { connect, mapProps, mapReadPretty, useField } from '@formily/react'; +import FileManagerPlugin from '../'; + +export function useStorage(storage) { + const name = storage ?? ''; + const url = `storages:getBasicInfo/${name}`; + const { loading, data, run } = useRequest( + { + url, + }, + { + manual: true, + refreshDeps: [name], + cacheKey: url, + }, + ); + useEffect(() => { + run(); + }, [run]); + return (!loading && data?.data) || null; +} + +export function useStorageCfg() { + const field = useCollectionField(); + const cm = useCollectionManager(); + const targetCollection = cm.getCollection(field?.target); + const collection = useCollection(); + const plugin = usePlugin(FileManagerPlugin); + const storage = useStorage( + field?.storage || collection?.getOption('storage') || targetCollection?.getOption('storage'), + ); + const storageType = plugin.getStorageType(storage?.type); + return { + storage, + storageType, + }; +} +export function useStorageUploadProps(props) { + const { storage, storageType } = useStorageCfg(); + const useStorageTypeUploadProps = storageType?.useUploadProps; + const storageTypeUploadProps = useStorageTypeUploadProps?.({ storage, rules: storage.rules, ...props }) || {}; + return { + rules: storage?.rules, + ...storageTypeUploadProps, + }; +} diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useUploadFiles.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useUploadFiles.ts index f22dc7b4e9..f30aadafbc 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useUploadFiles.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useUploadFiles.ts @@ -16,7 +16,7 @@ import { useSourceId, } from '@nocobase/client'; import { useContext, useMemo } from 'react'; -import { useStorageRules } from './useStorageRules'; +import { useStorageUploadProps } from './useStorageUploadProps'; export const useUploadFiles = () => { const { getDataBlockRequest } = useDataBlockRequestGetter(); @@ -24,7 +24,6 @@ export const useUploadFiles = () => { const { setVisible } = useActionContext(); const collection = useCollection(); const sourceId = useSourceId(); - const rules = useStorageRules(collection?.getOption('storage')); const action = useMemo(() => { let action = `${collection.name}:create`; if (association) { @@ -38,7 +37,7 @@ export const useUploadFiles = () => { let pendingNumber = 0; - return { + const uploadProps = { action, onChange(fileList) { fileList.forEach((file) => { @@ -62,6 +61,11 @@ export const useUploadFiles = () => { setVisible(false); } }, - rules, + }; + + const storageUploadProps = useStorageUploadProps(uploadProps); + return { + ...uploadProps, + ...storageUploadProps, }; }; diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/index.tsx b/packages/plugins/@nocobase/plugin-file-manager/src/client/index.tsx index d25df29d4f..e7c4025a0b 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/index.tsx @@ -16,8 +16,11 @@ import { AttachmentFieldInterface } from './interfaces/attachment'; import { FileCollectionTemplate } from './templates'; import { useAttachmentFieldProps, useFileCollectionStorageRules } from './hooks'; import { FileSizeField } from './FileSizeField'; +import { STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_LOCAL, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS } from '../constants'; export class PluginFileManagerClient extends Plugin { + // refer by plugin-field-attachment-url + static buildInStorage = [STORAGE_TYPE_LOCAL, STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS]; storageTypes = new Map(); async load() { @@ -65,6 +68,10 @@ export class PluginFileManagerClient extends Plugin { registerStorageType(name: string, options) { this.storageTypes.set(name, options); } + + getStorageType(name: string) { + return this.storageTypes.get(name); + } } export default PluginFileManagerClient; diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts index 21aed67841..235aedc30c 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts @@ -35,6 +35,7 @@ describe('action', () => { local1 = await StorageRepo.create({ values: { name: 'local1', + title: 'local1', type: STORAGE_TYPE_LOCAL, baseUrl: DEFAULT_LOCAL_BASE_URL, rules: { @@ -478,34 +479,40 @@ describe('action', () => { }); describe('storage actions', () => { - describe('getRules', () => { - it('get rules without key as default storage', async () => { - const { body, status } = await agent.resource('storages').getRules(); + describe('getBasicInfo', () => { + it('get default storage', async () => { + const { body, status } = await agent.resource('storages').getBasicInfo(); expect(status).toBe(200); - expect(body.data).toEqual({ size: FILE_SIZE_LIMIT_DEFAULT }); + expect(body.data).toMatchObject({ id: 1 }); }); - it('get rules by storage id as default rules', async () => { - const { body, status } = await agent.resource('storages').getRules({ filterByTk: 1 }); - expect(status).toBe(200); - expect(body.data).toEqual({ size: FILE_SIZE_LIMIT_DEFAULT }); - }); - - it('get rules by unexisted id as 404', async () => { - const { body, status } = await agent.resource('storages').getRules({ filterByTk: -1 }); + it('get storage by unexisted id as 404', async () => { + const { body, status } = await agent.resource('storages').getBasicInfo({ filterByTk: -1 }); expect(status).toBe(404); }); - it('get rules by storage id', async () => { - const { body, status } = await agent.resource('storages').getRules({ filterByTk: local1.id }); + it('get by storage local id', async () => { + const { body, status } = await agent.resource('storages').getBasicInfo({ filterByTk: local1.id }); expect(status).toBe(200); - expect(body.data).toMatchObject({ size: 1024 }); + expect(body.data).toMatchObject({ + id: local1.id, + title: local1.title, + name: local1.name, + type: local1.type, + rules: local1.rules, + }); }); - it('get rules by storage name', async () => { - const { body, status } = await agent.resource('storages').getRules({ filterByTk: local1.name }); + it('get storage by name', async () => { + const { body, status } = await agent.resource('storages').getBasicInfo({ filterByTk: local1.name }); expect(status).toBe(200); - expect(body.data).toMatchObject({ size: 1024 }); + expect(body.data).toMatchObject({ + id: local1.id, + title: local1.title, + name: local1.name, + type: local1.type, + rules: local1.rules, + }); }); }); }); diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts index 1a97f9c961..d514653636 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts @@ -126,8 +126,11 @@ export async function createMiddleware(ctx: Context, next: Next) { const storage = await StorageRepo.findOne({ filter: storageName ? { name: storageName } : { default: true } }); ctx.storage = storage; - - await multipart(ctx, next); + if (ctx?.request.is('multipart/*')) { + await multipart(ctx, next); + } else { + await next(); + } } export async function destroyMiddleware(ctx: Context, next: Next) { diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/storages.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/storages.ts index ba995b7836..b1f4c218e3 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/storages.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/storages.ts @@ -1,6 +1,15 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + import Plugin from '..'; -export async function getRules(context, next) { +export async function getBasicInfo(context, next) { const { storagesCache } = context.app.pm.get(Plugin) as Plugin; let result; const { filterByTk } = context.action.params; @@ -15,7 +24,13 @@ export async function getRules(context, next) { if (!result) { return context.throw(404); } - context.body = result.rules; + context.body = { + id: result.id, + title: result.title, + name: result.name, + type: result.type, + rules: result.rules, + }; next(); } diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts index 70848696c8..7cf7e2cdcf 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts @@ -232,7 +232,7 @@ export class PluginFileManagerServer extends Plugin { this.app.acl.allow('attachments', 'upload', 'loggedIn'); this.app.acl.allow('attachments', 'create', 'loggedIn'); - this.app.acl.allow('storages', 'getRules', 'loggedIn'); + this.app.acl.allow('storages', 'getBasicInfo', 'loggedIn'); // this.app.resourcer.use(uploadMiddleware); // this.app.resourcer.use(createAction);