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,