mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
Merge branch 'main' of github.com:nocobase/nocobase into huoshijie/fix-import-export-permission-bug
This commit is contained in:
commit
06d4b211a1
291
CHANGELOG.md
291
CHANGELOG.md
@ -5,6 +5,297 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [v1.6.20](https://github.com/nocobase/nocobase/compare/v1.6.19...v1.6.20) - 2025-04-14
|
||||||
|
|
||||||
|
### 🎉 New Features
|
||||||
|
|
||||||
|
- **[Departments]** Make Department, Attachment URL, and Workflow response message plugins free ([#6663](https://github.com/nocobase/nocobase/pull/6663)) by @chenos
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- The filter form should not display the "Unsaved changes" prompt ([#6657](https://github.com/nocobase/nocobase/pull/6657)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- "allow multiple" option not working for relation field ([#6661](https://github.com/nocobase/nocobase/pull/6661)) by @katherinehhh
|
||||||
|
|
||||||
|
- In the filter form, when the filter button is clicked, if there are fields that have not passed validation, the filtering is still triggered ([#6659](https://github.com/nocobase/nocobase/pull/6659)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- Switching to the group menu should not jump to a page that has already been hidden in menu ([#6654](https://github.com/nocobase/nocobase/pull/6654)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[File storage: S3(Pro)]**
|
||||||
|
- Organize language by @jiannx
|
||||||
|
|
||||||
|
- Individual baseurl and public settings, improve S3 pro storage config UX by @jiannx
|
||||||
|
|
||||||
|
- **[Migration manager]** the skip auto backup option becomes invalid if environment variable popup appears during migration by @gchust
|
||||||
|
|
||||||
|
## [v1.6.19](https://github.com/nocobase/nocobase/compare/v1.6.18...v1.6.19) - 2025-04-14
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- Fix the issue of preview images being obscured ([#6651](https://github.com/nocobase/nocobase/pull/6651)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- In the form block, the default value of the field configuration will first be displayed as the original variable string and then disappear ([#6649](https://github.com/nocobase/nocobase/pull/6649)) by @zhangzhonghe
|
||||||
|
|
||||||
|
## [v1.6.18](https://github.com/nocobase/nocobase/compare/v1.6.17...v1.6.18) - 2025-04-11
|
||||||
|
|
||||||
|
### 🚀 Improvements
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- Add default type fallback API for `Variable.Input` ([#6644](https://github.com/nocobase/nocobase/pull/6644)) by @mytharcher
|
||||||
|
|
||||||
|
- Optimize prompts for unconfigured pages ([#6641](https://github.com/nocobase/nocobase/pull/6641)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[Workflow: Delay node]** Support to use variable for duration ([#6621](https://github.com/nocobase/nocobase/pull/6621)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Workflow: Custom action event]** Add refresh settings for trigger workflow button by @mytharcher
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- subtable description overlapping with add new button ([#6646](https://github.com/nocobase/nocobase/pull/6646)) by @katherinehhh
|
||||||
|
|
||||||
|
- dashed underline caused by horizontal form layout in modal ([#6639](https://github.com/nocobase/nocobase/pull/6639)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[File storage: S3(Pro)]** Fix missing await for next call. by @jiannx
|
||||||
|
|
||||||
|
- **[Email manager]** Fix missing await for next call. by @jiannx
|
||||||
|
|
||||||
|
## [v1.6.17](https://github.com/nocobase/nocobase/compare/v1.6.16...v1.6.17) - 2025-04-09
|
||||||
|
|
||||||
|
### 🚀 Improvements
|
||||||
|
|
||||||
|
- **[utils]** Add duration extension for dayjs ([#6630](https://github.com/nocobase/nocobase/pull/6630)) by @mytharcher
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- Support to search field in Filter component ([#6627](https://github.com/nocobase/nocobase/pull/6627)) by @mytharcher
|
||||||
|
|
||||||
|
- Add `trim` API for `Input` and `Variable.TextArea` ([#6624](https://github.com/nocobase/nocobase/pull/6624)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Error handler]** Support custom title in AppError component. ([#6409](https://github.com/nocobase/nocobase/pull/6409)) by @sheldon66
|
||||||
|
|
||||||
|
- **[IP restriction]** Update IP restriction message content. by @sheldon66
|
||||||
|
|
||||||
|
- **[File storage: S3(Pro)]** Support global variables in storage configuration by @mytharcher
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- rule with 'any' condition does not take effect when condition list is empty ([#6628](https://github.com/nocobase/nocobase/pull/6628)) by @katherinehhh
|
||||||
|
|
||||||
|
- data issue with Gantt block in tree collection ([#6617](https://github.com/nocobase/nocobase/pull/6617)) by @katherinehhh
|
||||||
|
|
||||||
|
- The relationship fields in the filter form report an error after the page is refreshed because x-data-source is not carried ([#6619](https://github.com/nocobase/nocobase/pull/6619)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- variable parse failure when URL parameters contain Chinese characters ([#6618](https://github.com/nocobase/nocobase/pull/6618)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Users]** Issue with parsing the user profile form schema ([#6635](https://github.com/nocobase/nocobase/pull/6635)) by @2013xile
|
||||||
|
|
||||||
|
- **[Mobile]** single-select field with 'contains' filter on mobile does not support multiple selection ([#6629](https://github.com/nocobase/nocobase/pull/6629)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Action: Export records]** missing filter params when exporting data after changing pagination ([#6633](https://github.com/nocobase/nocobase/pull/6633)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Email manager]** fix email management permission cannot view email list by @jiannx
|
||||||
|
|
||||||
|
- **[File storage: S3(Pro)]** Throw error to user when upload logo to S3 Pro storage (set to default) by @mytharcher
|
||||||
|
|
||||||
|
- **[Workflow: Approval]** Fix `updatedAt` changed after migration by @mytharcher
|
||||||
|
|
||||||
|
- **[Migration manager]** migration log creation time is displayed incorrectly in some environments by @gchust
|
||||||
|
|
||||||
|
## [v1.6.16](https://github.com/nocobase/nocobase/compare/v1.6.15...v1.6.16) - 2025-04-03
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- x-disabled property not taking effect on form fields ([#6610](https://github.com/nocobase/nocobase/pull/6610)) by @katherinehhh
|
||||||
|
|
||||||
|
- field label display issue to prevent truncation by colon ([#6599](https://github.com/nocobase/nocobase/pull/6599)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[database]** When deleting one-to-many records, both `filter` and `filterByTk` are passed and `filter` includes an association field, the `filterByTk` is ignored ([#6606](https://github.com/nocobase/nocobase/pull/6606)) by @2013xile
|
||||||
|
|
||||||
|
## [v1.6.15](https://github.com/nocobase/nocobase/compare/v1.6.14...v1.6.15) - 2025-04-01
|
||||||
|
|
||||||
|
### 🚀 Improvements
|
||||||
|
|
||||||
|
- **[database]**
|
||||||
|
- Add trim option for text field ([#6603](https://github.com/nocobase/nocobase/pull/6603)) by @mytharcher
|
||||||
|
|
||||||
|
- Add trim option for string field ([#6565](https://github.com/nocobase/nocobase/pull/6565)) by @mytharcher
|
||||||
|
|
||||||
|
- **[File manager]** Add trim option for text fields of storages collection ([#6604](https://github.com/nocobase/nocobase/pull/6604)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Workflow]** Improve code ([#6589](https://github.com/nocobase/nocobase/pull/6589)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Workflow: Approval]** Support to use block template for approval process form by @mytharcher
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[database]** Avoid "datetimeNoTz" field changes when value not changed in updating record ([#6588](https://github.com/nocobase/nocobase/pull/6588)) by @mytharcher
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- association field (select) displaying N/A when exposing related collection fields ([#6582](https://github.com/nocobase/nocobase/pull/6582)) by @katherinehhh
|
||||||
|
|
||||||
|
- Fix `disabled` property not works when `SchemaInitializerItem` has `items` ([#6597](https://github.com/nocobase/nocobase/pull/6597)) by @mytharcher
|
||||||
|
|
||||||
|
- cascade issue: 'The value of xxx cannot be in array format' when deleting and re-selecting ([#6585](https://github.com/nocobase/nocobase/pull/6585)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Collection field: Many to many (array)]** Issue of filtering by fields in an association collection with a many to many (array) field ([#6596](https://github.com/nocobase/nocobase/pull/6596)) by @2013xile
|
||||||
|
|
||||||
|
- **[Public forms]** View permissions include list and get ([#6607](https://github.com/nocobase/nocobase/pull/6607)) by @chenos
|
||||||
|
|
||||||
|
- **[Authentication]** token assignment in `AuthProvider` ([#6593](https://github.com/nocobase/nocobase/pull/6593)) by @2013xile
|
||||||
|
|
||||||
|
- **[Workflow]** Fix sync option display incorrectly ([#6595](https://github.com/nocobase/nocobase/pull/6595)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Block: Map]** map management validation should not pass with space input ([#6575](https://github.com/nocobase/nocobase/pull/6575)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Workflow: Approval]**
|
||||||
|
- Fix client variables to use in approval form by @mytharcher
|
||||||
|
|
||||||
|
- Fix branch mode when `endOnReject` configured as `true` by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.14](https://github.com/nocobase/nocobase/compare/v1.6.13...v1.6.14) - 2025-03-29
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[Calendar]** missing data on boundary dates in weekly calendar view ([#6587](https://github.com/nocobase/nocobase/pull/6587)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Auth: OIDC]** Incorrect redirection occurs when the callback path is the string 'null' by @2013xile
|
||||||
|
|
||||||
|
- **[Workflow: Approval]** Fix approval node configuration is incorrect after schema changed by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.13](https://github.com/nocobase/nocobase/compare/v1.6.12...v1.6.13) - 2025-03-28
|
||||||
|
|
||||||
|
### 🚀 Improvements
|
||||||
|
|
||||||
|
- **[Async task manager]** optimize import/export buttons in Pro ([#6531](https://github.com/nocobase/nocobase/pull/6531)) by @chenos
|
||||||
|
|
||||||
|
- **[Action: Export records Pro]** optimize import/export buttons in Pro by @katherinehhh
|
||||||
|
|
||||||
|
- **[Migration manager]** allow skip automatic backup and restore for migration by @gchust
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]** linkage conflict between same-named association fields in different sub-tables within the same form ([#6577](https://github.com/nocobase/nocobase/pull/6577)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Action: Batch edit]** Click the batch edit button, configure the pop-up window, and then open it again, the pop-up window is blank ([#6578](https://github.com/nocobase/nocobase/pull/6578)) by @zhangzhonghe
|
||||||
|
|
||||||
|
## [v1.6.12](https://github.com/nocobase/nocobase/compare/v1.6.11...v1.6.12) - 2025-03-27
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[Block: Multi-step form]**
|
||||||
|
- the submit button has the same color in its default and highlighted by @jiannx
|
||||||
|
|
||||||
|
- fixed the bug that form reset is invalid when the field is associated with other field by @jiannx
|
||||||
|
|
||||||
|
- **[Workflow: Approval]** Fix approval form values to submit by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.11](https://github.com/nocobase/nocobase/compare/v1.6.10...v1.6.11) - 2025-03-27
|
||||||
|
|
||||||
|
### 🚀 Improvements
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- Optimize 502 error message ([#6547](https://github.com/nocobase/nocobase/pull/6547)) by @chenos
|
||||||
|
|
||||||
|
- Only support plain text file to preview ([#6563](https://github.com/nocobase/nocobase/pull/6563)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Collection field: Sequence]** support setting sequence as the title field for calendar block ([#6562](https://github.com/nocobase/nocobase/pull/6562)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Workflow: Approval]** Support to skip validator in settings by @mytharcher
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- issue with date field display in data scope filtering ([#6564](https://github.com/nocobase/nocobase/pull/6564)) by @katherinehhh
|
||||||
|
|
||||||
|
- The 'Ellipsis overflow content' option requires a page refresh for the toggle state to take effect ([#6520](https://github.com/nocobase/nocobase/pull/6520)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- Unable to open another modal within a modal ([#6535](https://github.com/nocobase/nocobase/pull/6535)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[API documentation]** API document page cannot scroll ([#6566](https://github.com/nocobase/nocobase/pull/6566)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[Workflow]** Make sure workflow key is generated before save ([#6567](https://github.com/nocobase/nocobase/pull/6567)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Workflow: Post-action event]** Multiple records in bulk action should trigger multiple times ([#6559](https://github.com/nocobase/nocobase/pull/6559)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Authentication]** Localization issue for fields of sign up page ([#6556](https://github.com/nocobase/nocobase/pull/6556)) by @2013xile
|
||||||
|
|
||||||
|
- **[Public forms]** issue with public form page title displaying 'Loading...' ([#6569](https://github.com/nocobase/nocobase/pull/6569)) by @katherinehhh
|
||||||
|
|
||||||
|
## [v1.6.10](https://github.com/nocobase/nocobase/compare/v1.6.9...v1.6.10) - 2025-03-25
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- Unable to use 'Current User' variable when adding a link page ([#6536](https://github.com/nocobase/nocobase/pull/6536)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- field assignment with null value is ineffective ([#6549](https://github.com/nocobase/nocobase/pull/6549)) by @katherinehhh
|
||||||
|
|
||||||
|
- `yarn doc` command error ([#6540](https://github.com/nocobase/nocobase/pull/6540)) by @gchust
|
||||||
|
|
||||||
|
- Remove the 'Allow multiple selection' option from dropdown single-select fields in filter forms ([#6515](https://github.com/nocobase/nocobase/pull/6515)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- Relational field's data range linkage is not effective ([#6530](https://github.com/nocobase/nocobase/pull/6530)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[Collection: Tree]** Migration issue for plugin-collection-tree ([#6537](https://github.com/nocobase/nocobase/pull/6537)) by @2013xile
|
||||||
|
|
||||||
|
- **[Action: Custom request]** Unable to download UTF-8 encoded files ([#6541](https://github.com/nocobase/nocobase/pull/6541)) by @2013xile
|
||||||
|
|
||||||
|
## [v1.6.9](https://github.com/nocobase/nocobase/compare/v1.6.8...v1.6.9) - 2025-03-23
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]** action button transparency causing setting display issue on hover ([#6529](https://github.com/nocobase/nocobase/pull/6529)) by @katherinehhh
|
||||||
|
|
||||||
|
## [v1.6.8](https://github.com/nocobase/nocobase/compare/v1.6.7...v1.6.8) - 2025-03-22
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[server]** The upgrade command may cause workflow errors ([#6524](https://github.com/nocobase/nocobase/pull/6524)) by @gchust
|
||||||
|
|
||||||
|
- **[client]** the height of the subtable in the form is set along with the form height ([#6518](https://github.com/nocobase/nocobase/pull/6518)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Authentication]**
|
||||||
|
- X-Authenticator missing ([#6526](https://github.com/nocobase/nocobase/pull/6526)) by @chenos
|
||||||
|
|
||||||
|
- Trim authenticator options ([#6527](https://github.com/nocobase/nocobase/pull/6527)) by @2013xile
|
||||||
|
|
||||||
|
- **[Block: Map]** map block key management issue causing request failures due to invisible characters ([#6521](https://github.com/nocobase/nocobase/pull/6521)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Backup manager]** Restoration may cause workflow execution errors by @gchust
|
||||||
|
|
||||||
|
- **[WeCom]** Resolve environment variables and secrets when retrieving notification configuration. by @2013xile
|
||||||
|
|
||||||
|
## [v1.6.7](https://github.com/nocobase/nocobase/compare/v1.6.6...v1.6.7) - 2025-03-20
|
||||||
|
|
||||||
|
### 🚀 Improvements
|
||||||
|
|
||||||
|
- **[Workflow: mailer node]** Add secure field config description. ([#6510](https://github.com/nocobase/nocobase/pull/6510)) by @sheldon66
|
||||||
|
|
||||||
|
- **[Notification: Email]** Add secure field config description. ([#6501](https://github.com/nocobase/nocobase/pull/6501)) by @sheldon66
|
||||||
|
|
||||||
|
- **[Calendar]** Calendar plugin with optional settings to enable or disable quick event creation ([#6391](https://github.com/nocobase/nocobase/pull/6391)) by @Cyx649312038
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]** time field submission error in Chinese locale (invalid input syntax for type time) ([#6511](https://github.com/nocobase/nocobase/pull/6511)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[File manager]** Unable to access files stored in COS ([#6512](https://github.com/nocobase/nocobase/pull/6512)) by @chenos
|
||||||
|
|
||||||
|
- **[Block: Map]** secret key fields not triggering validation in map management ([#6509](https://github.com/nocobase/nocobase/pull/6509)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[WEB client]** The path in the route management table is different from the actual path ([#6483](https://github.com/nocobase/nocobase/pull/6483)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[Action: Export records Pro]** Unable to export attachments by @chenos
|
||||||
|
|
||||||
|
- **[Workflow: Approval]**
|
||||||
|
- Fix null user caused crash by @mytharcher
|
||||||
|
|
||||||
|
- Fix error thrown when add query node result by @mytharcher
|
||||||
|
|
||||||
## [v1.6.6](https://github.com/nocobase/nocobase/compare/v1.6.5...v1.6.6) - 2025-03-18
|
## [v1.6.6](https://github.com/nocobase/nocobase/compare/v1.6.5...v1.6.6) - 2025-03-18
|
||||||
|
|
||||||
### 🎉 New Features
|
### 🎉 New Features
|
||||||
|
@ -5,6 +5,297 @@
|
|||||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||||
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
||||||
|
|
||||||
|
## [v1.6.20](https://github.com/nocobase/nocobase/compare/v1.6.19...v1.6.20) - 2025-04-14
|
||||||
|
|
||||||
|
### 🎉 新特性
|
||||||
|
|
||||||
|
- **[部门]** 商业插件部门、附件 URL、工作流响应消息改为免费提供 ([#6663](https://github.com/nocobase/nocobase/pull/6663)) by @chenos
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 筛选表单不应该显示“未保存修改”提示 ([#6657](https://github.com/nocobase/nocobase/pull/6657)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- 筛选表单中关系字段的“允许多选”设置项不生效 ([#6661](https://github.com/nocobase/nocobase/pull/6661)) by @katherinehhh
|
||||||
|
|
||||||
|
- 筛选表单中,当点击筛选按钮时,如果有字段未校验通过,依然会触发筛选的问题 ([#6659](https://github.com/nocobase/nocobase/pull/6659)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- 切换到分组菜单时,不应该跳转到已经在菜单中被隐藏的页面 ([#6654](https://github.com/nocobase/nocobase/pull/6654)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[文件存储:S3 (Pro)]**
|
||||||
|
- 整理语言文案 by @jiannx
|
||||||
|
|
||||||
|
- baseurl 和 public 设置不再互相关联,改进 S3 pro 存储的配置交互体验 by @jiannx
|
||||||
|
|
||||||
|
- **[迁移管理]** 迁移时若弹出环境变量弹窗,跳过自动备份选项会失效 by @gchust
|
||||||
|
|
||||||
|
## [v1.6.19](https://github.com/nocobase/nocobase/compare/v1.6.18...v1.6.19) - 2025-04-14
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 修复预览图片被遮挡的问题 ([#6651](https://github.com/nocobase/nocobase/pull/6651)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- 表单区块中,字段配置的默认值会先显示为原始变量字符串然后再消失 ([#6649](https://github.com/nocobase/nocobase/pull/6649)) by @zhangzhonghe
|
||||||
|
|
||||||
|
## [v1.6.18](https://github.com/nocobase/nocobase/compare/v1.6.17...v1.6.18) - 2025-04-11
|
||||||
|
|
||||||
|
### 🚀 优化
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 为 `Variable.Input` 组件增加默认退避类型的 API ([#6644](https://github.com/nocobase/nocobase/pull/6644)) by @mytharcher
|
||||||
|
|
||||||
|
- 优化未配置页面时的提示 ([#6641](https://github.com/nocobase/nocobase/pull/6641)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[工作流:延时节点]** 支持延迟时间使用变量 ([#6621](https://github.com/nocobase/nocobase/pull/6621)) by @mytharcher
|
||||||
|
|
||||||
|
- **[工作流:自定义操作事件]** 为触发工作流按钮增加刷新配置项 by @mytharcher
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 子表格中描述信息与操作按钮遮挡 ([#6646](https://github.com/nocobase/nocobase/pull/6646)) by @katherinehhh
|
||||||
|
|
||||||
|
- 弹窗表单在 horizontal 布局下初始宽度计算错误,导致出现提示和 下划虚线 ([#6639](https://github.com/nocobase/nocobase/pull/6639)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[文件存储:S3 (Pro)]** 修复next调用缺少await by @jiannx
|
||||||
|
|
||||||
|
- **[邮件管理]** 修复next调用缺少await by @jiannx
|
||||||
|
|
||||||
|
## [v1.6.17](https://github.com/nocobase/nocobase/compare/v1.6.16...v1.6.17) - 2025-04-09
|
||||||
|
|
||||||
|
### 🚀 优化
|
||||||
|
|
||||||
|
- **[utils]** 为 dayjs 包增加时长扩展 ([#6630](https://github.com/nocobase/nocobase/pull/6630)) by @mytharcher
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 支持筛选组件中对字段进行搜索 ([#6627](https://github.com/nocobase/nocobase/pull/6627)) by @mytharcher
|
||||||
|
|
||||||
|
- 为 `Input` 和 `Variable.TextArea` 组件增加 `trim` API ([#6624](https://github.com/nocobase/nocobase/pull/6624)) by @mytharcher
|
||||||
|
|
||||||
|
- **[错误处理器]** 在 AppError 组件中支持自定义标题。 ([#6409](https://github.com/nocobase/nocobase/pull/6409)) by @sheldon66
|
||||||
|
|
||||||
|
- **[IP 限制]** 更新 IP 限制消息内容。 by @sheldon66
|
||||||
|
|
||||||
|
- **[文件存储:S3 (Pro)]** 支持存储引擎的配置中使用全局变量 by @mytharcher
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 联动规则条件设置为任意且无条件内容时属性设置不生效 ([#6628](https://github.com/nocobase/nocobase/pull/6628)) by @katherinehhh
|
||||||
|
|
||||||
|
- 树表使用甘特图区块时数据显示异常 ([#6617](https://github.com/nocobase/nocobase/pull/6617)) by @katherinehhh
|
||||||
|
|
||||||
|
- 筛选表单中的关系字段在刷新页面后,由于没有携带 x-data-source 而报错 ([#6619](https://github.com/nocobase/nocobase/pull/6619)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- 链接中中文参数变量值解析失败 ([#6618](https://github.com/nocobase/nocobase/pull/6618)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[用户]** 用户个人资料表单 schema 的解析问题 ([#6635](https://github.com/nocobase/nocobase/pull/6635)) by @2013xile
|
||||||
|
|
||||||
|
- **[移动端]** 下拉单选字段在移动端设置筛选符为包含时组件未支持多选 ([#6629](https://github.com/nocobase/nocobase/pull/6629)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[操作:导出记录]** 筛选数据后切换分页再导出时筛选参数丢失 ([#6633](https://github.com/nocobase/nocobase/pull/6633)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[邮件管理]** 邮件管理权限无法查看邮件列表 by @jiannx
|
||||||
|
|
||||||
|
- **[文件存储:S3 (Pro)]** 当用户上传 logo 失败时提示错误(设置为默认存储的 S3 Pro) by @mytharcher
|
||||||
|
|
||||||
|
- **[工作流:审批]** 修复更新时间在迁移后变化 by @mytharcher
|
||||||
|
|
||||||
|
- **[迁移管理]** 部分服务器环境下迁移日志创建日期显示不正确 by @gchust
|
||||||
|
|
||||||
|
## [v1.6.16](https://github.com/nocobase/nocobase/compare/v1.6.15...v1.6.16) - 2025-04-03
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 表单字段设置不可编辑不起作用 ([#6610](https://github.com/nocobase/nocobase/pull/6610)) by @katherinehhh
|
||||||
|
|
||||||
|
- 表单字段标题因冒号导致的截断问题 ([#6599](https://github.com/nocobase/nocobase/pull/6599)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[database]** 删除一对多记录时,同时传递 `filter` 和 `filterByTk` 参数,`filter` 包含关系字段时,`filterByTk` 参数失效 ([#6606](https://github.com/nocobase/nocobase/pull/6606)) by @2013xile
|
||||||
|
|
||||||
|
## [v1.6.15](https://github.com/nocobase/nocobase/compare/v1.6.14...v1.6.15) - 2025-04-01
|
||||||
|
|
||||||
|
### 🚀 优化
|
||||||
|
|
||||||
|
- **[database]**
|
||||||
|
- 为多行文本类型字段增加去除首尾空白字符的选项 ([#6603](https://github.com/nocobase/nocobase/pull/6603)) by @mytharcher
|
||||||
|
|
||||||
|
- 为单行文本增加自动去除首尾空白字符的选项 ([#6565](https://github.com/nocobase/nocobase/pull/6565)) by @mytharcher
|
||||||
|
|
||||||
|
- **[文件管理器]** 为存储引擎表的文本字段增加去除首尾空白字符的选项 ([#6604](https://github.com/nocobase/nocobase/pull/6604)) by @mytharcher
|
||||||
|
|
||||||
|
- **[工作流]** 优化代码 ([#6589](https://github.com/nocobase/nocobase/pull/6589)) by @mytharcher
|
||||||
|
|
||||||
|
- **[工作流:审批]** 支持审批表单使用区块模板 by @mytharcher
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[database]** 避免“日期时间(无时区)”字段在值未变动的更新时触发值改变 ([#6588](https://github.com/nocobase/nocobase/pull/6588)) by @mytharcher
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 关系字段(select)放出关系表字段时默认显示 N/A ([#6582](https://github.com/nocobase/nocobase/pull/6582)) by @katherinehhh
|
||||||
|
|
||||||
|
- 修复 `SchemaInitializerItem` 配置了 `items` 时 `disabled` 属性无效的问题 ([#6597](https://github.com/nocobase/nocobase/pull/6597)) by @mytharcher
|
||||||
|
|
||||||
|
- 级联组件删除后重新选择时出现 'The value of xxx cannot be in array format' ([#6585](https://github.com/nocobase/nocobase/pull/6585)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[数据表字段:多对多 (数组)]** 主表筛选带有多对多(数组)字段的关联表中的字段报错的问题 ([#6596](https://github.com/nocobase/nocobase/pull/6596)) by @2013xile
|
||||||
|
|
||||||
|
- **[公开表单]** 查看权限包括 list 和 get ([#6607](https://github.com/nocobase/nocobase/pull/6607)) by @chenos
|
||||||
|
|
||||||
|
- **[用户认证]** `AuthProvider` 中的 token 赋值 ([#6593](https://github.com/nocobase/nocobase/pull/6593)) by @2013xile
|
||||||
|
|
||||||
|
- **[工作流]** 修复同步选项展示问题 ([#6595](https://github.com/nocobase/nocobase/pull/6595)) by @mytharcher
|
||||||
|
|
||||||
|
- **[区块:地图]** 地图管理必填校验不应通过空格输入 ([#6575](https://github.com/nocobase/nocobase/pull/6575)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[工作流:审批]**
|
||||||
|
- 修复审批表单中的前端变量 by @mytharcher
|
||||||
|
|
||||||
|
- 修复分支模式下配置拒绝则结束时的流程问题 by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.14](https://github.com/nocobase/nocobase/compare/v1.6.13...v1.6.14) - 2025-03-29
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[日历]** 日历区块以周为视图时,边界日期不显示数据 ([#6587](https://github.com/nocobase/nocobase/pull/6587)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[认证:OIDC]** 回调路径是字符串'null'时导致跳转不正确 by @2013xile
|
||||||
|
|
||||||
|
- **[工作流:审批]** 修复审批节点界面配置变更后数据未同步的问题 by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.13](https://github.com/nocobase/nocobase/compare/v1.6.12...v1.6.13) - 2025-03-28
|
||||||
|
|
||||||
|
### 🚀 优化
|
||||||
|
|
||||||
|
- **[异步任务管理器]** 优化 Pro 导入导出按钮异步逻辑 ([#6531](https://github.com/nocobase/nocobase/pull/6531)) by @chenos
|
||||||
|
|
||||||
|
- **[操作:导出记录 Pro]** 优化 Pro 导入导出按钮 by @katherinehhh
|
||||||
|
|
||||||
|
- **[迁移管理]** 允许执行迁移时跳过自动备份还原 by @gchust
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]** 同一表单中不同关系字段的同名关系字段的联动互相影响 ([#6577](https://github.com/nocobase/nocobase/pull/6577)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[操作:批量编辑]** 点击批量编辑按钮,配置完弹窗再打开,弹窗是空白的 ([#6578](https://github.com/nocobase/nocobase/pull/6578)) by @zhangzhonghe
|
||||||
|
|
||||||
|
## [v1.6.12](https://github.com/nocobase/nocobase/compare/v1.6.11...v1.6.12) - 2025-03-27
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[区块:分步表单]**
|
||||||
|
- 提交按钮默认和高亮情况下颜色一样 by @jiannx
|
||||||
|
|
||||||
|
- 修复当字段与其他表单字段存在关联时,表单重置无效 by @jiannx
|
||||||
|
|
||||||
|
- **[工作流:审批]** 修复审批表单提交值的问题 by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.11](https://github.com/nocobase/nocobase/compare/v1.6.10...v1.6.11) - 2025-03-27
|
||||||
|
|
||||||
|
### 🚀 优化
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 优化 502 错误提示 ([#6547](https://github.com/nocobase/nocobase/pull/6547)) by @chenos
|
||||||
|
|
||||||
|
- 仅支持纯文本文件预览 ([#6563](https://github.com/nocobase/nocobase/pull/6563)) by @mytharcher
|
||||||
|
|
||||||
|
- **[数据表字段:自动编码]** 支持使用 sequence 作为日历区块的标题字段 ([#6562](https://github.com/nocobase/nocobase/pull/6562)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[工作流:审批]** 支持审批处理按钮跳过表单验证的设置 by @mytharcher
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 数据范围中筛选日期字段显示异常 ([#6564](https://github.com/nocobase/nocobase/pull/6564)) by @katherinehhh
|
||||||
|
|
||||||
|
- 选项“省略超出长度的内容”需要刷新页面,开关的状态才生效 ([#6520](https://github.com/nocobase/nocobase/pull/6520)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- 在弹窗中无法再次打开弹窗 ([#6535](https://github.com/nocobase/nocobase/pull/6535)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[API 文档]** API 文档页面不能滚动 ([#6566](https://github.com/nocobase/nocobase/pull/6566)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[工作流]** 确保创建工作流之前 key 已生成 ([#6567](https://github.com/nocobase/nocobase/pull/6567)) by @mytharcher
|
||||||
|
|
||||||
|
- **[工作流:操作后事件]** 多行记录的批量操作需要触发多次 ([#6559](https://github.com/nocobase/nocobase/pull/6559)) by @mytharcher
|
||||||
|
|
||||||
|
- **[用户认证]** 注册页面字段的本地化问题 ([#6556](https://github.com/nocobase/nocobase/pull/6556)) by @2013xile
|
||||||
|
|
||||||
|
- **[公开表单]** 公开表单页面标题不应该显示 Loading... ([#6569](https://github.com/nocobase/nocobase/pull/6569)) by @katherinehhh
|
||||||
|
|
||||||
|
## [v1.6.10](https://github.com/nocobase/nocobase/compare/v1.6.9...v1.6.10) - 2025-03-25
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 添加链接页面时,无法使用“当前用户”变量 ([#6536](https://github.com/nocobase/nocobase/pull/6536)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- 字段赋值对字段进行“空值”赋值无效 ([#6549](https://github.com/nocobase/nocobase/pull/6549)) by @katherinehhh
|
||||||
|
|
||||||
|
- `yarn doc` 命令报错 ([#6540](https://github.com/nocobase/nocobase/pull/6540)) by @gchust
|
||||||
|
|
||||||
|
- 筛选表单中,移除下拉单选字段的“允许多选”选项 ([#6515](https://github.com/nocobase/nocobase/pull/6515)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- 关系字段的数据范围联动不生效 ([#6530](https://github.com/nocobase/nocobase/pull/6530)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[数据表:树]** 树表插件的迁移脚本问题 ([#6537](https://github.com/nocobase/nocobase/pull/6537)) by @2013xile
|
||||||
|
|
||||||
|
- **[操作:自定义请求]** 无法下载utf8编码的文件 ([#6541](https://github.com/nocobase/nocobase/pull/6541)) by @2013xile
|
||||||
|
|
||||||
|
## [v1.6.9](https://github.com/nocobase/nocobase/compare/v1.6.8...v1.6.9) - 2025-03-23
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]** 操作按钮透明状态导致 hover 时按钮 setting 显示异常 ([#6529](https://github.com/nocobase/nocobase/pull/6529)) by @katherinehhh
|
||||||
|
|
||||||
|
## [v1.6.8](https://github.com/nocobase/nocobase/compare/v1.6.7...v1.6.8) - 2025-03-22
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[server]** Upgrade 命令可能造成工作流报错 ([#6524](https://github.com/nocobase/nocobase/pull/6524)) by @gchust
|
||||||
|
|
||||||
|
- **[client]** 表单中的子表格高度会随主表单高度一同设置 ([#6518](https://github.com/nocobase/nocobase/pull/6518)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[用户认证]**
|
||||||
|
- X-Authenticator 缺失 ([#6526](https://github.com/nocobase/nocobase/pull/6526)) by @chenos
|
||||||
|
|
||||||
|
- 移除认证器配置项前后的空格、换行符 ([#6527](https://github.com/nocobase/nocobase/pull/6527)) by @2013xile
|
||||||
|
|
||||||
|
- **[区块:地图]** 地图区块 密钥管理中不可见字符导致的密钥请求失败的问题 ([#6521](https://github.com/nocobase/nocobase/pull/6521)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[备份管理器]** 还原过程中可能引起工作流执行报错 by @gchust
|
||||||
|
|
||||||
|
- **[企业微信]** 获取通知配置时需要解析环境变量和密钥 by @2013xile
|
||||||
|
|
||||||
|
## [v1.6.7](https://github.com/nocobase/nocobase/compare/v1.6.6...v1.6.7) - 2025-03-20
|
||||||
|
|
||||||
|
### 🚀 优化
|
||||||
|
|
||||||
|
- **[工作流:邮件发送节点]** 增加安全字段配置描述。 ([#6510](https://github.com/nocobase/nocobase/pull/6510)) by @sheldon66
|
||||||
|
|
||||||
|
- **[通知:电子邮件]** 增加安全字段配置描述。 ([#6501](https://github.com/nocobase/nocobase/pull/6501)) by @sheldon66
|
||||||
|
|
||||||
|
- **[日历]** 日历插件添加开启或关闭快速创建事件可选设置 ([#6391](https://github.com/nocobase/nocobase/pull/6391)) by @Cyx649312038
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]** 时间字段在中文语言下提交时报错 invalid input syntax for type time ([#6511](https://github.com/nocobase/nocobase/pull/6511)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[文件管理器]** COS 存储的文件无法访问 ([#6512](https://github.com/nocobase/nocobase/pull/6512)) by @chenos
|
||||||
|
|
||||||
|
- **[区块:地图]** 地图管理中密钥必填校验失败 ([#6509](https://github.com/nocobase/nocobase/pull/6509)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[WEB 客户端]** 路由管理表格中的路径与实际路径不一样 ([#6483](https://github.com/nocobase/nocobase/pull/6483)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[操作:导出记录 Pro]** 无法导出附件 by @chenos
|
||||||
|
|
||||||
|
- **[工作流:审批]**
|
||||||
|
- 修复空用户造成页面崩溃 by @mytharcher
|
||||||
|
|
||||||
|
- 修复审批人界面配置添加查询节点时的页面崩溃 by @mytharcher
|
||||||
|
|
||||||
## [v1.6.6](https://github.com/nocobase/nocobase/compare/v1.6.5...v1.6.6) - 2025-03-18
|
## [v1.6.6](https://github.com/nocobase/nocobase/compare/v1.6.5...v1.6.6) - 2025-03-18
|
||||||
|
|
||||||
### 🎉 新特性
|
### 🎉 新特性
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Updated Date: February 20, 2025
|
Updated Date: April 1, 2025
|
||||||
|
|
||||||
NocoBase License Agreement
|
NocoBase License Agreement
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr
|
|||||||
|
|
||||||
6.6 Can sell plugins developed for Software in the Marketplace.
|
6.6 Can sell plugins developed for Software in the Marketplace.
|
||||||
|
|
||||||
6.7 The User with an Enterprise Edition License can sell Upper Layer Application to their clients.
|
6.7 The User with a Professional or Enterprise Edition License can sell Upper Layer Application to their clients.
|
||||||
|
|
||||||
6.8 Not restricted by the AGPL-3.0 agreement.
|
6.8 Not restricted by the AGPL-3.0 agreement.
|
||||||
|
|
||||||
@ -106,9 +106,9 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr
|
|||||||
|
|
||||||
7.4 It is not allowed to provide any form of no-code, zero-code, low-code platform SaaS products to the public using the original or modified Software.
|
7.4 It is not allowed to provide any form of no-code, zero-code, low-code platform SaaS products to the public using the original or modified Software.
|
||||||
|
|
||||||
7.5 It is not allowed for the User withot an Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license.
|
7.5 It is not allowed for the User withot a Professional or Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license.
|
||||||
|
|
||||||
7.6 It is not allowed for the User with an Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license with access to further development and configuration.
|
7.6 It is not allowed for the User with a Professional or Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license with access to further development and configuration.
|
||||||
|
|
||||||
7.7 It is not allowed to publicly sell plugins developed for Software outside of the Marketplace.
|
7.7 It is not allowed to publicly sell plugins developed for Software outside of the Marketplace.
|
||||||
|
|
||||||
|
@ -2,14 +2,10 @@
|
|||||||
|
|
||||||
https://github.com/user-attachments/assets/cf08bfe5-e6e6-453c-8b96-350a6a8bed17
|
https://github.com/user-attachments/assets/cf08bfe5-e6e6-453c-8b96-350a6a8bed17
|
||||||
|
|
||||||
## ご協力ありがとうございます!
|
<p align="center">
|
||||||
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
</p>
|
||||||
## リリースノート
|
|
||||||
|
|
||||||
リリースノートは[ブログ](https://www.nocobase.com/ja/blog/timeline)で随時更新され、週ごとにまとめて公開しています。
|
|
||||||
|
|
||||||
## NocoBaseはなに?
|
## NocoBaseはなに?
|
||||||
|
|
||||||
@ -28,6 +24,16 @@ https://docs-cn.nocobase.com/
|
|||||||
コミュニティ:
|
コミュニティ:
|
||||||
https://forum.nocobase.com/
|
https://forum.nocobase.com/
|
||||||
|
|
||||||
|
チュートリアル:
|
||||||
|
https://www.nocobase.com/ja/tutorials
|
||||||
|
|
||||||
|
顧客のストーリー:
|
||||||
|
https://www.nocobase.com/ja/blog/tags/customer-stories
|
||||||
|
|
||||||
|
## リリースノート
|
||||||
|
|
||||||
|
リリースノートは[ブログ](https://www.nocobase.com/ja/blog/timeline)で随時更新され、週ごとにまとめて公開しています。
|
||||||
|
|
||||||
## 他の製品との違い
|
## 他の製品との違い
|
||||||
|
|
||||||
### 1. データモデル駆動
|
### 1. データモデル駆動
|
||||||
|
22
README.md
22
README.md
@ -2,19 +2,14 @@ English | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md)
|
|||||||
|
|
||||||
https://github.com/user-attachments/assets/a50c100a-4561-4e06-b2d2-d48098659ec0
|
https://github.com/user-attachments/assets/a50c100a-4561-4e06-b2d2-d48098659ec0
|
||||||
|
|
||||||
## We'd love your support!
|
<p align="center">
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
</p>
|
||||||
## Release Notes
|
|
||||||
|
|
||||||
Our [blog](https://www.nocobase.com/en/blog/timeline) is regularly updated with release notes and provides a weekly summary.
|
|
||||||
|
|
||||||
## What is NocoBase
|
## What is NocoBase
|
||||||
|
|
||||||
NocoBase is a scalability-first, open-source no-code development platform.
|
NocoBase is an extensibility-first, open-source no-code development platform.
|
||||||
Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform!
|
Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform!
|
||||||
|
|
||||||
Homepage:
|
Homepage:
|
||||||
@ -29,6 +24,17 @@ https://docs.nocobase.com/
|
|||||||
Forum:
|
Forum:
|
||||||
https://forum.nocobase.com/
|
https://forum.nocobase.com/
|
||||||
|
|
||||||
|
Tutorials:
|
||||||
|
https://www.nocobase.com/en/tutorials
|
||||||
|
|
||||||
|
Use Cases:
|
||||||
|
https://www.nocobase.com/en/blog/tags/customer-stories
|
||||||
|
|
||||||
|
|
||||||
|
## Release Notes
|
||||||
|
|
||||||
|
Our [blog](https://www.nocobase.com/en/blog/timeline) is regularly updated with release notes and provides a weekly summary.
|
||||||
|
|
||||||
## Distinctive features
|
## Distinctive features
|
||||||
|
|
||||||
### 1. Data model-driven
|
### 1. Data model-driven
|
||||||
|
@ -2,13 +2,10 @@
|
|||||||
|
|
||||||
https://github.com/nocobase/nocobase/assets/1267426/29623e45-9a48-4598-bb9e-9dd173ade553
|
https://github.com/nocobase/nocobase/assets/1267426/29623e45-9a48-4598-bb9e-9dd173ade553
|
||||||
|
|
||||||
## 感谢支持
|
<p align="center">
|
||||||
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
</p>
|
||||||
## 发布日志
|
|
||||||
我们的[博客](https://www.nocobase.com/cn/blog/timeline)会及时更新发布日志,并每周进行汇总。
|
|
||||||
|
|
||||||
## NocoBase 是什么
|
## NocoBase 是什么
|
||||||
|
|
||||||
@ -27,6 +24,15 @@ https://docs-cn.nocobase.com/
|
|||||||
社区:
|
社区:
|
||||||
https://forum.nocobase.com/
|
https://forum.nocobase.com/
|
||||||
|
|
||||||
|
教程:
|
||||||
|
https://www.nocobase.com/cn/tutorials
|
||||||
|
|
||||||
|
用户故事:
|
||||||
|
https://www.nocobase.com/cn/blog/tags/customer-stories
|
||||||
|
|
||||||
|
## 发布日志
|
||||||
|
我们的[博客](https://www.nocobase.com/cn/blog/timeline)会及时更新发布日志,并每周进行汇总。
|
||||||
|
|
||||||
## 与众不同之处
|
## 与众不同之处
|
||||||
|
|
||||||
### 1. 数据模型驱动
|
### 1. 数据模型驱动
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.6.6",
|
"version": "1.6.20",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"npmClientArgs": ["--ignore-engines"],
|
"npmClientArgs": ["--ignore-engines"],
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/acl",
|
"name": "@nocobase/acl",
|
||||||
"version": "1.6.6",
|
"version": "1.6.20",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/resourcer": "1.6.6",
|
"@nocobase/resourcer": "1.6.20",
|
||||||
"@nocobase/utils": "1.6.6",
|
"@nocobase/utils": "1.6.20",
|
||||||
"minimatch": "^5.1.1"
|
"minimatch": "^5.1.1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/actions",
|
"name": "@nocobase/actions",
|
||||||
"version": "1.6.6",
|
"version": "1.6.20",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/cache": "1.6.6",
|
"@nocobase/cache": "1.6.20",
|
||||||
"@nocobase/database": "1.6.6",
|
"@nocobase/database": "1.6.20",
|
||||||
"@nocobase/resourcer": "1.6.6"
|
"@nocobase/resourcer": "1.6.20"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/app",
|
"name": "@nocobase/app",
|
||||||
"version": "1.6.6",
|
"version": "1.6.20",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/database": "1.6.6",
|
"@nocobase/database": "1.6.20",
|
||||||
"@nocobase/preset-nocobase": "1.6.6",
|
"@nocobase/preset-nocobase": "1.6.20",
|
||||||
"@nocobase/server": "1.6.6"
|
"@nocobase/server": "1.6.20"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nocobase/client": "1.6.6"
|
"@nocobase/client": "1.6.20"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mockDatabase } from '@nocobase/database';
|
import { createMockDatabase, mockDatabase } from '@nocobase/database';
|
||||||
import { uid } from '@nocobase/utils';
|
import { uid } from '@nocobase/utils';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import execa from 'execa';
|
import execa from 'execa';
|
||||||
@ -64,7 +64,7 @@ const createDatabase = async () => {
|
|||||||
if (process.env.DB_DIALECT === 'sqlite') {
|
if (process.env.DB_DIALECT === 'sqlite') {
|
||||||
return 'nocobase';
|
return 'nocobase';
|
||||||
}
|
}
|
||||||
const db = mockDatabase();
|
const db = await createMockDatabase();
|
||||||
const name = `d_${uid()}`;
|
const name = `d_${uid()}`;
|
||||||
await db.sequelize.query(`CREATE DATABASE ${name}`);
|
await db.sequelize.query(`CREATE DATABASE ${name}`);
|
||||||
await db.close();
|
await db.close();
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/auth",
|
"name": "@nocobase/auth",
|
||||||
"version": "1.6.6",
|
"version": "1.6.20",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/actions": "1.6.6",
|
"@nocobase/actions": "1.6.20",
|
||||||
"@nocobase/cache": "1.6.6",
|
"@nocobase/cache": "1.6.20",
|
||||||
"@nocobase/database": "1.6.6",
|
"@nocobase/database": "1.6.20",
|
||||||
"@nocobase/resourcer": "1.6.6",
|
"@nocobase/resourcer": "1.6.20",
|
||||||
"@nocobase/utils": "1.6.6",
|
"@nocobase/utils": "1.6.20",
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^8.5.8",
|
||||||
"jsonwebtoken": "^8.5.1"
|
"jsonwebtoken": "^8.5.1"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/build",
|
"name": "@nocobase/build",
|
||||||
"version": "1.6.6",
|
"version": "1.6.20",
|
||||||
"description": "Library build tool based on rollup.",
|
"description": "Library build tool based on rollup.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
@ -17,7 +17,7 @@
|
|||||||
"@lerna/project": "4.0.0",
|
"@lerna/project": "4.0.0",
|
||||||
"@rsbuild/plugin-babel": "^1.0.3",
|
"@rsbuild/plugin-babel": "^1.0.3",
|
||||||
"@rsdoctor/rspack-plugin": "^0.4.8",
|
"@rsdoctor/rspack-plugin": "^0.4.8",
|
||||||
"@rspack/core": "1.1.1",
|
"@rspack/core": "1.3.2",
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
"@types/gulp": "^4.0.13",
|
"@types/gulp": "^4.0.13",
|
||||||
"@types/lerna__package": "5.1.0",
|
"@types/lerna__package": "5.1.0",
|
||||||
|
@ -347,6 +347,7 @@ export async function buildPluginClient(cwd: string, userConfig: UserConfig, sou
|
|||||||
umdNamedDefine: true,
|
umdNamedDefine: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
amd: {},
|
||||||
resolve: {
|
resolve: {
|
||||||
tsConfig: path.join(process.cwd(), 'tsconfig.json'),
|
tsConfig: path.join(process.cwd(), 'tsconfig.json'),
|
||||||
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.less', '.css'],
|
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.less', '.css'],
|
||||||
|
2
packages/core/cache/package.json
vendored
2
packages/core/cache/package.json
vendored
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/cache",
|
"name": "@nocobase/cache",
|
||||||
"version": "1.6.6",
|
"version": "1.6.20",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/cli",
|
"name": "@nocobase/cli",
|
||||||
"version": "1.6.6",
|
"version": "1.6.20",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./src/index.js",
|
"main": "./src/index.js",
|
||||||
@ -8,7 +8,7 @@
|
|||||||
"nocobase": "./bin/index.js"
|
"nocobase": "./bin/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/app": "1.6.6",
|
"@nocobase/app": "1.6.20",
|
||||||
"@types/fs-extra": "^11.0.1",
|
"@types/fs-extra": "^11.0.1",
|
||||||
"@umijs/utils": "3.5.20",
|
"@umijs/utils": "3.5.20",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
@ -25,7 +25,7 @@
|
|||||||
"tsx": "^4.19.0"
|
"tsx": "^4.19.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nocobase/devtools": "1.6.6"
|
"@nocobase/devtools": "1.6.20"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -460,6 +460,16 @@ exports.initEnv = function initEnv() {
|
|||||||
process.env.SOCKET_PATH = generateGatewayPath();
|
process.env.SOCKET_PATH = generateGatewayPath();
|
||||||
fs.mkdirpSync(dirname(process.env.SOCKET_PATH), { force: true, recursive: true });
|
fs.mkdirpSync(dirname(process.env.SOCKET_PATH), { force: true, recursive: true });
|
||||||
fs.mkdirpSync(process.env.PM2_HOME, { force: true, recursive: true });
|
fs.mkdirpSync(process.env.PM2_HOME, { force: true, recursive: true });
|
||||||
|
const pkgs = [
|
||||||
|
'@nocobase/plugin-multi-app-manager',
|
||||||
|
'@nocobase/plugin-departments',
|
||||||
|
'@nocobase/plugin-field-attachment-url',
|
||||||
|
'@nocobase/plugin-workflow-response-message',
|
||||||
|
];
|
||||||
|
for (const pkg of pkgs) {
|
||||||
|
const pkgDir = resolve(process.cwd(), 'storage/plugins', pkg);
|
||||||
|
fs.existsSync(pkgDir) && fs.rmdirSync(pkgDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.generatePlugins = function () {
|
exports.generatePlugins = function () {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/client",
|
"name": "@nocobase/client",
|
||||||
"version": "1.6.6",
|
"version": "1.6.20",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "es/index.mjs",
|
"module": "es/index.mjs",
|
||||||
@ -27,9 +27,9 @@
|
|||||||
"@formily/reactive-react": "^2.2.27",
|
"@formily/reactive-react": "^2.2.27",
|
||||||
"@formily/shared": "^2.2.27",
|
"@formily/shared": "^2.2.27",
|
||||||
"@formily/validator": "^2.2.27",
|
"@formily/validator": "^2.2.27",
|
||||||
"@nocobase/evaluators": "1.6.6",
|
"@nocobase/evaluators": "1.6.20",
|
||||||
"@nocobase/sdk": "1.6.6",
|
"@nocobase/sdk": "1.6.20",
|
||||||
"@nocobase/utils": "1.6.6",
|
"@nocobase/utils": "1.6.20",
|
||||||
"ahooks": "^3.7.2",
|
"ahooks": "^3.7.2",
|
||||||
"antd": "5.12.8",
|
"antd": "5.12.8",
|
||||||
"antd-style": "3.7.1",
|
"antd-style": "3.7.1",
|
||||||
|
@ -74,6 +74,7 @@ export const ACLRolesCheckProvider = (props) => {
|
|||||||
url: 'roles:check',
|
url: 'roles:check',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
manual: !api.auth.token,
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
if (!data?.data?.snippets.includes('ui.*')) {
|
if (!data?.data?.snippets.includes('ui.*')) {
|
||||||
setDesignable(false);
|
setDesignable(false);
|
||||||
|
@ -139,7 +139,19 @@ export class APIClient extends APIClientSDK {
|
|||||||
if (typeof error?.response?.data === 'string') {
|
if (typeof error?.response?.data === 'string') {
|
||||||
const tempElement = document.createElement('div');
|
const tempElement = document.createElement('div');
|
||||||
tempElement.innerHTML = error?.response?.data;
|
tempElement.innerHTML = error?.response?.data;
|
||||||
return [{ message: tempElement.textContent || tempElement.innerText }];
|
let message = tempElement.textContent || tempElement.innerText;
|
||||||
|
if (message.includes('Error occurred while trying')) {
|
||||||
|
message = 'The application may be starting up. Please try again later.';
|
||||||
|
return [{ code: 'APP_WARNING', message }];
|
||||||
|
}
|
||||||
|
if (message.includes('502 Bad Gateway')) {
|
||||||
|
message = 'The application may be starting up. Please try again later.';
|
||||||
|
return [{ code: 'APP_WARNING', message }];
|
||||||
|
}
|
||||||
|
return [{ message }];
|
||||||
|
}
|
||||||
|
if (error?.response?.data?.error) {
|
||||||
|
return [error?.response?.data?.error];
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
error?.response?.data?.errors ||
|
error?.response?.data?.errors ||
|
||||||
|
@ -350,23 +350,9 @@ export class Application {
|
|||||||
setTimeout(() => resolve(null), 1000);
|
setTimeout(() => resolve(null), 1000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const toError = (error) => {
|
|
||||||
if (typeof error?.response?.data === 'string') {
|
|
||||||
const tempElement = document.createElement('div');
|
|
||||||
tempElement.innerHTML = error?.response?.data;
|
|
||||||
return { message: tempElement.textContent || tempElement.innerText };
|
|
||||||
}
|
|
||||||
if (error?.response?.data?.error) {
|
|
||||||
return error?.response?.data?.error;
|
|
||||||
}
|
|
||||||
if (error?.response?.data?.errors?.[0]) {
|
|
||||||
return error?.response?.data?.errors?.[0];
|
|
||||||
}
|
|
||||||
return { message: error?.message };
|
|
||||||
};
|
|
||||||
this.error = {
|
this.error = {
|
||||||
code: 'LOAD_ERROR',
|
code: 'LOAD_ERROR',
|
||||||
...toError(error),
|
...this.apiClient.toErrMessages(error)?.[0],
|
||||||
};
|
};
|
||||||
console.error(error, this.error);
|
console.error(error, this.error);
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,11 @@ import React, { FC } from 'react';
|
|||||||
import { MainComponent } from './MainComponent';
|
import { MainComponent } from './MainComponent';
|
||||||
|
|
||||||
const Loading: FC = () => <div>Loading...</div>;
|
const Loading: FC = () => <div>Loading...</div>;
|
||||||
const AppError: FC<{ error: Error }> = ({ error }) => {
|
const AppError: FC<{ error: Error & { title?: string } }> = ({ error }) => {
|
||||||
|
const title = error?.title || 'App Error';
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>App Error</div>
|
<div>{title}</div>
|
||||||
{error?.message}
|
{error?.message}
|
||||||
{process.env.__TEST__ && error?.stack}
|
{process.env.__TEST__ && error?.stack}
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,7 +63,7 @@ export const SchemaInitializerItem = memo(
|
|||||||
className: className,
|
className: className,
|
||||||
label: children || compile(title),
|
label: children || compile(title),
|
||||||
onClick: (info) => {
|
onClick: (info) => {
|
||||||
if (info.key !== name) return;
|
if (disabled || info.key !== name) return;
|
||||||
if (closeInitializerMenuWhenClick) {
|
if (closeInitializerMenuWhenClick) {
|
||||||
setVisible?.(false);
|
setVisible?.(false);
|
||||||
}
|
}
|
||||||
@ -73,10 +73,10 @@ export const SchemaInitializerItem = memo(
|
|||||||
children: childrenItems,
|
children: childrenItems,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [name, style, className, children, title, onClick, icon, childrenItems]);
|
}, [name, disabled, style, className, children, title, onClick, icon, childrenItems]);
|
||||||
|
|
||||||
if (items && items.length > 0) {
|
if (items && items.length > 0) {
|
||||||
return <SchemaInitializerMenu items={menuItems}></SchemaInitializerMenu>;
|
return <SchemaInitializerMenu disabled={disabled} items={menuItems}></SchemaInitializerMenu>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -10,11 +10,11 @@
|
|||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { withDynamicSchemaProps } from '../hoc/withDynamicSchemaProps';
|
import { withDynamicSchemaProps } from '../hoc/withDynamicSchemaProps';
|
||||||
import { DatePickerProvider, ActionBarProvider, SchemaComponentOptions } from '../schema-component';
|
import { FilterCollectionField } from '../modules/blocks/filter-blocks/FilterCollectionField';
|
||||||
|
import { ActionBarProvider, DatePickerProvider, SchemaComponentOptions } from '../schema-component';
|
||||||
import { DefaultValueProvider } from '../schema-settings';
|
import { DefaultValueProvider } from '../schema-settings';
|
||||||
import { CollectOperators } from './CollectOperators';
|
import { CollectOperators } from './CollectOperators';
|
||||||
import { FormBlockProvider } from './FormBlockProvider';
|
import { FormBlockProvider } from './FormBlockProvider';
|
||||||
import { FilterCollectionField } from '../modules/blocks/filter-blocks/FilterCollectionField';
|
|
||||||
|
|
||||||
export const FilterFormBlockProvider = withDynamicSchemaProps((props) => {
|
export const FilterFormBlockProvider = withDynamicSchemaProps((props) => {
|
||||||
const filedSchema = useFieldSchema();
|
const filedSchema = useFieldSchema();
|
||||||
@ -35,7 +35,7 @@ export const FilterFormBlockProvider = withDynamicSchemaProps((props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DefaultValueProvider isAllowToSetDefaultValue={() => false}>
|
<DefaultValueProvider isAllowToSetDefaultValue={() => false}>
|
||||||
<FormBlockProvider name="filter-form" {...props}></FormBlockProvider>
|
<FormBlockProvider name="filter-form" {...props} confirmBeforeClose={false}></FormBlockProvider>
|
||||||
</DefaultValueProvider>
|
</DefaultValueProvider>
|
||||||
</ActionBarProvider>
|
</ActionBarProvider>
|
||||||
</DatePickerProvider>
|
</DatePickerProvider>
|
||||||
|
@ -167,7 +167,7 @@ export function useCollectValuesToSubmit() {
|
|||||||
if (parsedValue !== null && parsedValue !== undefined) {
|
if (parsedValue !== null && parsedValue !== undefined) {
|
||||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||||
}
|
}
|
||||||
} else if (value != null && value !== '') {
|
} else if (value !== '') {
|
||||||
assignedValues[key] = value;
|
assignedValues[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -338,7 +338,7 @@ export const useAssociationCreateActionProps = () => {
|
|||||||
if (parsedValue) {
|
if (parsedValue) {
|
||||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||||
}
|
}
|
||||||
} else if (value != null && value !== '') {
|
} else if (value !== '') {
|
||||||
assignedValues[key] = value;
|
assignedValues[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -522,9 +522,11 @@ export const useFilterBlockActionProps = () => {
|
|||||||
const { doFilter } = useDoFilter();
|
const { doFilter } = useDoFilter();
|
||||||
const actionField = useField();
|
const actionField = useField();
|
||||||
actionField.data = actionField.data || {};
|
actionField.data = actionField.data || {};
|
||||||
|
const form = useForm();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async onClick() {
|
async onClick() {
|
||||||
|
await form.submit();
|
||||||
actionField.data.loading = true;
|
actionField.data.loading = true;
|
||||||
await doFilter();
|
await doFilter();
|
||||||
actionField.data.loading = false;
|
actionField.data.loading = false;
|
||||||
@ -605,7 +607,7 @@ export const useCustomizeUpdateActionProps = () => {
|
|||||||
if (parsedValue) {
|
if (parsedValue) {
|
||||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||||
}
|
}
|
||||||
} else if (value != null && value !== '') {
|
} else if (value !== '') {
|
||||||
assignedValues[key] = value;
|
assignedValues[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -708,7 +710,7 @@ export const useCustomizeBulkUpdateActionProps = () => {
|
|||||||
if (parsedValue) {
|
if (parsedValue) {
|
||||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||||
}
|
}
|
||||||
} else if (value != null && value !== '') {
|
} else if (value !== '') {
|
||||||
assignedValues[key] = value;
|
assignedValues[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -930,7 +932,7 @@ export const useUpdateActionProps = () => {
|
|||||||
if (parsedValue) {
|
if (parsedValue) {
|
||||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||||
}
|
}
|
||||||
} else if (value != null && value !== '') {
|
} else if (value !== '') {
|
||||||
assignedValues[key] = value;
|
assignedValues[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -62,6 +62,12 @@ export class InputFieldInterface extends CollectionFieldInterface {
|
|||||||
hasDefaultValue = true;
|
hasDefaultValue = true;
|
||||||
properties = {
|
properties = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
|
trim: {
|
||||||
|
type: 'boolean',
|
||||||
|
'x-content': '{{t("Automatically remove heading and tailing spaces")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
},
|
||||||
layout: {
|
layout: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: '{{t("Index")}}',
|
title: '{{t("Index")}}',
|
||||||
|
@ -129,12 +129,12 @@ export const enumType = [
|
|||||||
label: '{{t("is")}}',
|
label: '{{t("is")}}',
|
||||||
value: '$eq',
|
value: '$eq',
|
||||||
selected: true,
|
selected: true,
|
||||||
schema: { 'x-component': 'Select' },
|
schema: { 'x-component': 'Select', 'x-component-props': { mode: null } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '{{t("is not")}}',
|
label: '{{t("is not")}}',
|
||||||
value: '$ne',
|
value: '$ne',
|
||||||
schema: { 'x-component': 'Select' },
|
schema: { 'x-component': 'Select', 'x-component-props': { mode: null } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '{{t("is any of")}}',
|
label: '{{t("is any of")}}',
|
||||||
|
@ -31,6 +31,12 @@ export class TextareaFieldInterface extends CollectionFieldInterface {
|
|||||||
titleUsable = true;
|
titleUsable = true;
|
||||||
properties = {
|
properties = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
|
trim: {
|
||||||
|
type: 'boolean',
|
||||||
|
'x-content': '{{t("Automatically remove heading and tailing spaces")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
schemaInitialize(schema: ISchema, { block }) {
|
schemaInitialize(schema: ISchema, { block }) {
|
||||||
if (['Table', 'Kanban'].includes(block)) {
|
if (['Table', 'Kanban'].includes(block)) {
|
||||||
|
@ -18,7 +18,14 @@ export interface SelectWithTitleProps {
|
|||||||
onChange?: (...args: any[]) => void;
|
onChange?: (...args: any[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectWithTitle({ title, defaultValue, onChange, options, fieldNames }: SelectWithTitleProps) {
|
export function SelectWithTitle({
|
||||||
|
title,
|
||||||
|
defaultValue,
|
||||||
|
onChange,
|
||||||
|
options,
|
||||||
|
fieldNames,
|
||||||
|
...others
|
||||||
|
}: SelectWithTitleProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const timerRef = useRef<any>(null);
|
const timerRef = useRef<any>(null);
|
||||||
return (
|
return (
|
||||||
@ -36,6 +43,7 @@ export function SelectWithTitle({ title, defaultValue, onChange, options, fieldN
|
|||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
<Select
|
<Select
|
||||||
|
{...others}
|
||||||
open={open}
|
open={open}
|
||||||
data-testid={`select-${title}`}
|
data-testid={`select-${title}`}
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
|
@ -18,6 +18,7 @@ import { useCollectionFieldUISchema, useIsInNocoBaseRecursionFieldContext } from
|
|||||||
import { useDynamicComponentProps } from '../../hoc/withDynamicSchemaProps';
|
import { useDynamicComponentProps } from '../../hoc/withDynamicSchemaProps';
|
||||||
import { useCompile, useComponent } from '../../schema-component';
|
import { useCompile, useComponent } from '../../schema-component';
|
||||||
import { useIsAllowToSetDefaultValue } from '../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
import { useIsAllowToSetDefaultValue } from '../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||||
|
import { isVariable } from '../../variables/utils/isVariable';
|
||||||
import { CollectionFieldProvider, useCollectionField } from './CollectionFieldProvider';
|
import { CollectionFieldProvider, useCollectionField } from './CollectionFieldProvider';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -102,17 +103,43 @@ const CollectionFieldInternalField = (props) => {
|
|||||||
const dynamicProps = useDynamicComponentProps(uiSchema?.['x-use-component-props'], props);
|
const dynamicProps = useDynamicComponentProps(uiSchema?.['x-use-component-props'], props);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// There seems to be a bug in formily where after setting a field to readPretty, switching to editable,
|
/**
|
||||||
// then back to readPretty, and refreshing the page, the field remains in editable state. The expected state is readPretty.
|
* There seems to be a bug in formily where after setting a field to readPretty, switching to editable,
|
||||||
// This code is meant to fix this issue.
|
* then back to readPretty, and refreshing the page, the field remains in editable state. The expected state is readPretty.
|
||||||
|
* This code is meant to fix this issue.
|
||||||
|
*/
|
||||||
if (fieldSchema['x-read-pretty'] === true && !field.readPretty) {
|
if (fieldSchema['x-read-pretty'] === true && !field.readPretty) {
|
||||||
field.readPretty = true;
|
field.readPretty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This solves the issue: After creating a form and setting a field to "read-only", the field remains editable when refreshing the page and reopening the dialog.
|
||||||
|
*
|
||||||
|
* Note: This might be a bug in Formily
|
||||||
|
* When both x-disabled and x-read-pretty exist in the Schema:
|
||||||
|
* - If x-disabled appears before x-read-pretty in the Schema JSON, the disabled state becomes ineffective
|
||||||
|
* - The reason is that during field instance initialization, field.disabled is set before field.readPretty, which causes the pattern value to be changed to 'editable'
|
||||||
|
* - This issue is related to the order of JSON fields, which might return different orders in different environments (databases), thus making the issue inconsistent to reproduce
|
||||||
|
*
|
||||||
|
* Reference to Formily source code:
|
||||||
|
* 1. Setting readPretty may cause pattern to be changed to 'editable': https://github.com/alibaba/formily/blob/d4bb96c40e7918210b1bd7d57b8fadee0cfe4b26/packages/core/src/models/BaseField.ts#L208-L224
|
||||||
|
* 2. The execution order of the each method depends on the order of JSON fields: https://github.com/alibaba/formily/blob/123d536b6076196e00b4e02ee160d72480359f54/packages/json-schema/src/schema.ts#L486-L519
|
||||||
|
*/
|
||||||
|
if (fieldSchema['x-disabled'] === true) {
|
||||||
|
field.disabled = true;
|
||||||
|
}
|
||||||
}, [field, fieldSchema]);
|
}, [field, fieldSchema]);
|
||||||
|
|
||||||
if (!uiSchema) return null;
|
if (!uiSchema) return null;
|
||||||
|
|
||||||
return <Component {...props} {...dynamicProps} />;
|
const mergedProps = { ...props, ...dynamicProps };
|
||||||
|
|
||||||
|
// Prevent displaying the variable string first, then the variable value
|
||||||
|
if (isVariable(mergedProps.value) && mergedProps.value === fieldSchema.default) {
|
||||||
|
mergedProps.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Component {...mergedProps} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CollectionField = connect((props) => {
|
export const CollectionField = connect((props) => {
|
||||||
|
@ -48,6 +48,7 @@ interface INocoBaseRecursionFieldProps extends IRecursionFieldProps {
|
|||||||
* Whether to use Formily Field class - performance will be reduced but provides better compatibility with Formily
|
* Whether to use Formily Field class - performance will be reduced but provides better compatibility with Formily
|
||||||
*/
|
*/
|
||||||
isUseFormilyField?: boolean;
|
isUseFormilyField?: boolean;
|
||||||
|
parentSchema?: Schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollectionFieldUISchemaContext = React.createContext<CollectionFieldOptions>({});
|
const CollectionFieldUISchemaContext = React.createContext<CollectionFieldOptions>({});
|
||||||
@ -266,6 +267,7 @@ export const NocoBaseRecursionField: ReactFC<INocoBaseRecursionFieldProps> = Rea
|
|||||||
values,
|
values,
|
||||||
isUseFormilyField = true,
|
isUseFormilyField = true,
|
||||||
uiSchema,
|
uiSchema,
|
||||||
|
parentSchema,
|
||||||
} = props;
|
} = props;
|
||||||
const basePath = useBasePath(props);
|
const basePath = useBasePath(props);
|
||||||
const newFieldSchemaRef = useRef(null);
|
const newFieldSchemaRef = useRef(null);
|
||||||
@ -279,6 +281,14 @@ export const NocoBaseRecursionField: ReactFC<INocoBaseRecursionFieldProps> = Rea
|
|||||||
|
|
||||||
const fieldSchema: Schema = newFieldSchemaRef.current || oldFieldSchema;
|
const fieldSchema: Schema = newFieldSchemaRef.current || oldFieldSchema;
|
||||||
|
|
||||||
|
// Establish connection with the Schema tree
|
||||||
|
if (!fieldSchema.parent && parentSchema) {
|
||||||
|
fieldSchema.parent = parentSchema;
|
||||||
|
if (!fieldSchema.parent?.properties?.[fieldSchema.name] && fieldSchema.name) {
|
||||||
|
_.set(fieldSchema.parent, `properties.${fieldSchema.name}`, fieldSchema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const refresh = useCallback(() => {
|
const refresh = useCallback(() => {
|
||||||
const parent = fieldSchema.parent;
|
const parent = fieldSchema.parent;
|
||||||
newFieldSchemaRef.current = new Schema(fieldSchema.toJSON(), parent);
|
newFieldSchemaRef.current = new Schema(fieldSchema.toJSON(), parent);
|
||||||
|
@ -884,5 +884,7 @@
|
|||||||
"If selected, the page will display Tab pages.": "Wenn ausgewählt, zeigt die Seite Tab-Seiten an.",
|
"If selected, the page will display Tab pages.": "Wenn ausgewählt, zeigt die Seite Tab-Seiten an.",
|
||||||
"If selected, the route will be displayed in the menu.": "Wenn ausgewählt, wird die Route im Menü angezeigt.",
|
"If selected, the route will be displayed in the menu.": "Wenn ausgewählt, wird die Route im Menü angezeigt.",
|
||||||
"Are you sure you want to hide this tab?": "Sind Sie sicher, dass Sie diesen Tab ausblenden möchten?",
|
"Are you sure you want to hide this tab?": "Sind Sie sicher, dass Sie diesen Tab ausblenden möchten?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Nach dem Ausblenden wird dieser Tab nicht mehr in der Tableiste angezeigt. Um ihn wieder anzuzeigen, müssen Sie zur Routenverwaltungsseite gehen, um ihn einzustellen."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Nach dem Ausblenden wird dieser Tab nicht mehr in der Tableiste angezeigt. Um ihn wieder anzuzeigen, müssen Sie zur Routenverwaltungsseite gehen, um ihn einzustellen.",
|
||||||
|
"No pages yet, please configure first": "Noch keine Seiten, bitte zuerst konfigurieren",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Klicken Sie auf das \"UI-Editor\"-Symbol in der oberen rechten Ecke, um den UI-Editor-Modus zu betreten"
|
||||||
}
|
}
|
||||||
|
@ -884,5 +884,7 @@
|
|||||||
"If selected, the page will display Tab pages.": "If selected, the page will display Tab pages.",
|
"If selected, the page will display Tab pages.": "If selected, the page will display Tab pages.",
|
||||||
"If selected, the route will be displayed in the menu.": "If selected, the route will be displayed in the menu.",
|
"If selected, the route will be displayed in the menu.": "If selected, the route will be displayed in the menu.",
|
||||||
"Are you sure you want to hide this tab?": "Are you sure you want to hide this tab?",
|
"Are you sure you want to hide this tab?": "Are you sure you want to hide this tab?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.",
|
||||||
|
"No pages yet, please configure first": "No pages yet, please configure first",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode"
|
||||||
}
|
}
|
||||||
|
@ -801,5 +801,7 @@
|
|||||||
"If selected, the page will display Tab pages.": "Si se selecciona, la página mostrará páginas de pestañas.",
|
"If selected, the page will display Tab pages.": "Si se selecciona, la página mostrará páginas de pestañas.",
|
||||||
"If selected, the route will be displayed in the menu.": "Si se selecciona, la ruta se mostrará en el menú.",
|
"If selected, the route will be displayed in the menu.": "Si se selecciona, la ruta se mostrará en el menú.",
|
||||||
"Are you sure you want to hide this tab?": "¿Estás seguro de que quieres ocultar esta pestaña?",
|
"Are you sure you want to hide this tab?": "¿Estás seguro de que quieres ocultar esta pestaña?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Después de ocultar, esta pestaña ya no aparecerá en la barra de pestañas. Para mostrarla de nuevo, deberás ir a la página de gestión de rutas para configurarla."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Después de ocultar, esta pestaña ya no aparecerá en la barra de pestañas. Para mostrarla de nuevo, deberás ir a la página de gestión de rutas para configurarla.",
|
||||||
|
"No pages yet, please configure first": "Aún no hay páginas, por favor configura primero",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Haga clic en el icono \"Editor de UI\" en la esquina superior derecha para entrar en el modo de Editor de UI."
|
||||||
}
|
}
|
||||||
|
@ -821,5 +821,7 @@
|
|||||||
"If selected, the page will display Tab pages.": "Si sélectionné, la page affichera des onglets.",
|
"If selected, the page will display Tab pages.": "Si sélectionné, la page affichera des onglets.",
|
||||||
"If selected, the route will be displayed in the menu.": "Si sélectionné, la route sera affichée dans le menu.",
|
"If selected, the route will be displayed in the menu.": "Si sélectionné, la route sera affichée dans le menu.",
|
||||||
"Are you sure you want to hide this tab?": "Êtes-vous sûr de vouloir masquer cet onglet ?",
|
"Are you sure you want to hide this tab?": "Êtes-vous sûr de vouloir masquer cet onglet ?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Après avoir masqué, cette tab ne sera plus affichée dans la barre de tab. Pour la montrer à nouveau, vous devez vous rendre sur la page de gestion des routes pour la configurer."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Après avoir masqué, cette tab ne sera plus affichée dans la barre de tab. Pour la montrer à nouveau, vous devez vous rendre sur la page de gestion des routes pour la configurer.",
|
||||||
|
"No pages yet, please configure first": "Pas encore de pages, veuillez configurer d'abord",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur"
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
"Insert inner": "Inserire dentro",
|
"Insert inner": "Inserire dentro",
|
||||||
"Delete": "Eliminare",
|
"Delete": "Eliminare",
|
||||||
"Disassociate": "Dissociare",
|
"Disassociate": "Dissociare",
|
||||||
"Disassociate record": "Dissociare il record",
|
"Disassociate record": "Dissociare record",
|
||||||
"Are you sure you want to disassociate it?": "Sei sicuro di voler dissociare?",
|
"Are you sure you want to disassociate it?": "Sei sicuro di voler dissociare?",
|
||||||
"UI editor": "Editor UI",
|
"UI editor": "Editor UI",
|
||||||
"Collection": "Raccolta",
|
"Collection": "Raccolta",
|
||||||
@ -80,7 +80,7 @@
|
|||||||
"Value": "Valore",
|
"Value": "Valore",
|
||||||
"Disabled": "Disabilitato",
|
"Disabled": "Disabilitato",
|
||||||
"Enabled": "Abilitato",
|
"Enabled": "Abilitato",
|
||||||
"Problematic": "Problematico",
|
"Problematic": "Con problemi",
|
||||||
"Setting": "Impostazioni",
|
"Setting": "Impostazioni",
|
||||||
"On": "Acceso",
|
"On": "Acceso",
|
||||||
"Off": "Spento",
|
"Off": "Spento",
|
||||||
@ -121,7 +121,7 @@
|
|||||||
"pixels": "pixel",
|
"pixels": "pixel",
|
||||||
"Screen size": "Dimensione dello schermo",
|
"Screen size": "Dimensione dello schermo",
|
||||||
"Display title": "Visualizza titolo",
|
"Display title": "Visualizza titolo",
|
||||||
"Set the count of columns displayed in a row": "Imposta il conteggio delle colonne visualizzate in una riga",
|
"Set the count of columns displayed in a row": "Imposta conteggio delle colonne visualizzate in una riga",
|
||||||
"Column": "Colonna",
|
"Column": "Colonna",
|
||||||
"Phone device": "Telefono",
|
"Phone device": "Telefono",
|
||||||
"Tablet device": "Tablet",
|
"Tablet device": "Tablet",
|
||||||
@ -146,7 +146,7 @@
|
|||||||
"Half of day": "Metà del giorno",
|
"Half of day": "Metà del giorno",
|
||||||
"Year": "Anno",
|
"Year": "Anno",
|
||||||
"QuarterYear": "Quarto dell' anno",
|
"QuarterYear": "Quarto dell' anno",
|
||||||
"Select grouping field": "Seleziona il campo di raggruppamento",
|
"Select grouping field": "Seleziona campo di raggruppamento",
|
||||||
"Media": "Media",
|
"Media": "Media",
|
||||||
"Markdown": "Markdown",
|
"Markdown": "Markdown",
|
||||||
"Wysiwyg": "Wysiwyg",
|
"Wysiwyg": "Wysiwyg",
|
||||||
@ -175,7 +175,7 @@
|
|||||||
"Expand all": "Espandi tutto",
|
"Expand all": "Espandi tutto",
|
||||||
"Expand/Collapse": "Espandi/Comprimi",
|
"Expand/Collapse": "Espandi/Comprimi",
|
||||||
"Default collapse": "Comprimi di default",
|
"Default collapse": "Comprimi di default",
|
||||||
"Tree table": "Tabella ad albero",
|
"Tree table": "Tabella struttura ad albero",
|
||||||
"Custom field display name": "Nome visualizzato campo personalizzato ",
|
"Custom field display name": "Nome visualizzato campo personalizzato ",
|
||||||
"Display fields": "Visualizza campi",
|
"Display fields": "Visualizza campi",
|
||||||
"Edit record": "Modifica record",
|
"Edit record": "Modifica record",
|
||||||
@ -191,8 +191,8 @@
|
|||||||
"This is a demo text, **supports Markdown syntax**.": "Questo è un testo demo, ** supporta la sintassi di Markdown **.",
|
"This is a demo text, **supports Markdown syntax**.": "Questo è un testo demo, ** supporta la sintassi di Markdown **.",
|
||||||
"Filter": "Filtro",
|
"Filter": "Filtro",
|
||||||
"Connect data blocks": "Collega blocchi di dati",
|
"Connect data blocks": "Collega blocchi di dati",
|
||||||
"Action type": "Tipo di operazione",
|
"Action type": "Tipo di azione",
|
||||||
"Actions": "Operazioni",
|
"Actions": "Azioni",
|
||||||
"Insert": "Inserisci",
|
"Insert": "Inserisci",
|
||||||
"Insert if not exists": "Inserisci se non esiste",
|
"Insert if not exists": "Inserisci se non esiste",
|
||||||
"Insert if not exists, or update": "Inserisci se non esiste o aggiorna",
|
"Insert if not exists, or update": "Inserisci se non esiste o aggiorna",
|
||||||
@ -253,8 +253,6 @@
|
|||||||
"Percent": "Percentuale",
|
"Percent": "Percentuale",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
"Advanced type": "Avanzato",
|
"Advanced type": "Avanzato",
|
||||||
"Formula": "Formula",
|
|
||||||
"Formula description": "Calcola un valore in ciascun record in base ad altri campi nello stesso record.",
|
|
||||||
"Choices": "Scelte",
|
"Choices": "Scelte",
|
||||||
"Checkbox": "Casella di controllo",
|
"Checkbox": "Casella di controllo",
|
||||||
"Single select": "Selezione singola",
|
"Single select": "Selezione singola",
|
||||||
@ -321,7 +319,6 @@
|
|||||||
"Selector mode": "Modalità selettore",
|
"Selector mode": "Modalità selettore",
|
||||||
"Subtable mode": "Modalità sotto-tabella",
|
"Subtable mode": "Modalità sotto-tabella",
|
||||||
"Subform mode": "Modalità sotto-modulo",
|
"Subform mode": "Modalità sotto-modulo",
|
||||||
"Edit block title": "Modifica titolo blocco",
|
|
||||||
"Block title": "Titolo blocco",
|
"Block title": "Titolo blocco",
|
||||||
"Pattern": "Modello",
|
"Pattern": "Modello",
|
||||||
"Operator": "Operatore",
|
"Operator": "Operatore",
|
||||||
@ -342,11 +339,10 @@
|
|||||||
"is empty": "è vuoto",
|
"is empty": "è vuoto",
|
||||||
"is not empty": "non è vuoto",
|
"is not empty": "non è vuoto",
|
||||||
"Edit chart": "Modifica grafico",
|
"Edit chart": "Modifica grafico",
|
||||||
"Add text": "Aggiungi testo",
|
|
||||||
"Filterable fields": "Campi filtrabili",
|
"Filterable fields": "Campi filtrabili",
|
||||||
"Edit button": "Modifica pulsante",
|
"Edit button": "Modifica pulsante",
|
||||||
"Hide": "Nascondi",
|
"Hide": "Nascondi",
|
||||||
"Enable actions": "Abilita operazioni",
|
"Enable actions": "Abilita azioni",
|
||||||
"Import": "Importa",
|
"Import": "Importa",
|
||||||
"Export": "Esporta",
|
"Export": "Esporta",
|
||||||
"Customize": "Personalizza",
|
"Customize": "Personalizza",
|
||||||
@ -354,12 +350,12 @@
|
|||||||
"Function": "Funzione",
|
"Function": "Funzione",
|
||||||
"Popup form": "Modulo Popup",
|
"Popup form": "Modulo Popup",
|
||||||
"Flexible popup": "Popup flessibile",
|
"Flexible popup": "Popup flessibile",
|
||||||
"Configure actions": "Configura operazioni",
|
"Configure actions": "Configura azioni",
|
||||||
"Display order number": "Visualizza numero ordinamento",
|
"Display order number": "Visualizza numero ordinamento",
|
||||||
"Enable drag and drop sorting": "Abilita l'ordinamento con drag and drop",
|
"Enable drag and drop sorting": "Abilita l'ordinamento con drag and drop",
|
||||||
"Triggered when the row is clicked": "Attivato quando si fa clic sulla riga",
|
"Triggered when the row is clicked": "Attivato quando si fa clic sulla riga",
|
||||||
"Add tab": "Aggiungi scheda",
|
"Add tab": "Aggiungi scheda",
|
||||||
"Disable tabs": "Disabilita le schede",
|
"Disable tabs": "Disabilita schede",
|
||||||
"Details": "Dettagli",
|
"Details": "Dettagli",
|
||||||
"Edit form": "Modifica modulo",
|
"Edit form": "Modifica modulo",
|
||||||
"Create form": "Crea modulo",
|
"Create form": "Crea modulo",
|
||||||
@ -373,13 +369,13 @@
|
|||||||
"Custom column name": "Nome colonna personalizzato",
|
"Custom column name": "Nome colonna personalizzato",
|
||||||
"Edit description": "Modifica descrizione",
|
"Edit description": "Modifica descrizione",
|
||||||
"Required": "Richiesto",
|
"Required": "Richiesto",
|
||||||
"Unique": "Unico",
|
"Unique": "Univoco",
|
||||||
"Primary": "Primario",
|
"Primary": "Primario",
|
||||||
"Auto increment": "Incremento automatico",
|
"Auto increment": "Incremento automatico",
|
||||||
"Label field": "Campo etichetta",
|
"Label field": "Campo etichetta",
|
||||||
"Default is the ID field": "L'impostazione predefinita è il campo ID",
|
"Default is the ID field": "L'impostazione predefinita è il campo ID",
|
||||||
"Set default sorting rules": "Imposta le regole di ordinamento predefinite",
|
"Set default sorting rules": "Imposta regole di ordinamento predefinite",
|
||||||
"Set validation rules": "Imposta le regole di convalida",
|
"Set validation rules": "Imposta regole di convalida",
|
||||||
"Max length": "Lunghezza massima",
|
"Max length": "Lunghezza massima",
|
||||||
"Min length": "Lunghezza minima",
|
"Min length": "Lunghezza minima",
|
||||||
"Maximum": "Massimo",
|
"Maximum": "Massimo",
|
||||||
@ -430,8 +426,7 @@
|
|||||||
"Text Align": "Allineamento testo",
|
"Text Align": "Allineamento testo",
|
||||||
"Add option": "Aggiungi opzione",
|
"Add option": "Aggiungi opzione",
|
||||||
"Related collection": "Raccolta correlata",
|
"Related collection": "Raccolta correlata",
|
||||||
"Allow linking to multiple records": "Consenti il collegamento a più record",
|
"Allow linking to multiple records": "Consenti collegamento a più record",
|
||||||
"Allow uploading multiple files": "Consenti il caricamento di più file",
|
|
||||||
"Configure calendar": "Configura calendario",
|
"Configure calendar": "Configura calendario",
|
||||||
"Title field": "Campo titolo",
|
"Title field": "Campo titolo",
|
||||||
"Custom title": "Titolo personalizzato",
|
"Custom title": "Titolo personalizzato",
|
||||||
@ -471,13 +466,13 @@
|
|||||||
"Configure": "Configura",
|
"Configure": "Configura",
|
||||||
"Configure permissions": "Configura permessi",
|
"Configure permissions": "Configura permessi",
|
||||||
"Edit role": "Modifica ruolo",
|
"Edit role": "Modifica ruolo",
|
||||||
"Action permissions": "Permessi su operazioni",
|
"Action permissions": "Permessi su azioni",
|
||||||
"Menu permissions": "Permessi su menu",
|
"Menu permissions": "Permessi su menu",
|
||||||
"Menu item name": "Nome voce di menu",
|
"Menu item name": "Nome voce di menu",
|
||||||
"Allow access": "Consenti accesso",
|
"Allow access": "Consenti accesso",
|
||||||
"Action name": "Nome operazione",
|
"Action name": "Nome azione",
|
||||||
"Allow action": "Consenti operazione",
|
"Allow action": "Consenti azione",
|
||||||
"Action scope": "Ambito operazione",
|
"Action scope": "Ambito azione",
|
||||||
"Operate on new data": "Operare su nuovi dati",
|
"Operate on new data": "Operare su nuovi dati",
|
||||||
"Operate on existing data": "Operare su dati esistenti",
|
"Operate on existing data": "Operare su dati esistenti",
|
||||||
"Yes": "Si",
|
"Yes": "Si",
|
||||||
@ -503,11 +498,11 @@
|
|||||||
"Save as block template": "Salva come modello blocco",
|
"Save as block template": "Salva come modello blocco",
|
||||||
"Block templates": "Modelli blocco",
|
"Block templates": "Modelli blocco",
|
||||||
"Block template": "Modello blocco",
|
"Block template": "Modello blocco",
|
||||||
"Convert reference to duplicate": "Converti il riferimento a duplicato",
|
"Convert reference to duplicate": "Converti riferimento a duplicato",
|
||||||
"Template name": "Nome modello",
|
"Template name": "Nome modello",
|
||||||
"Block type": "Tipo blocco",
|
"Block type": "Tipo blocco",
|
||||||
"No blocks to connect": "Nessun blocco per connettersi",
|
"No blocks to connect": "Nessun blocco per connettersi",
|
||||||
"Action column": "Colonna operazioni",
|
"Action column": "Colonna azioni",
|
||||||
"Records per page": "Record per pagina",
|
"Records per page": "Record per pagina",
|
||||||
"(Fields only)": "(Solo campi)",
|
"(Fields only)": "(Solo campi)",
|
||||||
"Button title": "Titolo pulsante",
|
"Button title": "Titolo pulsante",
|
||||||
@ -542,7 +537,7 @@
|
|||||||
"Identifier for program usage. Support letters, numbers and underscores, must start with an letter.": "Identificatore per l'utilizzo del programma. Supporta lettere, numeri e underscore, deve iniziare con una lettera.",
|
"Identifier for program usage. Support letters, numbers and underscores, must start with an letter.": "Identificatore per l'utilizzo del programma. Supporta lettere, numeri e underscore, deve iniziare con una lettera.",
|
||||||
"Drawer": "Cassetto",
|
"Drawer": "Cassetto",
|
||||||
"Dialog": "Dialogo",
|
"Dialog": "Dialogo",
|
||||||
"Delete action": "Elimina operazione",
|
"Delete action": "Elimina azione",
|
||||||
"Custom column title": "Titolo colonna personalizzata",
|
"Custom column title": "Titolo colonna personalizzata",
|
||||||
"Column title": "Titolo colonna",
|
"Column title": "Titolo colonna",
|
||||||
"Original title: ": "Titolo originale: ",
|
"Original title: ": "Titolo originale: ",
|
||||||
@ -556,7 +551,7 @@
|
|||||||
"Then": "Poi",
|
"Then": "Poi",
|
||||||
"Stay on current page": "Resta sulla pagina corrente",
|
"Stay on current page": "Resta sulla pagina corrente",
|
||||||
"Redirect to": "Reindirizza a",
|
"Redirect to": "Reindirizza a",
|
||||||
"Save action": "Salva operazione",
|
"Save action": "Salva azione",
|
||||||
"Exists": "Esiste",
|
"Exists": "Esiste",
|
||||||
"Add condition": "Aggiungi condizione",
|
"Add condition": "Aggiungi condizione",
|
||||||
"Add condition group": "Aggiungi gruppo di condizioni",
|
"Add condition group": "Aggiungi gruppo di condizioni",
|
||||||
@ -571,10 +566,7 @@
|
|||||||
"≤": "≤",
|
"≤": "≤",
|
||||||
"Role UID": "Ruolo UID",
|
"Role UID": "Ruolo UID",
|
||||||
"Precision": "Precisione",
|
"Precision": "Precisione",
|
||||||
"Formula mode": "Modalità formula",
|
|
||||||
"Expression": "Espressione",
|
"Expression": "Espressione",
|
||||||
"Input +, -, *, /, ( ) to calculate, input @ to open field variables.": "Input +, -, *, /, () per calcolare, input @ per aprire le variabili campo.",
|
|
||||||
"Formula error.": "Errore formula.",
|
|
||||||
"Rich Text": "Testo ricco",
|
"Rich Text": "Testo ricco",
|
||||||
"Junction collection": "Raccolta giunzione",
|
"Junction collection": "Raccolta giunzione",
|
||||||
"Leave it blank, unless you need a custom intermediate table": "Lascialo vuoto, a meno che tu non abbia bisogno di una tabella intermedia personalizzata",
|
"Leave it blank, unless you need a custom intermediate table": "Lascialo vuoto, a meno che tu non abbia bisogno di una tabella intermedia personalizzata",
|
||||||
@ -595,7 +587,7 @@
|
|||||||
"Tab name": "Nome della scheda",
|
"Tab name": "Nome della scheda",
|
||||||
"Current record blocks": "Blocchi record attuale",
|
"Current record blocks": "Blocchi record attuale",
|
||||||
"Popup message": "Messaggio popup",
|
"Popup message": "Messaggio popup",
|
||||||
"Delete role": "Elimina il ruolo",
|
"Delete role": "Elimina ruolo",
|
||||||
"Role display name": "Nome visualizzato ruolo",
|
"Role display name": "Nome visualizzato ruolo",
|
||||||
"Default role": "Ruolo predefinito",
|
"Default role": "Ruolo predefinito",
|
||||||
"All collections use general action permissions by default; permission configured individually will override the default one.": "Tutte le raccolte utilizzano i permessi di operazioni generali per impostazione predefinita; I permessi configurati individualmente sovrascriveranno quelli predefiniti.",
|
"All collections use general action permissions by default; permission configured individually will override the default one.": "Tutte le raccolte utilizzano i permessi di operazioni generali per impostazione predefinita; I permessi configurati individualmente sovrascriveranno quelli predefiniti.",
|
||||||
@ -612,11 +604,11 @@
|
|||||||
"Allows to configure interface": "Consente di configurare l'interfaccia",
|
"Allows to configure interface": "Consente di configurare l'interfaccia",
|
||||||
"Allows to install, activate, disable plugins": "Consente di installare, attivare, disabilitare i plugin",
|
"Allows to install, activate, disable plugins": "Consente di installare, attivare, disabilitare i plugin",
|
||||||
"Allows to configure plugins": "Consente di configurare i plugin",
|
"Allows to configure plugins": "Consente di configurare i plugin",
|
||||||
"Action display name": "Nome visualizzato operazione",
|
"Action display name": "Nome visualizzato azione",
|
||||||
"Allow": "Permetti",
|
"Allow": "Permetti",
|
||||||
"Data scope": "Ambito dei dati",
|
"Data scope": "Ambito dei dati",
|
||||||
"Action on new records": "Operazione su nuovi record",
|
"Action on new records": "Azione su nuovi record",
|
||||||
"Action on existing records": "Operazione su record esistenti",
|
"Action on existing records": "Azione su record esistenti",
|
||||||
"All records": "Tutti i record",
|
"All records": "Tutti i record",
|
||||||
"Own records": "Record propri",
|
"Own records": "Record propri",
|
||||||
"Permission policy": "Policy di autorizzazione",
|
"Permission policy": "Policy di autorizzazione",
|
||||||
@ -624,14 +616,14 @@
|
|||||||
"General": "Generale",
|
"General": "Generale",
|
||||||
"Accessible": "Accessibile",
|
"Accessible": "Accessibile",
|
||||||
"Configure permission": "Configura permesso",
|
"Configure permission": "Configura permesso",
|
||||||
"Action permission": "Permesso operazione",
|
"Action permission": "Permesso azione",
|
||||||
"Field permission": "Permesso campo",
|
"Field permission": "Permesso campo",
|
||||||
"Scope name": "Nome ambito",
|
"Scope name": "Nome ambito",
|
||||||
"Unsaved changes": "Modifiche non salvate",
|
"Unsaved changes": "Modifiche non salvate",
|
||||||
"Are you sure you don't want to save?": "Sei sicuro di non voler salvare?",
|
"Are you sure you don't want to save?": "Sei sicuro di non voler salvare?",
|
||||||
"Dragging": "Trascina",
|
"Dragging": "Trascina",
|
||||||
"Popup": "Popup",
|
"Popup": "Popup",
|
||||||
"Trigger workflow": "Trigger flusso di lavoro",
|
"Trigger workflow": "Trigger workflow",
|
||||||
"Request API": "Richiesta API",
|
"Request API": "Richiesta API",
|
||||||
"Assign field values": "Assegna valori del campo",
|
"Assign field values": "Assegna valori del campo",
|
||||||
"Constant value": "Valore costante",
|
"Constant value": "Valore costante",
|
||||||
@ -763,11 +755,6 @@
|
|||||||
"Fix block": "Fissa blocco",
|
"Fix block": "Fissa blocco",
|
||||||
"Plugin name": "Nome plugin",
|
"Plugin name": "Nome plugin",
|
||||||
"Plugin tab name": "Nome scheda plugin",
|
"Plugin tab name": "Nome scheda plugin",
|
||||||
"AutoGenId": "Campo ID generato automaticamente",
|
|
||||||
"CreatedBy": "Creato da",
|
|
||||||
"UpdatedBy": "Aggiornato da",
|
|
||||||
"CreatedAt": "Creato il",
|
|
||||||
"UpdatedAt": "Aggiornato il",
|
|
||||||
"Column width": "Larghezza colonna",
|
"Column width": "Larghezza colonna",
|
||||||
"Sortable": "Ordinabile",
|
"Sortable": "Ordinabile",
|
||||||
"Enable link": "Abilita link",
|
"Enable link": "Abilita link",
|
||||||
@ -796,9 +783,8 @@
|
|||||||
"Current form": "Modulo corrente",
|
"Current form": "Modulo corrente",
|
||||||
"Current object": "Oggetto corrente",
|
"Current object": "Oggetto corrente",
|
||||||
"Linkage with form fields": "Collegamento con i campi del modulo",
|
"Linkage with form fields": "Collegamento con i campi del modulo",
|
||||||
"Allow add new, update and delete actions": "Consenti operazioni aggiungi nuovo, aggiorna ed elimina",
|
"Allow add new, update and delete actions": "Consenti azioni aggiungi nuovo, aggiorna ed elimina",
|
||||||
"Date display format": "Formato di visualizzazione della data",
|
"Date display format": "Formato di visualizzazione della data",
|
||||||
"Assign data scope for the template": "Assegna l'ambito dei dati per il modello",
|
|
||||||
"Table selected records": "Tabella record selezionati",
|
"Table selected records": "Tabella record selezionati",
|
||||||
"Tag": "Etichetta",
|
"Tag": "Etichetta",
|
||||||
"Tag color field": "Campo colore etichetta",
|
"Tag color field": "Campo colore etichetta",
|
||||||
@ -814,7 +800,6 @@
|
|||||||
"Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "Elimina automaticamente gli oggetti che dipendono dalla raccolta (come le viste) e, a loro volta, tutti gli oggetti che dipendono da tali oggetti",
|
"Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "Elimina automaticamente gli oggetti che dipendono dalla raccolta (come le viste) e, a loro volta, tutti gli oggetti che dipendono da tali oggetti",
|
||||||
"Sign in with another account": "Accedi con un altro account",
|
"Sign in with another account": "Accedi con un altro account",
|
||||||
"Return to the main application": "Torna alla applicazione principale",
|
"Return to the main application": "Torna alla applicazione principale",
|
||||||
"Permission deined": "Permesso negato",
|
|
||||||
"loading": "caricamento",
|
"loading": "caricamento",
|
||||||
"name is required": "nome richiesto",
|
"name is required": "nome richiesto",
|
||||||
"data source": "sorgente dati",
|
"data source": "sorgente dati",
|
||||||
@ -843,7 +828,7 @@
|
|||||||
"URL search params": "Parametri di ricerca URL",
|
"URL search params": "Parametri di ricerca URL",
|
||||||
"Expand All": "Espandi tutto",
|
"Expand All": "Espandi tutto",
|
||||||
"Search": "Ricerca",
|
"Search": "Ricerca",
|
||||||
"Clear default value": "Cancella il valore predefinito",
|
"Clear default value": "Cancella valore predefinito",
|
||||||
"Open in new window": "Apri in una nuova finestra",
|
"Open in new window": "Apri in una nuova finestra",
|
||||||
"Sorry, the page you visited does not exist.": "Spiacente, la pagina che hai visitato non esiste.",
|
"Sorry, the page you visited does not exist.": "Spiacente, la pagina che hai visitato non esiste.",
|
||||||
"is none of": "non è nessuno di",
|
"is none of": "non è nessuno di",
|
||||||
@ -857,5 +842,245 @@
|
|||||||
"Notification": "Notifica",
|
"Notification": "Notifica",
|
||||||
"Ellipsis overflow content": "Contenuto Ellipsis overflow",
|
"Ellipsis overflow content": "Contenuto Ellipsis overflow",
|
||||||
"Hide column": "Nascondi colonna",
|
"Hide column": "Nascondi colonna",
|
||||||
"In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "In modalità di configurazione, l'intera colonna diventa trasparente. In modalità non di configurazione, l'intera colonna verrà nascosta. Anche se l'intera colonna è nascosta, i suoi valori predefiniti configurati e le altre impostazioni avranno comunque effetto."
|
"In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "In modalità di configurazione, l'intera colonna diventa trasparente. In modalità non di configurazione, l'intera colonna verrà nascosta. Anche se l'intera colonna è nascosta, i suoi valori predefiniti configurati e le altre impostazioni avranno comunque effetto.",
|
||||||
|
"Page number": "Numero di pagina",
|
||||||
|
"Page size": "Numero di voci per pagina",
|
||||||
|
"Enable": "Abilita",
|
||||||
|
"Disable": "Disabilita",
|
||||||
|
"Tab": "Scheda",
|
||||||
|
"Calculation engine": "Motore di calcolo",
|
||||||
|
"Expression collection": "Raccolta espressioni",
|
||||||
|
"Tree collection": "Raccolta struttura ad albero",
|
||||||
|
"Parent ID": "ID record padre",
|
||||||
|
"Parent": "Record padre",
|
||||||
|
"Children": "Record figlio",
|
||||||
|
"Confirm": "Conferma",
|
||||||
|
"Block": "Blocco",
|
||||||
|
"Unnamed": "Senza nome",
|
||||||
|
"SQL collection": "Raccolta dati SQL",
|
||||||
|
"Configure field": "Configura campo",
|
||||||
|
"Username": "Nome utente",
|
||||||
|
"Null": "Null",
|
||||||
|
"Boolean": "Booleano",
|
||||||
|
"String": "Stringa",
|
||||||
|
"Syntax references": "Riferimenti sintassi",
|
||||||
|
"Math.js comes with a large set of built-in functions and constants, and offers an integrated solution to work with different data types.": "Math.js include un ampio set di funzioni e costanti integrate e offre una soluzione integrata per lavorare con diversi tipi di dati.",
|
||||||
|
"Formula.js supports most Microsoft Excel formula functions.": "Formula.js supporta la maggior parte delle funzioni delle formule di Microsoft Excel.",
|
||||||
|
"String template": "Modello stringa",
|
||||||
|
"Simple string replacement, can be used to interpolate variables in a string.": "Sostituzione semplice di stringhe, può essere utilizzata per interpolare variabili in una stringa.",
|
||||||
|
"https://docs.nocobase.com/handbook/calculation-engines/formula": "https://docs-cn.nocobase.com/handbook/calculation-engines/formula",
|
||||||
|
"https://docs.nocobase.com/handbook/calculation-engines/mathjs": "https://docs-cn.nocobase.com/handbook/calculation-engines/mathjs",
|
||||||
|
"Display <icon></icon> when unchecked": "Visualizza <icon></icon> quando deselezionato",
|
||||||
|
"Allow dissociate": "Consenti dissociazione",
|
||||||
|
"Edit block title & description": "Modifica titolo e descrizione blocco",
|
||||||
|
"Add Markdown": "Aggiungi Markdown",
|
||||||
|
"Must be 1-50 characters in length (excluding @.<>\"'/)": "Deve essere lungo 1-50 caratteri (esclusi @.<>\"'/)",
|
||||||
|
"Data source permissions": "Permessi origine dati",
|
||||||
|
"Now": "Adesso",
|
||||||
|
"Access control": "Controllo accessi",
|
||||||
|
"Remove": "Rimuovi",
|
||||||
|
"Docs": "Documenti",
|
||||||
|
"Enable page header": "Abilita intestazione pagina",
|
||||||
|
"Display page title": "Visualizza titolo pagina",
|
||||||
|
"Edit page title": "Modifica titolo pagina",
|
||||||
|
"Enable page tabs": "Abilita schede pagina",
|
||||||
|
"Constant": "Costante",
|
||||||
|
"Select a variable": "Seleziona una variabile",
|
||||||
|
"Double click to choose entire object": "Doppio clic per scegliere l'intero oggetto",
|
||||||
|
"True": "Vero",
|
||||||
|
"False": "Falso",
|
||||||
|
"Prettify": "Formatta",
|
||||||
|
"Theme": "Tema",
|
||||||
|
"Default theme": "Tema predefinito",
|
||||||
|
"Compact theme": "Tema compatto",
|
||||||
|
"Download": "Scarica",
|
||||||
|
"File type is not supported for previewing, please download it to preview.": "Il tipo di file non è supportato per l'anteprima, scaricalo per visualizzarlo.",
|
||||||
|
"Click or drag file to this area to upload": "Clicca o trascina il file in quest'area per caricarlo",
|
||||||
|
"Support for a single or bulk upload.": "Supporta il caricamento singolo o in blocco.",
|
||||||
|
"File size should not exceed {{size}}.": "La dimensione del file non deve superare {{size}}.",
|
||||||
|
"File size exceeds the limit": "La dimensione del file supera il limite",
|
||||||
|
"File type is not allowed": "Il tipo di file non è consentito",
|
||||||
|
"Incomplete uploading files need to be resolved": "I caricamenti incompleti dei file devono essere completati",
|
||||||
|
"Default title for each record": "Titolo predefinito per ogni record",
|
||||||
|
"If collection inherits, choose inherited collections as templates": "Se la raccolta eredita, scegli le raccolte ereditate come modelli",
|
||||||
|
"Select an existing piece of data as the initialization data for the form": "Seleziona un dato esistente come dati di inizializzazione per il modulo",
|
||||||
|
"Only the selected fields will be used as the initialization data for the form": "Solo i campi selezionati verranno utilizzati come dati di inizializzazione per il modulo",
|
||||||
|
"Template Data": "Dati modello",
|
||||||
|
"Data fields": "Campi dati",
|
||||||
|
"Add template": "Aggiungi modello",
|
||||||
|
"Enable form data template": "Abilita modello dati modulo",
|
||||||
|
"Form data templates": "Modelli dati modulo",
|
||||||
|
"No configuration available.": "Nessuna configurazione disponibile.",
|
||||||
|
"Reload application": "Ricarica applicazione",
|
||||||
|
"The application is reloading, please do not close the page.": "L'applicazione si sta ricaricando, non chiudere la pagina.",
|
||||||
|
"Application reloading": "Ricaricamento applicazione",
|
||||||
|
"Allows to clear cache, reboot application": "Consente di cancellare la cache, riavviare l'applicazione",
|
||||||
|
"The will interrupt service, it may take a few seconds to restart. Are you sure to continue?": "Il riavvio interromperà il servizio, potrebbero essere necessari alcuni secondi. Sei sicuro di continuare?",
|
||||||
|
"Clear cache": "Cancella cache",
|
||||||
|
"Quick create": "Creazione rapida",
|
||||||
|
"Dropdown": "Menu a discesa",
|
||||||
|
"Pop-up": "Popup",
|
||||||
|
"Direct duplicate": "Duplicazione diretta",
|
||||||
|
"Copy into the form and continue to fill in": "Copia nel modulo e continua a compilare",
|
||||||
|
"Failed to load plugin": "Caricamento plugin fallito",
|
||||||
|
"Date range limit": "Limite intervallo date",
|
||||||
|
"MinDate": "Data minima",
|
||||||
|
"MaxDate": "Data massima",
|
||||||
|
"Please select time or variable": "Seleziona ora o variabile",
|
||||||
|
"Filter out a single piece or a group of records as a template": "Filtra un singolo dato o un gruppo di record come modello",
|
||||||
|
"The title field is used to identify the template record": "Il campo titolo è utilizzato per identificare il record modello",
|
||||||
|
"Template fields": "Campi modello",
|
||||||
|
"The selected fields will automatically populate the form": "I campi selezionati popoleranno automaticamente il modulo",
|
||||||
|
"UnSelect all": "Deseleziona tutto",
|
||||||
|
"Secondary confirmation": "Conferma secondaria",
|
||||||
|
"Perform the {{title}}": "Esegui {{title}}",
|
||||||
|
"Are you sure you want to perform the {{title}} action?": "Sei sicuro di voler eseguire l'azione {{title}}?",
|
||||||
|
"Permission denied": "Permesso negato",
|
||||||
|
"Allow add new": "Consenti aggiunta",
|
||||||
|
"Data model": "Modello dati",
|
||||||
|
"Security": "Sicurezza",
|
||||||
|
"Action": "Azione",
|
||||||
|
"System": "Sistema",
|
||||||
|
"Other": "Altro",
|
||||||
|
"Allow selection of existing records": "Consenti selezione di record esistenti",
|
||||||
|
"Data Model": "Modello dati",
|
||||||
|
"Blocks": "Blocchi",
|
||||||
|
"Users & permissions": "Utenti e permessi",
|
||||||
|
"System management": "Gestione sistema",
|
||||||
|
"System & security": "Sistema e sicurezza",
|
||||||
|
"Workflow": "Workflow",
|
||||||
|
"Third party services": "Servizi di terze parti",
|
||||||
|
"Data model tools": "Strumenti modello dati",
|
||||||
|
"Data sources": "Origini dati",
|
||||||
|
"Collections": "Raccolte",
|
||||||
|
"Collection fields": "Campi raccolta",
|
||||||
|
"Authentication": "Autenticazione",
|
||||||
|
"Logging and monitoring": "Registrazione e monitoraggio",
|
||||||
|
"Main": "Principale",
|
||||||
|
"Index": "Indice",
|
||||||
|
"Field values must be unique.": "I valori dei campi devono essere univoci.",
|
||||||
|
"Alphabet": "Alfabeto",
|
||||||
|
"Accuracy": "Precisione",
|
||||||
|
"Millisecond": "Millisecondo",
|
||||||
|
"Second": "Secondo",
|
||||||
|
"Unix Timestamp": "Timestamp Unix",
|
||||||
|
"Field value do not meet the requirements": "Il valore del campo non soddisfa i requisiti",
|
||||||
|
"Field value size is": "La dimensione del valore del campo è",
|
||||||
|
"Unit conversion": "Conversione unità",
|
||||||
|
"Separator": "Separatore",
|
||||||
|
"Prefix": "Prefisso",
|
||||||
|
"Suffix": "Suffisso",
|
||||||
|
"Record unique key": "Chiave univoca record",
|
||||||
|
"Filter target key": "Chiave target filtro",
|
||||||
|
"If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.": "Se una raccolta non ha una chiave primaria, devi configurare un record come chiave univoca per individuare i record di ogni riga all'interno di un blocco, la mancata configurazione impedirà la creazione di blocchi di dati per la raccolta.",
|
||||||
|
"Filter data based on the specific field, with the requirement that the field value must be unique.": "Filtra i dati in base a un campo specifico, con il requisito che il valore del campo deve essere univoco.",
|
||||||
|
"Multiply by": "Moltiplica per",
|
||||||
|
"Divide by": "Dividi per",
|
||||||
|
"Scientifix notation": "Notazione scientifica",
|
||||||
|
"Normal": "Normale",
|
||||||
|
"Automatically generate default values": "Genera automaticamente valori predefiniti",
|
||||||
|
"Refresh data on close": "Refresh dei dati alla chiusura",
|
||||||
|
"Refresh data on action": "Refresh dei dati su azione",
|
||||||
|
"Unknown field type": "Tipo di campo sconosciuto",
|
||||||
|
"The following field types are not compatible and do not support output and display": "I seguenti tipi di campo non sono compatibili e non supportano l'output e la visualizzazione",
|
||||||
|
"Not fixed": "Non fissato",
|
||||||
|
"Left fixed": "Fissato a sinistra",
|
||||||
|
"Right fixed": "Fissato a destra",
|
||||||
|
"Fixed": "Colonna fissa",
|
||||||
|
"Set block height": "Imposta altezza del blocco",
|
||||||
|
"Specify height": "Specifica altezza",
|
||||||
|
"Full height": "Altezza completa",
|
||||||
|
"Please configure the URL": "Configura l'URL",
|
||||||
|
"URL": "URL",
|
||||||
|
"Search parameters": "Parametri di ricerca URL",
|
||||||
|
"Do not concatenate search params in the URL": "Non concatenare i parametri di ricerca nell'URL",
|
||||||
|
"Edit link": "Modifica link",
|
||||||
|
"Add parameter": "Aggiungi parametro",
|
||||||
|
"Use simple pagination mode": "Usa modalità di paginazione semplice",
|
||||||
|
"Set Template Engine": "Imposta motore template",
|
||||||
|
"Template engine": "Motore template",
|
||||||
|
"The current user only has the UI configuration permission, but don't have view permission for collection \"{{name}}\"": "L'utente corrente ha solo il permesso di configurazione dell'interfaccia utente, ma non ha il permesso di visualizzazione per la raccolta \"{{name}}\"",
|
||||||
|
"Default value to current time": "Imposta il valore predefinito del campo all'ora corrente",
|
||||||
|
"Automatically update timestamp on update": "Aggiorna automaticamente il timestamp all'aggiornamento",
|
||||||
|
"Default value to current server time": "Imposta il valore predefinito del campo all'ora corrente del server",
|
||||||
|
"Automatically update timestamp to the current server time on update": "Aggiorna automaticamente il timestamp all'ora corrente del server all'aggiornamento",
|
||||||
|
"Datetime (with time zone)": "Data e ora (con fuso orario)",
|
||||||
|
"Datetime (without time zone)": "Data e ora (senza fuso orario)",
|
||||||
|
"DateOnly": "Solo data",
|
||||||
|
"Content": "Contenuto",
|
||||||
|
"Perform the Update record": "Esegui aggiornamento record",
|
||||||
|
"Are you sure you want to perform the Update record action?": "Sei sicuro di voler eseguire l'azione di aggiornamento record?",
|
||||||
|
"Perform the Custom request": "Esegui richiesta personalizzata",
|
||||||
|
"Are you sure you want to perform the Custom request action": "Sei sicuro di voler eseguire l'azione di richiesta personalizzata?",
|
||||||
|
"Perform the Refresh": "Esegui refresh",
|
||||||
|
"Are you sure you want to perform the Refresh action?": "Sei sicuro di voler eseguire l'azione di refresh?",
|
||||||
|
"Perform the Submit": "Esegui l'invio",
|
||||||
|
"Are you sure you want to perform the Submit action?": "Sei sicuro di voler eseguire l'azione di invio?",
|
||||||
|
"Perform the Trigger workflow": "Esegui il trigger del workflow",
|
||||||
|
"Are you sure you want to perform the Trigger workflow action?": "Sei sicuro di voler eseguire l'azione di trigger del workflow?",
|
||||||
|
"Picker": "Selettore",
|
||||||
|
"Quarter": "Trimestre",
|
||||||
|
"Switching the picker, the value and default value will be cleared": "Cambiando il selettore, il valore e il valore predefinito verranno cancellati",
|
||||||
|
"Stay on the current popup or page": "Rimani nel popup o nella pagina corrente",
|
||||||
|
"Return to the previous popup or page": "Torna al popup o alla pagina precedente",
|
||||||
|
"Action after successful submission": "Azione dopo l'invio riuscito",
|
||||||
|
"Allow disassociation": "Consenti la dissociazione",
|
||||||
|
"Layout": "Layout",
|
||||||
|
"Vertical": "Verticale",
|
||||||
|
"Horizontal": "Orizzontale",
|
||||||
|
"Edit group title": "Modifica titolo del gruppo",
|
||||||
|
"Title position": "Posizione titolo",
|
||||||
|
"Dashed": "Tratteggiato",
|
||||||
|
"Left": "Sinistra",
|
||||||
|
"Center": "Centro",
|
||||||
|
"Right": "Destra",
|
||||||
|
"Divider line color": "Colore linea di divisione",
|
||||||
|
"Label align": "Allineamento etichetta",
|
||||||
|
"Label width": "Larghezza etichetta",
|
||||||
|
"When the Label exceeds the width": "Quando l'etichetta supera la larghezza",
|
||||||
|
"Line break": "A capo",
|
||||||
|
"Ellipsis": "Ellipsis",
|
||||||
|
"Set block layout": "Imposta layout del blocco",
|
||||||
|
"Add & Update": "Aggiungi e aggiorna",
|
||||||
|
"Table size": "Dimensione della tabella",
|
||||||
|
"Plugin": "Plugin",
|
||||||
|
"Bulk enable": "Abilitazione in blocco",
|
||||||
|
"Search plugin...": "Cerca plugin...",
|
||||||
|
"Package name": "Nome pacchetto",
|
||||||
|
"Associate": "Associa",
|
||||||
|
"Please add or select record": "Aggiungi o seleziona record",
|
||||||
|
"No data": "Nessun dato",
|
||||||
|
"Fields can only be used correctly if they are defined with an interface.": "I campi possono essere utilizzati correttamente solo se sono definiti con un'interfaccia.",
|
||||||
|
"Unauthenticated. Please sign in to continue.": "Non autenticato. Accedi per continuare.",
|
||||||
|
"User not found. Please sign in again to continue.": "Impossibile trovare l'utente. Accedi nuovamente per continuare.",
|
||||||
|
"Your session has expired. Please sign in again.": "La tua sessione è scaduta. Accedi nuovamente.",
|
||||||
|
"User password changed, please signin again.": "La password dell'utente è stata modificata, accedi di nuovo.",
|
||||||
|
"Show file name": "Mostra nome del file",
|
||||||
|
"Outlined": "Contornato",
|
||||||
|
"Filled": "Riempito",
|
||||||
|
"Two tone": "Due toni",
|
||||||
|
"Desktop routes": "Percorsi desktop",
|
||||||
|
"Route permissions": "Permessi percorso",
|
||||||
|
"New routes are allowed to be accessed by default": "I nuovi percorsi sono accessibili per impostazione predefinita",
|
||||||
|
"Route name": "Nome percorso",
|
||||||
|
"Mobile routes": "Percorsi mobile",
|
||||||
|
"Show in menu": "Mostra nel menu",
|
||||||
|
"Hide in menu": "Nascondi nel menu",
|
||||||
|
"Path": "Percorso",
|
||||||
|
"Type": "Tipo",
|
||||||
|
"Access": "Accesso",
|
||||||
|
"Routes": "Percorsi",
|
||||||
|
"Add child route": "Aggiungi percorso figlio",
|
||||||
|
"Delete routes": "Elimina percorsi",
|
||||||
|
"Delete route": "Elimina percorso",
|
||||||
|
"Are you sure you want to hide these routes in menu?": "Sei sicuro di voler nascondere questi percorsi nel menu?",
|
||||||
|
"Are you sure you want to show these routes in menu?": "Sei sicuro di voler mostrare questi percorsi nel menu?",
|
||||||
|
"Are you sure you want to hide this menu?": "Sei sicuro di voler nascondere questo menu?",
|
||||||
|
"After hiding, this menu will no longer appear in the menu bar. To show it again, you need to go to the route management page to configure it.": "Dopo averlo nascosto, questo menu non apparirà più nella barra dei menu. Per mostrarlo di nuovo, devi andare alla pagina di gestione dei percorsi per configurarlo.",
|
||||||
|
"If selected, the page will display Tab pages.": "Se selezionato, la pagina visualizzerà le pagine schede.",
|
||||||
|
"If selected, the route will be displayed in the menu.": "Se selezionato, il percorso verrà visualizzato nel menu.",
|
||||||
|
"Are you sure you want to hide this tab?": "Sei sicuro di voler nascondere questa scheda?",
|
||||||
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Dopo averla nascosta, questa scheda non apparirà più nella barra delle schede. Per mostrarla di nuovo, devi andare alla pagina di gestione dei percorsi per configurarlo.",
|
||||||
|
"No pages yet, please configure first": "Nessuna pagina ancora, si prega di configurare prima",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur"
|
||||||
}
|
}
|
||||||
|
@ -1039,5 +1039,7 @@
|
|||||||
"If selected, the page will display Tab pages.": "選択されている場合、ページはタブページを表示します。",
|
"If selected, the page will display Tab pages.": "選択されている場合、ページはタブページを表示します。",
|
||||||
"If selected, the route will be displayed in the menu.": "選択されている場合、ルートはメニューに表示されます。",
|
"If selected, the route will be displayed in the menu.": "選択されている場合、ルートはメニューに表示されます。",
|
||||||
"Are you sure you want to hide this tab?": "このタブを非表示にしますか?",
|
"Are you sure you want to hide this tab?": "このタブを非表示にしますか?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "非表示にすると、このタブはタブバーに表示されなくなります。再表示するには、ルート管理ページで設定する必要があります。"
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "非表示にすると、このタブはタブバーに表示されなくなります。再表示するには、ルート管理ページで設定する必要があります。",
|
||||||
|
"No pages yet, please configure first": "まだページがありません。最初に設定してください",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "ユーザーインターフェースエディターモードに入るには、右上隅の「UIエディタ」アイコンをクリックしてください"
|
||||||
}
|
}
|
||||||
|
@ -912,5 +912,7 @@
|
|||||||
"If selected, the page will display Tab pages.": "선택되면 페이지는 탭 페이지를 표시합니다.",
|
"If selected, the page will display Tab pages.": "선택되면 페이지는 탭 페이지를 표시합니다.",
|
||||||
"If selected, the route will be displayed in the menu.": "선택되면 라우트는 메뉴에 표시됩니다.",
|
"If selected, the route will be displayed in the menu.": "선택되면 라우트는 메뉴에 표시됩니다.",
|
||||||
"Are you sure you want to hide this tab?": "이 탭을 숨기시겠습니까?",
|
"Are you sure you want to hide this tab?": "이 탭을 숨기시겠습니까?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "숨기면 이 탭은 탭 바에 더 이상 표시되지 않습니다. 다시 표시하려면 라우트 관리 페이지에서 설정해야 합니다."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "숨기면 이 탭은 탭 바에 더 이상 표시되지 않습니다. 다시 표시하려면 라우트 관리 페이지에서 설정해야 합니다.",
|
||||||
|
"No pages yet, please configure first": "아직 페이지가 없습니다. 먼저 설정하십시오",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "사용자 인터페이스 편집기 모드에 들어가려면 오른쪽 상단의 \"UI 편집기\" 아이콘을 클릭하십시오"
|
||||||
}
|
}
|
||||||
|
@ -778,5 +778,7 @@
|
|||||||
"If selected, the page will display Tab pages.": "Se selecionado, a página exibirá páginas de abas.",
|
"If selected, the page will display Tab pages.": "Se selecionado, a página exibirá páginas de abas.",
|
||||||
"If selected, the route will be displayed in the menu.": "Se selecionado, a rota será exibida no menu.",
|
"If selected, the route will be displayed in the menu.": "Se selecionado, a rota será exibida no menu.",
|
||||||
"Are you sure you want to hide this tab?": "Tem certeza de que deseja ocultar esta guia?",
|
"Are you sure you want to hide this tab?": "Tem certeza de que deseja ocultar esta guia?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Depois de ocultar, esta guia não aparecerá mais na barra de guias. Para mostrá-la novamente, você precisa ir à página de gerenciamento de rotas para configurá-la."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Depois de ocultar, esta guia não aparecerá mais na barra de guias. Para mostrá-la novamente, você precisa ir à página de gerenciamento de rotas para configurá-la.",
|
||||||
|
"No pages yet, please configure first": "Ainda não há páginas, por favor configure primeiro",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur"
|
||||||
}
|
}
|
||||||
|
@ -607,5 +607,7 @@
|
|||||||
"If selected, the page will display Tab pages.": "Если выбран, страница будет отображать страницы с вкладками.",
|
"If selected, the page will display Tab pages.": "Если выбран, страница будет отображать страницы с вкладками.",
|
||||||
"If selected, the route will be displayed in the menu.": "Если выбран, маршрут будет отображаться в меню.",
|
"If selected, the route will be displayed in the menu.": "Если выбран, маршрут будет отображаться в меню.",
|
||||||
"Are you sure you want to hide this tab?": "Вы уверены, что хотите скрыть эту вкладку?",
|
"Are you sure you want to hide this tab?": "Вы уверены, что хотите скрыть эту вкладку?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "После скрытия этой вкладки она больше не будет отображаться во вкладке. Чтобы снова отобразить ее, вам нужно будет перейти на страницу управления маршрутами, чтобы установить ее."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "После скрытия этой вкладки она больше не будет отображаться во вкладке. Чтобы снова отобразить ее, вам нужно будет перейти на страницу управления маршрутами, чтобы установить ее.",
|
||||||
|
"No pages yet, please configure first": "Пока нет страниц, пожалуйста, настройте сначала",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Нажмите на значок \"Редактор пользовательского интерфейса\" в правом верхнем углу, чтобы войти в режим редактора пользовательского интерфейса"
|
||||||
}
|
}
|
||||||
|
@ -605,5 +605,7 @@
|
|||||||
"If selected, the page will display Tab pages.": "Seçildiğinde, sayfa Tab sayfalarını görüntüleyecektir.",
|
"If selected, the page will display Tab pages.": "Seçildiğinde, sayfa Tab sayfalarını görüntüleyecektir.",
|
||||||
"If selected, the route will be displayed in the menu.": "Seçildiğinde, yol menüde görüntülenecektir.",
|
"If selected, the route will be displayed in the menu.": "Seçildiğinde, yol menüde görüntülenecektir.",
|
||||||
"Are you sure you want to hide this tab?": "Bu sekmeyi gizlemek istediğinizden emin misiniz?",
|
"Are you sure you want to hide this tab?": "Bu sekmeyi gizlemek istediğinizden emin misiniz?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Gizlendikten sonra, bu sekme artık sekme çubuğunda görünmeyecek. Onu tekrar göstermek için, rotayı yönetim sayfasına gidip ayarlamanız gerekiyor."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Gizlendikten sonra, bu sekme artık sekme çubuğunda görünmeyecek. Onu tekrar göstermek için, rotayı yönetim sayfasına gidip ayarlamanız gerekiyor.",
|
||||||
|
"No pages yet, please configure first": "Henüz sayfa yok, lütfen önce yapılandırın",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Kullanıcı arayüzü düzenleyici moduna girmek için sağ üst köşedeki \"Kullanıcı Arayüzü Düzenleyici\" simgesine tıklayın"
|
||||||
}
|
}
|
||||||
|
@ -821,5 +821,7 @@
|
|||||||
"If selected, the page will display Tab pages.": "Якщо вибрано, сторінка відобразить сторінки з вкладками.",
|
"If selected, the page will display Tab pages.": "Якщо вибрано, сторінка відобразить сторінки з вкладками.",
|
||||||
"If selected, the route will be displayed in the menu.": "Якщо вибрано, маршрут буде відображений в меню.",
|
"If selected, the route will be displayed in the menu.": "Якщо вибрано, маршрут буде відображений в меню.",
|
||||||
"Are you sure you want to hide this tab?": "Ви впевнені, що хочете приховати цю вкладку?",
|
"Are you sure you want to hide this tab?": "Ви впевнені, що хочете приховати цю вкладку?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Після приховування цієї вкладки вона більше не з'явиться в панелі вкладок. Щоб знову показати її, вам потрібно перейти на сторінку керування маршрутами, щоб налаштувати її."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Після приховування цієї вкладки вона більше не з'явиться в панелі вкладок. Щоб знову показати її, вам потрібно перейти на сторінку керування маршрутами, щоб налаштувати її.",
|
||||||
|
"No pages yet, please configure first": "Ще немає сторінок, будь ласка, спочатку налаштуйте",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Натисніть на значок \"Редактор користувацького інтерфейсу\" в правому верхньому куті, щоб увійти в режим редактора користувацького інтерфейсу."
|
||||||
}
|
}
|
||||||
|
@ -258,6 +258,7 @@
|
|||||||
"Parent collection fields": "父表字段",
|
"Parent collection fields": "父表字段",
|
||||||
"Basic": "基本类型",
|
"Basic": "基本类型",
|
||||||
"Single line text": "单行文本",
|
"Single line text": "单行文本",
|
||||||
|
"Automatically remove heading and tailing spaces": "自动去除首尾空白字符",
|
||||||
"Long text": "多行文本",
|
"Long text": "多行文本",
|
||||||
"Phone": "手机号码",
|
"Phone": "手机号码",
|
||||||
"Email": "电子邮箱",
|
"Email": "电子邮箱",
|
||||||
@ -1080,5 +1081,7 @@
|
|||||||
"If selected, the page will display Tab pages.": "如果选中,该页面将显示标签页。",
|
"If selected, the page will display Tab pages.": "如果选中,该页面将显示标签页。",
|
||||||
"If selected, the route will be displayed in the menu.": "如果选中,该路由将显示在菜单中。",
|
"If selected, the route will be displayed in the menu.": "如果选中,该路由将显示在菜单中。",
|
||||||
"Are you sure you want to hide this tab?": "你确定要隐藏该标签页吗?",
|
"Are you sure you want to hide this tab?": "你确定要隐藏该标签页吗?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "隐藏后,该标签将不再显示在标签栏中。要想再次显示它,你需要到路由管理页面进行设置。"
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "隐藏后,该标签将不再显示在标签栏中。要想再次显示它,你需要到路由管理页面进行设置。",
|
||||||
|
"No pages yet, please configure first": "暂无页面,请先配置",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "点击右上角的“界面配置”图标,进入界面配置模式"
|
||||||
}
|
}
|
||||||
|
@ -912,6 +912,7 @@
|
|||||||
"If selected, the page will display Tab pages.": "如果選中,該頁面將顯示標籤頁。",
|
"If selected, the page will display Tab pages.": "如果選中,該頁面將顯示標籤頁。",
|
||||||
"If selected, the route will be displayed in the menu.": "如果選中,該路由將顯示在菜單中。",
|
"If selected, the route will be displayed in the menu.": "如果選中,該路由將顯示在菜單中。",
|
||||||
"Are you sure you want to hide this tab?": "你確定要隱藏這個標籤嗎?",
|
"Are you sure you want to hide this tab?": "你確定要隱藏這個標籤嗎?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "隱藏後,這個標籤將不再出現在標籤欄中。要再次顯示它,你需要到路由管理頁面進行設置。"
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "隱藏後,這個標籤將不再出現在標籤欄中。要再次顯示它,你需要到路由管理頁面進行設置。",
|
||||||
|
"No pages yet, please configure first": "尚未配置頁面,請先配置",
|
||||||
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "點擊右上角的 \"介面設定\" 圖示進入介面設定模式"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ test.describe('where list block can be added', () => {
|
|||||||
await page.getByLabel('schema-initializer-Grid-').nth(1).hover();
|
await page.getByLabel('schema-initializer-Grid-').nth(1).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Role name' }).click();
|
await page.getByRole('menuitem', { name: 'Role name' }).click();
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
|
await page.reload();
|
||||||
await expect(page.getByLabel('block-item-CollectionField-').getByText('Root')).toBeVisible();
|
await expect(page.getByLabel('block-item-CollectionField-').getByText('Root')).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-CollectionField-').getByText('Admin')).toBeVisible();
|
await expect(page.getByLabel('block-item-CollectionField-').getByText('Admin')).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-CollectionField-').getByText('Member')).toBeVisible();
|
await expect(page.getByLabel('block-item-CollectionField-').getByText('Member')).toBeVisible();
|
||||||
|
@ -94,9 +94,13 @@ export const FilterCollectionFieldInternalField: React.FC = (props: Props) => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
field.dataSource = uiSchema.enum;
|
field.dataSource = uiSchema.enum;
|
||||||
const originalProps =
|
const originalProps =
|
||||||
compile({ ...(operator?.schema?.['x-component-props'] || {}), ...(uiSchema['x-component-props'] || {}) }) || {};
|
compile({
|
||||||
|
...(operator?.schema?.['x-component-props'] || {}),
|
||||||
|
...(uiSchema['x-component-props'] || {}),
|
||||||
|
...(fieldSchema?.['x-component-props'] || {}),
|
||||||
|
}) || {};
|
||||||
|
|
||||||
field.componentProps = merge(originalProps, field.componentProps || {}, dynamicProps || {});
|
field.componentProps = merge(field.componentProps || {}, originalProps, dynamicProps || {});
|
||||||
}, [uiSchemaOrigin]);
|
}, [uiSchemaOrigin]);
|
||||||
|
|
||||||
if (!uiSchemaOrigin) return null;
|
if (!uiSchemaOrigin) return null;
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
|
import _ from 'lodash';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useBlockContext, useOpenModeContext } from '../../../../';
|
import { useBlockContext, useOpenModeContext } from '../../../../';
|
||||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||||
@ -45,11 +46,12 @@ export const ellipsisSettingsItem: SchemaSettingsItemType = {
|
|||||||
tableFieldInstanceList.forEach((fieldInstance) => {
|
tableFieldInstanceList.forEach((fieldInstance) => {
|
||||||
fieldInstance.componentProps.ellipsis = checked;
|
fieldInstance.componentProps.ellipsis = checked;
|
||||||
});
|
});
|
||||||
schema['x-component-props']['ellipsis'] = checked;
|
|
||||||
} else {
|
} else {
|
||||||
formField.componentProps.ellipsis = checked;
|
formField.componentProps.ellipsis = checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_.set(schema, 'x-component-props.ellipsis', checked);
|
||||||
|
|
||||||
await dn.emit('patch', {
|
await dn.emit('patch', {
|
||||||
schema: {
|
schema: {
|
||||||
'x-uid': schema['x-uid'],
|
'x-uid': schema['x-uid'],
|
||||||
|
@ -126,6 +126,10 @@ export const getAllowMultiple = (params?: { title: string }) => {
|
|||||||
return {
|
return {
|
||||||
name: 'allowMultiple',
|
name: 'allowMultiple',
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
|
useVisible() {
|
||||||
|
const isAssociationField = useIsAssociationField();
|
||||||
|
return isAssociationField;
|
||||||
|
},
|
||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const field = useField<Field>();
|
const field = useField<Field>();
|
||||||
|
@ -14,6 +14,14 @@ import React, { useCallback, useContext } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Router } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
import { SchemaInitializerItem } from '../../application';
|
import { SchemaInitializerItem } from '../../application';
|
||||||
|
import {
|
||||||
|
CollectionManagerProvider,
|
||||||
|
useCollectionManager,
|
||||||
|
} from '../../data-source/collection/CollectionManagerProvider';
|
||||||
|
import {
|
||||||
|
DataSourceManagerProvider,
|
||||||
|
useDataSourceManager,
|
||||||
|
} from '../../data-source/data-source/DataSourceManagerProvider';
|
||||||
import { useGlobalTheme } from '../../global-theme';
|
import { useGlobalTheme } from '../../global-theme';
|
||||||
import { NocoBaseDesktopRouteType } from '../../route-switch/antd/admin-layout/convertRoutesToSchema';
|
import { NocoBaseDesktopRouteType } from '../../route-switch/antd/admin-layout/convertRoutesToSchema';
|
||||||
import {
|
import {
|
||||||
@ -34,6 +42,8 @@ export const LinkMenuItem = () => {
|
|||||||
const { urlSchema, paramsSchema } = useURLAndHTMLSchema();
|
const { urlSchema, paramsSchema } = useURLAndHTMLSchema();
|
||||||
const parentRoute = useParentRoute();
|
const parentRoute = useParentRoute();
|
||||||
const { createRoute } = useNocoBaseRoutes();
|
const { createRoute } = useNocoBaseRoutes();
|
||||||
|
const dm = useDataSourceManager();
|
||||||
|
const cm = useCollectionManager();
|
||||||
|
|
||||||
const handleClick = useCallback(async () => {
|
const handleClick = useCallback(async () => {
|
||||||
const values = await FormDialog(
|
const values = await FormDialog(
|
||||||
@ -41,6 +51,8 @@ export const LinkMenuItem = () => {
|
|||||||
() => {
|
() => {
|
||||||
const history = createMemoryHistory();
|
const history = createMemoryHistory();
|
||||||
return (
|
return (
|
||||||
|
<DataSourceManagerProvider dataSourceManager={dm}>
|
||||||
|
<CollectionManagerProvider instance={cm} dataSource={cm?.dataSource?.key}>
|
||||||
<Router location={history.location} navigator={history}>
|
<Router location={history.location} navigator={history}>
|
||||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||||
<FormLayout layout={'vertical'}>
|
<FormLayout layout={'vertical'}>
|
||||||
@ -66,6 +78,8 @@ export const LinkMenuItem = () => {
|
|||||||
</FormLayout>
|
</FormLayout>
|
||||||
</SchemaComponentOptions>
|
</SchemaComponentOptions>
|
||||||
</Router>
|
</Router>
|
||||||
|
</CollectionManagerProvider>
|
||||||
|
</DataSourceManagerProvider>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
theme,
|
theme,
|
||||||
|
@ -74,9 +74,9 @@ const useErrorProps = (app: Application, error: any) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const AppError: FC<{ error: Error; app: Application }> = observer(
|
const AppError: FC<{ error: Error & { title?: string }; app: Application }> = observer(
|
||||||
({ app, error }) => {
|
({ app, error }) => {
|
||||||
const props = useErrorProps(app, error);
|
const props = getProps(app);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Result
|
<Result
|
||||||
@ -87,8 +87,9 @@ const AppError: FC<{ error: Error; app: Application }> = observer(
|
|||||||
transform: translate(0, -50%);
|
transform: translate(0, -50%);
|
||||||
`}
|
`}
|
||||||
status="error"
|
status="error"
|
||||||
title={app.i18n.t('App error')}
|
title={error?.title || app.i18n.t('App error', { ns: 'client' })}
|
||||||
subTitle={app.i18n.t(error?.message)}
|
subTitle={app.i18n.t(error?.message)}
|
||||||
|
{...props}
|
||||||
extra={[
|
extra={[
|
||||||
<Button type="primary" key="try" onClick={() => window.location.reload()}>
|
<Button type="primary" key="try" onClick={() => window.location.reload()}>
|
||||||
{app.i18n.t('Try again')}
|
{app.i18n.t('Try again')}
|
||||||
@ -124,6 +125,14 @@ const getProps = (app: Application) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (app.error.code === 'APP_WARNING') {
|
||||||
|
return {
|
||||||
|
status: 'warning',
|
||||||
|
title: 'App warning',
|
||||||
|
subTitle: app.error?.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (app.error.code === 'APP_INITIALIZING') {
|
if (app.error.code === 'APP_INITIALIZING') {
|
||||||
return {
|
return {
|
||||||
status: 'info',
|
status: 'info',
|
||||||
|
@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* 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 { findFirstPageRoute, NocoBaseDesktopRouteType } from '..';
|
||||||
|
import { NocoBaseDesktopRoute } from '../convertRoutesToSchema';
|
||||||
|
|
||||||
|
describe('findFirstPageRoute', () => {
|
||||||
|
// 基本测试:空路由数组
|
||||||
|
it('should return undefined for empty routes array', () => {
|
||||||
|
const result = findFirstPageRoute([]);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 基本测试:undefined 路由数组
|
||||||
|
it('should return undefined for undefined routes', () => {
|
||||||
|
const result = findFirstPageRoute(undefined);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:只有一个页面路由
|
||||||
|
it('should find the first page route when there is only one page', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:多个页面路由
|
||||||
|
it('should find the first page route when there are multiple pages', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
schemaUid: 'page2',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:不同类型的路由混合
|
||||||
|
it('should find the first page route among mixed route types', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
schemaUid: 'link1',
|
||||||
|
type: NocoBaseDesktopRouteType.link,
|
||||||
|
title: 'Link 1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:隐藏的菜单项
|
||||||
|
it('should ignore hidden menu items', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
schemaUid: 'page2',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:嵌套路由
|
||||||
|
it('should find page route in nested group', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: NocoBaseDesktopRouteType.group,
|
||||||
|
title: 'Group 1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[0].children[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:多层嵌套路由
|
||||||
|
it('should find page route in deeply nested groups', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: NocoBaseDesktopRouteType.group,
|
||||||
|
title: 'Group 1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
type: NocoBaseDesktopRouteType.group,
|
||||||
|
title: 'Group 1-1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 111,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[0].children[0].children[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:复杂路由结构
|
||||||
|
it('should find the first visible page in a complex route structure', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: NocoBaseDesktopRouteType.group,
|
||||||
|
title: 'Group 1',
|
||||||
|
hideInMenu: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
type: NocoBaseDesktopRouteType.group,
|
||||||
|
title: 'Group 2',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 21,
|
||||||
|
schemaUid: 'page2',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[1].children[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:空组
|
||||||
|
it('should skip empty groups and find page in next group', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: NocoBaseDesktopRouteType.group,
|
||||||
|
title: 'Empty Group',
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[1]);
|
||||||
|
});
|
||||||
|
});
|
@ -7,13 +7,14 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EllipsisOutlined } from '@ant-design/icons';
|
import { EllipsisOutlined, HighlightOutlined } from '@ant-design/icons';
|
||||||
import ProLayout, { RouteContext, RouteContextType } from '@ant-design/pro-layout';
|
import ProLayout, { RouteContext, RouteContextType } from '@ant-design/pro-layout';
|
||||||
import { HeaderViewProps } from '@ant-design/pro-layout/es/components/Header';
|
import { HeaderViewProps } from '@ant-design/pro-layout/es/components/Header';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Popover, Tooltip } from 'antd';
|
import { Popover, Result, Tooltip } from 'antd';
|
||||||
import React, { createContext, FC, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { createContext, FC, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link, Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
import { Link, Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
ACLRolesCheckProvider,
|
ACLRolesCheckProvider,
|
||||||
@ -197,12 +198,34 @@ const pageContentStyle: React.CSSProperties = {
|
|||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ShowTipWhenNoPages = () => {
|
||||||
|
const { allAccessRoutes } = useAllAccessDesktopRoutes();
|
||||||
|
const { designable } = useDesignable();
|
||||||
|
const { token } = useToken();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
// Check if there are any pages
|
||||||
|
if (allAccessRoutes.length === 0 && !designable && ['/admin', '/admin/'].includes(location.pathname)) {
|
||||||
|
return (
|
||||||
|
<Result
|
||||||
|
icon={<HighlightOutlined style={{ fontSize: '8em', color: token.colorText }} />}
|
||||||
|
title={t('No pages yet, please configure first')}
|
||||||
|
subTitle={t(`Click the "UI Editor" icon in the upper right corner to enter the UI Editor mode`)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const LayoutContent = () => {
|
export const LayoutContent = () => {
|
||||||
/* Use the "nb-subpages-slot-without-header-and-side" class name to locate the position of the subpages */
|
/* Use the "nb-subpages-slot-without-header-and-side" class name to locate the position of the subpages */
|
||||||
return (
|
return (
|
||||||
<div className={`${layoutContentClass} nb-subpages-slot-without-header-and-side`}>
|
<div className={`${layoutContentClass} nb-subpages-slot-without-header-and-side`}>
|
||||||
<div style={pageContentStyle}>
|
<div style={pageContentStyle}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
<ShowTipWhenNoPages />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -588,27 +611,11 @@ export const InternalAdminLayout = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getDefaultPageUid(routes: NocoBaseDesktopRoute[]) {
|
|
||||||
// Find the first route of type "page"
|
|
||||||
for (const route of routes) {
|
|
||||||
if (route.type === NocoBaseDesktopRouteType.page) {
|
|
||||||
return route.schemaUid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.children?.length) {
|
|
||||||
const result = getDefaultPageUid(route.children);
|
|
||||||
if (result) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const NavigateToDefaultPage: FC = (props) => {
|
const NavigateToDefaultPage: FC = (props) => {
|
||||||
const { allAccessRoutes } = useAllAccessDesktopRoutes();
|
const { allAccessRoutes } = useAllAccessDesktopRoutes();
|
||||||
const location = useLocationNoUpdate();
|
const location = useLocationNoUpdate();
|
||||||
|
|
||||||
const defaultPageUid = getDefaultPageUid(allAccessRoutes);
|
const defaultPageUid = findFirstPageRoute(allAccessRoutes)?.schemaUid;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -718,36 +725,6 @@ export function findRouteBySchemaUid(schemaUid: string, treeArray: any[]) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MenuItemIcon: FC<{ icon: string; title: string }> = (props) => {
|
|
||||||
const { inHeader } = useContext(headerContext);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RouteContext.Consumer>
|
|
||||||
{(value: RouteContextType) => {
|
|
||||||
const { collapsed } = value;
|
|
||||||
|
|
||||||
if (collapsed && !inHeader) {
|
|
||||||
return props.icon ? (
|
|
||||||
<Icon type={props.icon} />
|
|
||||||
) : (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: 'inline-block',
|
|
||||||
width: '100%',
|
|
||||||
textAlign: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.title.charAt(0)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return props.icon ? <Icon type={props.icon} /> : null;
|
|
||||||
}}
|
|
||||||
</RouteContext.Consumer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MenuDesignerButton: FC<{ testId: string }> = (props) => {
|
const MenuDesignerButton: FC<{ testId: string }> = (props) => {
|
||||||
const { render: renderInitializer } = useSchemaInitializerRender(menuItemInitializer);
|
const { render: renderInitializer } = useSchemaInitializerRender(menuItemInitializer);
|
||||||
|
|
||||||
@ -869,16 +846,17 @@ function findRouteById(id: string, treeArray: any[]) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findFirstPageRoute(routes: NocoBaseDesktopRoute[]) {
|
export function findFirstPageRoute(routes: NocoBaseDesktopRoute[]) {
|
||||||
if (!routes) return;
|
if (!routes) return;
|
||||||
|
|
||||||
for (const route of routes) {
|
for (const route of routes.filter((item) => !item.hideInMenu)) {
|
||||||
if (route.type === NocoBaseDesktopRouteType.page) {
|
if (route.type === NocoBaseDesktopRouteType.page) {
|
||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.children?.length) {
|
if (route.type === NocoBaseDesktopRouteType.group && route.children?.length) {
|
||||||
return findFirstPageRoute(route.children);
|
const result = findFirstPageRoute(route.children);
|
||||||
|
if (result) return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,12 @@ import { ComposedAction } from './types';
|
|||||||
export const ActionLink: ComposedAction = withDynamicSchemaProps(
|
export const ActionLink: ComposedAction = withDynamicSchemaProps(
|
||||||
observer((props: any) => {
|
observer((props: any) => {
|
||||||
return (
|
return (
|
||||||
<Action {...props} component={props.component || 'a'} className={classnames('nb-action-link', props.className)} />
|
<Action
|
||||||
|
{...props}
|
||||||
|
component={props.component || 'a'}
|
||||||
|
className={classnames('nb-action-link', props.className)}
|
||||||
|
isLink
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
{ displayName: 'ActionLink' },
|
{ displayName: 'ActionLink' },
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { observer, useField, useFieldSchema } from '@formily/react';
|
import { observer, useField, useFieldSchema } from '@formily/react';
|
||||||
import { Modal, ModalProps } from 'antd';
|
import { Modal, ModalProps, Skeleton } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { FC, startTransition, useEffect, useState } from 'react';
|
import React, { FC, startTransition, useEffect, useState } from 'react';
|
||||||
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
|
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
|
||||||
@ -53,7 +53,6 @@ const ActionModalContent: FC<{ footerNodeName: string; field: any; schema: any }
|
|||||||
if (!deferredVisible) {
|
if (!deferredVisible) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NocoBaseRecursionField
|
<NocoBaseRecursionField
|
||||||
basePath={field.address}
|
basePath={field.address}
|
||||||
@ -67,6 +66,19 @@ const ActionModalContent: FC<{ footerNodeName: string; field: any; schema: any }
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export function useDelayedVisible(visible: boolean, delay = 200) {
|
||||||
|
const [ready, setReady] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
const timer = setTimeout(() => setReady(true), delay);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
} else {
|
||||||
|
setReady(false);
|
||||||
|
}
|
||||||
|
}, [visible]);
|
||||||
|
return ready;
|
||||||
|
}
|
||||||
|
|
||||||
export const InternalActionModal: React.FC<ActionDrawerProps<ModalProps>> = observer(
|
export const InternalActionModal: React.FC<ActionDrawerProps<ModalProps>> = observer(
|
||||||
(props) => {
|
(props) => {
|
||||||
const { footerNodeName = 'Action.Modal.Footer', width, zIndex: _zIndex, ...others } = props;
|
const { footerNodeName = 'Action.Modal.Footer', width, zIndex: _zIndex, ...others } = props;
|
||||||
@ -90,6 +102,7 @@ export const InternalActionModal: React.FC<ActionDrawerProps<ModalProps>> = obse
|
|||||||
}
|
}
|
||||||
|
|
||||||
const zIndex = getZIndex('modal', _zIndex || parentZIndex, props.level || 0);
|
const zIndex = getZIndex('modal', _zIndex || parentZIndex, props.level || 0);
|
||||||
|
const ready = useDelayedVisible(visible, 200); // 200ms 与 Modal 动画时间一致
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionContextNoRerender>
|
<ActionContextNoRerender>
|
||||||
@ -154,7 +167,11 @@ export const InternalActionModal: React.FC<ActionDrawerProps<ModalProps>> = obse
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{ready ? (
|
||||||
<ActionModalContent footerNodeName={footerNodeName} field={field} schema={schema} />
|
<ActionModalContent footerNodeName={footerNodeName} field={field} schema={schema} />
|
||||||
|
) : (
|
||||||
|
<Skeleton active paragraph={{ rows: 6 }} />
|
||||||
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
</TabsContextProvider>
|
</TabsContextProvider>
|
||||||
</zIndexContext.Provider>
|
</zIndexContext.Provider>
|
||||||
|
@ -247,7 +247,6 @@ const InternalAction: React.FC<InternalActionProps> = observer(function Com(prop
|
|||||||
const aclCtx = useACLActionParamsContext();
|
const aclCtx = useACLActionParamsContext();
|
||||||
const { run, element, disabled: disableAction } = useAction?.(actionCallback) || ({} as any);
|
const { run, element, disabled: disableAction } = useAction?.(actionCallback) || ({} as any);
|
||||||
const disabled = form.disabled || field.disabled || field.data?.disabled || propsDisabled || disableAction;
|
const disabled = form.disabled || field.disabled || field.data?.disabled || propsDisabled || disableAction;
|
||||||
|
|
||||||
const buttonStyle = useMemo(() => {
|
const buttonStyle = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
...style,
|
...style,
|
||||||
@ -538,6 +537,7 @@ const RenderButtonInner = observer(
|
|||||||
Designer: React.ElementType;
|
Designer: React.ElementType;
|
||||||
designerProps: any;
|
designerProps: any;
|
||||||
title: string;
|
title: string;
|
||||||
|
isLink?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
designable,
|
designable,
|
||||||
@ -558,6 +558,7 @@ const RenderButtonInner = observer(
|
|||||||
Designer,
|
Designer,
|
||||||
designerProps,
|
designerProps,
|
||||||
title,
|
title,
|
||||||
|
isLink,
|
||||||
...others
|
...others
|
||||||
} = props;
|
} = props;
|
||||||
const debouncedClick = useCallback(
|
const debouncedClick = useCallback(
|
||||||
@ -582,7 +583,8 @@ const RenderButtonInner = observer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const actionTitle = title || field?.title;
|
const actionTitle = title || field?.title;
|
||||||
|
const { opacity, ...restButtonStyle } = buttonStyle;
|
||||||
|
const linkStyle = isLink && opacity ? { opacity } : undefined;
|
||||||
return (
|
return (
|
||||||
<SortableItem
|
<SortableItem
|
||||||
role="button"
|
role="button"
|
||||||
@ -591,15 +593,19 @@ const RenderButtonInner = observer(
|
|||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
loading={field?.data?.loading || loading}
|
loading={field?.data?.loading || loading}
|
||||||
icon={typeof icon === 'string' ? <Icon type={icon} /> : icon}
|
icon={typeof icon === 'string' ? <Icon type={icon} style={linkStyle} /> : icon}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={buttonStyle}
|
style={isLink ? restButtonStyle : buttonStyle}
|
||||||
onClick={process.env.__E2E__ ? handleButtonClick : debouncedClick} // E2E 中的点击操作都是很快的,如果加上 debounce 会导致 E2E 测试失败
|
onClick={process.env.__E2E__ ? handleButtonClick : debouncedClick} // E2E 中的点击操作都是很快的,如果加上 debounce 会导致 E2E 测试失败
|
||||||
component={tarComponent || Button}
|
component={tarComponent || Button}
|
||||||
className={classnames(componentCls, hashId, className, 'nb-action')}
|
className={classnames(componentCls, hashId, className, 'nb-action')}
|
||||||
type={type === 'danger' ? undefined : type}
|
type={type === 'danger' ? undefined : type}
|
||||||
>
|
>
|
||||||
{actionTitle && <span className={icon ? 'nb-action-title' : null}>{actionTitle}</span>}
|
{actionTitle && (
|
||||||
|
<span className={icon ? 'nb-action-title' : null} style={linkStyle}>
|
||||||
|
{actionTitle}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<Designer {...designerProps} />
|
<Designer {...designerProps} />
|
||||||
</SortableItem>
|
</SortableItem>
|
||||||
);
|
);
|
||||||
|
@ -14,6 +14,7 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|||||||
import { useAPIClient, useRequest } from '../../../api-client';
|
import { useAPIClient, useRequest } from '../../../api-client';
|
||||||
import { useCollectionManager } from '../../../data-source/collection';
|
import { useCollectionManager } from '../../../data-source/collection';
|
||||||
import { markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
|
import { markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
|
||||||
|
import { getDataSourceHeaders } from '../../../data-source/utils';
|
||||||
import { useKeepAlive } from '../../../route-switch/antd/admin-layout/KeepAlive';
|
import { useKeepAlive } from '../../../route-switch/antd/admin-layout/KeepAlive';
|
||||||
import { useSchemaComponentContext } from '../../hooks';
|
import { useSchemaComponentContext } from '../../hooks';
|
||||||
import { AssociationFieldContext } from './context';
|
import { AssociationFieldContext } from './context';
|
||||||
@ -67,9 +68,11 @@ export const AssociationFieldProvider = observer(
|
|||||||
if (_.isUndefined(ids) || _.isNil(ids) || _.isNaN(ids)) {
|
if (_.isUndefined(ids) || _.isNil(ids) || _.isNaN(ids)) {
|
||||||
return Promise.reject(null);
|
return Promise.reject(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return api.request({
|
return api.request({
|
||||||
resource: collectionField.target,
|
resource: collectionField.target,
|
||||||
action: Array.isArray(ids) ? 'list' : 'get',
|
action: Array.isArray(ids) ? 'list' : 'get',
|
||||||
|
headers: getDataSourceHeaders(cm?.dataSource?.key),
|
||||||
params: {
|
params: {
|
||||||
filter: {
|
filter: {
|
||||||
[targetKey]: ids,
|
[targetKey]: ids,
|
||||||
|
@ -14,22 +14,28 @@ import { uid } from '@formily/shared';
|
|||||||
import { Space, message } from 'antd';
|
import { Space, message } from 'antd';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { isFunction } from 'mathjs';
|
import { isFunction } from 'mathjs';
|
||||||
import React, { useEffect, useState, useContext } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ClearCollectionFieldContext,
|
ClearCollectionFieldContext,
|
||||||
NocoBaseRecursionField,
|
NocoBaseRecursionField,
|
||||||
RecordProvider,
|
RecordProvider,
|
||||||
|
SchemaComponentContext,
|
||||||
useAPIClient,
|
useAPIClient,
|
||||||
useCollectionRecordData,
|
useCollectionRecordData,
|
||||||
SchemaComponentContext,
|
useCollectionManager_deprecated,
|
||||||
} from '../../../';
|
} from '../../../';
|
||||||
import { Action } from '../action';
|
import { VariablePopupRecordProvider } from '../../../modules/variable/variablesProvider/VariablePopupRecordProvider';
|
||||||
import { isVariable } from '../../../variables/utils/isVariable';
|
import { isVariable } from '../../../variables/utils/isVariable';
|
||||||
import { getInnermostKeyAndValue } from '../../common/utils/uitls';
|
import { getInnermostKeyAndValue } from '../../common/utils/uitls';
|
||||||
|
import { Action } from '../action';
|
||||||
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
||||||
import useServiceOptions, { useAssociationFieldContext } from './hooks';
|
import useServiceOptions, { useAssociationFieldContext } from './hooks';
|
||||||
import { VariablePopupRecordProvider } from '../../../modules/variable/variablesProvider/VariablePopupRecordProvider';
|
|
||||||
|
const removeIfKeyEmpty = (obj, filterTargetKey) => {
|
||||||
|
if (!obj || typeof obj !== 'object' || !filterTargetKey || Array.isArray(obj)) return obj;
|
||||||
|
return !obj[filterTargetKey] ? null : obj;
|
||||||
|
};
|
||||||
|
|
||||||
export const AssociationFieldAddNewer = (props) => {
|
export const AssociationFieldAddNewer = (props) => {
|
||||||
const schemaComponentCtxValue = useContext(SchemaComponentContext);
|
const schemaComponentCtxValue = useContext(SchemaComponentContext);
|
||||||
@ -69,6 +75,11 @@ export const filterAnalyses = (filters): any[] => {
|
|||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getFieldPath(str) {
|
||||||
|
const lastIndex = str.lastIndexOf('.');
|
||||||
|
return lastIndex === -1 ? str : str.slice(0, lastIndex);
|
||||||
|
}
|
||||||
|
|
||||||
const InternalAssociationSelect = observer(
|
const InternalAssociationSelect = observer(
|
||||||
(props: AssociationSelectProps) => {
|
(props: AssociationSelectProps) => {
|
||||||
const { objectValue = true, addMode: propsAddMode, ...rest } = props;
|
const { objectValue = true, addMode: propsAddMode, ...rest } = props;
|
||||||
@ -88,6 +99,9 @@ const InternalAssociationSelect = observer(
|
|||||||
const resource = api.resource(collectionField.target);
|
const resource = api.resource(collectionField.target);
|
||||||
const recordData = useCollectionRecordData();
|
const recordData = useCollectionRecordData();
|
||||||
const schemaComponentCtxValue = useContext(SchemaComponentContext);
|
const schemaComponentCtxValue = useContext(SchemaComponentContext);
|
||||||
|
const { getCollection } = useCollectionManager_deprecated();
|
||||||
|
const associationCollection = getCollection(collectionField.target);
|
||||||
|
const { filterTargetKey } = associationCollection;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initValue = isVariable(field.value) ? undefined : field.value;
|
const initValue = isVariable(field.value) ? undefined : field.value;
|
||||||
@ -100,11 +114,14 @@ const InternalAssociationSelect = observer(
|
|||||||
//支持深层次子表单
|
//支持深层次子表单
|
||||||
onFieldInputValueChange('*', (fieldPath: any) => {
|
onFieldInputValueChange('*', (fieldPath: any) => {
|
||||||
const linkageFields = filterAnalyses(field.componentProps?.service?.params?.filter) || [];
|
const linkageFields = filterAnalyses(field.componentProps?.service?.params?.filter) || [];
|
||||||
|
const linageFieldEntire = getFieldPath(fieldPath.address.entire);
|
||||||
|
const targetFieldEntire = getFieldPath(field.address.entire);
|
||||||
if (
|
if (
|
||||||
linkageFields.includes(fieldPath?.props?.name) &&
|
linkageFields.includes(fieldPath?.props?.name) &&
|
||||||
field.value &&
|
field.value &&
|
||||||
isEqual(fieldPath?.indexes, field?.indexes) &&
|
isEqual(fieldPath?.indexes, field?.indexes) &&
|
||||||
fieldPath?.props?.name !== field.props.name
|
fieldPath?.props?.name !== field.props.name &&
|
||||||
|
(!field?.indexes?.length || isEqual(linageFieldEntire, targetFieldEntire))
|
||||||
) {
|
) {
|
||||||
field.setValue(null);
|
field.setValue(null);
|
||||||
setInnerValue(null);
|
setInnerValue(null);
|
||||||
@ -151,7 +168,6 @@ const InternalAssociationSelect = observer(
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
console.log(fieldSchema);
|
|
||||||
return (
|
return (
|
||||||
<div key={fieldSchema.name}>
|
<div key={fieldSchema.name}>
|
||||||
<Space.Compact style={{ display: 'flex' }}>
|
<Space.Compact style={{ display: 'flex' }}>
|
||||||
@ -160,7 +176,7 @@ const InternalAssociationSelect = observer(
|
|||||||
{...rest}
|
{...rest}
|
||||||
size={'middle'}
|
size={'middle'}
|
||||||
objectValue={objectValue}
|
objectValue={objectValue}
|
||||||
value={value || innerValue}
|
value={removeIfKeyEmpty(value || innerValue, filterTargetKey)}
|
||||||
service={service}
|
service={service}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
const val = value?.length !== 0 ? value : null;
|
const val = value?.length !== 0 ? value : null;
|
||||||
|
@ -13,6 +13,8 @@ import { FormProvider, connect, createSchemaField, observer, useField, useFieldS
|
|||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { Select as AntdSelect, Input, Space, Spin, Tag } from 'antd';
|
import { Select as AntdSelect, Input, Space, Spin, Tag } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAPIClient, useCollectionManager_deprecated } from '../../../';
|
import { useAPIClient, useCollectionManager_deprecated } from '../../../';
|
||||||
@ -152,7 +154,11 @@ const CascadeSelect = connect((props) => {
|
|||||||
} else {
|
} else {
|
||||||
associationField.value = option;
|
associationField.value = option;
|
||||||
}
|
}
|
||||||
|
if (options.length === 1 && !options[0].value) {
|
||||||
|
onChange?.(null);
|
||||||
|
} else {
|
||||||
onChange?.(options);
|
onChange?.(options);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDropdownVisibleChange = async (visible, selectedValue, index) => {
|
const onDropdownVisibleChange = async (visible, selectedValue, index) => {
|
||||||
@ -238,17 +244,17 @@ export const InternalCascadeSelect = observer(
|
|||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { loading, data: formData } = useDataBlockRequest() || {};
|
const { loading, data: formData } = useDataBlockRequest() || {};
|
||||||
const initialValue = formData?.data?.[fieldSchema.name];
|
const initialValue = formData?.data?.[fieldSchema.name];
|
||||||
useEffect(() => {
|
|
||||||
const id = uid();
|
const handleFormValuesChange = debounce((form) => {
|
||||||
selectForm.addEffects(id, () => {
|
|
||||||
onFormValuesChange((form) => {
|
|
||||||
if (collectionField.interface === 'm2o') {
|
if (collectionField.interface === 'm2o') {
|
||||||
|
// 对 m2o 类型字段,提取最后一个非 null 值
|
||||||
const value = extractLastNonNullValueObjects(form.values?.[fieldSchema.name]);
|
const value = extractLastNonNullValueObjects(form.values?.[fieldSchema.name]);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
form.setValuesIn(fieldSchema.name, value);
|
form.setValuesIn(fieldSchema.name, value);
|
||||||
field.value = value;
|
field.value = value;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// 对 select_array 类型字段,过滤掉空对象
|
||||||
const value = extractLastNonNullValueObjects(form.values?.select_array).filter(
|
const value = extractLastNonNullValueObjects(form.values?.select_array).filter(
|
||||||
(v) => v && Object.keys(v).length > 0,
|
(v) => v && Object.keys(v).length > 0,
|
||||||
);
|
);
|
||||||
@ -256,10 +262,20 @@ export const InternalCascadeSelect = observer(
|
|||||||
field.value = value;
|
field.value = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const id = uid();
|
||||||
|
selectForm.addEffects(id, () => {
|
||||||
|
onFormValuesChange((form) => {
|
||||||
|
handleFormValuesChange(form);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
selectForm.removeEffects(id);
|
selectForm.removeEffects(id);
|
||||||
|
// 清除防抖定时器
|
||||||
|
handleFormValuesChange.cancel();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -282,6 +298,24 @@ export const InternalCascadeSelect = observer(
|
|||||||
items: {
|
items: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Space',
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
className: css`
|
||||||
|
.ant-formily-item-control {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
.ant-space-item:nth-child(1) {
|
||||||
|
flex: 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-space-item:nth-child(2) {
|
||||||
|
flex: 3;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
properties: {
|
properties: {
|
||||||
sort: {
|
sort: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
|
@ -256,8 +256,7 @@ export const SubTable: any = observer(
|
|||||||
{field.editable && (
|
{field.editable && (
|
||||||
<Space
|
<Space
|
||||||
style={{
|
style={{
|
||||||
marginTop: '10px',
|
position: 'relative',
|
||||||
position: field.value?.length ? 'absolute' : 'relative',
|
|
||||||
bottom: '0',
|
bottom: '0',
|
||||||
gap: 15,
|
gap: 15,
|
||||||
}}
|
}}
|
||||||
|
@ -602,12 +602,6 @@ const InternalNocoBaseTable = React.memo(
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.ant-table-expanded-row-fixed {
|
|
||||||
min-height: ${tableHeight}px;
|
|
||||||
}
|
|
||||||
.ant-table-body {
|
|
||||||
min-height: ${tableHeight}px;
|
|
||||||
}
|
|
||||||
.ant-table-cell {
|
.ant-table-cell {
|
||||||
padding: 16px 8px;
|
padding: 16px 8px;
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ export function useAssociationFieldContext<F extends GeneralField>() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用于获取关系字段请求数据时所需的一些参数
|
||||||
export default function useServiceOptions(props) {
|
export default function useServiceOptions(props) {
|
||||||
const { action = 'list', service, useOriginalFilter } = props;
|
const { action = 'list', service, useOriginalFilter } = props;
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
@ -54,7 +54,7 @@ describe('CollectionSelect', () => {
|
|||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-1yh5po ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-11aiz3o"
|
class="css-a7w9kk ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-11aiz3o"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-formily-item-label"
|
class="ant-formily-item-label"
|
||||||
@ -191,7 +191,7 @@ describe('CollectionSelect', () => {
|
|||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-1yh5po ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-11aiz3o"
|
class="css-a7w9kk ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-11aiz3o"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-formily-item-label"
|
class="ant-formily-item-label"
|
||||||
|
@ -289,7 +289,7 @@ DatePicker.FilterWithPicker = function FilterWithPicker(props: any) {
|
|||||||
const [stateProps, setStateProps] = useState(newProps);
|
const [stateProps, setStateProps] = useState(newProps);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
newProps.picker = targetPicker;
|
newProps.picker = targetPicker;
|
||||||
const dateTimeFormat = getDateTimeFormat(targetPicker, format, showTime, timeFormat);
|
const dateTimeFormat = getDateTimeFormat(targetPicker, targetDateFormat, showTime, timeFormat);
|
||||||
newProps.format = dateTimeFormat;
|
newProps.format = dateTimeFormat;
|
||||||
setStateProps(newProps);
|
setStateProps(newProps);
|
||||||
}, [targetPicker]);
|
}, [targetPicker]);
|
||||||
|
@ -65,7 +65,6 @@ export const DynamicComponent = (props: Props) => {
|
|||||||
...props.style,
|
...props.style,
|
||||||
},
|
},
|
||||||
utc: false,
|
utc: false,
|
||||||
underFilter: true,
|
|
||||||
}),
|
}),
|
||||||
name: 'value',
|
name: 'value',
|
||||||
'x-read-pretty': false,
|
'x-read-pretty': false,
|
||||||
|
@ -70,6 +70,7 @@ export const FilterItem = observer(
|
|||||||
className={css`
|
className={css`
|
||||||
width: 160px;
|
width: 160px;
|
||||||
`}
|
`}
|
||||||
|
showSearch
|
||||||
fieldNames={fieldNames}
|
fieldNames={fieldNames}
|
||||||
changeOnSelect={false}
|
changeOnSelect={false}
|
||||||
value={dataIndex}
|
value={dataIndex}
|
||||||
|
@ -92,7 +92,7 @@ export const useGetFilterFieldOptions = () => {
|
|||||||
|
|
||||||
const getOptions = (fields, depth, usedInVariable?: boolean) => {
|
const getOptions = (fields, depth, usedInVariable?: boolean) => {
|
||||||
const options = [];
|
const options = [];
|
||||||
fields.forEach((field) => {
|
fields?.forEach((field) => {
|
||||||
const option = field2option(field, depth, usedInVariable);
|
const option = field2option(field, depth, usedInVariable);
|
||||||
if (option) {
|
if (option) {
|
||||||
options.push(option);
|
options.push(option);
|
||||||
|
@ -985,12 +985,11 @@ function useFormItemCollectionField() {
|
|||||||
|
|
||||||
export function useIsAssociationField() {
|
export function useIsAssociationField() {
|
||||||
const collectionField = useFormItemCollectionField();
|
const collectionField = useFormItemCollectionField();
|
||||||
const isAssociationField = ['obo', 'oho', 'o2o', 'o2m', 'm2m', 'm2o', 'updatedBy', 'createdBy', 'mbm'].includes(
|
const isAssociationField =
|
||||||
collectionField?.interface,
|
collectionField &&
|
||||||
);
|
['hasOne', 'hasMany', 'belongsTo', 'belongsToMany', 'belongsToArray'].includes(collectionField.type);
|
||||||
return isAssociationField;
|
return isAssociationField;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useIsFileField() {
|
export function useIsFileField() {
|
||||||
const cm = useCollectionManager();
|
const cm = useCollectionManager();
|
||||||
const collectionField = useFormItemCollectionField();
|
const collectionField = useFormItemCollectionField();
|
||||||
|
@ -37,6 +37,17 @@ const formItemWrapCss = css`
|
|||||||
.ant-description-textarea img {
|
.ant-description-textarea img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
&.ant-formily-item-layout-horizontal.ant-formily-item-label-wrap {
|
||||||
|
.ant-formily-item-label {
|
||||||
|
display: inline;
|
||||||
|
padding-right: 5px;
|
||||||
|
|
||||||
|
.ant-formily-item-label-tooltip-icon,
|
||||||
|
.ant-formily-item-label-content {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const formItemLabelCss = css`
|
const formItemLabelCss = css`
|
||||||
@ -44,7 +55,7 @@ const formItemLabelCss = css`
|
|||||||
padding: 0px !important;
|
padding: 0px !important;
|
||||||
}
|
}
|
||||||
> .ant-formily-item-label {
|
> .ant-formily-item-label {
|
||||||
display: none;
|
display: none !important;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -83,7 +94,6 @@ export const FormItem: any = withDynamicSchemaProps(
|
|||||||
[formItemLabelCss]: showTitle === false,
|
[formItemLabelCss]: showTitle === false,
|
||||||
});
|
});
|
||||||
}, [showTitle]);
|
}, [showTitle]);
|
||||||
|
|
||||||
// 联动规则中的“隐藏保留值”的效果
|
// 联动规则中的“隐藏保留值”的效果
|
||||||
if (field.data?.hidden) {
|
if (field.data?.hidden) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -86,11 +86,6 @@ const useParseDefaultValue = () => {
|
|||||||
field &&
|
field &&
|
||||||
((isVariable(fieldSchema.default) && field.value == null) || field.value === fieldSchema.default || forceUpdate)
|
((isVariable(fieldSchema.default) && field.value == null) || field.value === fieldSchema.default || forceUpdate)
|
||||||
) {
|
) {
|
||||||
// 一个变量字符串如果显示出来会比较奇怪
|
|
||||||
if (isVariable(field.value)) {
|
|
||||||
await field.reset({ forceClear: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
field.loading = true;
|
field.loading = true;
|
||||||
const collectionField = !fieldSchema.name.toString().includes('.') && collection?.getField(fieldSchema.name);
|
const collectionField = !fieldSchema.name.toString().includes('.') && collection?.getField(fieldSchema.name);
|
||||||
|
|
||||||
|
@ -16,8 +16,10 @@ import { ConfigProvider, theme } from 'antd';
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { useActionContext } from '..';
|
import { useActionContext } from '..';
|
||||||
import { useAttach, useComponent } from '../..';
|
import { useAttach, useComponent } from '../..';
|
||||||
|
import { useApp } from '../../../application';
|
||||||
import { getCardItemSchema } from '../../../block-provider';
|
import { getCardItemSchema } from '../../../block-provider';
|
||||||
import { useTemplateBlockContext } from '../../../block-provider/TemplateBlockProvider';
|
import { useTemplateBlockContext } from '../../../block-provider/TemplateBlockProvider';
|
||||||
|
import { useDataBlockProps } from '../../../data-source';
|
||||||
import { useDataBlockRequest } from '../../../data-source/data-block/DataBlockRequestProvider';
|
import { useDataBlockRequest } from '../../../data-source/data-block/DataBlockRequestProvider';
|
||||||
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
|
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
|
||||||
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
|
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
|
||||||
@ -27,7 +29,6 @@ import { useToken } from '../../../style';
|
|||||||
import { useLocalVariables, useVariables } from '../../../variables';
|
import { useLocalVariables, useVariables } from '../../../variables';
|
||||||
import { useProps } from '../../hooks/useProps';
|
import { useProps } from '../../hooks/useProps';
|
||||||
import { useFormBlockHeight } from './hook';
|
import { useFormBlockHeight } from './hook';
|
||||||
import { useApp } from '../../../application';
|
|
||||||
|
|
||||||
export interface FormProps extends IFormLayoutProps {
|
export interface FormProps extends IFormLayoutProps {
|
||||||
form?: FormilyForm;
|
form?: FormilyForm;
|
||||||
@ -141,12 +142,15 @@ const WithForm = (props: WithFormProps) => {
|
|||||||
const linkageRules: any[] =
|
const linkageRules: any[] =
|
||||||
(getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || [];
|
(getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || [];
|
||||||
|
|
||||||
|
// 关闭弹窗之前,如果有未保存的数据,是否要二次确认
|
||||||
|
const { confirmBeforeClose = true } = useDataBlockProps() || ({} as any);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = uid();
|
const id = uid();
|
||||||
|
|
||||||
form.addEffects(id, () => {
|
form.addEffects(id, () => {
|
||||||
onFormInputChange(() => {
|
onFormInputChange(() => {
|
||||||
setFormValueChanged?.(true);
|
setFormValueChanged?.(confirmBeforeClose);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -157,7 +161,7 @@ const WithForm = (props: WithFormProps) => {
|
|||||||
return () => {
|
return () => {
|
||||||
form.removeEffects(id);
|
form.removeEffects(id);
|
||||||
};
|
};
|
||||||
}, [form, props.disabled, setFormValueChanged]);
|
}, [form, props.disabled, setFormValueChanged, confirmBeforeClose]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@ -210,17 +214,19 @@ const WithForm = (props: WithFormProps) => {
|
|||||||
const WithoutForm = (props) => {
|
const WithoutForm = (props) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { setFormValueChanged } = useActionContext();
|
const { setFormValueChanged } = useActionContext();
|
||||||
|
// 关闭弹窗之前,如果有未保存的数据,是否要二次确认
|
||||||
|
const { confirmBeforeClose = true } = useDataBlockProps() || ({} as any);
|
||||||
const form = useMemo(
|
const form = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createForm({
|
createForm({
|
||||||
disabled: props.disabled,
|
disabled: props.disabled,
|
||||||
effects() {
|
effects() {
|
||||||
onFormInputChange((form) => {
|
onFormInputChange((form) => {
|
||||||
setFormValueChanged?.(true);
|
setFormValueChanged?.(confirmBeforeClose);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[],
|
[confirmBeforeClose],
|
||||||
);
|
);
|
||||||
return fieldSchema['x-decorator'] === 'FormV2' ? (
|
return fieldSchema['x-decorator'] === 'FormV2' ? (
|
||||||
<FormDecorator form={form} {...props} />
|
<FormDecorator form={form} {...props} />
|
||||||
|
@ -20,7 +20,6 @@ import { FilterBlockProvider } from '../../../filter-provider/FilterProvider';
|
|||||||
import {
|
import {
|
||||||
NocoBaseRecursionField,
|
NocoBaseRecursionField,
|
||||||
RefreshComponentProvider,
|
RefreshComponentProvider,
|
||||||
useRefreshComponent,
|
|
||||||
useRefreshFieldSchema,
|
useRefreshFieldSchema,
|
||||||
} from '../../../formily/NocoBaseRecursionField';
|
} from '../../../formily/NocoBaseRecursionField';
|
||||||
import { DndContext, DndContextProps } from '../../common/dnd-context';
|
import { DndContext, DndContextProps } from '../../common/dnd-context';
|
||||||
@ -379,11 +378,9 @@ export const Grid: any = observer(
|
|||||||
}, [fieldSchema, render, InitializerComponent, showDivider]);
|
}, [fieldSchema, render, InitializerComponent, showDivider]);
|
||||||
|
|
||||||
const refreshFieldSchema = useRefreshFieldSchema();
|
const refreshFieldSchema = useRefreshFieldSchema();
|
||||||
const refreshComponent = useRefreshComponent();
|
|
||||||
const refresh = useCallback(() => {
|
const refresh = useCallback(() => {
|
||||||
refreshFieldSchema?.();
|
refreshFieldSchema?.();
|
||||||
refreshComponent?.();
|
}, [refreshFieldSchema]);
|
||||||
}, [refreshComponent, refreshFieldSchema]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RefreshComponentProvider refresh={refresh}>
|
<RefreshComponentProvider refresh={refresh}>
|
||||||
|
@ -11,22 +11,40 @@ import { LoadingOutlined } from '@ant-design/icons';
|
|||||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||||
import { Input as AntdInput } from 'antd';
|
import { Input as AntdInput } from 'antd';
|
||||||
import { InputProps, TextAreaProps } from 'antd/es/input';
|
import { InputProps, TextAreaProps } from 'antd/es/input';
|
||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { JSONTextAreaProps, Json } from './Json';
|
import { JSONTextAreaProps, Json } from './Json';
|
||||||
import { InputReadPrettyComposed, ReadPretty } from './ReadPretty';
|
import { InputReadPrettyComposed, ReadPretty } from './ReadPretty';
|
||||||
|
|
||||||
export { ReadPretty as InputReadPretty } from './ReadPretty';
|
export { ReadPretty as InputReadPretty } from './ReadPretty';
|
||||||
|
|
||||||
type ComposedInput = React.FC<InputProps> & {
|
type ComposedInput = React.FC<NocoBaseInputProps> & {
|
||||||
ReadPretty: InputReadPrettyComposed['Input'];
|
ReadPretty: InputReadPrettyComposed['Input'];
|
||||||
TextArea: React.FC<TextAreaProps> & { ReadPretty: InputReadPrettyComposed['TextArea'] };
|
TextArea: React.FC<TextAreaProps> & { ReadPretty: InputReadPrettyComposed['TextArea'] };
|
||||||
URL: React.FC<InputProps> & { ReadPretty: InputReadPrettyComposed['URL'] };
|
URL: React.FC<InputProps> & { ReadPretty: InputReadPrettyComposed['URL'] };
|
||||||
JSON: React.FC<JSONTextAreaProps> & { ReadPretty: InputReadPrettyComposed['JSON'] };
|
JSON: React.FC<JSONTextAreaProps> & { ReadPretty: InputReadPrettyComposed['JSON'] };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NocoBaseInputProps = InputProps & {
|
||||||
|
trim?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function InputInner(props: NocoBaseInputProps) {
|
||||||
|
const { onChange, trim, ...others } = props;
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (trim) {
|
||||||
|
ev.target.value = ev.target.value.trim();
|
||||||
|
}
|
||||||
|
onChange?.(ev);
|
||||||
|
},
|
||||||
|
[onChange, trim],
|
||||||
|
);
|
||||||
|
return <AntdInput {...others} onChange={handleChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
export const Input: ComposedInput = Object.assign(
|
export const Input: ComposedInput = Object.assign(
|
||||||
connect(
|
connect(
|
||||||
AntdInput,
|
InputInner,
|
||||||
mapProps((props, field) => {
|
mapProps((props, field) => {
|
||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mockApp } from '@nocobase/client/demo-utils';
|
import { mockApp } from '@nocobase/client/demo-utils';
|
||||||
import { SchemaComponent, Plugin, ISchema } from '@nocobase/client';
|
import { SchemaComponent, Plugin, ISchema } from '@nocobase/client';
|
||||||
@ -15,15 +14,25 @@ const schema: ISchema = {
|
|||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Input',
|
'x-component': 'Input',
|
||||||
},
|
},
|
||||||
|
trim: {
|
||||||
|
type: 'string',
|
||||||
|
title: `Trim heading and tailing spaces`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {
|
||||||
|
trim: true,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const Demo = () => {
|
const Demo = () => {
|
||||||
return <SchemaComponent schema={schema} />;
|
return <SchemaComponent schema={schema} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DemoPlugin extends Plugin {
|
class DemoPlugin extends Plugin {
|
||||||
async load() {
|
async load() {
|
||||||
this.app.router.add('root', { path: '/', Component: Demo })
|
this.app.router.add('root', { path: '/', Component: Demo });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +273,8 @@ const InternalPagePopups = (props: { paramsList?: PopupParams[] }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
const schemas = await Promise.all(waitList);
|
const schemas = await Promise.all(waitList);
|
||||||
const clonedSchemas = schemas.map((schema, index) => {
|
const clonedSchemas = await Promise.all(
|
||||||
|
schemas.map(async (schema, index) => {
|
||||||
if (_.isEmpty(schema)) {
|
if (_.isEmpty(schema)) {
|
||||||
return get404Schema();
|
return get404Schema();
|
||||||
}
|
}
|
||||||
@ -284,6 +285,16 @@ const InternalPagePopups = (props: { paramsList?: PopupParams[] }) => {
|
|||||||
const popupSchema = findSchemaByUid(params.puid, fieldSchema?.root);
|
const popupSchema = findSchemaByUid(params.puid, fieldSchema?.root);
|
||||||
if (popupSchema) {
|
if (popupSchema) {
|
||||||
savePopupSchemaToSchema(_.omit(popupSchema, 'parent'), schema);
|
savePopupSchemaToSchema(_.omit(popupSchema, 'parent'), schema);
|
||||||
|
} else {
|
||||||
|
// 当本地找不到 popupSchema 时,通过接口请求 puid 对应的 schema
|
||||||
|
try {
|
||||||
|
const remoteSchema = await requestSchema(params.puid);
|
||||||
|
if (remoteSchema) {
|
||||||
|
savePopupSchemaToSchema(remoteSchema, schema);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch schema for puid:', params.puid, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +308,8 @@ const InternalPagePopups = (props: { paramsList?: PopupParams[] }) => {
|
|||||||
result['x-read-pretty'] = true;
|
result['x-read-pretty'] = true;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
popupPropsRef.current = clonedSchemas.map((schema, index, items) => {
|
popupPropsRef.current = clonedSchemas.map((schema, index, items) => {
|
||||||
const schemaContext = getPopupContextFromActionOrAssociationFieldSchema(schema);
|
const schemaContext = getPopupContextFromActionOrAssociationFieldSchema(schema);
|
||||||
let hidden = false;
|
let hidden = false;
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
# Page
|
|
||||||
|
|
||||||
Can be used in conjunction with DocumentTitleProvider to display the page title on document.title.
|
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx"></code>
|
|
@ -1,5 +0,0 @@
|
|||||||
# Page
|
|
||||||
|
|
||||||
可以与 DocumentTitleProvider 搭配使用,将 page title 显示在 document.title 上。
|
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx"></code>
|
|
@ -27,10 +27,10 @@ export const mapTimeFormat = function () {
|
|||||||
...props,
|
...props,
|
||||||
format,
|
format,
|
||||||
inputReadOnly: true,
|
inputReadOnly: true,
|
||||||
value: dayjsable(props.value, format),
|
value: dayjsable(props.value, 'HH:mm:ss'),
|
||||||
onChange: (value: dayjs.Dayjs | dayjs.Dayjs[]) => {
|
onChange: (value: dayjs.Dayjs | dayjs.Dayjs[]) => {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(formatDayjsValue(value, format) || null);
|
onChange(formatDayjsValue(value, 'HH:mm:ss') || null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@ import { DeleteOutlined, DownloadOutlined, InboxOutlined, LoadingOutlined, PlusO
|
|||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { connect, mapProps, mapReadPretty, useField } from '@formily/react';
|
import { connect, mapProps, mapReadPretty, useField } from '@formily/react';
|
||||||
import { Alert, Upload as AntdUpload, Button, Modal, Progress, Space, Tooltip } from 'antd';
|
import { Alert, Upload as AntdUpload, Button, Modal, Progress, Space, Tooltip } from 'antd';
|
||||||
|
import { createGlobalStyle } from 'antd-style';
|
||||||
import useUploadStyle from 'antd/es/upload/style';
|
import useUploadStyle from 'antd/es/upload/style';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
@ -36,6 +37,12 @@ import {
|
|||||||
import { useStyles } from './style';
|
import { useStyles } from './style';
|
||||||
import type { ComposedUpload, DraggerProps, DraggerV2Props, UploadProps } from './type';
|
import type { ComposedUpload, DraggerProps, DraggerV2Props, UploadProps } from './type';
|
||||||
|
|
||||||
|
const LightBoxGlobalStyle = createGlobalStyle`
|
||||||
|
.ReactModal__Overlay.ReactModal__Overlay--after-open {
|
||||||
|
z-index: 3000 !important; // 避免预览图片时被遮挡
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
attachmentFileTypes.add({
|
attachmentFileTypes.add({
|
||||||
match(file) {
|
match(file) {
|
||||||
return matchMimetype(file, 'image/*');
|
return matchMimetype(file, 'image/*');
|
||||||
@ -62,6 +69,8 @@ attachmentFileTypes.add({
|
|||||||
[index, list],
|
[index, list],
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<LightBoxGlobalStyle />
|
||||||
<LightBox
|
<LightBox
|
||||||
// discourageDownloads={true}
|
// discourageDownloads={true}
|
||||||
mainSrc={list[index]?.url}
|
mainSrc={list[index]?.url}
|
||||||
@ -85,11 +94,12 @@ attachmentFileTypes.add({
|
|||||||
</button>,
|
</button>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const iframePreviewSupportedTypes = ['application/pdf', 'audio/*', 'image/*', 'video/*', 'text/*'];
|
const iframePreviewSupportedTypes = ['application/pdf', 'audio/*', 'image/*', 'video/*', 'text/plain'];
|
||||||
|
|
||||||
function IframePreviewer({ index, list, onSwitchIndex }) {
|
function IframePreviewer({ index, list, onSwitchIndex }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -26,12 +26,13 @@ import { Json } from '../input';
|
|||||||
const JT_VALUE_RE = /^\s*{{\s*([^{}]+)\s*}}\s*$/;
|
const JT_VALUE_RE = /^\s*{{\s*([^{}]+)\s*}}\s*$/;
|
||||||
|
|
||||||
type ParseOptions = {
|
type ParseOptions = {
|
||||||
|
defaultTypeOnNull?: string;
|
||||||
stringToDate?: boolean;
|
stringToDate?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseValue(value: any, options: ParseOptions = {}): string | string[] {
|
function parseValue(value: any, options: ParseOptions = {}): string | string[] {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return 'null';
|
return options.defaultTypeOnNull ?? 'null';
|
||||||
}
|
}
|
||||||
const type = typeof value;
|
const type = typeof value;
|
||||||
if (type === 'string') {
|
if (type === 'string') {
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useRef, useState } from 'react';
|
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Button, Input } from 'antd';
|
import { Button, Input } from 'antd';
|
||||||
|
import React, { useRef, useState } from 'react';
|
||||||
import { VariableSelect } from './VariableSelect';
|
import { VariableSelect } from './VariableSelect';
|
||||||
|
|
||||||
// NOTE: https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-onchange-event-in-react-js/46012210#46012210
|
// NOTE: https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-onchange-event-in-react-js/46012210#46012210
|
||||||
@ -80,6 +80,7 @@ export function RawTextArea(props): JSX.Element {
|
|||||||
setOptions={setOptions}
|
setOptions={setOptions}
|
||||||
onInsert={onInsert}
|
onInsert={onInsert}
|
||||||
changeOnSelect={changeOnSelect}
|
changeOnSelect={changeOnSelect}
|
||||||
|
disabled={others.disabled}
|
||||||
/>
|
/>
|
||||||
</Button.Group>
|
</Button.Group>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { useForm } from '@formily/react';
|
import { useForm } from '@formily/react';
|
||||||
import { Space, theme } from 'antd';
|
import { Space, theme } from 'antd';
|
||||||
|
import type { CascaderProps, DefaultOptionType } from 'antd/lib/cascader';
|
||||||
import useInputStyle from 'antd/es/input/style';
|
import useInputStyle from 'antd/es/input/style';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { renderToString } from 'react-dom/server';
|
import { renderToString } from 'react-dom/server';
|
||||||
@ -110,7 +111,7 @@ function renderHTML(exp: string, keyLabelMap, delimiters: [string, string] = ['{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createOptionsValueLabelMap(options: any[], fieldNames = { value: 'value', label: 'label' }) {
|
function createOptionsValueLabelMap(options: any[], fieldNames: CascaderProps['fieldNames'] = defaultFieldNames) {
|
||||||
const map = new Map<string, string[]>();
|
const map = new Map<string, string[]>();
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
map.set(option[fieldNames.value], [option[fieldNames.label]]);
|
map.set(option[fieldNames.value], [option[fieldNames.label]]);
|
||||||
@ -220,10 +221,24 @@ function useVariablesFromValue(value: string, delimiters: [string, string] = ['{
|
|||||||
}, [value, delimitersString]);
|
}, [value, delimitersString]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TextArea(props) {
|
export type TextAreaProps = {
|
||||||
|
value?: string;
|
||||||
|
scope?: Partial<DefaultOptionType>[] | (() => Partial<DefaultOptionType>[]);
|
||||||
|
onChange?(value: string): void;
|
||||||
|
disabled?: boolean;
|
||||||
|
changeOnSelect?: CascaderProps['changeOnSelect'];
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
fieldNames?: CascaderProps['fieldNames'];
|
||||||
|
trim?: boolean;
|
||||||
|
delimiters?: [string, string];
|
||||||
|
addonBefore?: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TextArea(props: TextAreaProps) {
|
||||||
const { wrapSSR, hashId, componentCls } = useStyles();
|
const { wrapSSR, hashId, componentCls } = useStyles();
|
||||||
const { scope, onChange, changeOnSelect, style, fieldNames, delimiters = ['{{', '}}'], addonBefore } = props;
|
const { scope, changeOnSelect, style, fieldNames, delimiters = ['{{', '}}'], addonBefore, trim = true } = props;
|
||||||
const value = typeof props.value === 'string' ? props.value : props.value == null ? '' : props.value.toString();
|
const value =
|
||||||
|
typeof props.value === 'string' ? props.value : props.value == null ? '' : (props.value as any).toString();
|
||||||
const variables = useVariablesFromValue(value, delimiters);
|
const variables = useVariablesFromValue(value, delimiters);
|
||||||
const inputRef = useRef<HTMLDivElement>(null);
|
const inputRef = useRef<HTMLDivElement>(null);
|
||||||
const [options, setOptions] = useState([]);
|
const [options, setOptions] = useState([]);
|
||||||
@ -241,6 +256,14 @@ export function TextArea(props) {
|
|||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
const delimitersString = delimiters.join(' ');
|
const delimitersString = delimiters.join(' ');
|
||||||
|
|
||||||
|
const onChange = useCallback(
|
||||||
|
(target: HTMLDivElement) => {
|
||||||
|
const v = getValue(target, delimiters);
|
||||||
|
props.onChange?.(trim ? v.trim() : v);
|
||||||
|
},
|
||||||
|
[delimitersString, props.onChange, trim],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
preloadOptions(scope, variables)
|
preloadOptions(scope, variables)
|
||||||
.then((preloaded) => {
|
.then((preloaded) => {
|
||||||
@ -324,9 +347,9 @@ export function TextArea(props) {
|
|||||||
|
|
||||||
setChanged(true);
|
setChanged(true);
|
||||||
setRange(getCurrentRange(current));
|
setRange(getCurrentRange(current));
|
||||||
onChange(getValue(current, delimiters));
|
onChange(current);
|
||||||
},
|
},
|
||||||
[keyLabelMap, onChange, range, delimitersString],
|
[keyLabelMap, onChange, range],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onInput = useCallback(
|
const onInput = useCallback(
|
||||||
@ -336,9 +359,9 @@ export function TextArea(props) {
|
|||||||
}
|
}
|
||||||
setChanged(true);
|
setChanged(true);
|
||||||
setRange(getCurrentRange(currentTarget));
|
setRange(getCurrentRange(currentTarget));
|
||||||
onChange(getValue(currentTarget, delimiters));
|
onChange(currentTarget);
|
||||||
},
|
},
|
||||||
[ime, onChange, delimitersString],
|
[ime, onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onBlur = useCallback(function ({ currentTarget }) {
|
const onBlur = useCallback(function ({ currentTarget }) {
|
||||||
@ -360,9 +383,9 @@ export function TextArea(props) {
|
|||||||
setIME(false);
|
setIME(false);
|
||||||
setChanged(true);
|
setChanged(true);
|
||||||
setRange(getCurrentRange(currentTarget));
|
setRange(getCurrentRange(currentTarget));
|
||||||
onChange(getValue(currentTarget, delimiters));
|
onChange(currentTarget);
|
||||||
},
|
},
|
||||||
[onChange, delimitersString],
|
[onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onPaste = useCallback(
|
const onPaste = useCallback(
|
||||||
@ -393,9 +416,9 @@ export function TextArea(props) {
|
|||||||
setChanged(true);
|
setChanged(true);
|
||||||
pasteHTML(ev.currentTarget, sanitizedHTML);
|
pasteHTML(ev.currentTarget, sanitizedHTML);
|
||||||
setRange(getCurrentRange(ev.currentTarget));
|
setRange(getCurrentRange(ev.currentTarget));
|
||||||
onChange(getValue(ev.currentTarget, delimiters));
|
onChange(ev.currentTarget);
|
||||||
},
|
},
|
||||||
[onChange, delimitersString],
|
[onChange],
|
||||||
);
|
);
|
||||||
const disabled = props.disabled || form.disabled;
|
const disabled = props.disabled || form.disabled;
|
||||||
return wrapSSR(
|
return wrapSSR(
|
||||||
|
@ -96,7 +96,6 @@ export const conditionAnalyses = async (
|
|||||||
) => {
|
) => {
|
||||||
const type = Object.keys(ruleGroup)[0] || '$and';
|
const type = Object.keys(ruleGroup)[0] || '$and';
|
||||||
const conditions = ruleGroup[type];
|
const conditions = ruleGroup[type];
|
||||||
|
|
||||||
let results = conditions.map(async (condition) => {
|
let results = conditions.map(async (condition) => {
|
||||||
if ('$and' in condition || '$or' in condition) {
|
if ('$and' in condition || '$or' in condition) {
|
||||||
return await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic);
|
return await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic);
|
||||||
@ -147,8 +146,11 @@ export const conditionAnalyses = async (
|
|||||||
if (type === '$and') {
|
if (type === '$and') {
|
||||||
return every(results, (v) => v);
|
return every(results, (v) => v);
|
||||||
} else {
|
} else {
|
||||||
|
if (results.length) {
|
||||||
return some(results, (v) => v);
|
return some(results, (v) => v);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createForm } from '@formily/core';
|
import { createForm } from '@formily/core';
|
||||||
import { Schema } from '@formily/react';
|
import { Schema, useFieldSchema } from '@formily/react';
|
||||||
import { Spin } from 'antd';
|
import { Spin } from 'antd';
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { useRemoteCollectionManagerLoading } from '../../collection-manager/CollectionManagerProvider';
|
import { useRemoteCollectionManagerLoading } from '../../collection-manager/CollectionManagerProvider';
|
||||||
@ -62,6 +62,7 @@ const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) =>
|
|||||||
});
|
});
|
||||||
const NotFoundComponent = useComponent(NotFoundPage);
|
const NotFoundComponent = useComponent(NotFoundPage);
|
||||||
const collectionManagerLoading = useRemoteCollectionManagerLoading();
|
const collectionManagerLoading = useRemoteCollectionManagerLoading();
|
||||||
|
const parentSchema = useFieldSchema();
|
||||||
|
|
||||||
if (collectionManagerLoading || loading || hidden) {
|
if (collectionManagerLoading || loading || hidden) {
|
||||||
return <Spin style={{ width: '100%', marginTop: 20 }} delay={LOADING_DELAY} />;
|
return <Spin style={{ width: '100%', marginTop: 20 }} delay={LOADING_DELAY} />;
|
||||||
@ -73,10 +74,20 @@ const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return noForm ? (
|
return noForm ? (
|
||||||
<SchemaComponent components={components} scope={scope} schema={schemaTransform(schema || {})} />
|
<SchemaComponent
|
||||||
|
components={components}
|
||||||
|
scope={scope}
|
||||||
|
schema={schemaTransform(schema || {})}
|
||||||
|
parentSchema={parentSchema}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FormProvider form={form}>
|
<FormProvider form={form}>
|
||||||
<SchemaComponent components={components} scope={scope} schema={schemaTransform(schema || {})} />
|
<SchemaComponent
|
||||||
|
components={components}
|
||||||
|
scope={scope}
|
||||||
|
schema={schemaTransform(schema || {})}
|
||||||
|
parentSchema={parentSchema}
|
||||||
|
/>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -28,6 +28,7 @@ function toSchema(schema?: any) {
|
|||||||
properties: {
|
properties: {
|
||||||
[schema.name]: schema,
|
[schema.name]: schema,
|
||||||
},
|
},
|
||||||
|
name: `p_${schema.name}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return new Schema(schema);
|
return new Schema(schema);
|
||||||
@ -52,8 +53,9 @@ interface DistributedProps {
|
|||||||
*/
|
*/
|
||||||
export const SchemaComponentOnChangeContext = createContext<SchemaComponentOnChange>({ onChange: _.noop });
|
export const SchemaComponentOnChangeContext = createContext<SchemaComponentOnChange>({ onChange: _.noop });
|
||||||
|
|
||||||
const RecursionSchemaComponent = memo((props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps) => {
|
const RecursionSchemaComponent = memo(
|
||||||
const { components, scope, schema: _schema, distributed, onChange: _onChange, ...others } = props;
|
(props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps & { parentSchema?: Schema }) => {
|
||||||
|
const { components, scope, schema: _schema, distributed, onChange: _onChange, parentSchema, ...others } = props;
|
||||||
const ctx = useContext(SchemaComponentContext);
|
const ctx = useContext(SchemaComponentContext);
|
||||||
const schema = useMemo(() => toSchema(_schema), [_schema]);
|
const schema = useMemo(() => toSchema(_schema), [_schema]);
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
@ -84,26 +86,32 @@ const RecursionSchemaComponent = memo((props: ISchemaFieldProps & SchemaComponen
|
|||||||
<SchemaComponentOnChangeContext.Provider value={onChangeValue}>
|
<SchemaComponentOnChangeContext.Provider value={onChangeValue}>
|
||||||
<SchemaComponentContext.Provider value={value}>
|
<SchemaComponentContext.Provider value={value}>
|
||||||
<SchemaComponentOptions inherit components={components} scope={scope}>
|
<SchemaComponentOptions inherit components={components} scope={scope}>
|
||||||
<NocoBaseRecursionField {...others} schema={schema} isUseFormilyField />
|
<NocoBaseRecursionField {...others} schema={schema} isUseFormilyField parentSchema={parentSchema} />
|
||||||
</SchemaComponentOptions>
|
</SchemaComponentOptions>
|
||||||
</SchemaComponentContext.Provider>
|
</SchemaComponentContext.Provider>
|
||||||
</SchemaComponentOnChangeContext.Provider>
|
</SchemaComponentOnChangeContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
RecursionSchemaComponent.displayName = 'RecursionSchemaComponent';
|
RecursionSchemaComponent.displayName = 'RecursionSchemaComponent';
|
||||||
|
|
||||||
const MemoizedSchemaComponent = memo((props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps) => {
|
const MemoizedSchemaComponent = memo(
|
||||||
const { schema, ...others } = props;
|
(props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps & { parentSchema?: Schema }) => {
|
||||||
|
const { schema, parentSchema, ...others } = props;
|
||||||
const s = useMemoizedSchema(schema);
|
const s = useMemoizedSchema(schema);
|
||||||
return <RecursionSchemaComponent {...others} schema={s} />;
|
return <RecursionSchemaComponent {...others} schema={s} parentSchema={parentSchema} />;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
MemoizedSchemaComponent.displayName = 'MemoizedSchemaComponent';
|
MemoizedSchemaComponent.displayName = 'MemoizedSchemaComponent';
|
||||||
|
|
||||||
export const SchemaComponent = memo(
|
export const SchemaComponent = memo(
|
||||||
(
|
(
|
||||||
props: (ISchemaFieldProps | IRecursionFieldProps) & { memoized?: boolean } & SchemaComponentOnChange &
|
props: (ISchemaFieldProps | IRecursionFieldProps) & {
|
||||||
|
memoized?: boolean;
|
||||||
|
parentSchema?: Schema;
|
||||||
|
} & SchemaComponentOnChange &
|
||||||
DistributedProps,
|
DistributedProps,
|
||||||
) => {
|
) => {
|
||||||
const { memoized, ...others } = props;
|
const { memoized, ...others } = props;
|
||||||
|
@ -37,7 +37,9 @@ const getPageHeaderHeight = (disablePageHeader, enablePageTabs, hidePageTitle, t
|
|||||||
token.paddingContentHorizontalLG
|
token.paddingContentHorizontalLG
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return token.controlHeight + token.marginXS + (token.paddingXXS + 2) * 2 + token.paddingContentHorizontalLG;
|
return (
|
||||||
|
token.controlHeight + token.marginXS + (token.paddingContentVertical + 2) * 2 + token.paddingContentHorizontalLG
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (enablePageTabs) {
|
if (enablePageTabs) {
|
||||||
return (
|
return (
|
||||||
@ -140,12 +142,12 @@ export const useDataBlockHeight = (options?: UseDataBlockHeightOptions) => {
|
|||||||
const { heightMode, height, title, titleHeight } = heightProps || {};
|
const { heightMode, height, title, titleHeight } = heightProps || {};
|
||||||
|
|
||||||
const blockHeaderHeight = title ? titleHeight : 0;
|
const blockHeaderHeight = title ? titleHeight : 0;
|
||||||
|
|
||||||
if (!heightProps?.heightMode || heightMode === HeightMode.DEFAULT) {
|
if (!heightProps?.heightMode || heightMode === HeightMode.DEFAULT) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (heightMode === HeightMode.FULL_HEIGHT) {
|
if (heightMode === HeightMode.FULL_HEIGHT) {
|
||||||
let res = window.innerHeight - pageFullScreenHeight;
|
let res = window.innerHeight - pageFullScreenHeight;
|
||||||
console.log(res);
|
|
||||||
if (options?.removeBlockHeaderHeight) {
|
if (options?.removeBlockHeaderHeight) {
|
||||||
res = res - blockHeaderHeight;
|
res = res - blockHeaderHeight;
|
||||||
}
|
}
|
||||||
|
@ -268,10 +268,12 @@ function FinallyButton({
|
|||||||
const { getCollection } = useCollectionManager_deprecated();
|
const { getCollection } = useCollectionManager_deprecated();
|
||||||
const aclCtx = useACLActionParamsContext();
|
const aclCtx = useACLActionParamsContext();
|
||||||
const buttonStyle = useMemo(() => {
|
const buttonStyle = useMemo(() => {
|
||||||
|
const shouldApplyOpacity = designable && (field?.data?.hidden || !aclCtx);
|
||||||
|
const opacityValue = componentType !== 'link' ? (shouldApplyOpacity ? 0.1 : 1) : 1;
|
||||||
return {
|
return {
|
||||||
opacity: designable && (field?.data?.hidden || !aclCtx) && 0.1,
|
opacity: opacityValue,
|
||||||
};
|
};
|
||||||
}, [designable, field?.data?.hidden]);
|
}, [designable, field?.data?.hidden, aclCtx, componentType]);
|
||||||
|
|
||||||
if (inheritsCollections?.length > 0) {
|
if (inheritsCollections?.length > 0) {
|
||||||
if (!linkageFromForm) {
|
if (!linkageFromForm) {
|
||||||
|
@ -470,7 +470,6 @@ export const useFilterFormItemInitializerFields = (options?: any) => {
|
|||||||
'x-collection-field': `${name}.${field.name}`,
|
'x-collection-field': `${name}.${field.name}`,
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
utc: false,
|
utc: false,
|
||||||
underFilter: true,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (isAssocField(field)) {
|
if (isAssocField(field)) {
|
||||||
@ -485,7 +484,7 @@ export const useFilterFormItemInitializerFields = (options?: any) => {
|
|||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-use-decorator-props': 'useFormItemProps',
|
'x-use-decorator-props': 'useFormItemProps',
|
||||||
'x-collection-field': `${name}.${field.name}`,
|
'x-collection-field': `${name}.${field.name}`,
|
||||||
'x-component-props': { ...field.uiSchema?.['x-component-props'], utc: false, underFilter: true },
|
'x-component-props': { ...field.uiSchema?.['x-component-props'], utc: false },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const resultItem = {
|
const resultItem = {
|
||||||
@ -580,7 +579,7 @@ const associationFieldToMenu = (
|
|||||||
interface: field.interface,
|
interface: field.interface,
|
||||||
},
|
},
|
||||||
'x-component': 'CollectionField',
|
'x-component': 'CollectionField',
|
||||||
'x-component-props': { utc: false, underFilter: true },
|
'x-component-props': { utc: false },
|
||||||
'x-read-pretty': false,
|
'x-read-pretty': false,
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-collection-field': `${collectionName}.${schemaName}`,
|
'x-collection-field': `${collectionName}.${schemaName}`,
|
||||||
@ -697,7 +696,7 @@ export const useFilterInheritsFormItemInitializerFields = (options?) => {
|
|||||||
'x-component': 'CollectionField',
|
'x-component': 'CollectionField',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-collection-field': `${name}.${field.name}`,
|
'x-collection-field': `${name}.${field.name}`,
|
||||||
'x-component-props': { utc: false, underFilter: true },
|
'x-component-props': { utc: false },
|
||||||
'x-read-pretty': field?.uiSchema?.['x-read-pretty'],
|
'x-read-pretty': field?.uiSchema?.['x-read-pretty'],
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
@ -728,7 +727,7 @@ export const useCustomFormItemInitializerFields = (options?: any) => {
|
|||||||
const remove = useRemoveGridFormItem();
|
const remove = useRemoveGridFormItem();
|
||||||
return currentFields
|
return currentFields
|
||||||
?.filter((field) => {
|
?.filter((field) => {
|
||||||
return field?.interface && field.interface !== 'snapshot' && field.type !== 'sequence';
|
return !field.inherit && field?.interface && field.interface !== 'snapshot' && field.type !== 'sequence';
|
||||||
})
|
})
|
||||||
?.map((field) => {
|
?.map((field) => {
|
||||||
const interfaceConfig = getInterface(field.interface);
|
const interfaceConfig = getInterface(field.interface);
|
||||||
|
@ -568,13 +568,14 @@ export interface SchemaSettingsSelectItemProps
|
|||||||
extends Omit<SchemaSettingsItemProps, 'onChange' | 'onClick'>,
|
extends Omit<SchemaSettingsItemProps, 'onChange' | 'onClick'>,
|
||||||
Omit<SelectWithTitleProps, 'title' | 'defaultValue'> {
|
Omit<SelectWithTitleProps, 'title' | 'defaultValue'> {
|
||||||
value?: SelectWithTitleProps['defaultValue'];
|
value?: SelectWithTitleProps['defaultValue'];
|
||||||
|
optionRender?: (option: any, info: { index: number }) => React.ReactNode;
|
||||||
}
|
}
|
||||||
export const SchemaSettingsSelectItem: FC<SchemaSettingsSelectItemProps> = (props) => {
|
export const SchemaSettingsSelectItem: FC<SchemaSettingsSelectItemProps> = (props) => {
|
||||||
const { title, options, value, onChange, ...others } = props;
|
const { title, options, value, onChange, optionRender, ...others } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaSettingsItem title={title} {...others}>
|
<SchemaSettingsItem title={title} {...others}>
|
||||||
<SelectWithTitle {...{ title, defaultValue: value, onChange, options }} />
|
<SelectWithTitle {...{ title, defaultValue: value, onChange, options, optionRender }} />
|
||||||
</SchemaSettingsItem>
|
</SchemaSettingsItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -20,6 +20,11 @@ export const useCurrentUserContext = () => {
|
|||||||
return useContext(CurrentUserContext);
|
return useContext(CurrentUserContext);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useIsLoggedIn = () => {
|
||||||
|
const ctx = useContext(CurrentUserContext);
|
||||||
|
return !!ctx?.data?.data;
|
||||||
|
};
|
||||||
|
|
||||||
export const useCurrentRoles = () => {
|
export const useCurrentRoles = () => {
|
||||||
const { allowAnonymous } = useACLRoleContext();
|
const { allowAnonymous } = useACLRoleContext();
|
||||||
const { data } = useCurrentUserContext();
|
const { data } = useCurrentUserContext();
|
||||||
@ -39,7 +44,8 @@ export const useCurrentRoles = () => {
|
|||||||
|
|
||||||
export const CurrentUserProvider = (props) => {
|
export const CurrentUserProvider = (props) => {
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const result = useRequest<any>(() =>
|
const result = useRequest<any>(
|
||||||
|
() =>
|
||||||
api
|
api
|
||||||
.request({
|
.request({
|
||||||
url: '/auth:check',
|
url: '/auth:check',
|
||||||
@ -47,6 +53,9 @@ export const CurrentUserProvider = (props) => {
|
|||||||
skipAuth: true,
|
skipAuth: true,
|
||||||
})
|
})
|
||||||
.then((res) => res?.data),
|
.then((res) => res?.data),
|
||||||
|
{
|
||||||
|
manual: !api.auth.token,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
const { render } = useAppSpin();
|
const { render } = useAppSpin();
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ import { getDataSourceHeaders } from '../data-source/utils';
|
|||||||
import { useCompile } from '../schema-component';
|
import { useCompile } from '../schema-component';
|
||||||
import useBuiltInVariables from './hooks/useBuiltinVariables';
|
import useBuiltInVariables from './hooks/useBuiltinVariables';
|
||||||
import { VariableOption, VariablesContextType } from './types';
|
import { VariableOption, VariablesContextType } from './types';
|
||||||
import { cacheLazyLoadedValues, getCachedLazyLoadedValues } from './utils/cacheLazyLoadedValues';
|
|
||||||
import { filterEmptyValues } from './utils/filterEmptyValues';
|
import { filterEmptyValues } from './utils/filterEmptyValues';
|
||||||
import { getAction } from './utils/getAction';
|
import { getAction } from './utils/getAction';
|
||||||
import { getPath } from './utils/getPath';
|
import { getPath } from './utils/getPath';
|
||||||
@ -144,14 +143,13 @@ const VariablesProvider = ({ children, filterVariables }: any) => {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
clearRequested(url);
|
clearRequested(url);
|
||||||
const value = data.data.data;
|
const value = data.data.data;
|
||||||
cacheLazyLoadedValues(item, currentVariablePath, value);
|
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
stashRequested(url, result);
|
stashRequested(url, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return getCachedLazyLoadedValues(item, currentVariablePath) || item?.[key];
|
return item?.[key];
|
||||||
});
|
});
|
||||||
current = removeThroughCollectionFields(_.flatten(await Promise.all(result)), associationField);
|
current = removeThroughCollectionFields(_.flatten(await Promise.all(result)), associationField);
|
||||||
} else if (
|
} else if (
|
||||||
@ -180,17 +178,9 @@ const VariablesProvider = ({ children, filterVariables }: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const value = data.data.data;
|
const value = data.data.data;
|
||||||
if (!getCachedLazyLoadedValues(current, currentVariablePath)) {
|
|
||||||
// Cache the API response data to avoid repeated requests
|
|
||||||
cacheLazyLoadedValues(current, currentVariablePath, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
current = removeThroughCollectionFields(value, associationField);
|
current = removeThroughCollectionFields(value, associationField);
|
||||||
} else {
|
} else {
|
||||||
current = removeThroughCollectionFields(
|
current = removeThroughCollectionFields(getValuesByPath(current, key), associationField);
|
||||||
getCachedLazyLoadedValues(current, currentVariablePath) || getValuesByPath(current, key),
|
|
||||||
associationField,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (associationField?.target) {
|
if (associationField?.target) {
|
||||||
@ -359,13 +349,8 @@ export default VariablesProvider;
|
|||||||
function shouldToRequest(value, variableCtx: Record<string, any>, variablePath: string) {
|
function shouldToRequest(value, variableCtx: Record<string, any>, variablePath: string) {
|
||||||
let result = false;
|
let result = false;
|
||||||
|
|
||||||
if (getCachedLazyLoadedValues(variableCtx, variablePath)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// value may be a reactive object, using untracked to avoid unexpected autorun
|
// value may be a reactive object, using untracked to avoid unexpected autorun
|
||||||
untracked(() => {
|
untracked(() => {
|
||||||
// fix https://nocobase.height.app/T-2502
|
|
||||||
// Compatible with `xxx to many` and `xxx to one` subform fields and subtable fields
|
// Compatible with `xxx to many` and `xxx to one` subform fields and subtable fields
|
||||||
if (JSON.stringify(value) === '[{}]' || JSON.stringify(value) === '{}') {
|
if (JSON.stringify(value) === '[{}]' || JSON.stringify(value) === '{}') {
|
||||||
result = true;
|
result = true;
|
||||||
|
@ -1,25 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const cache = new Map<Record<string, any>, any>();
|
|
||||||
|
|
||||||
export const cacheLazyLoadedValues = (variableCtx: Record<string, any>, variablePath: string, value: any) => {
|
|
||||||
const cachedValue = cache.get(variableCtx);
|
|
||||||
|
|
||||||
if (cachedValue) {
|
|
||||||
cachedValue[variablePath] = value;
|
|
||||||
} else {
|
|
||||||
cache.set(variableCtx, { [variablePath]: value });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCachedLazyLoadedValues = (variableCtx: Record<string, any>, variablePath: string) => {
|
|
||||||
const cachedValue = cache.get(variableCtx);
|
|
||||||
return cachedValue?.[variablePath];
|
|
||||||
};
|
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const REGEX_OF_VARIABLE = /^\s*\{\{\s*([a-zA-Z0-9_$-.]+?)\s*\}\}\s*$/g;
|
export const REGEX_OF_VARIABLE = /^\s*\{\{\s*([\p{L}0-9_$-.]+?)\s*\}\}\s*$/u;
|
||||||
export const REGEX_OF_VARIABLE_IN_EXPRESSION = /\{\{\s*([a-zA-Z0-9_$-.]+?)\s*\}\}/g;
|
export const REGEX_OF_VARIABLE_IN_EXPRESSION = /\{\{\s*([a-zA-Z0-9_$-.]+?)\s*\}\}/g;
|
||||||
|
|
||||||
export const isVariable = (str: unknown) => {
|
export const isVariable = (str: unknown) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "create-nocobase-app",
|
"name": "create-nocobase-app",
|
||||||
"version": "1.6.6",
|
"version": "1.6.20",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/data-source-manager",
|
"name": "@nocobase/data-source-manager",
|
||||||
"version": "1.6.6",
|
"version": "1.6.20",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/actions": "1.6.6",
|
"@nocobase/actions": "1.6.20",
|
||||||
"@nocobase/cache": "1.6.6",
|
"@nocobase/cache": "1.6.20",
|
||||||
"@nocobase/database": "1.6.6",
|
"@nocobase/database": "1.6.20",
|
||||||
"@nocobase/resourcer": "1.6.6",
|
"@nocobase/resourcer": "1.6.20",
|
||||||
"@nocobase/utils": "1.6.6",
|
"@nocobase/utils": "1.6.20",
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^8.5.8",
|
||||||
"jsonwebtoken": "^8.5.1"
|
"jsonwebtoken": "^8.5.1"
|
||||||
},
|
},
|
||||||
|
@ -7,10 +7,10 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createMockServer, mockDatabase, supertest } from '@nocobase/test';
|
|
||||||
import { SequelizeDataSource } from '../sequelize-data-source';
|
|
||||||
import { vi } from 'vitest';
|
|
||||||
import { DataSourceManager } from '@nocobase/data-source-manager';
|
import { DataSourceManager } from '@nocobase/data-source-manager';
|
||||||
|
import { createMockDatabase, createMockServer, mockDatabase, supertest } from '@nocobase/test';
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
import { SequelizeDataSource } from '../sequelize-data-source';
|
||||||
|
|
||||||
describe('example', () => {
|
describe('example', () => {
|
||||||
test.skip('case1', async () => {
|
test.skip('case1', async () => {
|
||||||
@ -41,7 +41,7 @@ describe('example', () => {
|
|||||||
name: 'test2',
|
name: 'test2',
|
||||||
});
|
});
|
||||||
|
|
||||||
const database = mockDatabase({
|
const database = await createMockDatabase({
|
||||||
tablePrefix: 'ds1_',
|
tablePrefix: 'ds1_',
|
||||||
});
|
});
|
||||||
await database.clean({ drop: true });
|
await database.clean({ drop: true });
|
||||||
@ -82,7 +82,7 @@ describe('example', () => {
|
|||||||
name: 'update-filter',
|
name: 'update-filter',
|
||||||
});
|
});
|
||||||
|
|
||||||
const database = mockDatabase({
|
const database = await createMockDatabase({
|
||||||
tablePrefix: 'ds1_',
|
tablePrefix: 'ds1_',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ describe('example', () => {
|
|||||||
name: 'update-filter',
|
name: 'update-filter',
|
||||||
});
|
});
|
||||||
|
|
||||||
const database = mockDatabase({
|
const database = await createMockDatabase({
|
||||||
tablePrefix: 'ds1_',
|
tablePrefix: 'ds1_',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ describe('example', () => {
|
|||||||
// it should be called on main datasource
|
// it should be called on main datasource
|
||||||
expect(hook).toBeCalledTimes(1);
|
expect(hook).toBeCalledTimes(1);
|
||||||
|
|
||||||
const database = mockDatabase({
|
const database = await createMockDatabase({
|
||||||
tablePrefix: 'ds1_',
|
tablePrefix: 'ds1_',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mockDatabase } from '@nocobase/test';
|
import { createMockDatabase, mockDatabase } from '@nocobase/test';
|
||||||
import Koa from 'koa';
|
import Koa from 'koa';
|
||||||
import bodyParser from 'koa-bodyparser';
|
import bodyParser from 'koa-bodyparser';
|
||||||
import supertest from 'supertest';
|
import supertest from 'supertest';
|
||||||
@ -24,7 +24,7 @@ describe('example', () => {
|
|||||||
await next();
|
await next();
|
||||||
});
|
});
|
||||||
app.use(dsm.middleware());
|
app.use(dsm.middleware());
|
||||||
const database = mockDatabase({
|
const database = await createMockDatabase({
|
||||||
tablePrefix: 'ds1_',
|
tablePrefix: 'ds1_',
|
||||||
});
|
});
|
||||||
await database.clean({ drop: true });
|
await database.clean({ drop: true });
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/database",
|
"name": "@nocobase/database",
|
||||||
"version": "1.6.6",
|
"version": "1.6.20",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/logger": "1.6.6",
|
"@nocobase/logger": "1.6.20",
|
||||||
"@nocobase/utils": "1.6.6",
|
"@nocobase/utils": "1.6.20",
|
||||||
"async-mutex": "^0.3.2",
|
"async-mutex": "^0.3.2",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
"cron-parser": "4.4.0",
|
"cron-parser": "4.4.0",
|
||||||
|
@ -7,13 +7,13 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Database } from '../../database';
|
import { Database, createMockDatabase } from '@nocobase/database';
|
||||||
import { mockDatabase } from '../index';
|
|
||||||
describe('association references', () => {
|
describe('association references', () => {
|
||||||
let db: Database;
|
let db: Database;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = await createMockDatabase();
|
||||||
|
|
||||||
await db.clean({ drop: true });
|
await db.clean({ drop: true });
|
||||||
});
|
});
|
||||||
|
@ -7,13 +7,13 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Database, mockDatabase } from '@nocobase/database';
|
import { createMockDatabase, Database } from '@nocobase/database';
|
||||||
|
|
||||||
describe('association target key', async () => {
|
describe('association target key', async () => {
|
||||||
let db: Database;
|
let db: Database;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase();
|
db = await createMockDatabase();
|
||||||
|
|
||||||
await db.clean({ drop: true });
|
await db.clean({ drop: true });
|
||||||
});
|
});
|
||||||
|
@ -7,14 +7,13 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Database } from '../database';
|
import { createMockDatabase, Database } from '@nocobase/database';
|
||||||
import { mockDatabase } from './index';
|
|
||||||
|
|
||||||
describe.skipIf(process.env['DB_DIALECT'] === 'sqlite')('collection', () => {
|
describe.skipIf(process.env['DB_DIALECT'] === 'sqlite')('collection', () => {
|
||||||
let db: Database;
|
let db: Database;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
db = mockDatabase({
|
db = await createMockDatabase({
|
||||||
logging: console.log,
|
logging: console.log,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user