diff --git a/packages/core/client/src/collection-manager/interfaces/index.ts b/packages/core/client/src/collection-manager/interfaces/index.ts index 9679acf520..528773eb49 100644 --- a/packages/core/client/src/collection-manager/interfaces/index.ts +++ b/packages/core/client/src/collection-manager/interfaces/index.ts @@ -1,4 +1,3 @@ -export * from './attachment'; export * from './checkbox'; export * from './checkboxGroup'; export * from './chinaRegion'; diff --git a/packages/core/client/src/locale/en_US.ts b/packages/core/client/src/locale/en_US.ts index 8c6a8eedf3..1b0b25e62f 100644 --- a/packages/core/client/src/locale/en_US.ts +++ b/packages/core/client/src/locale/en_US.ts @@ -209,7 +209,6 @@ export default { "Field source":"Field source", "Preview":"Preview", "Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.", - "Storage type": "Storage type", "Edit": "Edit", "Edit collection": "Edit collection", "Configure fields": "Configure fields", @@ -245,7 +244,6 @@ export default { "Radio group": "Radio group", "Checkbox group": "Checkbox group", "China region": "China region", - "Attachment": "Attachment", "Date & Time": "Date & Time", "Datetime": "Datetime", "Relation": "Relation", @@ -523,7 +521,6 @@ export default { "Redirect to": "Redirect to", "Save action": "Save action", "Exists": "Exists", - "Filename": "Filename", "Add condition": "Add condition", "Add condition group": "Add condition group", "exists": "exists", @@ -540,7 +537,6 @@ export default { "Expression": "Expression", "Input +, -, *, /, ( ) to calculate, input @ to open field variables.": "Input +, -, *, /, ( ) to calculate, input @ to open field variables.", "Formula error.": "Formula error.", - "Accept": "Accept", "Rich Text": "Rich Text", "Junction collection": "Junction collection", "Leave it blank, unless you need a custom intermediate table": "Leave it blank, unless you need a custom intermediate table", @@ -593,22 +589,6 @@ export default { "Action permission": "Action permission", "Field permission": "Field permission", "Scope name": "Scope name", - "File storages": "File storages", - "Storage display name": "Storage display name", - "Storage name": "Storage name", - "Default storage": "Default storage", - "Add storage": "Add storage", - "Edit storage": "Edit storage", - "Storage base URL": "Storage base URL", - "Destination": "Destination", - "Use the built-in static file server": "Use the built-in static file server", - "Local storage": "Local storage", - "Aliyun OSS": "Aliyun OSS", - "Amazon S3": "Amazon S3", - "Tencent COS": "Tencent COS", - "Region": "Region", - "Bucket": "Bucket", - "Path": "Path", "Unsaved changes": "Unsaved changes", "Are you sure you don't want to save?": "Are you sure you don't want to save?", "Dragging": "Dragging", diff --git a/packages/core/client/src/locale/ja_JP.ts b/packages/core/client/src/locale/ja_JP.ts index 4dfa5a6a27..b6d57c72ac 100644 --- a/packages/core/client/src/locale/ja_JP.ts +++ b/packages/core/client/src/locale/ja_JP.ts @@ -189,7 +189,6 @@ export default { "Field source": "ソースフィールド", "Preview": "プレビュー", "Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "ランダムに生成され、変更可能です。 アルファベット、数字、アンダースコアをサポートし、アルファベットから始まる必要があります。", - "Storage type": "ストレージタイプ", "Edit": "編集", "Edit collection": "コレクションの編集", "Configure fields": "フィールドの設定", @@ -217,7 +216,6 @@ export default { "Radio group": "ラジオボタングループ", "Checkbox group": "チェックボックスグループ", "China region": "中国地域", - "Attachment": "添付ファイル", "Date & Time": "日付と時間", "Datetime": "日付", "Relation": "関連づけ", @@ -330,7 +328,6 @@ export default { "Add option": "オプションを追加", "Related collection": "関連付けコレクション", "Allow linking to multiple records": "複数のレコードの関連付けを許可する", - "Allow uploading multiple files": "複数ファイルのアップロードを許可する", "Configure calendar": "カレンダーの設定", "Title field": "タイトルフィールド", "Start date field": "開始日フィールド", @@ -429,7 +426,6 @@ export default { "Redirect to": "リダイレクトする", "Save action": "操作を保存", "Exists": "存在する", - "Filename": "ファイル名", "Add condition": "条件の追加", "Add condition group": "条件グループの追加", "exists": "が存在する", @@ -446,7 +442,6 @@ export default { "Expression": "表达式", "Input +, -, *, /, ( ) to calculate, input @ to open field variables.": "+、-、*、/、( ) で算術演算、@でフィールド変数を開くことができます。", "Formula error.": "式の検証エラーです。", - "Accept": "ファイル形式", "Rich Text": "リッチテキスト", "Junction collection": "中間コレクション", "Leave it blank, unless you need a custom intermediate table": "カスタム中間テーブルが必要でない限り、デフォルトで空白のままにします", @@ -495,19 +490,6 @@ export default { "Action permission": "操作権限", "Field permission": "フィールド権限", "Scope name": "スコープ名", - "File storages": "ファイルストレージ", - "Storage display name": "ファイルストレージ名", - "Storage name": "ファイルストレージ識別子", - "Default storage": "デフォルトストレージ", - "Add storage": "ファイルストレージを追加", - "Edit storage": "ファイルストレージを編集", - "Storage base URL": "Storage base URL", - "Destination": "ファイルパス", - "Use the built-in static file server": "組み込みの静的ファイル サービスを使用する", - "Local storage": "ローカルストレージ", - "Aliyun OSS": "Aliyun OSS", - "Tencent COS": "Tencent COS", - "Amazon S3": "Amazon S3", "Unsaved changes": "変更が保存されていません", "Are you sure you don't want to save?": "変更を保存しなくてもよいですか?", "Dragging": "ドラッグ", diff --git a/packages/core/client/src/locale/ru_RU.ts b/packages/core/client/src/locale/ru_RU.ts index ffdacaabd3..97fa9c52f5 100644 --- a/packages/core/client/src/locale/ru_RU.ts +++ b/packages/core/client/src/locale/ru_RU.ts @@ -133,7 +133,6 @@ export default { "Collection name": "Имя Коллекции", "Categories":"Категории таблиц данных", "Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "Случайно сгенерированный и может быть изменен. Поддерживает буквы, цифры и подчеркивания, должно начинаться с буквы.", - "Storage type": "Тип Хранилища", "Edit": "Изменить", "Edit collection": "Изменить Коллекцию", "Configure fields": "Конфигурировать поля", @@ -159,7 +158,6 @@ export default { "Radio group": "Радио группа", "Checkbox group": "Чекбокс группа", "China region": "Китай регион", - "Attachment": "Вложение", "Date & Time": "Дата & Время", "Datetime": "Датавремя", "Relation": "Связь", @@ -272,7 +270,6 @@ export default { "Add option": "Добавить опцию", "Related collection": "Связанная коллекция", "Allow linking to multiple records": "Позволить прилинковать много записей", - "Allow uploading multiple files": "Позволить загружать много файлов", "Configure calendar": "Настроить календарь", "Title field": "Поле заголовка", "Start date field": "Поле даты начала", @@ -371,7 +368,6 @@ export default { "Redirect to": "Перенаправить на", "Save action": "Сохранить действие", "Exists": "Существуют", - "Filename": "Имя файла", "Add condition": "Добавить правило", "Add condition group": "Добавить группу правил", "exists": "существуют", @@ -388,7 +384,6 @@ export default { "Expression": "Переменная", "Input +, -, *, /, ( ) to calculate, input @ to open field variables.": "Введите +, -, *, /, ( ) для вычисления, введите @ чтобы открыть переменные поля.", "Formula error.": "Ошибка формулы.", - "Accept": "Подтвердить", "Rich Text": "Rich Text", "Junction collection": "Коллекция Узлов", "Leave it blank, unless you need a custom intermediate table": "Оставьте это поле пустым, если вам не нужна пользовательская промежуточная таблица.", @@ -433,19 +428,6 @@ export default { "Action permission": "Разрешения на действия", "Field permission": "Разрешения на поля", "Scope name": "Имя области", - "File storages": "Файловые хранилища", - "Storage display name": "Имя храшилища на экране", - "Storage name": "Имя хранилища", - "Default storage": "Хранилище по умолчанию", - "Add storage": "Добавить хранилище", - "Edit storage": "Изменить хранилище", - "Storage base URL": "Базовый URL хранилища", - "Destination": "Назначение", - "Use the built-in static file server": "Использовать встроенный статический файл-сервер", - "Local storage": "Локальное хранилище", - "Aliyun OSS": "Aliyun OSS", - "Tencent COS": "Tencent COS", - "Amazon S3": "Amazon S3", "Unsaved changes": "Несохраненные изменения", "Are you sure you don't want to save?": "Вы уверены, что не хотите сохранить?", "Dragging": "Перетаскивание", diff --git a/packages/core/client/src/locale/tr_TR.ts b/packages/core/client/src/locale/tr_TR.ts index 4acf11b56b..6698b56511 100644 --- a/packages/core/client/src/locale/tr_TR.ts +++ b/packages/core/client/src/locale/tr_TR.ts @@ -132,7 +132,6 @@ export default { "Collection display name": "Koleksiyon görünen adı", "Collection name": "Koleksiyon adı", "Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "Rastgele oluşturulur ve değiştirilebilir. Desteklenen içerik; harfler, sayılar ve alt çizgiler. Bir harfle başlamalıdır.", - "Storage type": "Depolama türü", "Edit": "Düzenle", "Edit collection": "Koleksiyon düzenle", "Configure fields": "Alanları düzenle", @@ -158,7 +157,6 @@ export default { "Radio group": "Radio Seçim grup", "Checkbox group": "Checkbox grup", "China region": "Çin bölgesi", - "Attachment": "Dosya eki", "Date & Time": "Tarih & Saat", "Datetime": "Datetime", "Relation": "Relation", @@ -272,7 +270,6 @@ export default { "Add option": "Seçenek ekle", "Related collection": "Bağlantılı koleksiyon", "Allow linking to multiple records": "Birden çok kayda bağlanmaya izin ver", - "Allow uploading multiple files": "Birden çok dosya yüklemeye izin ver", "Configure calendar": "Takvimi yapılandır", "Start date field": "Başlangıç tarihi alanı", "End date field": "Bitiş tarihi alanı", @@ -370,7 +367,6 @@ export default { "Redirect to": "Yönlendirilecek yer", "Save action": "Kaydet işlemi", "Exists": "Var olanlar", - "Filename": "Dosya adı", "Add condition": "Koşul ekle", "Add condition group": "Koşul grubu ekle", "exists": "var olanlar", @@ -387,7 +383,6 @@ export default { "Expression": "Expression", "Input +, -, *, /, ( ) to calculate, input @ to open field variables.": "Input +, -, *, /, ( ) to calculate, input @ to open field variables.", "Formula error.": "Formül hatalı.", - "Accept": "Kabul et", "Rich Text": "Zengin Metin", "Junction collection": "Bağlantı koleksiyonu", "Leave it blank, unless you need a custom intermediate table": "Özel bir ara tabloya ihtiyacınız yoksa boş bırakın", @@ -432,18 +427,6 @@ export default { "Action permission": "İşlem yetkisi", "Field permission": "Alan yetkisi", "Scope name": "Kapsam adı", - "File storages": "Dosya depoları", - "Storage display name": "Depo görünen adı", - "Storage name": "Depo adı", - "Default storage": "Varsayılan depo", - "Add storage": "Depo ekle", - "Edit storage": "Depo düzenle", - "Storage base URL": "Depolama temel URLsi", - "Destination": "Hedef", - "Use the built-in static file server": "Yerleşik statik dosya sunucusunu kullanın", - "Local storage": "Lokal depolama", - "Aliyun OSS": "Aliyun OSS", - "Amazon S3": "Amazon S3", "Unsaved changes": "Değişiklikler kaydedilmedi", "Are you sure you don't want to save?": "kaydetmek istemediğinizden emin misiniz??", "Dragging": "Sürükleme", diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts index f9ca3cb92b..3a91dd0a9a 100644 --- a/packages/core/client/src/locale/zh_CN.ts +++ b/packages/core/client/src/locale/zh_CN.ts @@ -223,8 +223,6 @@ export default { "Field source":"来源字段", "Preview":"预览", "Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "随机生成,可修改。支持英文、数字和下划线,必须以英文字母开头。", - "Storage type": "存储类型", - "Types will be used in database": "数据库使用的类型", "Edit": "编辑", "Edit collection": "编辑数据表", "Configure fields": "配置字段", @@ -267,7 +265,6 @@ export default { "Radio group": "单选框", "Checkbox group": "复选框", "China region": "中国行政区", - "Attachment": "附件", "Date & Time": "日期 & 时间", "Datetime": "日期", "Relation": "关系类型", @@ -437,7 +434,6 @@ export default { "Add option": "添加选项", "Related collection": "关系表", "Allow linking to multiple records": "允许关联多条记录", - "Allow uploading multiple files": "允许上传多个文件", "Daily": "每天", "Weekly": "每周", @@ -564,7 +560,6 @@ export default { 'Save action': '保存操作', 'Exists': '存在', - 'Filename': '文件名', 'Add condition': '添加条件', 'Add condition group': '添加条件分组', 'exists': '存在', @@ -578,7 +573,6 @@ export default { 'Role UID': '角色标识', 'Precision': '精确度', - 'Accept': '文件格式', 'Rich Text': '富文本', 'Junction collection': '中间表', 'Leave it blank, unless you need a custom intermediate table': '默认留空,除非你需要一个自定义的中间表', @@ -637,23 +631,6 @@ export default { 'Field permission': '字段权限', 'Scope name': '数据范围名称', - 'File storages': '文件存储', - 'Storage display name': '文件存储名称', - 'Storage name': '文件存储标识', - 'Default storage': '默认存储', - 'Add storage': '添加文件存储', - 'Edit storage': '编辑文件存储', - 'Storage base URL': 'Base URL', - 'Destination': '存储路径', - 'Use the built-in static file server': '使用内置静态文件服务', - 'Local storage': '本地存储', - 'Aliyun OSS': '阿里云 OSS', - 'Amazon S3': '亚马逊 S3', - 'Tencent COS': '腾讯云 COS', - 'Region': '区域', - 'Bucket': '存储桶', - 'Path': '路径(相对)', - 'Unsaved changes': '未保存修改', 'Are you sure you don\'t want to save?': '你确定不保存修改吗?', 'Dragging': '拖拽中', @@ -708,7 +685,6 @@ export default { "Print": "打印", "Done": "完成", 'Sign up successfully, and automatically jump to the sign in page': '注册成功,即将跳转到登录页面', - 'File manager': '文件管理器', 'ACL': '访问控制', 'Collection manager': '数据表管理', 'Plugin manager': '插件管理器', diff --git a/packages/core/client/src/schema-component/antd/calendar/demos/collections.ts b/packages/core/client/src/schema-component/antd/calendar/demos/collections.ts index 2eed0197b8..d19d73227c 100644 --- a/packages/core/client/src/schema-component/antd/calendar/demos/collections.ts +++ b/packages/core/client/src/schema-component/antd/calendar/demos/collections.ts @@ -270,7 +270,7 @@ export default { uiSchema: { 'x-uid': '5xf7izxjny5', name: '9az9003ijcm', - 'x-component-props': { multiple: true, action: 'attachments:upload' }, + 'x-component-props': { multiple: true, action: 'attachments:create' }, type: 'array', 'x-component': 'Upload.Attachment', title: '附件', @@ -660,7 +660,7 @@ export default { uiSchema: { 'x-uid': 'wosew16td91', name: 'p82ihvtkxtf', - 'x-component-props': { multiple: true, action: 'attachments:upload' }, + 'x-component-props': { multiple: true, action: 'attachments:create' }, type: 'array', 'x-component': 'Upload.Attachment', title: '附件', diff --git a/packages/core/client/src/schema-component/antd/form-v2/demos/collections.ts b/packages/core/client/src/schema-component/antd/form-v2/demos/collections.ts index 2eed0197b8..d19d73227c 100644 --- a/packages/core/client/src/schema-component/antd/form-v2/demos/collections.ts +++ b/packages/core/client/src/schema-component/antd/form-v2/demos/collections.ts @@ -270,7 +270,7 @@ export default { uiSchema: { 'x-uid': '5xf7izxjny5', name: '9az9003ijcm', - 'x-component-props': { multiple: true, action: 'attachments:upload' }, + 'x-component-props': { multiple: true, action: 'attachments:create' }, type: 'array', 'x-component': 'Upload.Attachment', title: '附件', @@ -660,7 +660,7 @@ export default { uiSchema: { 'x-uid': 'wosew16td91', name: 'p82ihvtkxtf', - 'x-component-props': { multiple: true, action: 'attachments:upload' }, + 'x-component-props': { multiple: true, action: 'attachments:create' }, type: 'array', 'x-component': 'Upload.Attachment', title: '附件', diff --git a/packages/core/client/src/schema-component/antd/kanban/demos/collections.ts b/packages/core/client/src/schema-component/antd/kanban/demos/collections.ts index 2eed0197b8..d19d73227c 100644 --- a/packages/core/client/src/schema-component/antd/kanban/demos/collections.ts +++ b/packages/core/client/src/schema-component/antd/kanban/demos/collections.ts @@ -270,7 +270,7 @@ export default { uiSchema: { 'x-uid': '5xf7izxjny5', name: '9az9003ijcm', - 'x-component-props': { multiple: true, action: 'attachments:upload' }, + 'x-component-props': { multiple: true, action: 'attachments:create' }, type: 'array', 'x-component': 'Upload.Attachment', title: '附件', @@ -660,7 +660,7 @@ export default { uiSchema: { 'x-uid': 'wosew16td91', name: 'p82ihvtkxtf', - 'x-component-props': { multiple: true, action: 'attachments:upload' }, + 'x-component-props': { multiple: true, action: 'attachments:create' }, type: 'array', 'x-component': 'Upload.Attachment', title: '附件', diff --git a/packages/core/client/src/schema-component/antd/preview/demos/apiClient.ts b/packages/core/client/src/schema-component/antd/preview/demos/apiClient.ts index 9b564e20e1..b8979a48be 100644 --- a/packages/core/client/src/schema-component/antd/preview/demos/apiClient.ts +++ b/packages/core/client/src/schema-component/antd/preview/demos/apiClient.ts @@ -9,7 +9,7 @@ const mock = new MockAdapter(apiClient.axios); const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value)); -mock.onPost('/attachments:upload').reply(async (config) => { +mock.onPost('/attachments:create').reply(async (config) => { const total = 1024; // mocked file size for (const progress of [0, 0.2, 0.4, 0.6, 0.8, 1]) { await sleep(500); diff --git a/packages/core/client/src/schema-component/antd/preview/demos/demo1.tsx b/packages/core/client/src/schema-component/antd/preview/demos/demo1.tsx index 51cc526644..3b0c56dbf9 100644 --- a/packages/core/client/src/schema-component/antd/preview/demos/demo1.tsx +++ b/packages/core/client/src/schema-component/antd/preview/demos/demo1.tsx @@ -16,7 +16,7 @@ const schema = { 'x-decorator': 'FormItem', 'x-component': 'Preview', 'x-component-props': { - action: 'attachments:upload', + action: 'attachments:create', // multiple: true, }, 'x-reactions': { diff --git a/packages/core/client/src/schema-component/antd/preview/demos/demo2.tsx b/packages/core/client/src/schema-component/antd/preview/demos/demo2.tsx index 7bc4ad5323..a61cec2ac8 100644 --- a/packages/core/client/src/schema-component/antd/preview/demos/demo2.tsx +++ b/packages/core/client/src/schema-component/antd/preview/demos/demo2.tsx @@ -59,7 +59,7 @@ const schema = { 'x-decorator': 'FormItem', 'x-component': 'Preview', 'x-component-props': { - action: 'attachments:upload', + action: 'attachments:create', multiple: true, }, 'x-reactions': [ diff --git a/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx b/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx index eee803cf0d..cba42c12fd 100644 --- a/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx +++ b/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx @@ -180,9 +180,9 @@ const InternalRemoteSelect = connect( const options = useMemo(() => { if (!data?.data?.length) { - return value !== undefined && value !== null ? (Array.isArray(value) ? value : [value]) : []; + return value != null ? (Array.isArray(value) ? value : [value]) : []; } - const valueOptions = (value !== undefined && value !== null && (Array.isArray(value) ? value : [value])) || []; + const valueOptions = (value != null && (Array.isArray(value) ? value : [value])) || []; return uniqBy(data?.data?.concat(valueOptions) || [], fieldNames.value); }, [data?.data, getOptionsByFieldNames, normalizeOptions, value]); const onDropdownVisibleChange = () => { diff --git a/packages/core/client/src/schema-component/antd/table-v2/demos/collections.ts b/packages/core/client/src/schema-component/antd/table-v2/demos/collections.ts index 2eed0197b8..d19d73227c 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/demos/collections.ts +++ b/packages/core/client/src/schema-component/antd/table-v2/demos/collections.ts @@ -270,7 +270,7 @@ export default { uiSchema: { 'x-uid': '5xf7izxjny5', name: '9az9003ijcm', - 'x-component-props': { multiple: true, action: 'attachments:upload' }, + 'x-component-props': { multiple: true, action: 'attachments:create' }, type: 'array', 'x-component': 'Upload.Attachment', title: '附件', @@ -660,7 +660,7 @@ export default { uiSchema: { 'x-uid': 'wosew16td91', name: 'p82ihvtkxtf', - 'x-component-props': { multiple: true, action: 'attachments:upload' }, + 'x-component-props': { multiple: true, action: 'attachments:create' }, type: 'array', 'x-component': 'Upload.Attachment', title: '附件', diff --git a/packages/core/client/src/schema-component/antd/upload/demos/apiClient.ts b/packages/core/client/src/schema-component/antd/upload/demos/apiClient.ts index 9b564e20e1..b8979a48be 100644 --- a/packages/core/client/src/schema-component/antd/upload/demos/apiClient.ts +++ b/packages/core/client/src/schema-component/antd/upload/demos/apiClient.ts @@ -9,7 +9,7 @@ const mock = new MockAdapter(apiClient.axios); const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value)); -mock.onPost('/attachments:upload').reply(async (config) => { +mock.onPost('/attachments:create').reply(async (config) => { const total = 1024; // mocked file size for (const progress of [0, 0.2, 0.4, 0.6, 0.8, 1]) { await sleep(500); diff --git a/packages/core/client/src/schema-component/antd/upload/demos/demo1.tsx b/packages/core/client/src/schema-component/antd/upload/demos/demo1.tsx index 3e841c45c1..0d324471af 100644 --- a/packages/core/client/src/schema-component/antd/upload/demos/demo1.tsx +++ b/packages/core/client/src/schema-component/antd/upload/demos/demo1.tsx @@ -15,7 +15,7 @@ const schema = { 'x-decorator': 'FormItem', 'x-component': 'Upload.Attachment', 'x-component-props': { - action: 'attachments:upload', + action: 'attachments:create', // multiple: true, }, 'x-reactions': { diff --git a/packages/core/client/src/schema-component/antd/upload/demos/demo2.tsx b/packages/core/client/src/schema-component/antd/upload/demos/demo2.tsx index 9cbd3f5bd6..d867e5a6c0 100644 --- a/packages/core/client/src/schema-component/antd/upload/demos/demo2.tsx +++ b/packages/core/client/src/schema-component/antd/upload/demos/demo2.tsx @@ -58,7 +58,7 @@ const schema = { 'x-decorator': 'FormItem', 'x-component': 'Upload.Attachment', 'x-component-props': { - action: 'attachments:upload', + action: 'attachments:create', multiple: true, }, 'x-reactions': [ diff --git a/packages/core/client/src/system-settings/SystemSettingsShortcut.tsx b/packages/core/client/src/system-settings/SystemSettingsShortcut.tsx index 2a6172da01..a9cb2d5336 100644 --- a/packages/core/client/src/system-settings/SystemSettingsShortcut.tsx +++ b/packages/core/client/src/system-settings/SystemSettingsShortcut.tsx @@ -94,7 +94,7 @@ const schema: ISchema = { 'x-decorator': 'FormItem', 'x-component': 'Upload.Attachment', 'x-component-props': { - action: 'attachments:upload', + action: 'attachments:create', multiple: false, // accept: 'jpg,png' }, @@ -187,7 +187,7 @@ const schema2: ISchema = { 'x-decorator': 'FormItem', 'x-component': 'Upload.Attachment', 'x-component-props': { - action: 'attachments:upload', + action: 'attachments:create', multiple: false, // accept: 'jpg,png' }, diff --git a/packages/core/test/src/mockServer.ts b/packages/core/test/src/mockServer.ts index dbc05e3cfe..2249ea28fe 100644 --- a/packages/core/test/src/mockServer.ts +++ b/packages/core/test/src/mockServer.ts @@ -113,15 +113,19 @@ export class MockServer extends Application { const queryString = qs.stringify(restParams, { arrayFormat: 'brackets' }); + let request; + switch (method) { - case 'upload': - return agent.post(`${url}?${queryString}`).attach('file', file).field(values); case 'list': case 'get': - return agent.get(`${url}?${queryString}`); + request = agent.get(`${url}?${queryString}`); + break; default: - return agent.post(`${url}?${queryString}`).send(values); + request = agent.post(`${url}?${queryString}`); + break; } + + return file ? request.attach('file', file).field(values) : request.send(values); }; }, }, diff --git a/packages/plugins/file-manager/src/client/FileStorage.tsx b/packages/plugins/file-manager/src/client/FileStorage.tsx index 591d39e1d9..04833e41b8 100644 --- a/packages/plugins/file-manager/src/client/FileStorage.tsx +++ b/packages/plugins/file-manager/src/client/FileStorage.tsx @@ -1,6 +1,8 @@ -import { SchemaComponent } from '@nocobase/client'; -import { Card } from 'antd'; import React from 'react'; +import { Card } from 'antd'; + +import { SchemaComponent } from '@nocobase/client'; + import { StorageOptions } from './StorageOptions'; import { storageSchema } from './schemas/storage'; diff --git a/packages/plugins/file-manager/src/client/FileStorageShortcut.tsx b/packages/plugins/file-manager/src/client/FileStorageShortcut.tsx index fa7657fe80..456ecdb1d2 100644 --- a/packages/plugins/file-manager/src/client/FileStorageShortcut.tsx +++ b/packages/plugins/file-manager/src/client/FileStorageShortcut.tsx @@ -12,7 +12,7 @@ const schema = { [uid()]: { 'x-component': 'Action.Drawer', type: 'void', - title: '{{t("File storages")}}', + title: '{{t("File manager")}}', properties: { storageSchema, }, @@ -31,7 +31,7 @@ export const FileStorageShortcut = () => { setVisible(true); }} icon={} - title={t('File storages')} + title={t('File manager')} /> diff --git a/packages/plugins/file-manager/src/client/StorageOptions.tsx b/packages/plugins/file-manager/src/client/StorageOptions.tsx index b51650c405..f7fde12fcb 100644 --- a/packages/plugins/file-manager/src/client/StorageOptions.tsx +++ b/packages/plugins/file-manager/src/client/StorageOptions.tsx @@ -2,22 +2,23 @@ import { FormLayout } from '@formily/antd'; import { Field } from '@formily/core'; import { observer, RecursionField, Schema, useField, useForm } from '@formily/react'; import React, { useEffect, useState } from 'react'; +import { NAMESPACE } from './locale'; const schema = { local: { properties: { documentRoot: { - title: '{{t("Destination")}}', + title: `{{t("Destination", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', - required: true, + default: 'uploads', }, serve: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Checkbox', - 'x-content': '{{t("Use the built-in static file server")}}', + 'x-content': `{{t("Use the built-in static file server", { ns: "${NAMESPACE}" })}}`, default: true, }, }, @@ -25,28 +26,28 @@ const schema = { 'ali-oss': { properties: { region: { - title: '{{t("Region")}}', + title: `{{t("Region", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, accessKeyId: { - title: '{{t("AccessKey ID")}}', + title: `{{t("AccessKey ID", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, accessKeySecret: { - title: '{{t("AccessKey Secret")}}', + title: `{{t("AccessKey Secret", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Password', required: true, }, bucket: { - title: '{{t("Bucket")}}', + title: `{{t("Bucket", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', @@ -57,28 +58,28 @@ const schema = { 'tx-cos': { properties: { Region: { - title: '{{t("Region")}}', + title: `{{t("Region", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, SecretId: { - title: '{{t("SecretId")}}', + title: `{{t("SecretId", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, SecretKey: { - title: '{{t("SecretKey")}}', + title: `{{t("SecretKey", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Password', required: true, }, Bucket: { - title: '{{t("Bucket")}}', + title: `{{t("Bucket", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', @@ -89,39 +90,33 @@ const schema = { s3: { properties: { region: { - title: '{{t("Region")}}', + title: `{{t("Region", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, accessKeyId: { - title: '{{t("AccessKey ID")}}', + title: `{{t("AccessKey ID", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, secretAccessKey: { - title: '{{t("AccessKey Secret")}}', + title: `{{t("AccessKey Secret", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Password', required: true, }, bucket: { - title: '{{t("Bucket")}}', + title: `{{t("Bucket", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, - endpoint: { - title: '{{t("Endpoint")}}', - type: 'string', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - }, }, }, }; diff --git a/packages/plugins/file-manager/src/client/index.tsx b/packages/plugins/file-manager/src/client/index.tsx index be7f4d20c8..68ccab3c45 100644 --- a/packages/plugins/file-manager/src/client/index.tsx +++ b/packages/plugins/file-manager/src/client/index.tsx @@ -1,9 +1,11 @@ import { + CollectionManagerProvider, PluginManagerContext, SchemaComponentOptions, SchemaInitializerContext, SchemaInitializerProvider, SettingsCenterProvider, + registerField, registerTemplate, useCollection, } from '@nocobase/client'; @@ -14,12 +16,16 @@ import { FileStorageShortcut } from './FileStorageShortcut'; import * as hooks from './hooks'; import * as initializers from './initializers'; import * as templates from './templates'; +import { NAMESPACE } from './locale'; +import { attachment } from './interfaces/attachment'; // 注册之后就可以在 Crete collection 按钮中选择创建了 forEach(templates, (template, key: string) => { registerTemplate(key, template); }); +registerField(attachment.group, 'attachment', attachment); + export default function (props) { const initializes = useContext(SchemaInitializerContext); const hasUploadAction = initializes.TableActionInitializers.items[0].children.some( @@ -38,6 +44,7 @@ export default function (props) { }, }, visible: () => { + // eslint-disable-next-line react-hooks/rules-of-hooks const collection = useCollection(); return collection.template === 'file'; }, @@ -49,11 +56,11 @@ export default function (props) { - - {props.children} - + + + {props.children} + + ); diff --git a/packages/plugins/file-manager/src/client/initializers/UploadActionInitializer.tsx b/packages/plugins/file-manager/src/client/initializers/UploadActionInitializer.tsx index 6b08f95bfc..e4e8096098 100644 --- a/packages/plugins/file-manager/src/client/initializers/UploadActionInitializer.tsx +++ b/packages/plugins/file-manager/src/client/initializers/UploadActionInitializer.tsx @@ -17,7 +17,7 @@ export const UploadActionInitializer = (props) => { icon: 'UploadOutlined', }, properties: { - modal: { + drawer: { type: 'void', title: '{{ t("Upload files") }}', 'x-component': 'Action.Container', diff --git a/packages/core/client/src/collection-manager/interfaces/attachment.ts b/packages/plugins/file-manager/src/client/interfaces/attachment.ts similarity index 58% rename from packages/core/client/src/collection-manager/interfaces/attachment.ts rename to packages/plugins/file-manager/src/client/interfaces/attachment.ts index af6adbd104..20baa187d1 100644 --- a/packages/core/client/src/collection-manager/interfaces/attachment.ts +++ b/packages/plugins/file-manager/src/client/interfaces/attachment.ts @@ -1,13 +1,13 @@ import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; -import { defaultProps, operators } from './properties'; -import { IField } from './types'; +import { IField, interfacesProperties } from '@nocobase/client'; +import { NAMESPACE } from '../locale'; export const attachment: IField = { name: 'attachment', type: 'object', group: 'media', - title: '{{t("Attachment")}}', + title: `{{t("Attachment", { ns: "${NAMESPACE}" })}}`, isAssociation: true, default: { type: 'belongsToMany', @@ -17,17 +17,19 @@ export const attachment: IField = { type: 'array', // title, 'x-component': 'Upload.Attachment', - 'x-component-props': { - action: 'attachments:upload', - }, + 'x-component-props': {}, }, }, availableTypes: ['belongsToMany'], - schemaInitialize(schema: ISchema, { block }) { + schemaInitialize(schema: ISchema, { block, field }) { if (['Table', 'Kanban'].includes(block)) { schema['x-component-props'] = schema['x-component-props'] || {}; schema['x-component-props']['size'] = 'small'; } + + schema['x-component-props']['action'] = `${field.target}:create${ + field.storage ? `?attachementField=${field.collectionName}.${field.name}` : '' + }`; }, initialize: (values: any) => { if (!values.through) { @@ -47,21 +49,42 @@ export const attachment: IField = { } }, properties: { - ...defaultProps, + ...interfacesProperties.defaultProps, 'uiSchema.x-component-props.accept': { type: 'string', - title: '{{t("Accept")}}', + title: `{{t("MIME type", { ns: "${NAMESPACE}" })}}`, 'x-component': 'Input', 'x-decorator': 'FormItem', - description: 'Example: .doc,.docx', + description: 'Example: image/png', + default: 'image/*', }, 'uiSchema.x-component-props.multiple': { type: 'boolean', - 'x-content': "{{t('Allow uploading multiple files')}}", + 'x-content': `{{t('Allow uploading multiple files', { ns: "${NAMESPACE}" })}}`, 'x-decorator': 'FormItem', 'x-component': 'Checkbox', default: true, }, + storage: { + type: 'string', + title: `{{t("Storage", { ns: "${NAMESPACE}" })}}`, + description: `{{t('Default storage will be used when not selected', { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'FormItem', + 'x-component': 'RemoteSelect', + 'x-component-props': { + service: { + resource: 'storages', + params: { + // pageSize: -1 + }, + }, + manual: false, + fieldNames: { + label: 'title', + value: 'name', + }, + }, + }, }, filterable: { children: [ @@ -80,10 +103,10 @@ export const attachment: IField = { }, { name: 'filename', - title: '{{t("Filename")}}', - operators: operators.string, + title: `{{t("Filename", { ns: "${NAMESPACE}" })}}`, + operators: interfacesProperties.operators.string, schema: { - title: '{{t("Filename")}}', + title: `{{t("Filename", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-component': 'Input', }, diff --git a/packages/plugins/file-manager/src/client/locale/en-US.ts b/packages/plugins/file-manager/src/client/locale/en-US.ts new file mode 100644 index 0000000000..9f3414989c --- /dev/null +++ b/packages/plugins/file-manager/src/client/locale/en-US.ts @@ -0,0 +1,21 @@ +export default { + 'File manager': 'File manager', + 'Attachment': 'Attachment', + 'MIME type': 'MIME type', + 'Storage display name': 'Storage display name', + 'Storage name': 'Storage name', + 'Storage type': 'Storage type', + 'Default storage': 'Default storage', + 'Storage base URL': 'Storage base URL', + 'Destination': 'Destination', + 'Use the built-in static file server': 'Use the built-in static file server', + 'Local storage': 'Local storage', + 'Aliyun OSS': 'Aliyun OSS', + 'Tencent COS': 'Tencent COS', + 'Amazon S3': 'Amazon S3', + 'Region': 'Region', + 'Bucket': 'Bucket', + 'Path': 'Path', + 'Filename': 'Filename', + 'Will be used for API': 'Will be used for API', +}; diff --git a/packages/plugins/file-manager/src/client/locale/index.ts b/packages/plugins/file-manager/src/client/locale/index.ts index 668745c46c..45b4aa40bd 100644 --- a/packages/plugins/file-manager/src/client/locale/index.ts +++ b/packages/plugins/file-manager/src/client/locale/index.ts @@ -1,11 +1,7 @@ -import { i18n } from '@nocobase/client'; import { useTranslation } from 'react-i18next'; -import zhCN from './zh-CN'; export const NAMESPACE = 'file-manager'; -i18n.addResources('zh-CN', NAMESPACE, zhCN); - export function useFmTranslation() { return useTranslation(NAMESPACE); } diff --git a/packages/plugins/file-manager/src/client/locale/ja-JP.ts b/packages/plugins/file-manager/src/client/locale/ja-JP.ts new file mode 100644 index 0000000000..3b92490a07 --- /dev/null +++ b/packages/plugins/file-manager/src/client/locale/ja-JP.ts @@ -0,0 +1,18 @@ +export default { + 'File manager': 'ファイルストレージ', + 'Attachment': '添付ファイル', + 'MIME type': 'ファイル形式', + 'Allow uploading multiple files': '複数ファイルのアップロードを許可する', + 'Storage display name': 'ファイルストレージ名', + 'Storage name': 'ファイルストレージ識別子', + 'Storage type': 'ストレージタイプ', + 'Default storage': 'デフォルトストレージ', + 'Storage base URL': 'Storage base URL', + 'Destination': 'ファイルパス', + 'Use the built-in static file server': '組み込みの静的ファイル サービスを使用する', + 'Local storage': 'ローカルストレージ', + 'Aliyun OSS': 'Aliyun OSS', + 'Tencent COS': 'Tencent COS', + 'Amazon S3': 'Amazon S3', + 'Filename': 'ファイル名', +}; diff --git a/packages/plugins/file-manager/src/client/locale/ru-RU.ts b/packages/plugins/file-manager/src/client/locale/ru-RU.ts new file mode 100644 index 0000000000..6b9cd9f660 --- /dev/null +++ b/packages/plugins/file-manager/src/client/locale/ru-RU.ts @@ -0,0 +1,18 @@ +export default { + 'File manager': 'Файловые хранилища', + 'Attachment': 'Вложение', + 'MIME type': 'Подтвердить', + 'Allow uploading multiple files': 'Позволить загружать много файлов', + 'Storage display name': 'Имя храшилища на экране', + 'Storage name': 'Имя хранилища', + 'Storage type': 'Тип Хранилища', + 'Default storage': 'Хранилище по умолчанию', + 'Storage base URL': 'Базовый URL хранилища', + 'Destination': 'Назначение', + 'Use the built-in static file server': 'Использовать встроенный статический файл-сервер', + 'Local storage': 'Локальное хранилище', + 'Aliyun OSS': 'Aliyun OSS', + 'Amazon S3': 'Amazon S3', + 'Tencent COS': 'Tencent COS', + 'Filename': 'Имя файла', +} diff --git a/packages/plugins/file-manager/src/client/locale/tr-TR.ts b/packages/plugins/file-manager/src/client/locale/tr-TR.ts new file mode 100644 index 0000000000..e582f6c604 --- /dev/null +++ b/packages/plugins/file-manager/src/client/locale/tr-TR.ts @@ -0,0 +1,17 @@ +export default { + 'File manager': 'Dosya depoları', + 'Attachment': 'Dosya eki', + 'MIME type': 'Kabul et', + 'Allow uploading multiple files': 'Birden çok dosya yüklemeye izin ver', + 'Storage name': 'Depo adı', + 'Storage type': 'Depolama türü', + 'Default storage': 'Varsayılan depo', + 'Storage base URL': 'Depolama temel URLsi', + 'Destination': 'Hedef', + 'Use the built-in static file server': 'Yerleşik statik dosya sunucusunu kullanın', + 'Local storage': 'Lokal depolama', + 'Aliyun OSS': 'Aliyun OSS', + 'Amazon S3': 'Amazon S3', + 'Tencent COS': 'Tencent COS', + 'Filename': 'Dosya adı', +}; diff --git a/packages/plugins/file-manager/src/client/locale/zh-CN.ts b/packages/plugins/file-manager/src/client/locale/zh-CN.ts index b516c22528..f889488656 100644 --- a/packages/plugins/file-manager/src/client/locale/zh-CN.ts +++ b/packages/plugins/file-manager/src/client/locale/zh-CN.ts @@ -1,11 +1,30 @@ -const locale = { +export default { 'File collection': '文件数据表', 'File name': '文件名', 'Extension name': '扩展名', Size: '文件大小', - 'Mime Type': 'Mime 类型', + 'MIME type': 'MIME 类型', URL: 'URL', 'File storage': '文件存储', + 'File manager': '文件管理器', + Attachment: '附件', + 'Allow uploading multiple files': '允许上传多个文件', + Storage: '存储空间', + Storages: '存储空间', + 'Storage name': '存储空间标识', + 'Storage type': '存储类型', + 'Default storage': '默认存储空间', + 'Storage base URL': '访问 URL 基础', + Destination: '上传目标文件夹', + 'Use the built-in static file server': '使用内置静态文件服务', + 'Local storage': '本地存储', + 'Aliyun OSS': '阿里云 OSS', + 'Amazon S3': '亚马逊 S3', + 'Tencent COS': '腾讯云 COS', + Region: '区域', + Bucket: '存储桶', + Path: '相对路径', + Filename: '文件名', + 'Will be used for API': '将用于 API', + 'Default storage will be used when not selected': '留空将使用默认存储空间', }; - -export default locale; diff --git a/packages/plugins/file-manager/src/client/schemas/storage.ts b/packages/plugins/file-manager/src/client/schemas/storage.ts index fbcb8ecfc3..c78c7d4462 100644 --- a/packages/plugins/file-manager/src/client/schemas/storage.ts +++ b/packages/plugins/file-manager/src/client/schemas/storage.ts @@ -1,6 +1,7 @@ import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; import { useActionContext, useRequest } from '@nocobase/client'; +import { NAMESPACE } from '../locale'; const collection = { name: 'storages', @@ -10,7 +11,7 @@ const collection = { name: 'title', interface: 'input', uiSchema: { - title: '{{t("Storage display name")}}', + title: '{{t("Title")}}', type: 'string', 'x-component': 'Input', required: true, @@ -21,7 +22,8 @@ const collection = { name: 'name', interface: 'input', uiSchema: { - title: '{{t("Storage name")}}', + title: `{{t("Storage name", { ns: "${NAMESPACE}" })}}`, + descriptions: `{{t("Will be used for API", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-component': 'Input', } as ISchema, @@ -31,15 +33,15 @@ const collection = { name: 'type', interface: 'select', uiSchema: { - title: '{{t("Storage type")}}', + title: `{{t("Storage type", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-component': 'Select', required: true, enum: [ - { label: '{{t("Local storage")}}', value: 'local' }, - { label: '{{t("Aliyun OSS")}}', value: 'ali-oss' }, - { label: '{{t("Amazon S3")}}', value: 's3' }, - { label: '{{t("Tencent COS")}}', value: 'tx-cos' }, + { label: `{{t("Local storage", { ns: "${NAMESPACE}" })}}`, value: 'local' }, + { label: `{{t("Aliyun OSS", { ns: "${NAMESPACE}" })}}`, value: 'ali-oss' }, + { label: `{{t("Amazon S3", { ns: "${NAMESPACE}" })}}`, value: 's3' }, + { label: `{{t("Tencent COS", { ns: "${NAMESPACE}" })}}`, value: 'tx-cos' }, ], } as ISchema, }, @@ -48,7 +50,7 @@ const collection = { name: 'baseUrl', interface: 'input', uiSchema: { - title: '{{t("Storage base URL")}}', + title: `{{t("Storage base URL", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-component': 'Input', required: true, @@ -59,7 +61,7 @@ const collection = { name: 'path', interface: 'input', uiSchema: { - title: '{{t("Path")}}', + title: `{{t("Path", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-component': 'Input', } as ISchema, @@ -69,7 +71,7 @@ const collection = { name: 'default', interface: 'boolean', uiSchema: { - title: '{{t("Default storage")}}', + title: `{{t("Default storage", { ns: "${NAMESPACE}" })}}`, type: 'boolean', 'x-component': 'Checkbox', } as ISchema, @@ -117,14 +119,14 @@ export const storageSchema: ISchema = { 'x-component-props': { useAction: '{{ cm.useBulkDestroyAction }}', confirm: { - title: "{{t('Delete storage')}}", + title: "{{t('Delete')}}", content: "{{t('Are you sure you want to delete it?')}}", }, }, }, create: { type: 'void', - title: '{{t("Add storage")}}', + title: '{{t("Add new")}}', 'x-component': 'Action', 'x-component-props': { type: 'primary', @@ -148,7 +150,7 @@ export const storageSchema: ISchema = { ); }, }, - title: '{{t("Add storage")}}', + title: '{{t("Add new")}}', properties: { title: { 'x-component': 'CollectionField', @@ -180,7 +182,7 @@ export const storageSchema: ISchema = { 'x-component': 'CollectionField', 'x-decorator': 'FormItem', title: '', - 'x-content': '{{t("Default storage")}}', + 'x-content': `{{t("Default storage", { ns: "${NAMESPACE}" })}}`, }, footer: { type: 'void', @@ -284,7 +286,7 @@ export const storageSchema: ISchema = { 'x-decorator-props': { useValues: '{{ cm.useValuesFromRecord }}', }, - title: '{{t("Edit storage")}}', + title: '{{t("Edit")}}', properties: { title: { 'x-component': 'CollectionField', @@ -349,8 +351,8 @@ export const storageSchema: ISchema = { 'x-component': 'Action.Link', 'x-component-props': { confirm: { - title: "{{t('Delete role')}}", - content: "{{t('Are you sure you want to delete it?')}}", + title: '{{t("Delete")}}', + content: '{{t("Are you sure you want to delete it?")}}', }, useAction: '{{cm.useDestroyAction}}', }, diff --git a/packages/plugins/file-manager/src/client/templates/file.ts b/packages/plugins/file-manager/src/client/templates/file.ts index 6bb00812a9..ce1253947e 100644 --- a/packages/plugins/file-manager/src/client/templates/file.ts +++ b/packages/plugins/file-manager/src/client/templates/file.ts @@ -72,7 +72,7 @@ export const file = { deletable: false, uiSchema: { type: 'string', - title: `{{t("Mime type", { ns: "${NAMESPACE}" })}}`, + title: `{{t("MIME type", { ns: "${NAMESPACE}" })}}`, 'x-component': 'Input', 'x-read-pretty': true, }, diff --git a/packages/plugins/file-manager/src/server/__tests__/action.test.ts b/packages/plugins/file-manager/src/server/__tests__/action.test.ts index 2940adf4ba..fe38e4a131 100644 --- a/packages/plugins/file-manager/src/server/__tests__/action.test.ts +++ b/packages/plugins/file-manager/src/server/__tests__/action.test.ts @@ -3,14 +3,15 @@ import path from 'path'; import { getApp } from '.'; import { FILE_FIELD_NAME, STORAGE_TYPE_LOCAL } from '../constants'; -const { LOCAL_STORAGE_BASE_URL, APP_PORT = '13000' } = process.env; +const { LOCAL_STORAGE_BASE_URL, LOCAL_STORAGE_DEST = 'storage/uploads', APP_PORT = '13000' } = process.env; -const DEFAULT_LOCAL_BASE_URL = LOCAL_STORAGE_BASE_URL || `http://localhost:${APP_PORT}/uploads`; +const DEFAULT_LOCAL_BASE_URL = LOCAL_STORAGE_BASE_URL || `http://localhost:${APP_PORT}/storage/uploads`; describe('action', () => { let app; let agent; let db; + let StorageModel; beforeEach(async () => { app = await getApp({ @@ -19,12 +20,14 @@ describe('action', () => { agent = app.agent(); db = app.db; - const Storage = db.getCollection('storages').model; - await Storage.create({ - name: `local1_${db.getTablePrefix()}`, + StorageModel = db.getCollection('storages').model; + await StorageModel.create({ + name: 'local1', type: STORAGE_TYPE_LOCAL, baseUrl: DEFAULT_LOCAL_BASE_URL, - default: true, + rules: { + size: 1024, + }, }); }); @@ -32,9 +35,9 @@ describe('action', () => { await db.close(); }); - describe('direct attachment', () => { + describe('default storage', () => { it('upload file should be ok', async () => { - const { body } = await agent.resource('attachments').upload({ + const { body } = await agent.resource('attachments').create({ [FILE_FIELD_NAME]: path.resolve(__dirname, './files/text.txt'), }); @@ -45,6 +48,7 @@ describe('action', () => { size: 13, mimetype: 'text/plain', meta: {}, + storageId: 1, }; // 文件上传和解析是否正常 @@ -52,25 +56,26 @@ describe('action', () => { // 文件的 url 是否正常生成 expect(body.data.url).toBe(`${DEFAULT_LOCAL_BASE_URL}${body.data.path}/${body.data.filename}`); - const Attachment = db.getCollection('attachments').model; + const Attachment = db.getModel('attachments'); const attachment = await Attachment.findOne({ where: { id: body.data.id }, include: ['storage'], }); - const storage = attachment.get('storage'); // 文件的数据是否正常保存 expect(attachment).toMatchObject(matcher); + // 关联的存储引擎是否正确 + const storage = await attachment.getStorage(); expect(storage).toMatchObject({ type: 'local', - options: {}, + options: { documentRoot: LOCAL_STORAGE_DEST }, rules: {}, path: '', baseUrl: DEFAULT_LOCAL_BASE_URL, default: true, }); - const { documentRoot = 'uploads' } = storage.options || {}; + const { documentRoot = 'storage/uploads' } = storage.options || {}; const destPath = path.resolve( path.isAbsolute(documentRoot) ? documentRoot : path.join(process.cwd(), documentRoot), storage.path, @@ -86,59 +91,70 @@ describe('action', () => { }); }); - describe.skip('belongsTo attachment', () => { - it('upload with associatedIndex, fail as 400 because file mimetype does not match', async () => { - const User = db.getCollection('users').model; - const user = await User.create(); - const response = await agent.resource('users.avatar').upload({ - associatedIndex: user.id, - file: path.resolve(__dirname, './files/text.txt'), + describe('specific storage', () => { + it('fail as 400 because file size greater than rules', async () => { + db.collection({ + name: 'customers', + fields: [ + { + name: 'avatar', + type: 'belongsTo', + target: 'attachments', + storage: 'local1', + }, + ], + }); + + const response = await agent.resource('attachments').create({ + attachmentField: 'customers.avatar', + file: path.resolve(__dirname, './files/image.jpg'), }); expect(response.status).toBe(400); }); - it.skip('upload with associatedIndex', async () => { - const User = db.getCollection('users').model; - const user = await User.create(); - - const { body } = await agent.resource('users.avatar').upload({ - associatedIndex: user.id, - file: path.resolve(__dirname, './files/image.png'), - values: { width: 100, height: 100 }, + it('fail as 400 because file mimetype does not match', async () => { + const textStorage = await StorageModel.create({ + name: 'local2', + type: STORAGE_TYPE_LOCAL, + baseUrl: DEFAULT_LOCAL_BASE_URL, + rules: { + mimetype: ['text/*'], + }, }); - const matcher = { - title: 'image', - extname: '.png', - path: '', - size: 255, - mimetype: 'image/png', - // TODO(optimize): 可以考虑使用 qs 的 decoder 来进行类型解析 - // see: https://github.com/ljharb/qs/issues/91 - // 或考虑使用 query-string 库的 parseNumbers 等配置项 - meta: { width: '100', height: '100' }, - }; - // 上传正常返回 - expect(body.data).toMatchObject(matcher); - - // 由于初始没有外键,无法获取 - // await user.getAvatar() - const updatedUser = await User.findByPk(user.id, { - include: ['avatar'], + db.collection({ + name: 'customers', + fields: [ + { + name: 'avatar', + type: 'belongsTo', + target: 'attachments', + storage: textStorage.name, + }, + ], }); - // 外键更新正常 - expect(updatedUser.get('avatar').id).toBe(body.data.id); + + // await db.sync(); + + const response = await agent.resource('attachments').create({ + attachmentField: 'customers.avatar', + file: path.resolve(__dirname, './files/image.jpg'), + }); + + expect(response.status).toBe(400); }); - it.skip('upload to assoiciated field and storage with full base url should be ok', async () => { + it('upload to storage which is not default', async () => { const BASE_URL = `http://localhost:${APP_PORT}/another-uploads`; - const storageName = 'local_private'; const urlPath = 'test/path'; - const Storage = db.getCollection('storages').model; + // 动态添加 storage - await Storage.create({ - name: storageName, + const storage = await StorageModel.create({ + name: 'local_private', type: STORAGE_TYPE_LOCAL, + rules: { + mimetype: ['text/*'], + }, path: urlPath, baseUrl: BASE_URL, options: { @@ -146,12 +162,21 @@ describe('action', () => { }, }); - const User = db.getCollection('users').model; - const user = await User.create(); - const { body } = await agent.resource('users.pubkeys').upload({ - associatedIndex: user.id, + db.collection({ + name: 'customers', + fields: [ + { + name: 'file', + type: 'belongsTo', + target: 'attachments', + storage: storage.name, + }, + ], + }); + + const { body } = await agent.resource('attachments').create({ + attachmentField: 'customers.file', file: path.resolve(__dirname, './files/text.txt'), - values: {}, }); // 文件的 url 是否正常生成 @@ -161,27 +186,5 @@ describe('action', () => { const content = await agent.get(url); expect(content.text).toBe('Hello world!\n'); }); - - // TODO(bug): 没有 associatedIndex 时路径解析资源名称不对,无法进入 action - it.skip('upload without associatedIndex', async () => { - const { body } = await agent.resource('users.avatar').upload({ - file: path.resolve(__dirname, './files/image.png'), - values: { width: 100, height: 100 }, - }); - const matcher = { - title: 'image', - extname: '.png', - path: '', - size: 255, - mimetype: 'image/png', - // TODO(optimize): 可以考虑使用 qs 的 decoder 来进行类型解析 - // @see: https://github.com/ljharb/qs/issues/91 - // 或考虑使用 query-string 库的 parseNumbers 等配置项 - meta: { width: '100', height: '100' }, - storage_id: 1, - }; - // 上传返回正常 - expect(body.data).toMatchObject(matcher); - }); }); }); diff --git a/packages/plugins/file-manager/src/server/__tests__/storages/ali-oss.test.ts b/packages/plugins/file-manager/src/server/__tests__/storages/ali-oss.test.ts index ed9a7d1f72..c0f9864893 100644 --- a/packages/plugins/file-manager/src/server/__tests__/storages/ali-oss.test.ts +++ b/packages/plugins/file-manager/src/server/__tests__/storages/ali-oss.test.ts @@ -21,7 +21,7 @@ describe('storage:ali-oss', () => { const Storage = db.getCollection('storages').model; storage = await Storage.create({ ...aliossStorage.defaults(), - name: `ali-oss_${db.getTablePrefix()}`, + name: 'ali-oss', default: true, path: 'test/path', }); @@ -33,7 +33,7 @@ describe('storage:ali-oss', () => { describe('direct attachment', () => { itif('upload file should be ok', async () => { - const { body } = await agent.resource('attachments').upload({ + const { body } = await agent.resource('attachments').create({ [FILE_FIELD_NAME]: path.resolve(__dirname, '../files/text.txt'), }); diff --git a/packages/plugins/file-manager/src/server/__tests__/storages/s3.test.ts b/packages/plugins/file-manager/src/server/__tests__/storages/s3.test.ts index 76175d552b..3712d59001 100644 --- a/packages/plugins/file-manager/src/server/__tests__/storages/s3.test.ts +++ b/packages/plugins/file-manager/src/server/__tests__/storages/s3.test.ts @@ -21,7 +21,7 @@ describe('storage:s3', () => { const Storage = db.getCollection('storages').model; storage = await Storage.create({ ...s3Storage.defaults(), - name: `s3_${db.getTablePrefix()}`, + name: 's3', default: true, path: 'test/path', }); @@ -33,7 +33,7 @@ describe('storage:s3', () => { describe('direct attachment', () => { itif('upload file should be ok', async () => { - const { body } = await agent.resource('attachments').upload({ + const { body } = await agent.resource('attachments').create({ [FILE_FIELD_NAME]: path.resolve(__dirname, '../files/text.txt'), }); diff --git a/packages/plugins/file-manager/src/server/__tests__/storages/tx-cos.test.ts b/packages/plugins/file-manager/src/server/__tests__/storages/tx-cos.test.ts index edef5e0c81..2358dbe9dd 100644 --- a/packages/plugins/file-manager/src/server/__tests__/storages/tx-cos.test.ts +++ b/packages/plugins/file-manager/src/server/__tests__/storages/tx-cos.test.ts @@ -21,7 +21,7 @@ describe('storage:tx-cos', () => { const Storage = db.getCollection('storages').model; storage = await Storage.create({ ...txStorage.defaults(), - name: `tx-cos_${db.getTablePrefix()}`, + name: 'tx-cos', default: true, path: 'test/path', }); @@ -33,7 +33,7 @@ describe('storage:tx-cos', () => { describe('direct attachment', () => { itif('upload file should be ok', async () => { - const { body } = await agent.resource('attachments').upload({ + const { body } = await agent.resource('attachments').create({ [FILE_FIELD_NAME]: path.resolve(__dirname, '../files/text.txt'), }); diff --git a/packages/plugins/file-manager/src/server/__tests__/tables/users.ts b/packages/plugins/file-manager/src/server/__tests__/tables/users.ts index 4b86297b02..67c6303d40 100644 --- a/packages/plugins/file-manager/src/server/__tests__/tables/users.ts +++ b/packages/plugins/file-manager/src/server/__tests__/tables/users.ts @@ -11,35 +11,16 @@ export default { type: 'belongsTo', name: 'avatar', target: 'attachments', - attachment: { - // storage 为配置的默认引擎 - rules: { - size: 1024 * 10, - mimetype: ['image/png'], - }, - }, }, { type: 'belongsToMany', name: 'pubkeys', target: 'attachments', - attachment: { - storage: 'local_private', - rules: { - mimetype: ['text/*'], - }, - }, }, { type: 'belongsToMany', name: 'photos', target: 'attachments', - attachment: { - rules: { - size: 1024 * 100, - mimetype: ['image/*'], - }, - }, }, ], } as CollectionOptions; diff --git a/packages/plugins/file-manager/src/server/actions/attachments.ts b/packages/plugins/file-manager/src/server/actions/attachments.ts new file mode 100644 index 0000000000..8dbf8f7808 --- /dev/null +++ b/packages/plugins/file-manager/src/server/actions/attachments.ts @@ -0,0 +1,111 @@ +import multer from '@koa/multer'; +import { Context, Next } from '@nocobase/actions'; +import path from 'path'; + +import { DEFAULT_MAX_FILE_SIZE, FILE_FIELD_NAME, LIMIT_FILES } from '../constants'; +import * as Rules from '../rules'; +import { getStorageConfig } from '../storages'; + +// TODO(optimize): 需要优化错误处理,计算失败后需要抛出对应错误,以便程序处理 +function getFileFilter(storage) { + return (req, file, cb) => { + // size 交给 limits 处理 + const { size, ...rules } = storage.rules; + const ruleKeys = Object.keys(rules); + const result = + !ruleKeys.length || !ruleKeys.some((key) => typeof Rules[key] !== 'function' || !Rules[key](file, rules[key])); + cb(null, result); + }; +} + +function getFileData(ctx: Context) { + const { [FILE_FIELD_NAME]: file, storage } = ctx; + if (!file) { + return ctx.throw(400, 'file validation failed'); + } + + const storageConfig = getStorageConfig(storage.type); + const { [storageConfig.filenameKey || 'filename']: name } = file; + // make compatible filename across cloud service (with path) + const filename = path.basename(name); + const extname = path.extname(filename); + const urlPath = storage.path ? storage.path.replace(/^([^/])/, '/$1') : ''; + + return { + title: file.originalname.replace(extname, ''), + filename, + extname, + // TODO(feature): 暂时两者相同,后面 storage.path 模版化以后,这里只是 file 实际的 path + path: storage.path, + size: file.size, + // 直接缓存起来 + url: `${storage.baseUrl}${urlPath}/${filename}`, + mimetype: file.mimetype, + // @ts-ignore + meta: ctx.request.body, + storageId: storage.id, + ...(storageConfig.getFileData ? storageConfig.getFileData(file) : {}), + }; +} + +export async function middleware(ctx: Context, next: Next) { + const { resourceName, actionName } = ctx.action; + const { attachmentField } = ctx.action.params; + const collection = ctx.db.getCollection(resourceName); + + if (collection?.options?.template !== 'file' || !['upload', 'create'].includes(actionName)) { + return next(); + } + + const storageName = ctx.db.getFieldByPath(attachmentField)?.options?.storage || collection.options.storage; + const StorageRepo = ctx.db.getRepository('storages'); + const storage = await StorageRepo.findOne({ filter: storageName ? { name: storageName } : { default: true } }); + + ctx.storage = storage; + + await multipart(ctx, async () => { + const values = getFileData(ctx); + + ctx.action.mergeParams({ + values, + }); + + await next(); + }); +} + +async function multipart(ctx: Context, next: Next) { + const { storage } = ctx; + if (!storage) { + console.error('[file-manager] no linked or default storage provided'); + return ctx.throw(500); + } + + const storageConfig = getStorageConfig(storage.type); + if (!storageConfig) { + console.error(`[file-manager] storage type "${storage.type}" is not defined`); + return ctx.throw(500); + } + + const multerOptions = { + fileFilter: getFileFilter(storage), + limits: { + fileSize: storage.rules.size ?? DEFAULT_MAX_FILE_SIZE, + // 每次只允许提交一个文件 + files: LIMIT_FILES, + }, + storage: storageConfig.make(storage), + }; + const upload = multer(multerOptions).single(FILE_FIELD_NAME); + try { + // NOTE: empty next and invoke after success + await upload(ctx, () => {}); + } catch (err) { + if (err.name === 'MulterError') { + return ctx.throw(400, err); + } + return ctx.throw(500); + } + + await next(); +} diff --git a/packages/plugins/file-manager/src/server/actions/index.ts b/packages/plugins/file-manager/src/server/actions/index.ts new file mode 100644 index 0000000000..3332756bb0 --- /dev/null +++ b/packages/plugins/file-manager/src/server/actions/index.ts @@ -0,0 +1,7 @@ +import actions from '@nocobase/actions'; +import { middleware } from './attachments'; + +export default function ({ app }) { + app.resourcer.use(middleware); + app.resourcer.registerActionHandler('upload', actions.create); +} diff --git a/packages/plugins/file-manager/src/server/actions/upload.ts b/packages/plugins/file-manager/src/server/actions/upload.ts deleted file mode 100644 index 0f1a5980aa..0000000000 --- a/packages/plugins/file-manager/src/server/actions/upload.ts +++ /dev/null @@ -1,169 +0,0 @@ -import multer from '@koa/multer'; -import { Context, Next } from '@nocobase/actions'; -import path from 'path'; -import { FILE_FIELD_NAME, LIMIT_FILES, LIMIT_MAX_FILE_SIZE } from '../constants'; -import * as Rules from '../rules'; -import { getStorageConfig } from '../storages'; - -function getRules(ctx: Context) { - const { resourceField } = ctx; - if (!resourceField) { - return ctx.storage.rules; - } - const { rules = {} } = resourceField.options.attachment || {}; - return Object.assign({}, ctx.storage.rules, rules); -} - -// TODO(optimize): 需要优化错误处理,计算失败后需要抛出对应错误,以便程序处理 -function getFileFilter(ctx: Context) { - return (req, file, cb) => { - // size 交给 limits 处理 - const { size, ...rules } = getRules(ctx); - const ruleKeys = Object.keys(rules); - const result = - !ruleKeys.length || - !ruleKeys.some((key) => typeof Rules[key] !== 'function' || !Rules[key](file, rules[key], ctx)); - cb(null, result); - }; -} - -const isUploadAction = (ctx: Context) => { - const { resourceName, actionName } = ctx.action; - if (actionName === 'upload' && resourceName === 'attachments') { - return true; - } - const collection = ctx.db.getCollection(resourceName); - if (collection?.options?.template === 'file' && ['upload', 'create'].includes(actionName)) { - return true; - } -}; - -export async function middleware(ctx: Context, next: Next) { - const { resourceName } = ctx.action; - const collection = ctx.db.getCollection(resourceName); - - if (!isUploadAction(ctx)) { - return next(); - } - - const Storage = ctx.db.getCollection('storages'); - let storage; - - if (collection.options.storage) { - storage = await Storage.repository.findOne({ filter: { name: collection.options.storage } }); - } else { - storage = await Storage.repository.findOne({ filter: { default: true } }); - } - - if (!storage) { - console.error('[file-manager] no default or linked storage provided'); - return ctx.throw(500); - } - // 传递已取得的存储引擎,避免重查 - ctx.storage = storage; - - const storageConfig = getStorageConfig(storage.type); - if (!storageConfig) { - console.error(`[file-manager] storage type "${storage.type}" is not defined`); - return ctx.throw(500); - } - const multerOptions = { - fileFilter: getFileFilter(ctx), - limits: { - fileSize: Math.min(getRules(ctx).size || LIMIT_MAX_FILE_SIZE, LIMIT_MAX_FILE_SIZE), - // 每次只允许提交一个文件 - files: LIMIT_FILES, - }, - storage: storageConfig.make(storage), - }; - const upload = multer(multerOptions).single(FILE_FIELD_NAME); - return upload(ctx, next); -} - -export async function createAction(ctx: Context, next: Next) { - if (!isUploadAction(ctx)) { - return next(); - } - - const { [FILE_FIELD_NAME]: file, storage } = ctx; - if (!file) { - return ctx.throw(400, 'file validation failed'); - } - - const storageConfig = getStorageConfig(storage.type); - const { [storageConfig.filenameKey || 'filename']: name } = file; - // make compatible filename across cloud service (with path) - const filename = path.basename(name); - const extname = path.extname(filename); - const urlPath = storage.path ? storage.path.replace(/^([^\/])/, '/$1') : ''; - - const values = { - title: file.originalname.replace(extname, ''), - filename, - extname, - // TODO(feature): 暂时两者相同,后面 storage.path 模版化以后,这里只是 file 实际的 path - path: storage.path, - size: file.size, - // 直接缓存起来 - url: `${storage.baseUrl}${urlPath}/${filename}`, - mimetype: file.mimetype, - storageId: storage.id, - // @ts-ignore - meta: ctx.request.body, - ...(storageConfig.getFileData ? storageConfig.getFileData(file) : {}), - }; - - ctx.action.mergeParams({ - values, - }); - - await next(); -} - -export async function uploadAction(ctx: Context, next: Next) { - const { [FILE_FIELD_NAME]: file, storage } = ctx; - if (!file) { - return ctx.throw(400, 'file validation failed'); - } - - const storageConfig = getStorageConfig(storage.type); - const { [storageConfig.filenameKey || 'filename']: name } = file; - // make compatible filename across cloud service (with path) - const filename = path.basename(name); - const extname = path.extname(filename); - const urlPath = storage.path ? storage.path.replace(/^([^\/])/, '/$1') : ''; - - const data = { - title: file.originalname.replace(extname, ''), - filename, - extname, - // TODO(feature): 暂时两者相同,后面 storage.path 模版化以后,这里只是 file 实际的 path - path: storage.path, - size: file.size, - // 直接缓存起来 - url: `${storage.baseUrl}${urlPath}/${filename}`, - mimetype: file.mimetype, - storageId: storage.id, - // @ts-ignore - meta: ctx.request.body, - ...(storageConfig.getFileData ? storageConfig.getFileData(file) : {}), - }; - - const fileData = await ctx.db.sequelize.transaction(async (transaction) => { - const { resourceName } = ctx.action; - const repository = ctx.db.getRepository(resourceName); - - const result = await repository.create({ - values: { - ...data, - }, - transaction, - }); - - return result; - }); - - ctx.body = fileData; - - await next(); -} diff --git a/packages/plugins/file-manager/src/server/collections/attachments.ts b/packages/plugins/file-manager/src/server/collections/attachments.ts index a44f8b1cf0..390d0e8734 100644 --- a/packages/plugins/file-manager/src/server/collections/attachments.ts +++ b/packages/plugins/file-manager/src/server/collections/attachments.ts @@ -7,6 +7,7 @@ export default { title: '文件管理器', createdBy: true, updatedBy: true, + template: 'file', fields: [ { comment: '用户文件名(不含扩展名)', diff --git a/packages/plugins/file-manager/src/server/constants.ts b/packages/plugins/file-manager/src/server/constants.ts index 2a1f54a062..32fa6df221 100644 --- a/packages/plugins/file-manager/src/server/constants.ts +++ b/packages/plugins/file-manager/src/server/constants.ts @@ -1,6 +1,6 @@ export const FILE_FIELD_NAME = 'file'; export const LIMIT_FILES = 1; -export const LIMIT_MAX_FILE_SIZE = 1024 * 1024 * 1024; +export const DEFAULT_MAX_FILE_SIZE = 1024 * 1024 * 1024; export const STORAGE_TYPE_LOCAL = 'local'; export const STORAGE_TYPE_ALI_OSS = 'ali-oss'; diff --git a/packages/plugins/file-manager/src/server/rules/mimetype.ts b/packages/plugins/file-manager/src/server/rules/mimetype.ts index e3db47334e..46569c9fb3 100644 --- a/packages/plugins/file-manager/src/server/rules/mimetype.ts +++ b/packages/plugins/file-manager/src/server/rules/mimetype.ts @@ -1,5 +1,5 @@ import match from 'mime-match'; -export default function (file, options: string | string[] = '*', ctx): boolean { +export default function (file, options: string | string[] = '*'): boolean { return options.toString().split(',').some(match(file.mimetype)); } diff --git a/packages/plugins/file-manager/src/server/server.ts b/packages/plugins/file-manager/src/server/server.ts index 86db35c28b..7317a01833 100644 --- a/packages/plugins/file-manager/src/server/server.ts +++ b/packages/plugins/file-manager/src/server/server.ts @@ -1,12 +1,12 @@ import { Plugin } from '@nocobase/server'; import { resolve } from 'path'; -import { createAction, uploadAction, middleware as uploadMiddleware } from './actions/upload'; +import initActions from './actions'; import { STORAGE_TYPE_LOCAL } from './constants'; import { getStorageConfig } from './storages'; export default class PluginFileManager extends Plugin { storageType() { - return process.env.DEFAULT_STORAGE_TYPE; + return process.env.DEFAULT_STORAGE_TYPE ?? 'local'; } async install() { @@ -41,14 +41,16 @@ export default class PluginFileManager extends Plugin { actions: ['storages:*'], }); - this.app.acl.allow('attachments', 'upload', 'loggedIn'); + initActions(this); - this.app.resourcer.use(uploadMiddleware); - this.app.resourcer.use(createAction); - this.app.resourcer.registerActionHandler('upload', uploadAction); + // this.app.acl.allow('attachments', 'upload', 'loggedIn'); + + // this.app.resourcer.use(uploadMiddleware); + // this.app.resourcer.use(createAction); + // this.app.resourcer.registerActionHandler('upload', uploadAction); if (process.env.APP_ENV !== 'production') { - await getStorageConfig(STORAGE_TYPE_LOCAL).middleware(this.app); + await getStorageConfig(STORAGE_TYPE_LOCAL).middleware!(this.app); } const defaultStorageName = getStorageConfig(this.storageType()).defaults().name; diff --git a/packages/plugins/file-manager/src/server/storages/local.ts b/packages/plugins/file-manager/src/server/storages/local.ts index 3b73ef2acb..04307047e5 100644 --- a/packages/plugins/file-manager/src/server/storages/local.ts +++ b/packages/plugins/file-manager/src/server/storages/local.ts @@ -49,7 +49,7 @@ function createLocalServerUpdateHook(app, storages) { } function getDocumentRoot(storage): string { - const { documentRoot = 'uploads' } = storage.options || {}; + const { documentRoot = 'storage/uploads' } = storage.options || {}; // TODO(feature): 后面考虑以字符串模板的方式使用,可注入 req/action 相关变量,以便于区分文件夹 return path.resolve(path.isAbsolute(documentRoot) ? documentRoot : path.join(process.cwd(), documentRoot)); } @@ -126,7 +126,7 @@ export default { }, defaults() { const { LOCAL_STORAGE_DEST, LOCAL_STORAGE_BASE_URL, APP_PORT } = process.env; - const documentRoot = LOCAL_STORAGE_DEST || 'uploads'; + const documentRoot = LOCAL_STORAGE_DEST || 'storage/uploads'; return { title: '本地存储', type: STORAGE_TYPE_LOCAL,