diff --git a/CHANGELOG.md b/CHANGELOG.md index c5cd19e823..71e7944a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +## [v1.2.36-alpha](https://github.com/nocobase/nocobase/compare/v1.2.35-alpha...v1.2.36-alpha) - 2024-08-19 + +### Merged + +- 日本語readmeを追加する [`#4971`](https://github.com/nocobase/nocobase/pull/4971) +- fix: mysql2 version [`#5082`](https://github.com/nocobase/nocobase/pull/5082) +- fix: sorting of Table block data [`#5071`](https://github.com/nocobase/nocobase/pull/5071) +- fix: the selected data in the sub table is overwritten by default values [`#5075`](https://github.com/nocobase/nocobase/pull/5075) + +### Commits + +- chore(versions): 😊 publish v1.2.36-alpha [`271a829`](https://github.com/nocobase/nocobase/commit/271a82944ea1fd88ff0f32ce1ff4084a614d693e) +- chore: update changelog [`84ca0eb`](https://github.com/nocobase/nocobase/commit/84ca0eb29609d1575874e2392bbe319bad82bf7c) +- Update README.ja-JP.md [`d5b8f1f`](https://github.com/nocobase/nocobase/commit/d5b8f1fe22fdfa5dcae556c7b4b69c7fdeb3494f) + ## [v1.2.35-alpha](https://github.com/nocobase/nocobase/compare/v1.2.34-alpha...v1.2.35-alpha) - 2024-08-16 ### Merged diff --git a/README.ja-JP.md b/README.ja-JP.md new file mode 100644 index 0000000000..ed16e72ee0 --- /dev/null +++ b/README.ja-JP.md @@ -0,0 +1,74 @@ +[English](./README.md) | [简体中文](./README.zh-CN.md) | 日本語 + +https://github.com/user-attachments/assets/b11cbb68-76bc-4e8b-a2aa-2a1feed0ab77 + +## ご協力ありがとうございます! +nocobase%2Fnocobase | Trendshift + +NocoBase - Scalability-first, open-source no-code platform | Product Hunt + +## 最近の重要なリリース +- [v1.0.1-alpha.1:ブロックの高さ設定をサポート - 2024/06/07](https://docs-cn.nocobase.com/welcome/changelog/20240607) +- [v1.0.0-alpha.15:新しいプラグインの追加、「設定操作」インターフェースの改善 - 2024/05/19](https://docs-cn.nocobase.com/welcome/changelog/20240519) +- [v1.0:新しいマイルストーン - 2024/04/28](https://docs-cn.nocobase.com/welcome/release/v1001-changelog) +- [v0.21:ブロックのパフォーマンスの最適化 - 2024/03/29](https://docs-cn.nocobase.com/welcome/release/v0210-changelog) +- [v0.20:複数のデータソースをサポート - 2024/03/03](https://docs-cn.nocobase.com/welcome/release/v0200-changelog) +- [v0.19:アプリケーションフローの最適化 - 2024/01/08](https://blog-cn.nocobase.com/posts/release-v019/) +- [v0.18:完全なテストシステムの確立 - 2023/12/21](https://blog-cn.nocobase.com/posts/release-v018/) +- [v0.17:新しいSchemaInitializerおよびSchemaSettings - 2023/12/11](https://blog-cn.nocobase.com/posts/release-v017/) +- [v0.16:新しいキャッシュモジュール - 2023/11/20](https://blog-cn.nocobase.com/posts/release-v016/) +- [v0.15:新しいプラグイン設定センター - 2023/11/13](https://blog-cn.nocobase.com/posts/release-v015/) +- [v0.14:新しいプラグインマネージャー、インターフェースを通じたプラグインの追加をサポート - 2023/09/11](https://blog-cn.nocobase.com/posts/release-v014/) +- [v0.13: 新しいアプリケーションステートフロー - 2023/08/24](https://blog-cn.nocobase.com/posts/release-v013/) + +## NocoBaseはなに? + +NocoBaseは非常に拡張性の高いオープンソースのノーコード開発プラットフォームです。 +大量のお時間と資金を投入して開発する必要がなく、NocoBaseをデプロイすることですぐにでもプライベートで制御可能かつ非常に拡張性の高いノーコード開発プラットフォームを構築することができます。 + +ホームページ: +https://www.nocobase.com/ + +オンライン体験: +https://demo-cn.nocobase.com/new + +ドキュメント: +https://docs-cn.nocobase.com/ + +コミュニティ: +https://forum.nocobase.com/ + +## 他の製品との違い + +### 1. データモデル駆動 + +多くのノーコード製品はフォーム、表、またはプロセス駆動型であり、表に項目を追加することでフィールドを新しく作成するなど、ユーザーインターフェース上で直接データ構造を作成します。この方法のメリットは使いやすさですが、機能と柔軟性が制限されており、複雑なシナリオには対応しにくいという欠点があります。 + +NocoBaseはデータ構造とユーザーインターフェースを分離する設計理念を採用しており、データテーブルに任意の数や形態のブロック(データビュー)を作成できます。各ブロックには異なるスタイル、テキスト、操作を定義できるため、ノーコードの簡単な操作性とネイティブ開発の柔軟性を両立しています。 +![model](https://static-docs.nocobase.com/model.png) + +### 2. リアルタイムエディタ +NocoBaseは複雑で特徴的な業務システムを開発できますが、複雑で専門的な知識は必要としません。ワンクリックで設定オプションをユーザーインターフェースに表示でき、システム設定権限を持つ管理者は、見たままの操作方法でユーザーインターフェースを直接設定できます。 +![wysiwyg](https://static-docs.nocobase.com/wysiwyg.gif) + +### 3. プラグインによる高拡張性 + +NocoBaseはプラグイン化されたアーキテクチャを採用しており、新しい機能はすべてプラグインの開発とインストールによって実現できます。機能の拡張は、スマートフォンにアプリをインストールするのと同じくらい簡単です。 + +![plugins](https://static-docs.nocobase.com/plugins.png) + +## インストール + +NocoBaseは3つのインストール方法をサポートしています: + +- Dockerインストール(推奨) + + コードを書く必要がないノーコードシナリオに適しています。アップグレード時は最新のイメージをダウンロードして再起動するだけです。 + +- create-nocobase-appインストール + + プロジェクトのビジネスコードが完全に独立しており、ローコード開発をサポートします。 + +- Gitソースコードインストール + + 最新の未公開バージョンを体験したい場合や、貢献したい場合、ソースコードを変更、デバッグする必要がある場合にこの方法を選択することをお勧めします。この方法は高度な開発技術が必要です。コードが更新された場合、gitフローを使用して最新のコードを取得できます。 diff --git a/README.md b/README.md index cb959ab290..58b7bbf81b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -English | [中文](./README.zh-CN.md) +English | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md) https://github.com/nocobase/nocobase/assets/1267426/1d6a3979-d1eb-4e50-b726-2f90c3f82eeb diff --git a/README.zh-CN.md b/README.zh-CN.md index 7b7fc50ea3..733a5acc68 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,4 +1,4 @@ -[English](./README.md) | 简体中文 +[English](./README.md) | 简体中文 | [日本語](./README.ja-JP.md) https://github.com/nocobase/nocobase/assets/1267426/29623e45-9a48-4598-bb9e-9dd173ade553 diff --git a/packages/core/client/src/collection-manager/collectionPlugin.ts b/packages/core/client/src/collection-manager/collectionPlugin.ts index 270393cf9d..8d71266aaa 100644 --- a/packages/core/client/src/collection-manager/collectionPlugin.ts +++ b/packages/core/client/src/collection-manager/collectionPlugin.ts @@ -52,7 +52,6 @@ import { UUIDFieldInterface, NanoidFieldInterface, UnixTimestampFieldInterface, - DateFieldInterface, } from './interfaces'; import { GeneralCollectionTemplate, @@ -174,7 +173,6 @@ export class CollectionPlugin extends Plugin { UUIDFieldInterface, NanoidFieldInterface, UnixTimestampFieldInterface, - DateFieldInterface, ]); } diff --git a/packages/core/client/src/collection-manager/interfaces/components/index.tsx b/packages/core/client/src/collection-manager/interfaces/components/index.tsx index 2fbd571127..ccb5915a60 100644 --- a/packages/core/client/src/collection-manager/interfaces/components/index.tsx +++ b/packages/core/client/src/collection-manager/interfaces/components/index.tsx @@ -7,8 +7,8 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { Switch, Radio, Input } from 'antd'; -import React, { useEffect, useState } from 'react'; +import { Switch } from 'antd'; +import React from 'react'; export const TargetKey = () => { return
Target key
; @@ -50,37 +50,3 @@ export const ForeignKey2 = () => { ); }; - -// 自定义 Radio 组件 -export const CustomRadio = (props) => { - const { options, onChange } = props; - const [value, setValue] = useState(props.value); - useEffect(() => { - setValue(['server', 'client'].includes(props.value) ? props.value : 'custom'); - }, [props.value]); - const handleRadioChange = (e) => { - setValue(e.target.value); - if (e.target.value !== 'custom') { - onChange?.(e.target.value); - } - }; - - return ( - - {options.map((option) => ( - - {option.label} - {option.value === 'custom' && value === 'custom' ? ( - { - onChange?.(e.target.value); - }} - value={['server', 'client', 'custom'].includes(props.value) ? null : props.value} - /> - ) : null} - - ))} - - ); -}; diff --git a/packages/core/client/src/collection-manager/interfaces/date.ts b/packages/core/client/src/collection-manager/interfaces/date.ts deleted file mode 100644 index 59d23be7fa..0000000000 --- a/packages/core/client/src/collection-manager/interfaces/date.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface'; -import { dateTimeProps, defaultProps, operators } from './properties'; - -export class DateFieldInterface extends CollectionFieldInterface { - name = 'date'; - type = 'object'; - group = 'datetime'; - order = 1; - title = '{{t("Date")}}'; - sortable = true; - default = { - type: 'dateOnly', - uiSchema: { - type: 'string', - 'x-component': 'DatePicker', - 'x-component-props': { - dateOnly: true, - }, - }, - }; - availableTypes = ['date', 'dateOnly', 'string']; - hasDefaultValue = true; - properties = { - ...defaultProps, - 'uiSchema.x-component-props.dateFormat': { - type: 'string', - title: '{{t("Date format")}}', - 'x-component': 'Radio.Group', - 'x-decorator': 'FormItem', - default: 'YYYY-MM-DD', - enum: [ - { - label: '{{t("Year/Month/Day")}}', - value: 'YYYY/MM/DD', - }, - { - label: '{{t("Year-Month-Day")}}', - value: 'YYYY-MM-DD', - }, - { - label: '{{t("Day/Month/Year")}}', - value: 'DD/MM/YYYY', - }, - ], - }, - }; - filterable = { - operators: operators.datetime, - }; - titleUsable = true; -} diff --git a/packages/core/client/src/collection-manager/interfaces/datetime.ts b/packages/core/client/src/collection-manager/interfaces/datetime.ts index 3492a87b6a..615d11e9ea 100644 --- a/packages/core/client/src/collection-manager/interfaces/datetime.ts +++ b/packages/core/client/src/collection-manager/interfaces/datetime.ts @@ -19,9 +19,6 @@ export class DatetimeFieldInterface extends CollectionFieldInterface { sortable = true; default = { type: 'date', - defaultToCurrentTime: false, - onUpdateToCurrentTime: false, - timezone: 'server', uiSchema: { type: 'string', 'x-component': 'DatePicker', diff --git a/packages/core/client/src/collection-manager/interfaces/index.ts b/packages/core/client/src/collection-manager/interfaces/index.ts index 663f55ac2d..6778d83413 100644 --- a/packages/core/client/src/collection-manager/interfaces/index.ts +++ b/packages/core/client/src/collection-manager/interfaces/index.ts @@ -46,4 +46,3 @@ export * from './sort'; export * from './uuid'; export * from './nanoid'; export * from './unixTimestamp'; -export * from './date'; diff --git a/packages/core/client/src/collection-manager/interfaces/properties/index.ts b/packages/core/client/src/collection-manager/interfaces/properties/index.ts index df63fd215b..1ef36e4b61 100644 --- a/packages/core/client/src/collection-manager/interfaces/properties/index.ts +++ b/packages/core/client/src/collection-manager/interfaces/properties/index.ts @@ -10,7 +10,6 @@ import { Field } from '@formily/core'; import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; -import { CustomRadio } from '../components'; export * as operators from './operators'; export const type: ISchema = { @@ -226,29 +225,6 @@ export const reverseFieldProperties: Record = { }; export const dateTimeProps: { [key: string]: ISchema } = { - timezone: { - type: 'string', - title: '{{t("Timezone")}}', - 'x-component': CustomRadio, - 'x-decorator': 'FormItem', - default: 'server', - 'x-component-props': { - options: [ - { - label: '{{t("None")}}', - value: 'server', - }, - { - label: '{{t("Client\'s time zone")}}', - value: 'client', - }, - { - label: '{{t("Custom")}}', - value: 'custom', - }, - ], - }, - }, 'uiSchema.x-component-props.dateFormat': { type: 'string', title: '{{t("Date format")}}', @@ -277,10 +253,10 @@ export const dateTimeProps: { [key: string]: ISchema } = { 'x-content': '{{t("Show time")}}', 'x-reactions': [ `{{(field) => { - field.query('..[].timeFormat').take(f => { - f.display = field.value ? 'visible' : 'none'; - }); - }}}`, + field.query('..[].timeFormat').take(f => { + f.display = field.value ? 'visible' : 'none'; + }); + }}}`, ], }, 'uiSchema.x-component-props.timeFormat': { @@ -300,18 +276,6 @@ export const dateTimeProps: { [key: string]: ISchema } = { }, ], }, - defaultToCurrentTime: { - type: 'boolean', - 'x-decorator': 'FormItem', - 'x-component': 'Checkbox', - 'x-content': '{{t("Default value to current time")}}', - }, - onUpdateToCurrentTime: { - type: 'boolean', - 'x-decorator': 'FormItem', - 'x-component': 'Checkbox', - 'x-content': '{{t("Automatically update timestamp on update")}}', - }, }; export const dataSource: ISchema = { diff --git a/packages/core/client/src/collection-manager/interfaces/unixTimestamp.tsx b/packages/core/client/src/collection-manager/interfaces/unixTimestamp.tsx index 8e9db2b6d1..47b3ebc2cf 100644 --- a/packages/core/client/src/collection-manager/interfaces/unixTimestamp.tsx +++ b/packages/core/client/src/collection-manager/interfaces/unixTimestamp.tsx @@ -8,8 +8,8 @@ */ import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface'; -import { defaultProps, operators } from './properties'; -import { CustomRadio } from './components'; +import { dateTimeProps, defaultProps, operators } from './properties'; + export class UnixTimestampFieldInterface extends CollectionFieldInterface { name = 'unixTimestamp'; type = 'object'; @@ -18,47 +18,21 @@ export class UnixTimestampFieldInterface extends CollectionFieldInterface { title = '{{t("Unix Timestamp")}}'; sortable = true; default = { - type: 'unixTimestamp', - accuracy: 'second', - timezone: 'server', - defaultToCurrentTime: false, - onUpdateToCurrentTime: false, + type: 'bigInt', uiSchema: { type: 'number', 'x-component': 'UnixTimestamp', 'x-component-props': { + accuracy: 'second', showTime: true, }, }, }; - availableTypes = ['integer', 'bigInt', 'unixTimestamp']; - hasDefaultValue = false; + availableTypes = ['integer', 'bigInt']; + hasDefaultValue = true; properties = { ...defaultProps, - timezone: { - type: 'string', - title: '{{t("Timezone")}}', - 'x-component': CustomRadio, - 'x-decorator': 'FormItem', - default: 'server', - 'x-component-props': { - options: [ - { - label: '{{t("None")}}', - value: 'server', - }, - { - label: '{{t("Client\'s time zone")}}', - value: 'client', - }, - { - label: 'custom', - value: 'custom', - }, - ], - }, - }, - accuracy: { + 'uiSchema.x-component-props.accuracy': { type: 'string', title: '{{t("Accuracy")}}', 'x-component': 'Radio.Group', @@ -69,20 +43,6 @@ export class UnixTimestampFieldInterface extends CollectionFieldInterface { { value: 'second', label: '{{t("Second")}}' }, ], }, - defaultToCurrentTime: { - type: 'boolean', - 'x-decorator': 'FormItem', - 'x-component': 'Checkbox', - 'x-content': '{{t("Default value to current time")}}', - default: true, - }, - onUpdateToCurrentTime: { - type: 'boolean', - 'x-decorator': 'FormItem', - 'x-component': 'Checkbox', - 'x-content': '{{t("Automatically update timestamp on update")}}', - default: true, - }, }; filterable = { operators: operators.number, diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index 53b5c0fcb6..9162e64e24 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -283,7 +283,7 @@ "Checkbox group": "复选框", "China region": "中国行政区", "Date & Time": "日期 & 时间", - "Datetime": "日期时间", + "Datetime": "日期", "Relation": "关系类型", "Link to": "关联", "Link to description": "用于快速创建表关系,可兼容大多数普通场景。适合非开发人员使用。作为字段存在时,它是一个下拉选择用于选择目标数据表的数据。创建后,将同时在目标数据表中生成当前数据表的关联字段。", @@ -928,7 +928,7 @@ "Unix Timestamp": "Unix 时间戳", "Field value do not meet the requirements": "字符不符合要求", "Field value size is": "字符长度要求", - "Style": "风格", + "Style": "样式", "Unit conversion": "单位换算", "Separator": "分隔符", "Prefix": "前缀", @@ -967,8 +967,5 @@ "Clear default value": "清除默认值", "Open in new window": "新窗口打开", "Sorry, the page you visited does not exist.": "抱歉,你访问的页面不存在。", - "Template engine": "模板引擎", - "Default value to current time":"设置字段默认值为当前时间", - "Automatically update timestamp on update":"当记录更新时自动设置字段值为当前时间", - "Client's time zone":"客户端时区" + "Set Template Engine": "设置模板引擎" } diff --git a/packages/core/client/src/schema-component/antd/date-picker/util.ts b/packages/core/client/src/schema-component/antd/date-picker/util.ts index 9c80b91a28..59026b3efb 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/util.ts +++ b/packages/core/client/src/schema-component/antd/date-picker/util.ts @@ -78,20 +78,17 @@ export const mapDatePicker = function () { return (props: any) => { const format = getDefaultFormat(props) as any; const onChange = props.onChange; + return { ...props, format: format, value: str2moment(props.value, props), - onChange: (value: Dayjs | null, dateString) => { + onChange: (value: Dayjs | null) => { if (onChange) { if (!props.showTime && value) { value = value.startOf('day'); } - if (props.dateOnly) { - onChange(dateString); - } else { - onChange(moment2str(value, props)); - } + onChange(moment2str(value, props)); } }, }; diff --git a/packages/core/client/src/schema-component/antd/unix-timestamp/UnixTimestamp.tsx b/packages/core/client/src/schema-component/antd/unix-timestamp/UnixTimestamp.tsx index e83f9d00bc..5d19a1e118 100644 --- a/packages/core/client/src/schema-component/antd/unix-timestamp/UnixTimestamp.tsx +++ b/packages/core/client/src/schema-component/antd/unix-timestamp/UnixTimestamp.tsx @@ -8,32 +8,57 @@ */ import { connect, mapReadPretty } from '@formily/react'; -import React from 'react'; +import React, { useMemo } from 'react'; import { DatePicker } from '../date-picker'; +import dayjs from 'dayjs'; + +const toValue = (value: any, accuracy) => { + if (value) { + return timestampToDate(value, accuracy); + } + return null; +}; + +function timestampToDate(timestamp, accuracy = 'millisecond') { + if (accuracy === 'second') { + timestamp *= 1000; // 如果精确度是秒级,则将时间戳乘以1000转换为毫秒级 + } + return dayjs(timestamp); +} + +function getTimestamp(date, accuracy = 'millisecond') { + if (accuracy === 'second') { + return dayjs(date).unix(); + } else { + return dayjs(date).valueOf(); // 默认返回毫秒级时间戳 + } +} interface UnixTimestampProps { - value?: any; + value?: number; + accuracy?: 'millisecond' | 'second'; onChange?: (value: number) => void; } export const UnixTimestamp = connect( (props: UnixTimestampProps) => { - const { value, onChange } = props; - + const { value, onChange, accuracy = 'second' } = props; + const v = useMemo(() => toValue(value, accuracy), [value, accuracy]); return ( { if (onChange) { - onChange(v); + onChange(getTimestamp(v, accuracy)); } }} /> ); }, mapReadPretty((props) => { - const { value } = props; - return ; + const { value, accuracy = 'second' } = props; + const v = useMemo(() => toValue(value, accuracy), [value, accuracy]); + return ; }), ); diff --git a/packages/core/client/src/schema-component/antd/unix-timestamp/__tests__/UnixTimestamp.test.tsx b/packages/core/client/src/schema-component/antd/unix-timestamp/__tests__/UnixTimestamp.test.tsx index c9c107f45c..ba82c73147 100644 --- a/packages/core/client/src/schema-component/antd/unix-timestamp/__tests__/UnixTimestamp.test.tsx +++ b/packages/core/client/src/schema-component/antd/unix-timestamp/__tests__/UnixTimestamp.test.tsx @@ -13,9 +13,11 @@ import { UnixTimestamp } from '@nocobase/client'; describe('UnixTimestamp', () => { it('renders without errors', async () => { const { container } = await renderAppOptions({ - Component: UnixTimestamp as any, - props: {}, - value: null, + Component: UnixTimestamp, + props: { + accuracy: 'millisecond', + }, + value: 0, }); expect(container).toMatchInlineSnapshot(`
@@ -67,10 +69,78 @@ describe('UnixTimestamp', () => { `); }); + it('millisecond', async () => { + await renderAppOptions({ + Component: UnixTimestamp, + value: 1712819630000, + props: { + accuracy: 'millisecond', + }, + }); + await waitFor(() => { + expect(screen.getByRole('textbox')).toHaveValue('2024-04-11'); + }); + }); + + it('second', async () => { + await renderAppOptions({ + Component: UnixTimestamp, + value: 1712819630, + props: { + accuracy: 'second', + }, + }); + + await waitFor(() => { + expect(screen.getByRole('textbox')).toHaveValue('2024-04-11'); + }); + }); + + it('string', async () => { + await renderAppOptions({ + Component: UnixTimestamp, + value: '2024-04-11', + props: { + accuracy: 'millisecond', + }, + }); + + await waitFor(() => { + expect(screen.getByRole('textbox')).toHaveValue('2024-04-11'); + }); + }); + + it('change', async () => { + const onChange = vitest.fn(); + await renderAppOptions({ + Component: UnixTimestamp, + value: '2024-04-11', + onChange, + props: { + accuracy: 'millisecond', + }, + }); + await userEvent.click(screen.getByRole('textbox')); + + await waitFor(() => { + expect(screen.queryByRole('table')).toBeInTheDocument(); + }); + + await userEvent.click(document.querySelector('td[title="2024-04-12"]')); + + await waitFor(() => { + expect(screen.getByRole('textbox')).toHaveValue('2024-04-12'); + }); + expect(onChange).toBeCalledWith(1712880000000); + }); + it('read pretty', async () => { const { container } = await renderReadPrettyApp({ - Component: UnixTimestamp as any, + Component: UnixTimestamp, value: '2024-04-11', + props: { + accuracy: 'millisecond', + }, }); expect(screen.getByText('2024-04-11')).toBeInTheDocument(); diff --git a/packages/core/database/src/__tests__/fields/date-only.test.ts b/packages/core/database/src/__tests__/fields/date-only.test.ts deleted file mode 100644 index 1b6d9a60f7..0000000000 --- a/packages/core/database/src/__tests__/fields/date-only.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import { Database, mockDatabase } from '@nocobase/database'; - -describe('date only', () => { - let db: Database; - - beforeEach(async () => { - db = mockDatabase({ - timezone: '+08:00', - }); - await db.clean({ drop: true }); - }); - - afterEach(async () => { - await db.close(); - }); - - it('should set date field with dateOnly', async () => { - db.collection({ - name: 'tests', - fields: [{ name: 'date1', type: 'dateOnly' }], - }); - - await db.sync(); - - const item = await db.getRepository('tests').create({ - values: { - date1: '2023-03-24', - }, - }); - - expect(item.get('date1')).toBe('2023-03-24'); - }); -}); diff --git a/packages/core/database/src/__tests__/fields/date.test.ts b/packages/core/database/src/__tests__/fields/date.test.ts index 4c6b373721..e1a2ff189e 100644 --- a/packages/core/database/src/__tests__/fields/date.test.ts +++ b/packages/core/database/src/__tests__/fields/date.test.ts @@ -11,69 +11,6 @@ import { mockDatabase } from '../'; import { Database } from '../../database'; import { Repository } from '../../repository'; -describe('timezone', () => { - let db: Database; - - beforeEach(async () => { - db = mockDatabase({ - timezone: '+08:00', - }); - await db.clean({ drop: true }); - }); - - afterEach(async () => { - await db.close(); - }); - - describe('timezone', () => { - test('custom', async () => { - db.collection({ - name: 'tests', - timestamps: false, - fields: [{ name: 'date1', type: 'date', timezone: '+06:00' }], - }); - - await db.sync(); - const repository = db.getRepository('tests'); - const instance = await repository.create({ values: { date1: '2023-03-24 00:00:00' } }); - const date1 = instance.get('date1'); - expect(date1.toISOString()).toEqual('2023-03-23T18:00:00.000Z'); - }); - - test('client', async () => { - db.collection({ - name: 'tests', - timestamps: false, - fields: [{ name: 'date1', type: 'date', timezone: 'client' }], - }); - - await db.sync(); - const repository = db.getRepository('tests'); - const instance = await repository.create({ - values: { date1: '2023-03-24 01:00:00' }, - context: { - timezone: '+01:00', - }, - }); - const date1 = instance.get('date1'); - expect(date1.toISOString()).toEqual('2023-03-24T00:00:00.000Z'); - }); - - test('server', async () => { - db.collection({ - name: 'tests', - fields: [{ name: 'date1', type: 'date', timezone: 'server' }], - }); - - await db.sync(); - const repository = db.getRepository('tests'); - const instance = await repository.create({ values: { date1: '2023-03-24 08:00:00' } }); - const date1 = instance.get('date1'); - expect(date1.toISOString()).toEqual('2023-03-24T00:00:00.000Z'); - }); - }); -}); - describe('date-field', () => { let db: Database; let repository: Repository; @@ -93,80 +30,16 @@ describe('date-field', () => { await db.close(); }); - it('should set default to current time', async () => { - const c1 = db.collection({ - name: 'test11', - fields: [ - { - name: 'date1', - type: 'date', - defaultToCurrentTime: true, - }, - ], - }); - - await db.sync(); - - const instance = await c1.repository.create({}); - const date1 = instance.get('date1'); - expect(date1).toBeDefined(); - }); - - it('should set to current time when update', async () => { - const c1 = db.collection({ - name: 'test11', - fields: [ - { - name: 'date1', - type: 'date', - onUpdateToCurrentTime: true, - }, - { - name: 'title', - type: 'string', - }, - ], - }); - - await db.sync(); - - const instance = await c1.repository.create({ + const createExpectToBe = async (key, actual, expected) => { + const instance = await repository.create({ values: { - title: 'test', + [key]: actual, }, }); - - const date1Val = instance.get('date1'); - expect(date1Val).toBeDefined(); - - console.log('update'); - await c1.repository.update({ - values: { - title: 'test2', - }, - filter: { - id: instance.get('id'), - }, - }); - - await instance.reload(); - - const date1Val2 = instance.get('date1'); - expect(date1Val2).toBeDefined(); - - expect(date1Val2.getTime()).toBeGreaterThan(date1Val.getTime()); - }); + return expect(instance.get(key).toISOString()).toEqual(expected); + }; test('create', async () => { - const createExpectToBe = async (key, actual, expected) => { - const instance = await repository.create({ - values: { - [key]: actual, - }, - }); - return expect(instance.get(key).toISOString()).toEqual(expected); - }; - // sqlite 时区不能自定义,只有 +00:00,postgres 和 mysql 可以自定义 DB_TIMEZONE await createExpectToBe('date1', '2023-03-24', '2023-03-24T00:00:00.000Z'); await createExpectToBe('date1', '2023-03-24T16:00:00.000Z', '2023-03-24T16:00:00.000Z'); diff --git a/packages/core/database/src/__tests__/fields/unix-timestamp-field.tests.ts b/packages/core/database/src/__tests__/fields/unix-timestamp-field.tests.ts deleted file mode 100644 index d590653811..0000000000 --- a/packages/core/database/src/__tests__/fields/unix-timestamp-field.tests.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import { Database, mockDatabase } from '@nocobase/database'; -import moment from 'moment'; - -describe('unix timestamp field', () => { - let db: Database; - - beforeEach(async () => { - db = mockDatabase(); - await db.clean({ drop: true }); - }); - - afterEach(async () => { - await db.close(); - }); - - it('should set default to current time', async () => { - const c1 = db.collection({ - name: 'test11', - fields: [ - { - name: 'date1', - type: 'unixTimestamp', - defaultToCurrentTime: true, - }, - ], - }); - - await db.sync(); - - const instance = await c1.repository.create({}); - const date1 = instance.get('date1'); - expect(date1).toBeDefined(); - - console.log(instance.toJSON()); - }); - - it('should set date value', async () => { - const c1 = db.collection({ - name: 'test12', - fields: [ - { - name: 'date1', - type: 'unixTimestamp', - }, - ], - }); - - await db.sync(); - - await c1.repository.create({ - values: { - date1: '2021-01-01T00:00:00Z', - }, - }); - - const item = await c1.repository.findOne(); - const val = item.get('date1'); - const date = moment(val).utc().format('YYYY-MM-DD HH:mm:ss'); - expect(date).toBe('2021-01-01 00:00:00'); - }); - - describe('timezone', () => { - test('custom', async () => { - db.collection({ - name: 'tests', - timestamps: false, - fields: [{ name: 'date1', type: 'unixTimestamp', timezone: '+06:00' }], - }); - - await db.sync(); - const repository = db.getRepository('tests'); - const instance = await repository.create({ values: { date1: '2023-03-24 00:00:00' } }); - const date1 = instance.get('date1'); - expect(date1.toISOString()).toEqual('2023-03-23T18:00:00.000Z'); - }); - }); -}); diff --git a/packages/core/database/src/database.ts b/packages/core/database/src/database.ts index 993f1be825..a3b813b96b 100644 --- a/packages/core/database/src/database.ts +++ b/packages/core/database/src/database.ts @@ -34,6 +34,7 @@ import { import { SequelizeStorage, Umzug } from 'umzug'; import { Collection, CollectionOptions, RepositoryType } from './collection'; import { CollectionFactory } from './collection-factory'; +import { CollectionGroupManager } from './collection-group-manager'; import { ImporterReader, ImportFileExtension } from './collection-importer'; import DatabaseUtils from './database-utils'; import ReferencesMap from './features/references-map'; @@ -41,6 +42,7 @@ import { referentialIntegrityCheck } from './features/referential-integrity-chec import { ArrayFieldRepository } from './field-repository/array-field-repository'; import * as FieldTypes from './fields'; import { Field, FieldContext, RelationField } from './fields'; +import { checkDatabaseVersion } from './helpers'; import { InheritedCollection } from './inherited-collection'; import InheritanceMap from './inherited-map'; import { InterfaceManager } from './interface-manager'; @@ -219,9 +221,6 @@ export class Database extends EventEmitter implements AsyncEmitter { } } - // @ts-ignore - opts.rawTimezone = opts.timezone; - if (options.dialect === 'sqlite') { delete opts.timezone; } else if (!opts.timezone) { @@ -852,8 +851,7 @@ export class Database extends EventEmitter implements AsyncEmitter { * @internal */ async checkVersion() { - return true; - // return await checkDatabaseVersion(this); + return await checkDatabaseVersion(this); } /** diff --git a/packages/core/database/src/fields/date-field.ts b/packages/core/database/src/fields/date-field.ts index 8cae2221d5..f40b27de3e 100644 --- a/packages/core/database/src/fields/date-field.ts +++ b/packages/core/database/src/fields/date-field.ts @@ -10,14 +10,8 @@ import { DataTypes } from 'sequelize'; import { BaseColumnFieldOptions, Field } from './field'; -const datetimeRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/; - -function isValidDatetime(str) { - return datetimeRegex.test(str); -} - export class DateField extends Field { - get dataType(): any { + get dataType() { return DataTypes.DATE(3); } @@ -39,59 +33,6 @@ export class DateField extends Field { return props.gmt; } - init() { - const { name, defaultToCurrentTime, onUpdateToCurrentTime, timezone } = this.options; - - this.resolveTimeZone = (context) => { - // @ts-ignore - const serverTimeZone = this.database.options.rawTimezone; - if (timezone === 'server') { - return serverTimeZone; - } - - if (timezone === 'client') { - return context?.timezone || serverTimeZone; - } - - if (timezone) { - return timezone; - } - - return serverTimeZone; - }; - - this.beforeSave = async (instance, options) => { - const value = instance.get(name); - - if (!value && instance.isNewRecord && defaultToCurrentTime) { - instance.set(name, new Date()); - return; - } - - if (onUpdateToCurrentTime) { - instance.set(name, new Date()); - return; - } - }; - } - - setter(value, options) { - if (value === null) { - return value; - } - if (value instanceof Date) { - return value; - } - - if (typeof value === 'string' && isValidDatetime(value)) { - const dateTimezone = this.resolveTimeZone(options?.context); - const dateString = `${value} ${dateTimezone}`; - return new Date(dateString); - } - - return value; - } - bind() { super.bind(); @@ -110,13 +51,6 @@ export class DateField extends Field { // @ts-ignore model.refreshAttributes(); } - - this.on('beforeSave', this.beforeSave); - } - - unbind() { - super.unbind(); - this.off('beforeSave', this.beforeSave); } } diff --git a/packages/core/database/src/fields/date-only-field.ts b/packages/core/database/src/fields/date-only-field.ts deleted file mode 100644 index 5fce9d1b74..0000000000 --- a/packages/core/database/src/fields/date-only-field.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import { BaseColumnFieldOptions, Field } from './field'; -import { DataTypes } from 'sequelize'; - -export class DateOnlyField extends Field { - get dataType(): any { - return DataTypes.DATEONLY; - } -} - -export interface DateOnlyFieldOptions extends BaseColumnFieldOptions { - type: 'dateOnly'; -} diff --git a/packages/core/database/src/fields/field.ts b/packages/core/database/src/fields/field.ts index e4b0b70024..b9f230dd4f 100644 --- a/packages/core/database/src/fields/field.ts +++ b/packages/core/database/src/fields/field.ts @@ -56,7 +56,7 @@ export abstract class Field { return this.options.type; } - abstract get dataType(): any; + abstract get dataType(); isRelationField() { return false; @@ -171,13 +171,11 @@ export abstract class Field { Object.assign(opts, { type: this.database.sequelize.normalizeDataType(this.dataType) }); } - Object.assign(opts, this.additionalSequelizeOptions()); - return opts; } - additionalSequelizeOptions() { - return {}; + isSqlite() { + return this.database.sequelize.getDialect() === 'sqlite'; } typeToString() { diff --git a/packages/core/database/src/fields/index.ts b/packages/core/database/src/fields/index.ts index d698e44cd6..610b6f1ad1 100644 --- a/packages/core/database/src/fields/index.ts +++ b/packages/core/database/src/fields/index.ts @@ -36,8 +36,6 @@ import { UUIDFieldOptions } from './uuid-field'; import { VirtualFieldOptions } from './virtual-field'; import { NanoidFieldOptions } from './nanoid-field'; import { EncryptionField } from './encryption-field'; -import { UnixTimestampFieldOptions } from './unix-timestamp-field'; -import { DateOnlyFieldOptions } from './date-only-field'; export * from './array-field'; export * from './belongs-to-field'; @@ -45,7 +43,6 @@ export * from './belongs-to-many-field'; export * from './boolean-field'; export * from './context-field'; export * from './date-field'; -export * from './date-only-field'; export * from './field'; export * from './has-many-field'; export * from './has-one-field'; @@ -64,7 +61,6 @@ export * from './uuid-field'; export * from './virtual-field'; export * from './nanoid-field'; export * from './encryption-field'; -export * from './unix-timestamp-field'; export type FieldOptions = | BaseFieldOptions @@ -85,8 +81,6 @@ export type FieldOptions = | SetFieldOptions | TimeFieldOptions | DateFieldOptions - | DateOnlyFieldOptions - | UnixTimestampFieldOptions | UidFieldOptions | UUIDFieldOptions | NanoidFieldOptions diff --git a/packages/core/database/src/fields/unix-timestamp-field.ts b/packages/core/database/src/fields/unix-timestamp-field.ts deleted file mode 100644 index fc634ecde5..0000000000 --- a/packages/core/database/src/fields/unix-timestamp-field.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import { DataTypes } from 'sequelize'; -import { DateField } from './date-field'; -import { BaseColumnFieldOptions } from './field'; - -export class UnixTimestampField extends DateField { - get dataType() { - return DataTypes.BIGINT; - } - - additionalSequelizeOptions(): {} { - const { name } = this.options; - let { accuracy } = this.options; - - if (this.options?.uiSchema['x-component-props']?.accuracy) { - accuracy = this.options?.uiSchema['x-component-props']?.accuracy; - } - - if (!accuracy) { - accuracy = 'second'; - } - - let rationalNumber = 1000; - - if (accuracy === 'millisecond') { - rationalNumber = 1; - } - - return { - get() { - const value = this.getDataValue(name); - if (value === null || value === undefined) { - return value; - } - - return new Date(value * rationalNumber); - }, - set(value) { - if (value === null || value === undefined) { - this.setDataValue(name, value); - } else { - // date to unix timestamp - this.setDataValue(name, Math.floor(new Date(value).getTime() / rationalNumber)); - } - }, - }; - } -} - -export interface UnixTimestampFieldOptions extends BaseColumnFieldOptions { - type: 'unix-timestamp'; -} diff --git a/packages/core/database/src/model.ts b/packages/core/database/src/model.ts index 9ec745fcca..9152ae6b39 100644 --- a/packages/core/database/src/model.ts +++ b/packages/core/database/src/model.ts @@ -7,7 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import lodash from 'lodash'; +import lodash, { isPlainObject } from 'lodash'; import { Model as SequelizeModel, ModelStatic } from 'sequelize'; import { Collection } from './collection'; import { Database } from './database'; @@ -50,21 +50,6 @@ export class Model(values, { ...options, @@ -645,7 +645,7 @@ export class Repository exten public requestLogger: Logger; protected plugins = new Map(); protected _appSupervisor: AppSupervisor = AppSupervisor.getInstance(); + protected _started: Date | null = null; private _authenticated = false; private _maintaining = false; private _maintainingCommandStatus: MaintainingCommandStatus; private _maintainingStatusBeforeCommand: MaintainingCommandStatus | null; private _actionCommand: Command; + + /** + * @internal + */ private sqlLogger: Logger; + protected _logger: SystemLogger; constructor(public options: ApplicationOptions) { super(); @@ -242,8 +248,6 @@ export class Application exten } } - protected _started: Date | null = null; - /** * @experimental */ @@ -251,8 +255,6 @@ export class Application exten return this._started; } - protected _logger: SystemLogger; - get logger() { return this._logger; } diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts index af75379d95..df6c12f697 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts @@ -11,6 +11,7 @@ import { promises as fs } from 'fs'; import path from 'path'; import { getApp } from '.'; import { FILE_FIELD_NAME, FILE_SIZE_LIMIT_DEFAULT, STORAGE_TYPE_LOCAL } from '../../constants'; +import PluginFileManagerServer from '../server'; const { LOCAL_STORAGE_BASE_URL, LOCAL_STORAGE_DEST = 'storage/uploads', APP_PORT = '13000' } = process.env; @@ -50,6 +51,72 @@ describe('action', () => { describe('create / upload', () => { describe('default storage', () => { + it('should be create file record', async () => { + const Plugin = app.pm.get(PluginFileManagerServer) as PluginFileManagerServer; + const model = await Plugin.createFileRecord({ + collectionName: 'attachments', + filePath: path.resolve(__dirname, './files/text.txt'), + }); + const matcher = { + title: 'text', + extname: '.txt', + path: '', + // size: 13, + meta: {}, + storageId: 1, + }; + expect(model.toJSON()).toMatchObject(matcher); + }); + + it('should be local2 storage', async () => { + const storage = await StorageRepo.create({ + values: { + name: 'local2', + type: STORAGE_TYPE_LOCAL, + baseUrl: DEFAULT_LOCAL_BASE_URL, + rules: { + size: 1024, + }, + paranoid: true, + }, + }); + const Plugin = app.pm.get(PluginFileManagerServer) as PluginFileManagerServer; + const model = await Plugin.createFileRecord({ + collectionName: 'attachments', + storageName: 'local2', + filePath: path.resolve(__dirname, './files/text.txt'), + }); + const matcher = { + title: 'text', + extname: '.txt', + path: '', + // size: 13, + meta: {}, + storageId: storage.id, + }; + expect(model.toJSON()).toMatchObject(matcher); + }); + + it.only('should be custom values', async () => { + const Plugin = app.pm.get(PluginFileManagerServer) as PluginFileManagerServer; + const model = await Plugin.createFileRecord({ + collectionName: 'attachments', + filePath: path.resolve(__dirname, './files/text.txt'), + values: { + size: 22, + }, + }); + const matcher = { + title: 'text', + extname: '.txt', + path: '', + size: 22, + meta: {}, + storageId: 1, + }; + expect(model.toJSON()).toMatchObject(matcher); + }); + it('upload file should be ok', async () => { const { body } = await agent.resource('attachments').create({ [FILE_FIELD_NAME]: path.resolve(__dirname, './files/text.txt'), diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts index 34eb2edf17..b757236179 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts @@ -11,15 +11,15 @@ import { Context, Next } from '@nocobase/actions'; import { koaMulter as multer } from '@nocobase/utils'; import Path from 'path'; +import Plugin from '..'; import { + FILE_FIELD_NAME, FILE_SIZE_LIMIT_DEFAULT, FILE_SIZE_LIMIT_MAX, - FILE_FIELD_NAME, - LIMIT_FILES, FILE_SIZE_LIMIT_MIN, + LIMIT_FILES, } from '../../constants'; import * as Rules from '../rules'; -import Plugin from '..'; // TODO(optimize): 需要优化错误处理,计算失败后需要抛出对应错误,以便程序处理 function getFileFilter(storage) { @@ -33,7 +33,7 @@ function getFileFilter(storage) { }; } -function getFileData(ctx: Context) { +export function getFileData(ctx: Context) { const { [FILE_FIELD_NAME]: file, storage } = ctx; if (!file) { return ctx.throw(400, 'file validation failed'); diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts index 807e3a4de5..3a67235a56 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts @@ -7,14 +7,17 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { resolve } from 'path'; - import { Plugin } from '@nocobase/server'; import { Registry } from '@nocobase/utils'; +import { basename, resolve } from 'path'; + +import { Transactionable } from '@nocobase/database'; +import fs from 'fs'; import { STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_LOCAL, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS } from '../constants'; import { FileModel } from './FileModel'; import initActions from './actions'; +import { getFileData } from './actions/attachments'; import { AttachmentInterface } from './interfaces/attachment-interface'; import { IStorage, StorageModel } from './storages'; import StorageTypeAliOss from './storages/ali-oss'; @@ -26,10 +29,78 @@ export type * from './storages'; const DEFAULT_STORAGE_TYPE = STORAGE_TYPE_LOCAL; +export type FileRecordOptions = { + collectionName: string; + filePath: string; + storageName?: string; + values?: any; +} & Transactionable; + export default class PluginFileManagerServer extends Plugin { storageTypes = new Registry(); storagesCache = new Map(); + async createFileRecord(options: FileRecordOptions) { + const { values, storageName, collectionName, filePath, transaction } = options; + const collection = this.db.getCollection(collectionName); + if (!collection) { + throw new Error(`collection does not exist`); + } + const storageRepository = this.db.getRepository('storages'); + const collectionRepository = this.db.getRepository(collectionName); + const name = storageName || collection.options.storage; + + let storageInstance; + if (name) { + storageInstance = await storageRepository.findOne({ + filter: { + name, + }, + }); + } + + if (!storageInstance) { + storageInstance = await storageRepository.findOne({ + filter: { + default: true, + }, + }); + } + + const fileStream = fs.createReadStream(filePath); + + if (!storageInstance) { + throw new Error('[file-manager] no linked or default storage provided'); + } + + const storageConfig = this.storageTypes.get(storageInstance.type); + + if (!storageConfig) { + throw new Error(`[file-manager] storage type "${storageInstance.type}" is not defined`); + } + + const engine = storageConfig.make(storageInstance); + + const file = { + originalname: basename(filePath), + path: filePath, + stream: fileStream, + } as any; + + await new Promise((resolve, reject) => { + engine._handleFile({} as any, file, (error, info) => { + if (error) { + reject(error); + } + Object.assign(file, info); + resolve(info); + }); + }); + + const data = getFileData({ app: this.app, file, storage: storageInstance, request: { body: {} } } as any); + return await collectionRepository.create({ values: { ...data, ...values }, transaction }); + } + async loadStorages(options?: { transaction: any }) { const repository = this.db.getRepository('storages'); const storages = await repository.find({ diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Header.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Header.tsx index c36b079151..7725b03359 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Header.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/desktop-mode/Header.tsx @@ -7,14 +7,14 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import React, { FC, useState } from 'react'; -import { QRCode, Popover, Button } from 'antd'; import { QrcodeOutlined } from '@ant-design/icons'; +import { Button, Popover, QRCode } from 'antd'; +import React, { FC, useState } from 'react'; +import { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon'; +import { css, DesignableSwitch, Icon, useApp } from '@nocobase/client'; import { usePluginTranslation } from '../locale'; import { useSize } from './sizeContext'; -import { css, DesignableSwitch, Icon } from '@nocobase/client'; -import { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon'; const PadSvg = () => ( ) => { const { t } = usePluginTranslation(); + const app = useApp(); const { setSize } = useSize(); const [open, setOpen] = useState(false); const handleQrcodeOpen = (newOpen: boolean) => { @@ -93,7 +94,7 @@ export const DesktopModeHeader: FC = () => { } `} > -
diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx index 56420cae82..a51ed3facd 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx @@ -7,19 +7,19 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import React, { FC, useCallback } from 'react'; import { SafeArea } from 'antd-mobile'; import 'antd-mobile/es/components/tab-bar/tab-bar.css'; +import React, { FC, useCallback } from 'react'; import { Navigate } from 'react-router-dom'; -import { useStyles } from './styles'; import { useMobileRoutes } from '../../mobile-providers'; +import { useStyles } from './styles'; -import { getMobileTabBarItemSchema, MobileTabBarItem } from './MobileTabBar.Item'; -import { MobileTabBarPage, MobileTabBarLink } from './types'; -import { cx, DndContext, DndContextProps, SchemaComponent, useDesignable, css, useApp } from '@nocobase/client'; -import { MobileTabBarInitializer } from './initializer'; +import { css, cx, DndContext, DndContextProps, SchemaComponent, useApp, useDesignable } from '@nocobase/client'; import { isInnerLink } from '../../utils'; +import { MobileTabBarInitializer } from './initializer'; +import { getMobileTabBarItemSchema, MobileTabBarItem } from './MobileTabBar.Item'; +import { MobileTabBarLink, MobileTabBarPage } from './types'; export interface MobileTabBarProps { /** @@ -57,7 +57,7 @@ export const MobileTabBar: FC & { ); if (!hasAuth) { - return ; + return ; } if (!enableTabBar) {