Merge branch 'main' of github.com:nocobase/nocobase into huoshijie/fix-import-export-permission-bug

This commit is contained in:
aaaaaajie 2025-04-15 21:28:35 +08:00
commit 06d4b211a1
563 changed files with 14529 additions and 2713 deletions

View File

@ -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/)
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
### 🎉 New Features

View File

@ -5,6 +5,297 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
并且本项目遵循 [语义化版本](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
### 🎉 新特性

View File

@ -1,4 +1,4 @@
Updated Date: February 20, 2025
Updated Date: April 1, 2025
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.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.
@ -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.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.

View File

@ -2,14 +2,10 @@
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://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&#0045;first&#0044;&#0032;open&#0045;source&#0032;no&#0045;code&#0032;platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## リリースノート
リリースノートは[ブログ](https://www.nocobase.com/ja/blog/timeline)で随時更新され、週ごとにまとめて公開しています。
</p>
## NocoBaseはなに
@ -28,6 +24,16 @@ https://docs-cn.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. データモデル駆動

View File

@ -2,19 +2,14 @@ English | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md)
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://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&#0045;first&#0044;&#0032;open&#0045;source&#0032;no&#0045;code&#0032;platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## Release Notes
Our [blog](https://www.nocobase.com/en/blog/timeline) is regularly updated with release notes and provides a weekly summary.
</p>
## 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!
Homepage:
@ -29,6 +24,17 @@ https://docs.nocobase.com/
Forum:
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
### 1. Data model-driven

View File

@ -2,13 +2,10 @@
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://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&#0045;first&#0044;&#0032;open&#0045;source&#0032;no&#0045;code&#0032;platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## 发布日志
我们的[博客](https://www.nocobase.com/cn/blog/timeline)会及时更新发布日志,并每周进行汇总。
</p>
## NocoBase 是什么
@ -27,6 +24,15 @@ https://docs-cn.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. 数据模型驱动

View File

@ -1,5 +1,5 @@
{
"version": "1.6.6",
"version": "1.6.20",
"npmClient": "yarn",
"useWorkspaces": true,
"npmClientArgs": ["--ignore-engines"],

View File

@ -1,13 +1,13 @@
{
"name": "@nocobase/acl",
"version": "1.6.6",
"version": "1.6.20",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
"@nocobase/resourcer": "1.6.6",
"@nocobase/utils": "1.6.6",
"@nocobase/resourcer": "1.6.20",
"@nocobase/utils": "1.6.20",
"minimatch": "^5.1.1"
},
"repository": {

View File

@ -1,14 +1,14 @@
{
"name": "@nocobase/actions",
"version": "1.6.6",
"version": "1.6.20",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
"@nocobase/cache": "1.6.6",
"@nocobase/database": "1.6.6",
"@nocobase/resourcer": "1.6.6"
"@nocobase/cache": "1.6.20",
"@nocobase/database": "1.6.20",
"@nocobase/resourcer": "1.6.20"
},
"repository": {
"type": "git",

View File

@ -1,17 +1,17 @@
{
"name": "@nocobase/app",
"version": "1.6.6",
"version": "1.6.20",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
"@nocobase/database": "1.6.6",
"@nocobase/preset-nocobase": "1.6.6",
"@nocobase/server": "1.6.6"
"@nocobase/database": "1.6.20",
"@nocobase/preset-nocobase": "1.6.20",
"@nocobase/server": "1.6.20"
},
"devDependencies": {
"@nocobase/client": "1.6.6"
"@nocobase/client": "1.6.20"
},
"repository": {
"type": "git",

View File

@ -7,7 +7,7 @@
* 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 axios from 'axios';
import execa from 'execa';
@ -64,7 +64,7 @@ const createDatabase = async () => {
if (process.env.DB_DIALECT === 'sqlite') {
return 'nocobase';
}
const db = mockDatabase();
const db = await createMockDatabase();
const name = `d_${uid()}`;
await db.sequelize.query(`CREATE DATABASE ${name}`);
await db.close();

View File

@ -1,16 +1,16 @@
{
"name": "@nocobase/auth",
"version": "1.6.6",
"version": "1.6.20",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
"@nocobase/actions": "1.6.6",
"@nocobase/cache": "1.6.6",
"@nocobase/database": "1.6.6",
"@nocobase/resourcer": "1.6.6",
"@nocobase/utils": "1.6.6",
"@nocobase/actions": "1.6.20",
"@nocobase/cache": "1.6.20",
"@nocobase/database": "1.6.20",
"@nocobase/resourcer": "1.6.20",
"@nocobase/utils": "1.6.20",
"@types/jsonwebtoken": "^8.5.8",
"jsonwebtoken": "^8.5.1"
},

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/build",
"version": "1.6.6",
"version": "1.6.20",
"description": "Library build tool based on rollup.",
"main": "lib/index.js",
"types": "./lib/index.d.ts",
@ -17,7 +17,7 @@
"@lerna/project": "4.0.0",
"@rsbuild/plugin-babel": "^1.0.3",
"@rsdoctor/rspack-plugin": "^0.4.8",
"@rspack/core": "1.1.1",
"@rspack/core": "1.3.2",
"@svgr/webpack": "^8.1.0",
"@types/gulp": "^4.0.13",
"@types/lerna__package": "5.1.0",

View File

@ -347,6 +347,7 @@ export async function buildPluginClient(cwd: string, userConfig: UserConfig, sou
umdNamedDefine: true,
},
},
amd: {},
resolve: {
tsConfig: path.join(process.cwd(), 'tsconfig.json'),
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.less', '.css'],

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/cache",
"version": "1.6.6",
"version": "1.6.20",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/cli",
"version": "1.6.6",
"version": "1.6.20",
"description": "",
"license": "AGPL-3.0",
"main": "./src/index.js",
@ -8,7 +8,7 @@
"nocobase": "./bin/index.js"
},
"dependencies": {
"@nocobase/app": "1.6.6",
"@nocobase/app": "1.6.20",
"@types/fs-extra": "^11.0.1",
"@umijs/utils": "3.5.20",
"chalk": "^4.1.1",
@ -25,7 +25,7 @@
"tsx": "^4.19.0"
},
"devDependencies": {
"@nocobase/devtools": "1.6.6"
"@nocobase/devtools": "1.6.20"
},
"repository": {
"type": "git",

View File

@ -460,6 +460,16 @@ exports.initEnv = function initEnv() {
process.env.SOCKET_PATH = generateGatewayPath();
fs.mkdirpSync(dirname(process.env.SOCKET_PATH), { 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 () {

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/client",
"version": "1.6.6",
"version": "1.6.20",
"license": "AGPL-3.0",
"main": "lib/index.js",
"module": "es/index.mjs",
@ -27,9 +27,9 @@
"@formily/reactive-react": "^2.2.27",
"@formily/shared": "^2.2.27",
"@formily/validator": "^2.2.27",
"@nocobase/evaluators": "1.6.6",
"@nocobase/sdk": "1.6.6",
"@nocobase/utils": "1.6.6",
"@nocobase/evaluators": "1.6.20",
"@nocobase/sdk": "1.6.20",
"@nocobase/utils": "1.6.20",
"ahooks": "^3.7.2",
"antd": "5.12.8",
"antd-style": "3.7.1",

View File

@ -74,6 +74,7 @@ export const ACLRolesCheckProvider = (props) => {
url: 'roles:check',
},
{
manual: !api.auth.token,
onSuccess(data) {
if (!data?.data?.snippets.includes('ui.*')) {
setDesignable(false);

View File

@ -139,7 +139,19 @@ export class APIClient extends APIClientSDK {
if (typeof error?.response?.data === 'string') {
const tempElement = document.createElement('div');
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 (
error?.response?.data?.errors ||

View File

@ -350,23 +350,9 @@ export class Application {
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 = {
code: 'LOAD_ERROR',
...toError(error),
...this.apiClient.toErrMessages(error)?.[0],
};
console.error(error, this.error);
}

View File

@ -11,10 +11,11 @@ import React, { FC } from 'react';
import { MainComponent } from './MainComponent';
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 (
<div>
<div>App Error</div>
<div>{title}</div>
{error?.message}
{process.env.__TEST__ && error?.stack}
</div>

View File

@ -63,7 +63,7 @@ export const SchemaInitializerItem = memo(
className: className,
label: children || compile(title),
onClick: (info) => {
if (info.key !== name) return;
if (disabled || info.key !== name) return;
if (closeInitializerMenuWhenClick) {
setVisible?.(false);
}
@ -73,10 +73,10 @@ export const SchemaInitializerItem = memo(
children: childrenItems,
},
];
}, [name, style, className, children, title, onClick, icon, childrenItems]);
}, [name, disabled, style, className, children, title, onClick, icon, childrenItems]);
if (items && items.length > 0) {
return <SchemaInitializerMenu items={menuItems}></SchemaInitializerMenu>;
return <SchemaInitializerMenu disabled={disabled} items={menuItems}></SchemaInitializerMenu>;
}
return (
<div

View File

@ -10,11 +10,11 @@
import { useFieldSchema } from '@formily/react';
import React from 'react';
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 { CollectOperators } from './CollectOperators';
import { FormBlockProvider } from './FormBlockProvider';
import { FilterCollectionField } from '../modules/blocks/filter-blocks/FilterCollectionField';
export const FilterFormBlockProvider = withDynamicSchemaProps((props) => {
const filedSchema = useFieldSchema();
@ -35,7 +35,7 @@ export const FilterFormBlockProvider = withDynamicSchemaProps((props) => {
}}
>
<DefaultValueProvider isAllowToSetDefaultValue={() => false}>
<FormBlockProvider name="filter-form" {...props}></FormBlockProvider>
<FormBlockProvider name="filter-form" {...props} confirmBeforeClose={false}></FormBlockProvider>
</DefaultValueProvider>
</ActionBarProvider>
</DatePickerProvider>

View File

@ -167,7 +167,7 @@ export function useCollectValuesToSubmit() {
if (parsedValue !== null && parsedValue !== undefined) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
} else if (value !== '') {
assignedValues[key] = value;
}
});
@ -338,7 +338,7 @@ export const useAssociationCreateActionProps = () => {
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
} else if (value !== '') {
assignedValues[key] = value;
}
});
@ -522,9 +522,11 @@ export const useFilterBlockActionProps = () => {
const { doFilter } = useDoFilter();
const actionField = useField();
actionField.data = actionField.data || {};
const form = useForm();
return {
async onClick() {
await form.submit();
actionField.data.loading = true;
await doFilter();
actionField.data.loading = false;
@ -605,7 +607,7 @@ export const useCustomizeUpdateActionProps = () => {
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
} else if (value !== '') {
assignedValues[key] = value;
}
});
@ -708,7 +710,7 @@ export const useCustomizeBulkUpdateActionProps = () => {
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
} else if (value !== '') {
assignedValues[key] = value;
}
});
@ -930,7 +932,7 @@ export const useUpdateActionProps = () => {
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
} else if (value !== '') {
assignedValues[key] = value;
}
});

View File

@ -62,6 +62,12 @@ export class InputFieldInterface extends CollectionFieldInterface {
hasDefaultValue = true;
properties = {
...defaultProps,
trim: {
type: 'boolean',
'x-content': '{{t("Automatically remove heading and tailing spaces")}}',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
},
layout: {
type: 'void',
title: '{{t("Index")}}',

View File

@ -129,12 +129,12 @@ export const enumType = [
label: '{{t("is")}}',
value: '$eq',
selected: true,
schema: { 'x-component': 'Select' },
schema: { 'x-component': 'Select', 'x-component-props': { mode: null } },
},
{
label: '{{t("is not")}}',
value: '$ne',
schema: { 'x-component': 'Select' },
schema: { 'x-component': 'Select', 'x-component-props': { mode: null } },
},
{
label: '{{t("is any of")}}',

View File

@ -31,6 +31,12 @@ export class TextareaFieldInterface extends CollectionFieldInterface {
titleUsable = true;
properties = {
...defaultProps,
trim: {
type: 'boolean',
'x-content': '{{t("Automatically remove heading and tailing spaces")}}',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
},
};
schemaInitialize(schema: ISchema, { block }) {
if (['Table', 'Kanban'].includes(block)) {

View File

@ -18,7 +18,14 @@ export interface SelectWithTitleProps {
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 timerRef = useRef<any>(null);
return (
@ -36,6 +43,7 @@ export function SelectWithTitle({ title, defaultValue, onChange, options, fieldN
>
{title}
<Select
{...others}
open={open}
data-testid={`select-${title}`}
popupMatchSelectWidth={false}

View File

@ -18,6 +18,7 @@ import { useCollectionFieldUISchema, useIsInNocoBaseRecursionFieldContext } from
import { useDynamicComponentProps } from '../../hoc/withDynamicSchemaProps';
import { useCompile, useComponent } from '../../schema-component';
import { useIsAllowToSetDefaultValue } from '../../schema-settings/hooks/useIsAllowToSetDefaultValue';
import { isVariable } from '../../variables/utils/isVariable';
import { CollectionFieldProvider, useCollectionField } from './CollectionFieldProvider';
type Props = {
@ -102,17 +103,43 @@ const CollectionFieldInternalField = (props) => {
const dynamicProps = useDynamicComponentProps(uiSchema?.['x-use-component-props'], props);
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.
// This code is meant to fix this issue.
/**
* 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.
* This code is meant to fix this issue.
*/
if (fieldSchema['x-read-pretty'] === true && !field.readPretty) {
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]);
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) => {

View File

@ -48,6 +48,7 @@ interface INocoBaseRecursionFieldProps extends IRecursionFieldProps {
* Whether to use Formily Field class - performance will be reduced but provides better compatibility with Formily
*/
isUseFormilyField?: boolean;
parentSchema?: Schema;
}
const CollectionFieldUISchemaContext = React.createContext<CollectionFieldOptions>({});
@ -266,6 +267,7 @@ export const NocoBaseRecursionField: ReactFC<INocoBaseRecursionFieldProps> = Rea
values,
isUseFormilyField = true,
uiSchema,
parentSchema,
} = props;
const basePath = useBasePath(props);
const newFieldSchemaRef = useRef(null);
@ -279,6 +281,14 @@ export const NocoBaseRecursionField: ReactFC<INocoBaseRecursionFieldProps> = Rea
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 parent = fieldSchema.parent;
newFieldSchemaRef.current = new Schema(fieldSchema.toJSON(), parent);

View File

@ -884,5 +884,7 @@
"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.",
"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"
}

View File

@ -884,5 +884,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."
"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"
}

View File

@ -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 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?",
"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."
}

View File

@ -821,5 +821,7 @@
"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.",
"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"
}

View File

@ -54,7 +54,7 @@
"Insert inner": "Inserire dentro",
"Delete": "Eliminare",
"Disassociate": "Dissociare",
"Disassociate record": "Dissociare il record",
"Disassociate record": "Dissociare record",
"Are you sure you want to disassociate it?": "Sei sicuro di voler dissociare?",
"UI editor": "Editor UI",
"Collection": "Raccolta",
@ -80,7 +80,7 @@
"Value": "Valore",
"Disabled": "Disabilitato",
"Enabled": "Abilitato",
"Problematic": "Problematico",
"Problematic": "Con problemi",
"Setting": "Impostazioni",
"On": "Acceso",
"Off": "Spento",
@ -121,7 +121,7 @@
"pixels": "pixel",
"Screen size": "Dimensione dello schermo",
"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",
"Phone device": "Telefono",
"Tablet device": "Tablet",
@ -146,7 +146,7 @@
"Half of day": "Metà del giorno",
"Year": "Anno",
"QuarterYear": "Quarto dell' anno",
"Select grouping field": "Seleziona il campo di raggruppamento",
"Select grouping field": "Seleziona campo di raggruppamento",
"Media": "Media",
"Markdown": "Markdown",
"Wysiwyg": "Wysiwyg",
@ -175,7 +175,7 @@
"Expand all": "Espandi tutto",
"Expand/Collapse": "Espandi/Comprimi",
"Default collapse": "Comprimi di default",
"Tree table": "Tabella ad albero",
"Tree table": "Tabella struttura ad albero",
"Custom field display name": "Nome visualizzato campo personalizzato ",
"Display fields": "Visualizza campi",
"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 **.",
"Filter": "Filtro",
"Connect data blocks": "Collega blocchi di dati",
"Action type": "Tipo di operazione",
"Actions": "Operazioni",
"Action type": "Tipo di azione",
"Actions": "Azioni",
"Insert": "Inserisci",
"Insert if not exists": "Inserisci se non esiste",
"Insert if not exists, or update": "Inserisci se non esiste o aggiorna",
@ -253,8 +253,6 @@
"Percent": "Percentuale",
"Password": "Password",
"Advanced type": "Avanzato",
"Formula": "Formula",
"Formula description": "Calcola un valore in ciascun record in base ad altri campi nello stesso record.",
"Choices": "Scelte",
"Checkbox": "Casella di controllo",
"Single select": "Selezione singola",
@ -321,7 +319,6 @@
"Selector mode": "Modalità selettore",
"Subtable mode": "Modalità sotto-tabella",
"Subform mode": "Modalità sotto-modulo",
"Edit block title": "Modifica titolo blocco",
"Block title": "Titolo blocco",
"Pattern": "Modello",
"Operator": "Operatore",
@ -342,11 +339,10 @@
"is empty": "è vuoto",
"is not empty": "non è vuoto",
"Edit chart": "Modifica grafico",
"Add text": "Aggiungi testo",
"Filterable fields": "Campi filtrabili",
"Edit button": "Modifica pulsante",
"Hide": "Nascondi",
"Enable actions": "Abilita operazioni",
"Enable actions": "Abilita azioni",
"Import": "Importa",
"Export": "Esporta",
"Customize": "Personalizza",
@ -354,12 +350,12 @@
"Function": "Funzione",
"Popup form": "Modulo Popup",
"Flexible popup": "Popup flessibile",
"Configure actions": "Configura operazioni",
"Configure actions": "Configura azioni",
"Display order number": "Visualizza numero ordinamento",
"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",
"Add tab": "Aggiungi scheda",
"Disable tabs": "Disabilita le schede",
"Disable tabs": "Disabilita schede",
"Details": "Dettagli",
"Edit form": "Modifica modulo",
"Create form": "Crea modulo",
@ -373,13 +369,13 @@
"Custom column name": "Nome colonna personalizzato",
"Edit description": "Modifica descrizione",
"Required": "Richiesto",
"Unique": "Unico",
"Unique": "Univoco",
"Primary": "Primario",
"Auto increment": "Incremento automatico",
"Label field": "Campo etichetta",
"Default is the ID field": "L'impostazione predefinita è il campo ID",
"Set default sorting rules": "Imposta le regole di ordinamento predefinite",
"Set validation rules": "Imposta le regole di convalida",
"Set default sorting rules": "Imposta regole di ordinamento predefinite",
"Set validation rules": "Imposta regole di convalida",
"Max length": "Lunghezza massima",
"Min length": "Lunghezza minima",
"Maximum": "Massimo",
@ -430,8 +426,7 @@
"Text Align": "Allineamento testo",
"Add option": "Aggiungi opzione",
"Related collection": "Raccolta correlata",
"Allow linking to multiple records": "Consenti il collegamento a più record",
"Allow uploading multiple files": "Consenti il caricamento di più file",
"Allow linking to multiple records": "Consenti collegamento a più record",
"Configure calendar": "Configura calendario",
"Title field": "Campo titolo",
"Custom title": "Titolo personalizzato",
@ -471,13 +466,13 @@
"Configure": "Configura",
"Configure permissions": "Configura permessi",
"Edit role": "Modifica ruolo",
"Action permissions": "Permessi su operazioni",
"Action permissions": "Permessi su azioni",
"Menu permissions": "Permessi su menu",
"Menu item name": "Nome voce di menu",
"Allow access": "Consenti accesso",
"Action name": "Nome operazione",
"Allow action": "Consenti operazione",
"Action scope": "Ambito operazione",
"Action name": "Nome azione",
"Allow action": "Consenti azione",
"Action scope": "Ambito azione",
"Operate on new data": "Operare su nuovi dati",
"Operate on existing data": "Operare su dati esistenti",
"Yes": "Si",
@ -503,11 +498,11 @@
"Save as block template": "Salva come modello blocco",
"Block templates": "Modelli 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",
"Block type": "Tipo blocco",
"No blocks to connect": "Nessun blocco per connettersi",
"Action column": "Colonna operazioni",
"Action column": "Colonna azioni",
"Records per page": "Record per pagina",
"(Fields only)": "(Solo campi)",
"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.",
"Drawer": "Cassetto",
"Dialog": "Dialogo",
"Delete action": "Elimina operazione",
"Delete action": "Elimina azione",
"Custom column title": "Titolo colonna personalizzata",
"Column title": "Titolo colonna",
"Original title: ": "Titolo originale: ",
@ -556,7 +551,7 @@
"Then": "Poi",
"Stay on current page": "Resta sulla pagina corrente",
"Redirect to": "Reindirizza a",
"Save action": "Salva operazione",
"Save action": "Salva azione",
"Exists": "Esiste",
"Add condition": "Aggiungi condizione",
"Add condition group": "Aggiungi gruppo di condizioni",
@ -571,10 +566,7 @@
"≤": "≤",
"Role UID": "Ruolo UID",
"Precision": "Precisione",
"Formula mode": "Modalità formula",
"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",
"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",
@ -595,7 +587,7 @@
"Tab name": "Nome della scheda",
"Current record blocks": "Blocchi record attuale",
"Popup message": "Messaggio popup",
"Delete role": "Elimina il ruolo",
"Delete role": "Elimina ruolo",
"Role display name": "Nome visualizzato ruolo",
"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.",
@ -612,11 +604,11 @@
"Allows to configure interface": "Consente di configurare l'interfaccia",
"Allows to install, activate, disable plugins": "Consente di installare, attivare, disabilitare 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",
"Data scope": "Ambito dei dati",
"Action on new records": "Operazione su nuovi record",
"Action on existing records": "Operazione su record esistenti",
"Action on new records": "Azione su nuovi record",
"Action on existing records": "Azione su record esistenti",
"All records": "Tutti i record",
"Own records": "Record propri",
"Permission policy": "Policy di autorizzazione",
@ -624,14 +616,14 @@
"General": "Generale",
"Accessible": "Accessibile",
"Configure permission": "Configura permesso",
"Action permission": "Permesso operazione",
"Action permission": "Permesso azione",
"Field permission": "Permesso campo",
"Scope name": "Nome ambito",
"Unsaved changes": "Modifiche non salvate",
"Are you sure you don't want to save?": "Sei sicuro di non voler salvare?",
"Dragging": "Trascina",
"Popup": "Popup",
"Trigger workflow": "Trigger flusso di lavoro",
"Trigger workflow": "Trigger workflow",
"Request API": "Richiesta API",
"Assign field values": "Assegna valori del campo",
"Constant value": "Valore costante",
@ -763,11 +755,6 @@
"Fix block": "Fissa blocco",
"Plugin name": "Nome 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",
"Sortable": "Ordinabile",
"Enable link": "Abilita link",
@ -796,9 +783,8 @@
"Current form": "Modulo corrente",
"Current object": "Oggetto corrente",
"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",
"Assign data scope for the template": "Assegna l'ambito dei dati per il modello",
"Table selected records": "Tabella record selezionati",
"Tag": "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",
"Sign in with another account": "Accedi con un altro account",
"Return to the main application": "Torna alla applicazione principale",
"Permission deined": "Permesso negato",
"loading": "caricamento",
"name is required": "nome richiesto",
"data source": "sorgente dati",
@ -843,7 +828,7 @@
"URL search params": "Parametri di ricerca URL",
"Expand All": "Espandi tutto",
"Search": "Ricerca",
"Clear default value": "Cancella il valore predefinito",
"Clear default value": "Cancella valore predefinito",
"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.",
"is none of": "non è nessuno di",
@ -857,5 +842,245 @@
"Notification": "Notifica",
"Ellipsis overflow content": "Contenuto Ellipsis overflow",
"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"
}

View File

@ -1039,5 +1039,7 @@
"If selected, the page will display Tab pages.": "選択されている場合、ページはタブページを表示します。",
"If selected, the route will be displayed in the menu.": "選択されている場合、ルートはメニューに表示されます。",
"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エディタ」アイコンをクリックしてください"
}

View File

@ -912,5 +912,7 @@
"If selected, the page will display Tab pages.": "선택되면 페이지는 탭 페이지를 표시합니다.",
"If selected, the route will be displayed in the menu.": "선택되면 라우트는 메뉴에 표시됩니다.",
"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 편집기\" 아이콘을 클릭하십시오"
}

View File

@ -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 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?",
"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"
}

View File

@ -607,5 +607,7 @@
"If selected, the page will display Tab pages.": "Если выбран, страница будет отображать страницы с вкладками.",
"If selected, the route will be displayed in the menu.": "Если выбран, маршрут будет отображаться в меню.",
"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": "Нажмите на значок \"Редактор пользовательского интерфейса\" в правом верхнем углу, чтобы войти в режим редактора пользовательского интерфейса"
}

View File

@ -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 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?",
"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"
}

View File

@ -821,5 +821,7 @@
"If selected, the page will display Tab pages.": "Якщо вибрано, сторінка відобразить сторінки з вкладками.",
"If selected, the route will be displayed in the menu.": "Якщо вибрано, маршрут буде відображений в меню.",
"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": "Натисніть на значок \"Редактор користувацького інтерфейсу\" в правому верхньому куті, щоб увійти в режим редактора користувацького інтерфейсу."
}

View File

@ -258,6 +258,7 @@
"Parent collection fields": "父表字段",
"Basic": "基本类型",
"Single line text": "单行文本",
"Automatically remove heading and tailing spaces": "自动去除首尾空白字符",
"Long text": "多行文本",
"Phone": "手机号码",
"Email": "电子邮箱",
@ -1080,5 +1081,7 @@
"If selected, the page will display Tab pages.": "如果选中,该页面将显示标签页。",
"If selected, the route will be displayed in the menu.": "如果选中,该路由将显示在菜单中。",
"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": "点击右上角的“界面配置”图标,进入界面配置模式"
}

View File

@ -912,6 +912,7 @@
"If selected, the page will display Tab pages.": "如果選中,該頁面將顯示標籤頁。",
"If selected, the route will be displayed in the menu.": "如果選中,該路由將顯示在菜單中。",
"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": "點擊右上角的 \"介面設定\" 圖示進入介面設定模式"
}

View File

@ -40,6 +40,7 @@ test.describe('where list block can be added', () => {
await page.getByLabel('schema-initializer-Grid-').nth(1).hover();
await page.getByRole('menuitem', { name: 'Role name' }).click();
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('Admin')).toBeVisible();
await expect(page.getByLabel('block-item-CollectionField-').getByText('Member')).toBeVisible();

View File

@ -94,9 +94,13 @@ export const FilterCollectionFieldInternalField: React.FC = (props: Props) => {
// @ts-ignore
field.dataSource = uiSchema.enum;
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]);
if (!uiSchemaOrigin) return null;

View File

@ -8,6 +8,7 @@
*/
import { useField, useFieldSchema } from '@formily/react';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
import { useBlockContext, useOpenModeContext } from '../../../../';
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
@ -45,11 +46,12 @@ export const ellipsisSettingsItem: SchemaSettingsItemType = {
tableFieldInstanceList.forEach((fieldInstance) => {
fieldInstance.componentProps.ellipsis = checked;
});
schema['x-component-props']['ellipsis'] = checked;
} else {
formField.componentProps.ellipsis = checked;
}
_.set(schema, 'x-component-props.ellipsis', checked);
await dn.emit('patch', {
schema: {
'x-uid': schema['x-uid'],

View File

@ -126,6 +126,10 @@ export const getAllowMultiple = (params?: { title: string }) => {
return {
name: 'allowMultiple',
type: 'switch',
useVisible() {
const isAssociationField = useIsAssociationField();
return isAssociationField;
},
useComponentProps() {
const { t } = useTranslation();
const field = useField<Field>();

View File

@ -14,6 +14,14 @@ import React, { useCallback, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { Router } from 'react-router-dom';
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 { NocoBaseDesktopRouteType } from '../../route-switch/antd/admin-layout/convertRoutesToSchema';
import {
@ -34,6 +42,8 @@ export const LinkMenuItem = () => {
const { urlSchema, paramsSchema } = useURLAndHTMLSchema();
const parentRoute = useParentRoute();
const { createRoute } = useNocoBaseRoutes();
const dm = useDataSourceManager();
const cm = useCollectionManager();
const handleClick = useCallback(async () => {
const values = await FormDialog(
@ -41,6 +51,8 @@ export const LinkMenuItem = () => {
() => {
const history = createMemoryHistory();
return (
<DataSourceManagerProvider dataSourceManager={dm}>
<CollectionManagerProvider instance={cm} dataSource={cm?.dataSource?.key}>
<Router location={history.location} navigator={history}>
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
<FormLayout layout={'vertical'}>
@ -66,6 +78,8 @@ export const LinkMenuItem = () => {
</FormLayout>
</SchemaComponentOptions>
</Router>
</CollectionManagerProvider>
</DataSourceManagerProvider>
);
},
theme,

View File

@ -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 }) => {
const props = useErrorProps(app, error);
const props = getProps(app);
return (
<div>
<Result
@ -87,8 +87,9 @@ const AppError: FC<{ error: Error; app: Application }> = observer(
transform: translate(0, -50%);
`}
status="error"
title={app.i18n.t('App error')}
title={error?.title || app.i18n.t('App error', { ns: 'client' })}
subTitle={app.i18n.t(error?.message)}
{...props}
extra={[
<Button type="primary" key="try" onClick={() => window.location.reload()}>
{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') {
return {
status: 'info',

View File

@ -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]);
});
});

View File

@ -7,13 +7,14 @@
* 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 { HeaderViewProps } from '@ant-design/pro-layout/es/components/Header';
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 ReactDOM from 'react-dom';
import { useTranslation } from 'react-i18next';
import { Link, Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
import {
ACLRolesCheckProvider,
@ -197,12 +198,34 @@ const pageContentStyle: React.CSSProperties = {
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 = () => {
/* Use the "nb-subpages-slot-without-header-and-side" class name to locate the position of the subpages */
return (
<div className={`${layoutContentClass} nb-subpages-slot-without-header-and-side`}>
<div style={pageContentStyle}>
<Outlet />
<ShowTipWhenNoPages />
</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 { allAccessRoutes } = useAllAccessDesktopRoutes();
const location = useLocationNoUpdate();
const defaultPageUid = getDefaultPageUid(allAccessRoutes);
const defaultPageUid = findFirstPageRoute(allAccessRoutes)?.schemaUid;
return (
<>
@ -718,36 +725,6 @@ export function findRouteBySchemaUid(schemaUid: string, treeArray: any[]) {
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 { render: renderInitializer } = useSchemaInitializerRender(menuItemInitializer);
@ -869,16 +846,17 @@ function findRouteById(id: string, treeArray: any[]) {
return null;
}
function findFirstPageRoute(routes: NocoBaseDesktopRoute[]) {
export function findFirstPageRoute(routes: NocoBaseDesktopRoute[]) {
if (!routes) return;
for (const route of routes) {
for (const route of routes.filter((item) => !item.hideInMenu)) {
if (route.type === NocoBaseDesktopRouteType.page) {
return route;
}
if (route.children?.length) {
return findFirstPageRoute(route.children);
if (route.type === NocoBaseDesktopRouteType.group && route.children?.length) {
const result = findFirstPageRoute(route.children);
if (result) return result;
}
}
}

View File

@ -17,7 +17,12 @@ import { ComposedAction } from './types';
export const ActionLink: ComposedAction = withDynamicSchemaProps(
observer((props: any) => {
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' },

View File

@ -9,7 +9,7 @@
import { css } from '@emotion/css';
import { observer, useField, useFieldSchema } from '@formily/react';
import { Modal, ModalProps } from 'antd';
import { Modal, ModalProps, Skeleton } from 'antd';
import classNames from 'classnames';
import React, { FC, startTransition, useEffect, useState } from 'react';
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
@ -53,7 +53,6 @@ const ActionModalContent: FC<{ footerNodeName: string; field: any; schema: any }
if (!deferredVisible) {
return null;
}
return (
<NocoBaseRecursionField
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(
(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 ready = useDelayedVisible(visible, 200); // 200ms 与 Modal 动画时间一致
return (
<ActionContextNoRerender>
@ -154,7 +167,11 @@ export const InternalActionModal: React.FC<ActionDrawerProps<ModalProps>> = obse
)
}
>
{ready ? (
<ActionModalContent footerNodeName={footerNodeName} field={field} schema={schema} />
) : (
<Skeleton active paragraph={{ rows: 6 }} />
)}
</Modal>
</TabsContextProvider>
</zIndexContext.Provider>

View File

@ -247,7 +247,6 @@ const InternalAction: React.FC<InternalActionProps> = observer(function Com(prop
const aclCtx = useACLActionParamsContext();
const { run, element, disabled: disableAction } = useAction?.(actionCallback) || ({} as any);
const disabled = form.disabled || field.disabled || field.data?.disabled || propsDisabled || disableAction;
const buttonStyle = useMemo(() => {
return {
...style,
@ -538,6 +537,7 @@ const RenderButtonInner = observer(
Designer: React.ElementType;
designerProps: any;
title: string;
isLink?: boolean;
}) => {
const {
designable,
@ -558,6 +558,7 @@ const RenderButtonInner = observer(
Designer,
designerProps,
title,
isLink,
...others
} = props;
const debouncedClick = useCallback(
@ -582,7 +583,8 @@ const RenderButtonInner = observer(
}
const actionTitle = title || field?.title;
const { opacity, ...restButtonStyle } = buttonStyle;
const linkStyle = isLink && opacity ? { opacity } : undefined;
return (
<SortableItem
role="button"
@ -591,15 +593,19 @@ const RenderButtonInner = observer(
onMouseEnter={handleMouseEnter}
// @ts-ignore
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}
style={buttonStyle}
style={isLink ? restButtonStyle : buttonStyle}
onClick={process.env.__E2E__ ? handleButtonClick : debouncedClick} // E2E 中的点击操作都是很快的,如果加上 debounce 会导致 E2E 测试失败
component={tarComponent || Button}
className={classnames(componentCls, hashId, className, 'nb-action')}
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} />
</SortableItem>
);

View File

@ -14,6 +14,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { useAPIClient, useRequest } from '../../../api-client';
import { useCollectionManager } from '../../../data-source/collection';
import { markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
import { getDataSourceHeaders } from '../../../data-source/utils';
import { useKeepAlive } from '../../../route-switch/antd/admin-layout/KeepAlive';
import { useSchemaComponentContext } from '../../hooks';
import { AssociationFieldContext } from './context';
@ -67,9 +68,11 @@ export const AssociationFieldProvider = observer(
if (_.isUndefined(ids) || _.isNil(ids) || _.isNaN(ids)) {
return Promise.reject(null);
}
return api.request({
resource: collectionField.target,
action: Array.isArray(ids) ? 'list' : 'get',
headers: getDataSourceHeaders(cm?.dataSource?.key),
params: {
filter: {
[targetKey]: ids,

View File

@ -14,22 +14,28 @@ import { uid } from '@formily/shared';
import { Space, message } from 'antd';
import { isEqual } from 'lodash';
import { isFunction } from 'mathjs';
import React, { useEffect, useState, useContext } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
ClearCollectionFieldContext,
NocoBaseRecursionField,
RecordProvider,
SchemaComponentContext,
useAPIClient,
useCollectionRecordData,
SchemaComponentContext,
useCollectionManager_deprecated,
} from '../../../';
import { Action } from '../action';
import { VariablePopupRecordProvider } from '../../../modules/variable/variablesProvider/VariablePopupRecordProvider';
import { isVariable } from '../../../variables/utils/isVariable';
import { getInnermostKeyAndValue } from '../../common/utils/uitls';
import { Action } from '../action';
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
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) => {
const schemaComponentCtxValue = useContext(SchemaComponentContext);
@ -69,6 +75,11 @@ export const filterAnalyses = (filters): any[] => {
return results;
};
function getFieldPath(str) {
const lastIndex = str.lastIndexOf('.');
return lastIndex === -1 ? str : str.slice(0, lastIndex);
}
const InternalAssociationSelect = observer(
(props: AssociationSelectProps) => {
const { objectValue = true, addMode: propsAddMode, ...rest } = props;
@ -88,6 +99,9 @@ const InternalAssociationSelect = observer(
const resource = api.resource(collectionField.target);
const recordData = useCollectionRecordData();
const schemaComponentCtxValue = useContext(SchemaComponentContext);
const { getCollection } = useCollectionManager_deprecated();
const associationCollection = getCollection(collectionField.target);
const { filterTargetKey } = associationCollection;
useEffect(() => {
const initValue = isVariable(field.value) ? undefined : field.value;
@ -100,11 +114,14 @@ const InternalAssociationSelect = observer(
//支持深层次子表单
onFieldInputValueChange('*', (fieldPath: any) => {
const linkageFields = filterAnalyses(field.componentProps?.service?.params?.filter) || [];
const linageFieldEntire = getFieldPath(fieldPath.address.entire);
const targetFieldEntire = getFieldPath(field.address.entire);
if (
linkageFields.includes(fieldPath?.props?.name) &&
field.value &&
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);
setInnerValue(null);
@ -151,7 +168,6 @@ const InternalAssociationSelect = observer(
</div>
);
};
console.log(fieldSchema);
return (
<div key={fieldSchema.name}>
<Space.Compact style={{ display: 'flex' }}>
@ -160,7 +176,7 @@ const InternalAssociationSelect = observer(
{...rest}
size={'middle'}
objectValue={objectValue}
value={value || innerValue}
value={removeIfKeyEmpty(value || innerValue, filterTargetKey)}
service={service}
onChange={(value) => {
const val = value?.length !== 0 ? value : null;

View File

@ -13,6 +13,8 @@ import { FormProvider, connect, createSchemaField, observer, useField, useFieldS
import { uid } from '@formily/shared';
import { Select as AntdSelect, Input, Space, Spin, Tag } from 'antd';
import dayjs from 'dayjs';
import { css } from '@emotion/css';
import { debounce } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useAPIClient, useCollectionManager_deprecated } from '../../../';
@ -152,7 +154,11 @@ const CascadeSelect = connect((props) => {
} else {
associationField.value = option;
}
if (options.length === 1 && !options[0].value) {
onChange?.(null);
} else {
onChange?.(options);
}
};
const onDropdownVisibleChange = async (visible, selectedValue, index) => {
@ -238,17 +244,17 @@ export const InternalCascadeSelect = observer(
const fieldSchema = useFieldSchema();
const { loading, data: formData } = useDataBlockRequest() || {};
const initialValue = formData?.data?.[fieldSchema.name];
useEffect(() => {
const id = uid();
selectForm.addEffects(id, () => {
onFormValuesChange((form) => {
const handleFormValuesChange = debounce((form) => {
if (collectionField.interface === 'm2o') {
// 对 m2o 类型字段,提取最后一个非 null 值
const value = extractLastNonNullValueObjects(form.values?.[fieldSchema.name]);
setTimeout(() => {
form.setValuesIn(fieldSchema.name, value);
field.value = value;
});
} else {
// 对 select_array 类型字段,过滤掉空对象
const value = extractLastNonNullValueObjects(form.values?.select_array).filter(
(v) => v && Object.keys(v).length > 0,
);
@ -256,10 +262,20 @@ export const InternalCascadeSelect = observer(
field.value = value;
});
}
}, 300);
useEffect(() => {
const id = uid();
selectForm.addEffects(id, () => {
onFormValuesChange((form) => {
handleFormValuesChange(form);
});
});
return () => {
selectForm.removeEffects(id);
// 清除防抖定时器
handleFormValuesChange.cancel();
};
}, []);
@ -282,6 +298,24 @@ export const InternalCascadeSelect = observer(
items: {
type: 'void',
'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: {
sort: {
type: 'void',

View File

@ -256,8 +256,7 @@ export const SubTable: any = observer(
{field.editable && (
<Space
style={{
marginTop: '10px',
position: field.value?.length ? 'absolute' : 'relative',
position: 'relative',
bottom: '0',
gap: 15,
}}

View File

@ -602,12 +602,6 @@ const InternalNocoBaseTable = React.memo(
height: 100%;
display: flex;
flex-direction: column;
.ant-table-expanded-row-fixed {
min-height: ${tableHeight}px;
}
.ant-table-body {
min-height: ${tableHeight}px;
}
.ant-table-cell {
padding: 16px 8px;
}

View File

@ -53,6 +53,7 @@ export function useAssociationFieldContext<F extends GeneralField>() {
};
}
// 用于获取关系字段请求数据时所需的一些参数
export default function useServiceOptions(props) {
const { action = 'list', service, useOriginalFilter } = props;
const fieldSchema = useFieldSchema();

View File

@ -54,7 +54,7 @@ describe('CollectionSelect', () => {
role="button"
>
<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
class="ant-formily-item-label"
@ -191,7 +191,7 @@ describe('CollectionSelect', () => {
role="button"
>
<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
class="ant-formily-item-label"

View File

@ -289,7 +289,7 @@ DatePicker.FilterWithPicker = function FilterWithPicker(props: any) {
const [stateProps, setStateProps] = useState(newProps);
useEffect(() => {
newProps.picker = targetPicker;
const dateTimeFormat = getDateTimeFormat(targetPicker, format, showTime, timeFormat);
const dateTimeFormat = getDateTimeFormat(targetPicker, targetDateFormat, showTime, timeFormat);
newProps.format = dateTimeFormat;
setStateProps(newProps);
}, [targetPicker]);

View File

@ -65,7 +65,6 @@ export const DynamicComponent = (props: Props) => {
...props.style,
},
utc: false,
underFilter: true,
}),
name: 'value',
'x-read-pretty': false,

View File

@ -70,6 +70,7 @@ export const FilterItem = observer(
className={css`
width: 160px;
`}
showSearch
fieldNames={fieldNames}
changeOnSelect={false}
value={dataIndex}

View File

@ -92,7 +92,7 @@ export const useGetFilterFieldOptions = () => {
const getOptions = (fields, depth, usedInVariable?: boolean) => {
const options = [];
fields.forEach((field) => {
fields?.forEach((field) => {
const option = field2option(field, depth, usedInVariable);
if (option) {
options.push(option);

View File

@ -985,12 +985,11 @@ function useFormItemCollectionField() {
export function useIsAssociationField() {
const collectionField = useFormItemCollectionField();
const isAssociationField = ['obo', 'oho', 'o2o', 'o2m', 'm2m', 'm2o', 'updatedBy', 'createdBy', 'mbm'].includes(
collectionField?.interface,
);
const isAssociationField =
collectionField &&
['hasOne', 'hasMany', 'belongsTo', 'belongsToMany', 'belongsToArray'].includes(collectionField.type);
return isAssociationField;
}
export function useIsFileField() {
const cm = useCollectionManager();
const collectionField = useFormItemCollectionField();

View File

@ -37,6 +37,17 @@ const formItemWrapCss = css`
.ant-description-textarea img {
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`
@ -44,7 +55,7 @@ const formItemLabelCss = css`
padding: 0px !important;
}
> .ant-formily-item-label {
display: none;
display: none !important;
}
`;
@ -83,7 +94,6 @@ export const FormItem: any = withDynamicSchemaProps(
[formItemLabelCss]: showTitle === false,
});
}, [showTitle]);
// 联动规则中的“隐藏保留值”的效果
if (field.data?.hidden) {
return null;

View File

@ -86,11 +86,6 @@ const useParseDefaultValue = () => {
field &&
((isVariable(fieldSchema.default) && field.value == null) || field.value === fieldSchema.default || forceUpdate)
) {
// 一个变量字符串如果显示出来会比较奇怪
if (isVariable(field.value)) {
await field.reset({ forceClear: true });
}
field.loading = true;
const collectionField = !fieldSchema.name.toString().includes('.') && collection?.getField(fieldSchema.name);

View File

@ -16,8 +16,10 @@ import { ConfigProvider, theme } from 'antd';
import React, { useEffect, useMemo } from 'react';
import { useActionContext } from '..';
import { useAttach, useComponent } from '../..';
import { useApp } from '../../../application';
import { getCardItemSchema } from '../../../block-provider';
import { useTemplateBlockContext } from '../../../block-provider/TemplateBlockProvider';
import { useDataBlockProps } from '../../../data-source';
import { useDataBlockRequest } from '../../../data-source/data-block/DataBlockRequestProvider';
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
@ -27,7 +29,6 @@ import { useToken } from '../../../style';
import { useLocalVariables, useVariables } from '../../../variables';
import { useProps } from '../../hooks/useProps';
import { useFormBlockHeight } from './hook';
import { useApp } from '../../../application';
export interface FormProps extends IFormLayoutProps {
form?: FormilyForm;
@ -141,12 +142,15 @@ const WithForm = (props: WithFormProps) => {
const linkageRules: any[] =
(getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || [];
// 关闭弹窗之前,如果有未保存的数据,是否要二次确认
const { confirmBeforeClose = true } = useDataBlockProps() || ({} as any);
useEffect(() => {
const id = uid();
form.addEffects(id, () => {
onFormInputChange(() => {
setFormValueChanged?.(true);
setFormValueChanged?.(confirmBeforeClose);
});
});
@ -157,7 +161,7 @@ const WithForm = (props: WithFormProps) => {
return () => {
form.removeEffects(id);
};
}, [form, props.disabled, setFormValueChanged]);
}, [form, props.disabled, setFormValueChanged, confirmBeforeClose]);
useEffect(() => {
if (loading) {
@ -210,17 +214,19 @@ const WithForm = (props: WithFormProps) => {
const WithoutForm = (props) => {
const fieldSchema = useFieldSchema();
const { setFormValueChanged } = useActionContext();
// 关闭弹窗之前,如果有未保存的数据,是否要二次确认
const { confirmBeforeClose = true } = useDataBlockProps() || ({} as any);
const form = useMemo(
() =>
createForm({
disabled: props.disabled,
effects() {
onFormInputChange((form) => {
setFormValueChanged?.(true);
setFormValueChanged?.(confirmBeforeClose);
});
},
}),
[],
[confirmBeforeClose],
);
return fieldSchema['x-decorator'] === 'FormV2' ? (
<FormDecorator form={form} {...props} />

View File

@ -20,7 +20,6 @@ import { FilterBlockProvider } from '../../../filter-provider/FilterProvider';
import {
NocoBaseRecursionField,
RefreshComponentProvider,
useRefreshComponent,
useRefreshFieldSchema,
} from '../../../formily/NocoBaseRecursionField';
import { DndContext, DndContextProps } from '../../common/dnd-context';
@ -379,11 +378,9 @@ export const Grid: any = observer(
}, [fieldSchema, render, InitializerComponent, showDivider]);
const refreshFieldSchema = useRefreshFieldSchema();
const refreshComponent = useRefreshComponent();
const refresh = useCallback(() => {
refreshFieldSchema?.();
refreshComponent?.();
}, [refreshComponent, refreshFieldSchema]);
}, [refreshFieldSchema]);
return (
<RefreshComponentProvider refresh={refresh}>

View File

@ -11,22 +11,40 @@ import { LoadingOutlined } from '@ant-design/icons';
import { connect, mapProps, mapReadPretty } from '@formily/react';
import { Input as AntdInput } from 'antd';
import { InputProps, TextAreaProps } from 'antd/es/input';
import React from 'react';
import React, { useCallback } from 'react';
import { JSONTextAreaProps, Json } from './Json';
import { InputReadPrettyComposed, ReadPretty } from './ReadPretty';
export { ReadPretty as InputReadPretty } from './ReadPretty';
type ComposedInput = React.FC<InputProps> & {
type ComposedInput = React.FC<NocoBaseInputProps> & {
ReadPretty: InputReadPrettyComposed['Input'];
TextArea: React.FC<TextAreaProps> & { ReadPretty: InputReadPrettyComposed['TextArea'] };
URL: React.FC<InputProps> & { ReadPretty: InputReadPrettyComposed['URL'] };
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(
connect(
AntdInput,
InputInner,
mapProps((props, field) => {
return {
...props,

View File

@ -1,4 +1,3 @@
import React from 'react';
import { mockApp } from '@nocobase/client/demo-utils';
import { SchemaComponent, Plugin, ISchema } from '@nocobase/client';
@ -15,15 +14,25 @@ const schema: ISchema = {
'x-decorator': 'FormItem',
'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 = () => {
return <SchemaComponent schema={schema} />;
};
class DemoPlugin extends Plugin {
async load() {
this.app.router.add('root', { path: '/', Component: Demo })
this.app.router.add('root', { path: '/', Component: Demo });
}
}

View File

@ -273,7 +273,8 @@ const InternalPagePopups = (props: { paramsList?: PopupParams[] }) => {
);
});
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)) {
return get404Schema();
}
@ -284,6 +285,16 @@ const InternalPagePopups = (props: { paramsList?: PopupParams[] }) => {
const popupSchema = findSchemaByUid(params.puid, fieldSchema?.root);
if (popupSchema) {
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;
return result;
});
}),
);
popupPropsRef.current = clonedSchemas.map((schema, index, items) => {
const schemaContext = getPopupContextFromActionOrAssociationFieldSchema(schema);
let hidden = false;

View File

@ -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>

View File

@ -1,5 +0,0 @@
# Page
可以与 DocumentTitleProvider 搭配使用,将 page title 显示在 document.title 上。
<code src="./demos/demo1.tsx"></code>

View File

@ -27,10 +27,10 @@ export const mapTimeFormat = function () {
...props,
format,
inputReadOnly: true,
value: dayjsable(props.value, format),
value: dayjsable(props.value, 'HH:mm:ss'),
onChange: (value: dayjs.Dayjs | dayjs.Dayjs[]) => {
if (onChange) {
onChange(formatDayjsValue(value, format) || null);
onChange(formatDayjsValue(value, 'HH:mm:ss') || null);
}
},
};

View File

@ -11,6 +11,7 @@ import { DeleteOutlined, DownloadOutlined, InboxOutlined, LoadingOutlined, PlusO
import { Field } from '@formily/core';
import { connect, mapProps, mapReadPretty, useField } from '@formily/react';
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 cls from 'classnames';
import { saveAs } from 'file-saver';
@ -36,6 +37,12 @@ import {
import { useStyles } from './style';
import type { ComposedUpload, DraggerProps, DraggerV2Props, UploadProps } from './type';
const LightBoxGlobalStyle = createGlobalStyle`
.ReactModal__Overlay.ReactModal__Overlay--after-open {
z-index: 3000 !important; // 避免预览图片时被遮挡
}
`;
attachmentFileTypes.add({
match(file) {
return matchMimetype(file, 'image/*');
@ -62,6 +69,8 @@ attachmentFileTypes.add({
[index, list],
);
return (
<>
<LightBoxGlobalStyle />
<LightBox
// discourageDownloads={true}
mainSrc={list[index]?.url}
@ -85,11 +94,12 @@ attachmentFileTypes.add({
</button>,
]}
/>
</>
);
},
});
const iframePreviewSupportedTypes = ['application/pdf', 'audio/*', 'image/*', 'video/*', 'text/*'];
const iframePreviewSupportedTypes = ['application/pdf', 'audio/*', 'image/*', 'video/*', 'text/plain'];
function IframePreviewer({ index, list, onSwitchIndex }) {
const { t } = useTranslation();

View File

@ -26,12 +26,13 @@ import { Json } from '../input';
const JT_VALUE_RE = /^\s*{{\s*([^{}]+)\s*}}\s*$/;
type ParseOptions = {
defaultTypeOnNull?: string;
stringToDate?: boolean;
};
function parseValue(value: any, options: ParseOptions = {}): string | string[] {
if (value == null) {
return 'null';
return options.defaultTypeOnNull ?? 'null';
}
const type = typeof value;
if (type === 'string') {

View File

@ -7,9 +7,9 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import React, { useRef, useState } from 'react';
import { css } from '@emotion/css';
import { Button, Input } from 'antd';
import React, { useRef, useState } from 'react';
import { VariableSelect } from './VariableSelect';
// 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}
onInsert={onInsert}
changeOnSelect={changeOnSelect}
disabled={others.disabled}
/>
</Button.Group>
</div>

View File

@ -10,6 +10,7 @@
import { css, cx } from '@emotion/css';
import { useForm } from '@formily/react';
import { Space, theme } from 'antd';
import type { CascaderProps, DefaultOptionType } from 'antd/lib/cascader';
import useInputStyle from 'antd/es/input/style';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
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[]>();
for (const option of options) {
map.set(option[fieldNames.value], [option[fieldNames.label]]);
@ -220,10 +221,24 @@ function useVariablesFromValue(value: string, delimiters: [string, string] = ['{
}, [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 { scope, onChange, changeOnSelect, style, fieldNames, delimiters = ['{{', '}}'], addonBefore } = props;
const value = typeof props.value === 'string' ? props.value : props.value == null ? '' : props.value.toString();
const { scope, changeOnSelect, style, fieldNames, delimiters = ['{{', '}}'], addonBefore, trim = true } = props;
const value =
typeof props.value === 'string' ? props.value : props.value == null ? '' : (props.value as any).toString();
const variables = useVariablesFromValue(value, delimiters);
const inputRef = useRef<HTMLDivElement>(null);
const [options, setOptions] = useState([]);
@ -241,6 +256,14 @@ export function TextArea(props) {
const { token } = theme.useToken();
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(() => {
preloadOptions(scope, variables)
.then((preloaded) => {
@ -324,9 +347,9 @@ export function TextArea(props) {
setChanged(true);
setRange(getCurrentRange(current));
onChange(getValue(current, delimiters));
onChange(current);
},
[keyLabelMap, onChange, range, delimitersString],
[keyLabelMap, onChange, range],
);
const onInput = useCallback(
@ -336,9 +359,9 @@ export function TextArea(props) {
}
setChanged(true);
setRange(getCurrentRange(currentTarget));
onChange(getValue(currentTarget, delimiters));
onChange(currentTarget);
},
[ime, onChange, delimitersString],
[ime, onChange],
);
const onBlur = useCallback(function ({ currentTarget }) {
@ -360,9 +383,9 @@ export function TextArea(props) {
setIME(false);
setChanged(true);
setRange(getCurrentRange(currentTarget));
onChange(getValue(currentTarget, delimiters));
onChange(currentTarget);
},
[onChange, delimitersString],
[onChange],
);
const onPaste = useCallback(
@ -393,9 +416,9 @@ export function TextArea(props) {
setChanged(true);
pasteHTML(ev.currentTarget, sanitizedHTML);
setRange(getCurrentRange(ev.currentTarget));
onChange(getValue(ev.currentTarget, delimiters));
onChange(ev.currentTarget);
},
[onChange, delimitersString],
[onChange],
);
const disabled = props.disabled || form.disabled;
return wrapSSR(

View File

@ -96,7 +96,6 @@ export const conditionAnalyses = async (
) => {
const type = Object.keys(ruleGroup)[0] || '$and';
const conditions = ruleGroup[type];
let results = conditions.map(async (condition) => {
if ('$and' in condition || '$or' in condition) {
return await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic);
@ -147,8 +146,11 @@ export const conditionAnalyses = async (
if (type === '$and') {
return every(results, (v) => v);
} else {
if (results.length) {
return some(results, (v) => v);
}
return true;
}
};
/**

View File

@ -8,7 +8,7 @@
*/
import { createForm } from '@formily/core';
import { Schema } from '@formily/react';
import { Schema, useFieldSchema } from '@formily/react';
import { Spin } from 'antd';
import React, { memo, useMemo } from 'react';
import { useRemoteCollectionManagerLoading } from '../../collection-manager/CollectionManagerProvider';
@ -62,6 +62,7 @@ const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) =>
});
const NotFoundComponent = useComponent(NotFoundPage);
const collectionManagerLoading = useRemoteCollectionManagerLoading();
const parentSchema = useFieldSchema();
if (collectionManagerLoading || loading || hidden) {
return <Spin style={{ width: '100%', marginTop: 20 }} delay={LOADING_DELAY} />;
@ -73,10 +74,20 @@ const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) =>
}
return noForm ? (
<SchemaComponent components={components} scope={scope} schema={schemaTransform(schema || {})} />
<SchemaComponent
components={components}
scope={scope}
schema={schemaTransform(schema || {})}
parentSchema={parentSchema}
/>
) : (
<FormProvider form={form}>
<SchemaComponent components={components} scope={scope} schema={schemaTransform(schema || {})} />
<SchemaComponent
components={components}
scope={scope}
schema={schemaTransform(schema || {})}
parentSchema={parentSchema}
/>
</FormProvider>
);
};

View File

@ -28,6 +28,7 @@ function toSchema(schema?: any) {
properties: {
[schema.name]: schema,
},
name: `p_${schema.name}`,
});
}
return new Schema(schema);
@ -52,8 +53,9 @@ interface DistributedProps {
*/
export const SchemaComponentOnChangeContext = createContext<SchemaComponentOnChange>({ onChange: _.noop });
const RecursionSchemaComponent = memo((props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps) => {
const { components, scope, schema: _schema, distributed, onChange: _onChange, ...others } = props;
const RecursionSchemaComponent = memo(
(props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps & { parentSchema?: Schema }) => {
const { components, scope, schema: _schema, distributed, onChange: _onChange, parentSchema, ...others } = props;
const ctx = useContext(SchemaComponentContext);
const schema = useMemo(() => toSchema(_schema), [_schema]);
const value = useMemo(
@ -84,26 +86,32 @@ const RecursionSchemaComponent = memo((props: ISchemaFieldProps & SchemaComponen
<SchemaComponentOnChangeContext.Provider value={onChangeValue}>
<SchemaComponentContext.Provider value={value}>
<SchemaComponentOptions inherit components={components} scope={scope}>
<NocoBaseRecursionField {...others} schema={schema} isUseFormilyField />
<NocoBaseRecursionField {...others} schema={schema} isUseFormilyField parentSchema={parentSchema} />
</SchemaComponentOptions>
</SchemaComponentContext.Provider>
</SchemaComponentOnChangeContext.Provider>
);
});
},
);
RecursionSchemaComponent.displayName = 'RecursionSchemaComponent';
const MemoizedSchemaComponent = memo((props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps) => {
const { schema, ...others } = props;
const MemoizedSchemaComponent = memo(
(props: ISchemaFieldProps & SchemaComponentOnChange & DistributedProps & { parentSchema?: Schema }) => {
const { schema, parentSchema, ...others } = props;
const s = useMemoizedSchema(schema);
return <RecursionSchemaComponent {...others} schema={s} />;
});
return <RecursionSchemaComponent {...others} schema={s} parentSchema={parentSchema} />;
},
);
MemoizedSchemaComponent.displayName = 'MemoizedSchemaComponent';
export const SchemaComponent = memo(
(
props: (ISchemaFieldProps | IRecursionFieldProps) & { memoized?: boolean } & SchemaComponentOnChange &
props: (ISchemaFieldProps | IRecursionFieldProps) & {
memoized?: boolean;
parentSchema?: Schema;
} & SchemaComponentOnChange &
DistributedProps,
) => {
const { memoized, ...others } = props;

View File

@ -37,7 +37,9 @@ const getPageHeaderHeight = (disablePageHeader, enablePageTabs, hidePageTitle, t
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 {
if (enablePageTabs) {
return (
@ -140,12 +142,12 @@ export const useDataBlockHeight = (options?: UseDataBlockHeightOptions) => {
const { heightMode, height, title, titleHeight } = heightProps || {};
const blockHeaderHeight = title ? titleHeight : 0;
if (!heightProps?.heightMode || heightMode === HeightMode.DEFAULT) {
return;
}
if (heightMode === HeightMode.FULL_HEIGHT) {
let res = window.innerHeight - pageFullScreenHeight;
console.log(res);
if (options?.removeBlockHeaderHeight) {
res = res - blockHeaderHeight;
}

View File

@ -268,10 +268,12 @@ function FinallyButton({
const { getCollection } = useCollectionManager_deprecated();
const aclCtx = useACLActionParamsContext();
const buttonStyle = useMemo(() => {
const shouldApplyOpacity = designable && (field?.data?.hidden || !aclCtx);
const opacityValue = componentType !== 'link' ? (shouldApplyOpacity ? 0.1 : 1) : 1;
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 (!linkageFromForm) {

View File

@ -470,7 +470,6 @@ export const useFilterFormItemInitializerFields = (options?: any) => {
'x-collection-field': `${name}.${field.name}`,
'x-component-props': {
utc: false,
underFilter: true,
},
};
if (isAssocField(field)) {
@ -485,7 +484,7 @@ export const useFilterFormItemInitializerFields = (options?: any) => {
'x-decorator': 'FormItem',
'x-use-decorator-props': 'useFormItemProps',
'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 = {
@ -580,7 +579,7 @@ const associationFieldToMenu = (
interface: field.interface,
},
'x-component': 'CollectionField',
'x-component-props': { utc: false, underFilter: true },
'x-component-props': { utc: false },
'x-read-pretty': false,
'x-decorator': 'FormItem',
'x-collection-field': `${collectionName}.${schemaName}`,
@ -697,7 +696,7 @@ export const useFilterInheritsFormItemInitializerFields = (options?) => {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'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'],
};
return {
@ -728,7 +727,7 @@ export const useCustomFormItemInitializerFields = (options?: any) => {
const remove = useRemoveGridFormItem();
return currentFields
?.filter((field) => {
return field?.interface && field.interface !== 'snapshot' && field.type !== 'sequence';
return !field.inherit && field?.interface && field.interface !== 'snapshot' && field.type !== 'sequence';
})
?.map((field) => {
const interfaceConfig = getInterface(field.interface);

View File

@ -568,13 +568,14 @@ export interface SchemaSettingsSelectItemProps
extends Omit<SchemaSettingsItemProps, 'onChange' | 'onClick'>,
Omit<SelectWithTitleProps, 'title' | 'defaultValue'> {
value?: SelectWithTitleProps['defaultValue'];
optionRender?: (option: any, info: { index: number }) => React.ReactNode;
}
export const SchemaSettingsSelectItem: FC<SchemaSettingsSelectItemProps> = (props) => {
const { title, options, value, onChange, ...others } = props;
const { title, options, value, onChange, optionRender, ...others } = props;
return (
<SchemaSettingsItem title={title} {...others}>
<SelectWithTitle {...{ title, defaultValue: value, onChange, options }} />
<SelectWithTitle {...{ title, defaultValue: value, onChange, options, optionRender }} />
</SchemaSettingsItem>
);
};

View File

@ -20,6 +20,11 @@ export const useCurrentUserContext = () => {
return useContext(CurrentUserContext);
};
export const useIsLoggedIn = () => {
const ctx = useContext(CurrentUserContext);
return !!ctx?.data?.data;
};
export const useCurrentRoles = () => {
const { allowAnonymous } = useACLRoleContext();
const { data } = useCurrentUserContext();
@ -39,7 +44,8 @@ export const useCurrentRoles = () => {
export const CurrentUserProvider = (props) => {
const api = useAPIClient();
const result = useRequest<any>(() =>
const result = useRequest<any>(
() =>
api
.request({
url: '/auth:check',
@ -47,6 +53,9 @@ export const CurrentUserProvider = (props) => {
skipAuth: true,
})
.then((res) => res?.data),
{
manual: !api.auth.token,
},
);
const { render } = useAppSpin();

View File

@ -18,7 +18,6 @@ import { getDataSourceHeaders } from '../data-source/utils';
import { useCompile } from '../schema-component';
import useBuiltInVariables from './hooks/useBuiltinVariables';
import { VariableOption, VariablesContextType } from './types';
import { cacheLazyLoadedValues, getCachedLazyLoadedValues } from './utils/cacheLazyLoadedValues';
import { filterEmptyValues } from './utils/filterEmptyValues';
import { getAction } from './utils/getAction';
import { getPath } from './utils/getPath';
@ -144,14 +143,13 @@ const VariablesProvider = ({ children, filterVariables }: any) => {
.then((data) => {
clearRequested(url);
const value = data.data.data;
cacheLazyLoadedValues(item, currentVariablePath, value);
return value;
});
stashRequested(url, result);
return result;
}
}
return getCachedLazyLoadedValues(item, currentVariablePath) || item?.[key];
return item?.[key];
});
current = removeThroughCollectionFields(_.flatten(await Promise.all(result)), associationField);
} else if (
@ -180,17 +178,9 @@ const VariablesProvider = ({ children, filterVariables }: any) => {
}
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);
} else {
current = removeThroughCollectionFields(
getCachedLazyLoadedValues(current, currentVariablePath) || getValuesByPath(current, key),
associationField,
);
current = removeThroughCollectionFields(getValuesByPath(current, key), associationField);
}
if (associationField?.target) {
@ -359,13 +349,8 @@ export default VariablesProvider;
function shouldToRequest(value, variableCtx: Record<string, any>, variablePath: string) {
let result = false;
if (getCachedLazyLoadedValues(variableCtx, variablePath)) {
return false;
}
// value may be a reactive object, using untracked to avoid unexpected autorun
untracked(() => {
// fix https://nocobase.height.app/T-2502
// Compatible with `xxx to many` and `xxx to one` subform fields and subtable fields
if (JSON.stringify(value) === '[{}]' || JSON.stringify(value) === '{}') {
result = true;

View File

@ -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];
};

View File

@ -7,7 +7,7 @@
* 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 isVariable = (str: unknown) => {

View File

@ -1,6 +1,6 @@
{
"name": "create-nocobase-app",
"version": "1.6.6",
"version": "1.6.20",
"main": "src/index.js",
"license": "AGPL-3.0",
"dependencies": {

View File

@ -1,16 +1,16 @@
{
"name": "@nocobase/data-source-manager",
"version": "1.6.6",
"version": "1.6.20",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
"@nocobase/actions": "1.6.6",
"@nocobase/cache": "1.6.6",
"@nocobase/database": "1.6.6",
"@nocobase/resourcer": "1.6.6",
"@nocobase/utils": "1.6.6",
"@nocobase/actions": "1.6.20",
"@nocobase/cache": "1.6.20",
"@nocobase/database": "1.6.20",
"@nocobase/resourcer": "1.6.20",
"@nocobase/utils": "1.6.20",
"@types/jsonwebtoken": "^8.5.8",
"jsonwebtoken": "^8.5.1"
},

View File

@ -7,10 +7,10 @@
* 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 { createMockDatabase, createMockServer, mockDatabase, supertest } from '@nocobase/test';
import { vi } from 'vitest';
import { SequelizeDataSource } from '../sequelize-data-source';
describe('example', () => {
test.skip('case1', async () => {
@ -41,7 +41,7 @@ describe('example', () => {
name: 'test2',
});
const database = mockDatabase({
const database = await createMockDatabase({
tablePrefix: 'ds1_',
});
await database.clean({ drop: true });
@ -82,7 +82,7 @@ describe('example', () => {
name: 'update-filter',
});
const database = mockDatabase({
const database = await createMockDatabase({
tablePrefix: 'ds1_',
});
@ -128,7 +128,7 @@ describe('example', () => {
name: 'update-filter',
});
const database = mockDatabase({
const database = await createMockDatabase({
tablePrefix: 'ds1_',
});
@ -198,7 +198,7 @@ describe('example', () => {
// it should be called on main datasource
expect(hook).toBeCalledTimes(1);
const database = mockDatabase({
const database = await createMockDatabase({
tablePrefix: 'ds1_',
});

View File

@ -7,7 +7,7 @@
* 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 bodyParser from 'koa-bodyparser';
import supertest from 'supertest';
@ -24,7 +24,7 @@ describe('example', () => {
await next();
});
app.use(dsm.middleware());
const database = mockDatabase({
const database = await createMockDatabase({
tablePrefix: 'ds1_',
});
await database.clean({ drop: true });

View File

@ -1,13 +1,13 @@
{
"name": "@nocobase/database",
"version": "1.6.6",
"version": "1.6.20",
"description": "",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
"dependencies": {
"@nocobase/logger": "1.6.6",
"@nocobase/utils": "1.6.6",
"@nocobase/logger": "1.6.20",
"@nocobase/utils": "1.6.20",
"async-mutex": "^0.3.2",
"chalk": "^4.1.1",
"cron-parser": "4.4.0",

View File

@ -7,13 +7,13 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Database } from '../../database';
import { mockDatabase } from '../index';
import { Database, createMockDatabase } from '@nocobase/database';
describe('association references', () => {
let db: Database;
beforeEach(async () => {
db = mockDatabase();
db = await createMockDatabase();
await db.clean({ drop: true });
});

View File

@ -7,13 +7,13 @@
* 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 () => {
let db: Database;
beforeEach(async () => {
db = mockDatabase();
db = await createMockDatabase();
await db.clean({ drop: true });
});

View File

@ -7,14 +7,13 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Database } from '../database';
import { mockDatabase } from './index';
import { createMockDatabase, Database } from '@nocobase/database';
describe.skipIf(process.env['DB_DIALECT'] === 'sqlite')('collection', () => {
let db: Database;
beforeEach(async () => {
db = mockDatabase({
db = await createMockDatabase({
logging: console.log,
});

Some files were not shown because too many files have changed in this diff Show More