mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-08 23:19:26 +08:00
Merge branch 'develop' into feat/commercial
This commit is contained in:
commit
e24b828dab
336
CHANGELOG.md
336
CHANGELOG.md
@ -5,6 +5,342 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [v1.6.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
|
||||||
|
|
||||||
|
- **[client]** support long text fields as title fields for association field ([#6495](https://github.com/nocobase/nocobase/pull/6495)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Workflow: Aggregate node]** Support to configure precision for aggregation result ([#6491](https://github.com/nocobase/nocobase/pull/6491)) by @mytharcher
|
||||||
|
|
||||||
|
### 🚀 Improvements
|
||||||
|
|
||||||
|
- **[File storage: S3(Pro)]** Change the text 'Access URL Base' to 'Base URL' by @zhangzhonghe
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[evaluators]** Revert round decimal places to 9 ([#6492](https://github.com/nocobase/nocobase/pull/6492)) by @mytharcher
|
||||||
|
|
||||||
|
- **[File manager]** encode url ([#6497](https://github.com/nocobase/nocobase/pull/6497)) by @chenos
|
||||||
|
|
||||||
|
- **[Data source: Main]** Unable to create a MySQL view. ([#6477](https://github.com/nocobase/nocobase/pull/6477)) by @aaaaaajie
|
||||||
|
|
||||||
|
- **[Workflow]** Fix legacy tasks count after workflow deleted ([#6493](https://github.com/nocobase/nocobase/pull/6493)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Embed NocoBase]** Page displays blank by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[Backup manager]**
|
||||||
|
- Upload files have not been restored when creating sub-app from backup template by @gchust
|
||||||
|
|
||||||
|
- MySQL database restore failure caused by GTID set overlap by @gchust
|
||||||
|
|
||||||
|
- **[Workflow: Approval]**
|
||||||
|
- Change returned approval as todo by @mytharcher
|
||||||
|
|
||||||
|
- Fix action button missed in process table by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.5](https://github.com/nocobase/nocobase/compare/v1.6.4...v1.6.5) - 2025-03-17
|
||||||
|
|
||||||
|
### 🚀 Improvements
|
||||||
|
|
||||||
|
- **[File manager]** Simplify file URL generating logic and API ([#6472](https://github.com/nocobase/nocobase/pull/6472)) by @mytharcher
|
||||||
|
|
||||||
|
- **[File storage: S3(Pro)]** Change to a simple way to generate file URL by @mytharcher
|
||||||
|
|
||||||
|
- **[Backup manager]** Allow restore backup between pre release and release version of the same version by @gchust
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- rich text field not clearing data on submission ([#6486](https://github.com/nocobase/nocobase/pull/6486)) by @katherinehhh
|
||||||
|
|
||||||
|
- The color of the icons in the upper right corner of the page does not change with the theme ([#6482](https://github.com/nocobase/nocobase/pull/6482)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- Clicking the reset button on the filter form cannot clear the filtering conditions of the grid card block ([#6475](https://github.com/nocobase/nocobase/pull/6475)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[Workflow: Manual node]**
|
||||||
|
- Fix migration ([#6484](https://github.com/nocobase/nocobase/pull/6484)) by @mytharcher
|
||||||
|
|
||||||
|
- Change migration name to ensure rerun ([#6487](https://github.com/nocobase/nocobase/pull/6487)) by @mytharcher
|
||||||
|
|
||||||
|
- Fix workflow title field in filter ([#6480](https://github.com/nocobase/nocobase/pull/6480)) by @mytharcher
|
||||||
|
|
||||||
|
- Fix migration error when id column is not exists ([#6470](https://github.com/nocobase/nocobase/pull/6470)) by @chenos
|
||||||
|
|
||||||
|
- Avoid collection synchronized from fields ([#6478](https://github.com/nocobase/nocobase/pull/6478)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Workflow: Aggregate node]** Fix round on null result ([#6473](https://github.com/nocobase/nocobase/pull/6473)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Workflow]** Don't count tasks when workflow deleted ([#6474](https://github.com/nocobase/nocobase/pull/6474)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Backup manager]** Not able to start server when missing default backup settings by @gchust
|
||||||
|
|
||||||
|
- **[Workflow: Approval]**
|
||||||
|
- Fix file association field error in process form by @mytharcher
|
||||||
|
|
||||||
|
- Fix tasks count based on hooks by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.4](https://github.com/nocobase/nocobase/compare/v1.6.3...v1.6.4) - 2025-03-14
|
||||||
|
|
||||||
|
### 🎉 New Features
|
||||||
|
|
||||||
|
- **[client]** Cascade Selection Component Add Data Scope Setting ([#6386](https://github.com/nocobase/nocobase/pull/6386)) by @Cyx649312038
|
||||||
|
|
||||||
|
### 🚀 Improvements
|
||||||
|
|
||||||
|
- **[utils]** Move `md5` to utils ([#6468](https://github.com/nocobase/nocobase/pull/6468)) by @mytharcher
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]** In the tree block, when unchecked, the data in the data block is not being cleared ([#6460](https://github.com/nocobase/nocobase/pull/6460)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[File manager]** Unable to delete files stored in S3. ([#6467](https://github.com/nocobase/nocobase/pull/6467)) by @chenos
|
||||||
|
|
||||||
|
- **[Workflow]** Remove bind workflow settings button from data picker ([#6455](https://github.com/nocobase/nocobase/pull/6455)) by @mytharcher
|
||||||
|
|
||||||
|
- **[File storage: S3(Pro)]** Resolve issue with inaccessible S3 Pro signed URLs by @chenos
|
||||||
|
|
||||||
|
- **[Workflow: Approval]** Avoid page crash when no applicant in approval process table by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.3](https://github.com/nocobase/nocobase/compare/v1.6.2...v1.6.3) - 2025-03-13
|
||||||
|
|
||||||
|
### 🎉 New Features
|
||||||
|
|
||||||
|
- **[WeCom]** Allows setting a custom tooltip for the sign-in button by @2013xile
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- Fix special character in image URL caused not showing ([#6459](https://github.com/nocobase/nocobase/pull/6459)) by @mytharcher
|
||||||
|
|
||||||
|
- incorrect page number when adding data after subtable page size change ([#6437](https://github.com/nocobase/nocobase/pull/6437)) by @katherinehhh
|
||||||
|
|
||||||
|
- The logo style is inconsistent with the previous one ([#6444](https://github.com/nocobase/nocobase/pull/6444)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[Workflow: Manual node]** Fix error thrown in migration ([#6445](https://github.com/nocobase/nocobase/pull/6445)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Data visualization]** Removed fields appear when adding custom filter fields ([#6450](https://github.com/nocobase/nocobase/pull/6450)) by @2013xile
|
||||||
|
|
||||||
|
- **[File manager]** Fix a few issues of file manager ([#6436](https://github.com/nocobase/nocobase/pull/6436)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Action: Custom request]** custom request server-side permission validation error ([#6438](https://github.com/nocobase/nocobase/pull/6438)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Data source manager]** switching data source in role management does not load corresponding collections ([#6431](https://github.com/nocobase/nocobase/pull/6431)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Action: Batch edit]** Fix workflow can not be triggered in bulk edit submission ([#6440](https://github.com/nocobase/nocobase/pull/6440)) by @mytharcher
|
||||||
|
|
||||||
|
- **[Workflow: Custom action event]** Remove `only` in E2E test case by @mytharcher
|
||||||
|
|
||||||
|
- **[Workflow: Approval]**
|
||||||
|
- Fix association data not showing in approval form by @mytharcher
|
||||||
|
|
||||||
|
- Fix error thrown when approve on external data source by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.2](https://github.com/nocobase/nocobase/compare/v1.6.1...v1.6.2) - 2025-03-12
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **[client]** date field range selection excludes the max date ([#6418](https://github.com/nocobase/nocobase/pull/6418)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[Notification: In-app message]** Avoid wrong receivers configuration query all users ([#6424](https://github.com/nocobase/nocobase/pull/6424)) by @sheldon66
|
||||||
|
|
||||||
|
- **[Workflow: Manual node]**
|
||||||
|
- Fix migration which missed table prefix and schema logic ([#6425](https://github.com/nocobase/nocobase/pull/6425)) by @mytharcher
|
||||||
|
|
||||||
|
- Change version limit of migration to `<1.7.0` ([#6430](https://github.com/nocobase/nocobase/pull/6430)) by @mytharcher
|
||||||
|
|
||||||
## [v1.6.1](https://github.com/nocobase/nocobase/compare/v1.6.0...v1.6.1) - 2025-03-11
|
## [v1.6.1](https://github.com/nocobase/nocobase/compare/v1.6.0...v1.6.1) - 2025-03-11
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
@ -5,6 +5,342 @@
|
|||||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||||
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
||||||
|
|
||||||
|
## [v1.6.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
|
||||||
|
|
||||||
|
### 🎉 新特性
|
||||||
|
|
||||||
|
- **[client]** 支持长文本字段作为关系字段的标题字段 ([#6495](https://github.com/nocobase/nocobase/pull/6495)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[工作流:聚合查询节点]** 支持为聚合结果配置精度选项 ([#6491](https://github.com/nocobase/nocobase/pull/6491)) by @mytharcher
|
||||||
|
|
||||||
|
### 🚀 优化
|
||||||
|
|
||||||
|
- **[文件存储:S3 (Pro)]** 将文案“访问 URL 基础”改为“基础 URL” by @zhangzhonghe
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[evaluators]** 将表达式计算保留小数调整回 9 位 ([#6492](https://github.com/nocobase/nocobase/pull/6492)) by @mytharcher
|
||||||
|
|
||||||
|
- **[文件管理器]** URL 转义 ([#6497](https://github.com/nocobase/nocobase/pull/6497)) by @chenos
|
||||||
|
|
||||||
|
- **[数据源:主数据库]** 无法创建 MySQL 视图 ([#6477](https://github.com/nocobase/nocobase/pull/6477)) by @aaaaaajie
|
||||||
|
|
||||||
|
- **[工作流]** 修复历史遗留任务数量工作流删除后统计错误 ([#6493](https://github.com/nocobase/nocobase/pull/6493)) by @mytharcher
|
||||||
|
|
||||||
|
- **[嵌入 NocoBase]** 页面显示空白 by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[备份管理器]**
|
||||||
|
- 通过多应用模板创建子应用时备份中的上传文件未被正确还原 by @gchust
|
||||||
|
|
||||||
|
- 还原 MySQL 数据库备份时由于 GTID 集合重叠导致的失败 by @gchust
|
||||||
|
|
||||||
|
- **[工作流:审批]**
|
||||||
|
- 将退回的审批单据列入待办 by @mytharcher
|
||||||
|
|
||||||
|
- 修复审批过程表格中发起人查看按钮消失的问题 by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.5](https://github.com/nocobase/nocobase/compare/v1.6.4...v1.6.5) - 2025-03-17
|
||||||
|
|
||||||
|
### 🚀 优化
|
||||||
|
|
||||||
|
- **[文件管理器]** 简化生成文件 URL 的逻辑和 API ([#6472](https://github.com/nocobase/nocobase/pull/6472)) by @mytharcher
|
||||||
|
|
||||||
|
- **[文件存储:S3 (Pro)]** 优化生成文件 URL 的方法 by @mytharcher
|
||||||
|
|
||||||
|
- **[备份管理器]** 允许在相同版本的预发布和发布版本之间恢复备份 by @gchust
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 富文本字段清空后提交时数据未删除 ([#6486](https://github.com/nocobase/nocobase/pull/6486)) by @katherinehhh
|
||||||
|
|
||||||
|
- 页面右上角图标的颜色不会随主题变化 ([#6482](https://github.com/nocobase/nocobase/pull/6482)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- 点击筛选表单的重置按钮无法清除网格卡片区块的筛选条件 ([#6475](https://github.com/nocobase/nocobase/pull/6475)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[工作流:人工处理节点]**
|
||||||
|
- 修复迁移脚本 ([#6484](https://github.com/nocobase/nocobase/pull/6484)) by @mytharcher
|
||||||
|
|
||||||
|
- 修改迁移脚本确保执行 ([#6487](https://github.com/nocobase/nocobase/pull/6487)) by @mytharcher
|
||||||
|
|
||||||
|
- 修复区块的筛选组件中工作流标题项 ([#6480](https://github.com/nocobase/nocobase/pull/6480)) by @mytharcher
|
||||||
|
|
||||||
|
- 修复 id 列不存在时迁移脚本报错 ([#6470](https://github.com/nocobase/nocobase/pull/6470)) by @chenos
|
||||||
|
|
||||||
|
- 避免历史表被关系字段同步出来 ([#6478](https://github.com/nocobase/nocobase/pull/6478)) by @mytharcher
|
||||||
|
|
||||||
|
- **[工作流:聚合查询节点]** 修复对聚合结果为 null 时取整报错 ([#6473](https://github.com/nocobase/nocobase/pull/6473)) by @mytharcher
|
||||||
|
|
||||||
|
- **[工作流]** 不统计已删除的工作流的待办 ([#6474](https://github.com/nocobase/nocobase/pull/6474)) by @mytharcher
|
||||||
|
|
||||||
|
- **[备份管理器]** 默认的备份设置不存在时服务器无法启动 by @gchust
|
||||||
|
|
||||||
|
- **[工作流:审批]**
|
||||||
|
- 修复审批表单中文件字段报错问题 by @mytharcher
|
||||||
|
|
||||||
|
- 基于钩子事件修复待办任务数量 by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.4](https://github.com/nocobase/nocobase/compare/v1.6.3...v1.6.4) - 2025-03-14
|
||||||
|
|
||||||
|
### 🎉 新特性
|
||||||
|
|
||||||
|
- **[client]** 级联选择组件添加数据范围设置 ([#6386](https://github.com/nocobase/nocobase/pull/6386)) by @Cyx649312038
|
||||||
|
|
||||||
|
### 🚀 优化
|
||||||
|
|
||||||
|
- **[utils]** 将 `md5` 方法移到通用包 ([#6468](https://github.com/nocobase/nocobase/pull/6468)) by @mytharcher
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]** 在树区块中,取消选中时,数据区块的数据没有被清空 ([#6460](https://github.com/nocobase/nocobase/pull/6460)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[文件管理器]** 无法删除 s3 文件存储的文件 ([#6467](https://github.com/nocobase/nocobase/pull/6467)) by @chenos
|
||||||
|
|
||||||
|
- **[工作流]** 在数据选择器中移除绑定工作流的配置按钮 ([#6455](https://github.com/nocobase/nocobase/pull/6455)) by @mytharcher
|
||||||
|
|
||||||
|
- **[文件存储:S3 (Pro)]** 修复 s3 pro 的签名 url 无法访问的问题 by @chenos
|
||||||
|
|
||||||
|
- **[工作流:审批]** 避免审批流程表格中由于没有发起人时的页面崩溃 by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.3](https://github.com/nocobase/nocobase/compare/v1.6.2...v1.6.3) - 2025-03-13
|
||||||
|
|
||||||
|
### 🎉 新特性
|
||||||
|
|
||||||
|
- **[企业微信]** 支持自定义登录按钮提示 by @2013xile
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]**
|
||||||
|
- 修复图片中特殊字符导致不显示的问题 ([#6459](https://github.com/nocobase/nocobase/pull/6459)) by @mytharcher
|
||||||
|
|
||||||
|
- 子表格切换分页数后新增数据页码显示错误 ([#6437](https://github.com/nocobase/nocobase/pull/6437)) by @katherinehhh
|
||||||
|
|
||||||
|
- Logo 的样式与之前的不一致 ([#6444](https://github.com/nocobase/nocobase/pull/6444)) by @zhangzhonghe
|
||||||
|
|
||||||
|
- **[工作流:人工处理节点]** 修复迁移脚本报错 ([#6445](https://github.com/nocobase/nocobase/pull/6445)) by @mytharcher
|
||||||
|
|
||||||
|
- **[数据可视化]** 添加自定义筛选字段时会出现已移除字段 ([#6450](https://github.com/nocobase/nocobase/pull/6450)) by @2013xile
|
||||||
|
|
||||||
|
- **[文件管理器]** 修复文件管理一些问题 ([#6436](https://github.com/nocobase/nocobase/pull/6436)) by @mytharcher
|
||||||
|
|
||||||
|
- **[操作:自定义请求]** 自定义请求的服务端权限校验错误 ([#6438](https://github.com/nocobase/nocobase/pull/6438)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[数据源管理]** 角色管理中切换数据源没有加载对应数据表 ([#6431](https://github.com/nocobase/nocobase/pull/6431)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[操作:批量编辑]** 修复批量编辑提交时未能触发工作流的问题 ([#6440](https://github.com/nocobase/nocobase/pull/6440)) by @mytharcher
|
||||||
|
|
||||||
|
- **[工作流:自定义操作事件]** 移除 E2E 测试中的 `only` by @mytharcher
|
||||||
|
|
||||||
|
- **[工作流:审批]**
|
||||||
|
- 修复审批表单中关系数据未展示的问题 by @mytharcher
|
||||||
|
|
||||||
|
- 修复外部数据源审批时的报错 by @mytharcher
|
||||||
|
|
||||||
|
## [v1.6.2](https://github.com/nocobase/nocobase/compare/v1.6.1...v1.6.2) - 2025-03-12
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
|
||||||
|
- **[client]** 表单日期字段日期范围,最大日期可选范围少一天 ([#6418](https://github.com/nocobase/nocobase/pull/6418)) by @katherinehhh
|
||||||
|
|
||||||
|
- **[通知:站内信]** 避免错误的接收人配置导致查询出全部用户 ([#6424](https://github.com/nocobase/nocobase/pull/6424)) by @sheldon66
|
||||||
|
|
||||||
|
- **[工作流:人工处理节点]**
|
||||||
|
- 修复遗漏表前缀和 schema 的迁移脚本 ([#6425](https://github.com/nocobase/nocobase/pull/6425)) by @mytharcher
|
||||||
|
|
||||||
|
- 调整迁移脚本版本范围限制为 `<1.7.0` ([#6430](https://github.com/nocobase/nocobase/pull/6430)) by @mytharcher
|
||||||
|
|
||||||
## [v1.6.1](https://github.com/nocobase/nocobase/compare/v1.6.0...v1.6.1) - 2025-03-11
|
## [v1.6.1](https://github.com/nocobase/nocobase/compare/v1.6.0...v1.6.1) - 2025-03-11
|
||||||
|
|
||||||
### 🐛 修复
|
### 🐛 修复
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Updated Date: February 20, 2025
|
Updated Date: April 1, 2025
|
||||||
|
|
||||||
NocoBase License Agreement
|
NocoBase License Agreement
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr
|
|||||||
|
|
||||||
6.6 Can sell plugins developed for Software in the Marketplace.
|
6.6 Can sell plugins developed for Software in the Marketplace.
|
||||||
|
|
||||||
6.7 The User with an Enterprise Edition License can sell Upper Layer Application to their clients.
|
6.7 The User with a Professional or Enterprise Edition License can sell Upper Layer Application to their clients.
|
||||||
|
|
||||||
6.8 Not restricted by the AGPL-3.0 agreement.
|
6.8 Not restricted by the AGPL-3.0 agreement.
|
||||||
|
|
||||||
@ -106,9 +106,9 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr
|
|||||||
|
|
||||||
7.4 It is not allowed to provide any form of no-code, zero-code, low-code platform SaaS products to the public using the original or modified Software.
|
7.4 It is not allowed to provide any form of no-code, zero-code, low-code platform SaaS products to the public using the original or modified Software.
|
||||||
|
|
||||||
7.5 It is not allowed for the User withot an Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license.
|
7.5 It is not allowed for the User withot a Professional or Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license.
|
||||||
|
|
||||||
7.6 It is not allowed for the User with an Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license with access to further development and configuration.
|
7.6 It is not allowed for the User with a Professional or Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license with access to further development and configuration.
|
||||||
|
|
||||||
7.7 It is not allowed to publicly sell plugins developed for Software outside of the Marketplace.
|
7.7 It is not allowed to publicly sell plugins developed for Software outside of the Marketplace.
|
||||||
|
|
||||||
|
@ -2,14 +2,10 @@
|
|||||||
|
|
||||||
https://github.com/user-attachments/assets/cf08bfe5-e6e6-453c-8b96-350a6a8bed17
|
https://github.com/user-attachments/assets/cf08bfe5-e6e6-453c-8b96-350a6a8bed17
|
||||||
|
|
||||||
## ご協力ありがとうございます!
|
<p align="center">
|
||||||
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
</p>
|
||||||
## リリースノート
|
|
||||||
|
|
||||||
リリースノートは[ブログ](https://www.nocobase.com/ja/blog/timeline)で随時更新され、週ごとにまとめて公開しています。
|
|
||||||
|
|
||||||
## NocoBaseはなに?
|
## NocoBaseはなに?
|
||||||
|
|
||||||
@ -28,6 +24,16 @@ https://docs-cn.nocobase.com/
|
|||||||
コミュニティ:
|
コミュニティ:
|
||||||
https://forum.nocobase.com/
|
https://forum.nocobase.com/
|
||||||
|
|
||||||
|
チュートリアル:
|
||||||
|
https://www.nocobase.com/ja/tutorials
|
||||||
|
|
||||||
|
顧客のストーリー:
|
||||||
|
https://www.nocobase.com/ja/blog/tags/customer-stories
|
||||||
|
|
||||||
|
## リリースノート
|
||||||
|
|
||||||
|
リリースノートは[ブログ](https://www.nocobase.com/ja/blog/timeline)で随時更新され、週ごとにまとめて公開しています。
|
||||||
|
|
||||||
## 他の製品との違い
|
## 他の製品との違い
|
||||||
|
|
||||||
### 1. データモデル駆動
|
### 1. データモデル駆動
|
||||||
|
22
README.md
22
README.md
@ -2,19 +2,14 @@ English | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md)
|
|||||||
|
|
||||||
https://github.com/user-attachments/assets/a50c100a-4561-4e06-b2d2-d48098659ec0
|
https://github.com/user-attachments/assets/a50c100a-4561-4e06-b2d2-d48098659ec0
|
||||||
|
|
||||||
## We'd love your support!
|
<p align="center">
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
</p>
|
||||||
## Release Notes
|
|
||||||
|
|
||||||
Our [blog](https://www.nocobase.com/en/blog/timeline) is regularly updated with release notes and provides a weekly summary.
|
|
||||||
|
|
||||||
## What is NocoBase
|
## What is NocoBase
|
||||||
|
|
||||||
NocoBase is a scalability-first, open-source no-code development platform.
|
NocoBase is an extensibility-first, open-source no-code development platform.
|
||||||
Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform!
|
Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform!
|
||||||
|
|
||||||
Homepage:
|
Homepage:
|
||||||
@ -29,6 +24,17 @@ https://docs.nocobase.com/
|
|||||||
Forum:
|
Forum:
|
||||||
https://forum.nocobase.com/
|
https://forum.nocobase.com/
|
||||||
|
|
||||||
|
Tutorials:
|
||||||
|
https://www.nocobase.com/en/tutorials
|
||||||
|
|
||||||
|
Use Cases:
|
||||||
|
https://www.nocobase.com/en/blog/tags/customer-stories
|
||||||
|
|
||||||
|
|
||||||
|
## Release Notes
|
||||||
|
|
||||||
|
Our [blog](https://www.nocobase.com/en/blog/timeline) is regularly updated with release notes and provides a weekly summary.
|
||||||
|
|
||||||
## Distinctive features
|
## Distinctive features
|
||||||
|
|
||||||
### 1. Data model-driven
|
### 1. Data model-driven
|
||||||
|
@ -2,13 +2,10 @@
|
|||||||
|
|
||||||
https://github.com/nocobase/nocobase/assets/1267426/29623e45-9a48-4598-bb9e-9dd173ade553
|
https://github.com/nocobase/nocobase/assets/1267426/29623e45-9a48-4598-bb9e-9dd173ade553
|
||||||
|
|
||||||
## 感谢支持
|
<p align="center">
|
||||||
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
</p>
|
||||||
## 发布日志
|
|
||||||
我们的[博客](https://www.nocobase.com/cn/blog/timeline)会及时更新发布日志,并每周进行汇总。
|
|
||||||
|
|
||||||
## NocoBase 是什么
|
## NocoBase 是什么
|
||||||
|
|
||||||
@ -27,6 +24,15 @@ https://docs-cn.nocobase.com/
|
|||||||
社区:
|
社区:
|
||||||
https://forum.nocobase.com/
|
https://forum.nocobase.com/
|
||||||
|
|
||||||
|
教程:
|
||||||
|
https://www.nocobase.com/cn/tutorials
|
||||||
|
|
||||||
|
用户故事:
|
||||||
|
https://www.nocobase.com/cn/blog/tags/customer-stories
|
||||||
|
|
||||||
|
## 发布日志
|
||||||
|
我们的[博客](https://www.nocobase.com/cn/blog/timeline)会及时更新发布日志,并每周进行汇总。
|
||||||
|
|
||||||
## 与众不同之处
|
## 与众不同之处
|
||||||
|
|
||||||
### 1. 数据模型驱动
|
### 1. 数据模型驱动
|
||||||
|
@ -17,7 +17,7 @@ server {
|
|||||||
server_name _;
|
server_name _;
|
||||||
root /app/nocobase/packages/app/client/dist;
|
root /app/nocobase/packages/app/client/dist;
|
||||||
index index.html;
|
index index.html;
|
||||||
client_max_body_size 20M;
|
client_max_body_size 0;
|
||||||
|
|
||||||
access_log /var/log/nginx/nocobase.log apm;
|
access_log /var/log/nginx/nocobase.log apm;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ server {
|
|||||||
server_name _;
|
server_name _;
|
||||||
root /app/nocobase/node_modules/@nocobase/app/dist/client;
|
root /app/nocobase/node_modules/@nocobase/app/dist/client;
|
||||||
index index.html;
|
index index.html;
|
||||||
client_max_body_size 1000M;
|
client_max_body_size 0;
|
||||||
access_log /var/log/nginx/nocobase.log apm;
|
access_log /var/log/nginx/nocobase.log apm;
|
||||||
|
|
||||||
gzip on;
|
gzip on;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.0-beta.1",
|
"version": "1.7.0-alpha.10",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"npmClientArgs": ["--ignore-engines"],
|
"npmClientArgs": ["--ignore-engines"],
|
||||||
|
@ -52,7 +52,9 @@
|
|||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"nwsapi": "2.2.7",
|
"nwsapi": "2.2.7",
|
||||||
"antd": "5.12.8",
|
"antd": "5.24.2",
|
||||||
|
"@formily/antd-v5": "1.2.3",
|
||||||
|
"dayjs": "1.11.13",
|
||||||
"@ant-design/icons": "^5.6.1"
|
"@ant-design/icons": "^5.6.1"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/acl",
|
"name": "@nocobase/acl",
|
||||||
"version": "1.7.0-beta.1",
|
"version": "1.7.0-alpha.10",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/resourcer": "1.7.0-beta.1",
|
"@nocobase/resourcer": "1.7.0-alpha.10",
|
||||||
"@nocobase/utils": "1.7.0-beta.1",
|
"@nocobase/utils": "1.7.0-alpha.10",
|
||||||
"minimatch": "^5.1.1"
|
"minimatch": "^5.1.1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
579
packages/core/acl/src/__tests__/acl-role.test.ts
Normal file
579
packages/core/acl/src/__tests__/acl-role.test.ts
Normal file
@ -0,0 +1,579 @@
|
|||||||
|
/**
|
||||||
|
* 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 { ACL } from '..';
|
||||||
|
describe('multiple roles merge', () => {
|
||||||
|
let acl: ACL;
|
||||||
|
beforeEach(() => {
|
||||||
|
acl = new ACL();
|
||||||
|
});
|
||||||
|
describe('filter merge', () => {
|
||||||
|
test('should allow all(params:{}) when filter1 = undefined, filter2 is not exists', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should allow all(params:{}) when filter1 = undefined, filter2 = {}', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should allow all(params={}) when filter1 = {}, filter2 = {}', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should union filter(params.filter={$or:[{id:1}, {id:2}]}) when filter1 = {id: 1}, filter2 = {id: 2}', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { id: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { id: 2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {
|
||||||
|
filter: {
|
||||||
|
$or: expect.arrayContaining([{ id: 1 }, { id: 2 }]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should union filter(filter={$or:[{id:1}, {name: zhangsan}]}) when filter1 = {id: 1}, filter2 = {name: zhangsan}', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { id: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { name: 'zhangsan' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {
|
||||||
|
filter: {
|
||||||
|
$or: expect.arrayContaining([{ id: 1 }, { name: 'zhangsan' }]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should union filter(filter={$or:[{id:1}, {name: zhangsan}]}) when filter1 = {id: 1}, filter2 = { $or: [{name: zhangsan}]', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { id: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { name: 'zhangsan' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {
|
||||||
|
filter: {
|
||||||
|
$or: expect.arrayContaining([{ id: 1 }, { name: 'zhangsan' }]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('feilds merge', () => {
|
||||||
|
test('should allow all(params={}) when fields1 = undefined, fields2 is not exists', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should allow all(params={}) when fields1 = undefined, fields2 is not exists', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should allow all(params={}) when fields1 = [], fields2 =[]', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should union fields(params={ fields: [a,b]}) when fields1 = [a], fields2 =[b]', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: ['a'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: ['b'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {
|
||||||
|
fields: expect.arrayContaining(['a', 'b']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should union no repeat fields(params={ fields: [a,b,c]}) when fields1 = [a,b], fields2 =[b,c]', () => {
|
||||||
|
acl.setAvailableAction('edit', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: ['a', 'b'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: ['b', 'c'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {
|
||||||
|
fields: expect.arrayContaining(['a', 'b', 'c']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(canResult.params.fields.length).toStrictEqual(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('whitelist', () => {
|
||||||
|
test('should union whitelist(params={ fields: [a,b,c]}) when fields1 = [a,b], fields2 =[c]', () => {
|
||||||
|
acl.setAvailableAction('update');
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:update': {
|
||||||
|
whitelist: ['a', 'b'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:update': {
|
||||||
|
whitelist: ['c'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ resource: 'posts', action: 'update', roles: ['role1', 'role2'] });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'update',
|
||||||
|
params: {
|
||||||
|
whitelist: expect.arrayContaining(['a', 'b', 'c']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('appends', () => {
|
||||||
|
test('should union appends(params={ appends: [a,b,c]}) when appends = [a,b], appends =[c]', () => {
|
||||||
|
acl.setAvailableAction('update');
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:update': {
|
||||||
|
appends: ['a', 'b'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:update': {
|
||||||
|
appends: ['c'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ resource: 'posts', action: 'update', roles: ['role1', 'role2'] });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'update',
|
||||||
|
params: {
|
||||||
|
appends: expect.arrayContaining(['a', 'b', 'c']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should union appends(params={ appends: [a,b]}) when appends = [a,b], appends =[]', () => {
|
||||||
|
acl.setAvailableAction('update');
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:update': {
|
||||||
|
appends: ['a', 'b'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:update': {
|
||||||
|
appends: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ resource: 'posts', action: 'update', roles: ['role1', 'role2'] });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'update',
|
||||||
|
params: {
|
||||||
|
appends: expect.arrayContaining(['a', 'b']),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('filter & fields merge', () => {
|
||||||
|
test('should allow all(params={}) when actions1 = {filter: {}}, actions2 = {fields: []}', () => {
|
||||||
|
acl.setAvailableAction('view', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should allow all(params={}) when actions1 = {filter: {}}, actions2 = {fields: [a]}', () => {
|
||||||
|
acl.setAvailableAction('view', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: ['a'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should allow all(params={}) when actions1 = {filter: {a:1}}, actions2 = {fields: []}', () => {
|
||||||
|
acl.setAvailableAction('view', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { a: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should allow all(params={}) when actions1 = {filter: {a:1}}, actions2 = {fields: [a]}', () => {
|
||||||
|
acl.setAvailableAction('view', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { a: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
fields: ['a'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should union filter&fields(params={ filter:{ $or:[{a:1},{a:2}]}, fields:[a,b]}) when actions1={filter:{a:1}, fields:[a]}, actions2={filter: {a:1}},fields:[b]}', () => {
|
||||||
|
acl.setAvailableAction('view', {
|
||||||
|
type: 'old-data',
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role1',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { a: 1 },
|
||||||
|
fields: ['a'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
acl.define({
|
||||||
|
role: 'role2',
|
||||||
|
actions: {
|
||||||
|
'posts:view': {
|
||||||
|
filter: { a: 2 },
|
||||||
|
fields: ['b'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const canResult = acl.can({ roles: ['role1', 'role2'], resource: 'posts', action: 'view' });
|
||||||
|
expect(canResult).toStrictEqual({
|
||||||
|
role: 'role1',
|
||||||
|
resource: 'posts',
|
||||||
|
action: 'view',
|
||||||
|
params: expect.objectContaining({
|
||||||
|
filter: { $or: expect.arrayContaining([{ a: 1 }, { a: 2 }]) },
|
||||||
|
fields: expect.arrayContaining(['a', 'b']),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -7,11 +7,11 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { default as _, default as lodash } from 'lodash';
|
||||||
|
import minimatch from 'minimatch';
|
||||||
import { ACL, DefineOptions } from './acl';
|
import { ACL, DefineOptions } from './acl';
|
||||||
import { ACLAvailableStrategy, AvailableStrategyOptions } from './acl-available-strategy';
|
import { ACLAvailableStrategy, AvailableStrategyOptions } from './acl-available-strategy';
|
||||||
import { ACLResource } from './acl-resource';
|
import { ACLResource } from './acl-resource';
|
||||||
import lodash from 'lodash';
|
|
||||||
import minimatch from 'minimatch';
|
|
||||||
|
|
||||||
export interface RoleActionParams {
|
export interface RoleActionParams {
|
||||||
fields?: string[];
|
fields?: string[];
|
||||||
@ -185,12 +185,12 @@ export class ACLRole {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return _.cloneDeep({
|
||||||
role: this.name,
|
role: this.name,
|
||||||
strategy: this.strategy,
|
strategy: this.strategy,
|
||||||
actions,
|
actions,
|
||||||
snippets: Array.from(this.snippets),
|
snippets: Array.from(this.snippets),
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getResourceActionFromPath(path: string) {
|
protected getResourceActionFromPath(path: string) {
|
||||||
|
@ -19,6 +19,7 @@ import { AllowManager, ConditionFunc } from './allow-manager';
|
|||||||
import FixedParamsManager, { Merger } from './fixed-params-manager';
|
import FixedParamsManager, { Merger } from './fixed-params-manager';
|
||||||
import SnippetManager, { SnippetOptions } from './snippet-manager';
|
import SnippetManager, { SnippetOptions } from './snippet-manager';
|
||||||
import { NoPermissionError } from './errors/no-permission-error';
|
import { NoPermissionError } from './errors/no-permission-error';
|
||||||
|
import { mergeAclActionParams, removeEmptyParams } from './utils';
|
||||||
|
|
||||||
interface CanResult {
|
interface CanResult {
|
||||||
role: string;
|
role: string;
|
||||||
@ -54,11 +55,12 @@ export interface ListenerContext {
|
|||||||
type Listener = (ctx: ListenerContext) => void;
|
type Listener = (ctx: ListenerContext) => void;
|
||||||
|
|
||||||
interface CanArgs {
|
interface CanArgs {
|
||||||
role: string;
|
role?: string;
|
||||||
resource: string;
|
resource: string;
|
||||||
action: string;
|
action: string;
|
||||||
rawResourceName?: string;
|
rawResourceName?: string;
|
||||||
ctx?: any;
|
ctx?: any;
|
||||||
|
roles?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ACL extends EventEmitter {
|
export class ACL extends EventEmitter {
|
||||||
@ -169,6 +171,10 @@ export class ACL extends EventEmitter {
|
|||||||
return this.roles.get(name);
|
return this.roles.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRoles(names: string[]): ACLRole[] {
|
||||||
|
return names.map((name) => this.getRole(name)).filter((x) => Boolean(x));
|
||||||
|
}
|
||||||
|
|
||||||
removeRole(name: string) {
|
removeRole(name: string) {
|
||||||
return this.roles.delete(name);
|
return this.roles.delete(name);
|
||||||
}
|
}
|
||||||
@ -202,6 +208,36 @@ export class ACL extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
can(options: CanArgs): CanResult | null {
|
can(options: CanArgs): CanResult | null {
|
||||||
|
if (options.role) {
|
||||||
|
return lodash.cloneDeep(this.getCanByRole(options));
|
||||||
|
}
|
||||||
|
if (options.roles?.length) {
|
||||||
|
return lodash.cloneDeep(this.getCanByRoles(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCanByRoles(options: CanArgs) {
|
||||||
|
let canResult: CanResult | null = null;
|
||||||
|
|
||||||
|
for (const role of options.roles) {
|
||||||
|
const result = this.getCanByRole({
|
||||||
|
role,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
if (!canResult) {
|
||||||
|
canResult = result;
|
||||||
|
canResult && removeEmptyParams(canResult.params);
|
||||||
|
} else if (canResult && result) {
|
||||||
|
canResult.params = mergeAclActionParams(canResult.params, result.params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return canResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCanByRole(options: CanArgs) {
|
||||||
const { role, resource, action, rawResourceName } = options;
|
const { role, resource, action, rawResourceName } = options;
|
||||||
const aclRole = this.roles.get(role);
|
const aclRole = this.roles.get(role);
|
||||||
|
|
||||||
@ -351,9 +387,12 @@ export class ACL extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.can = (options: Omit<CanArgs, 'role'>) => {
|
ctx.can = (options: Omit<CanArgs, 'role'>) => {
|
||||||
const canResult = acl.can({ role: roleName, ...options });
|
const roles = ctx.state.currentRoles || [roleName];
|
||||||
|
const can = acl.can({ roles, ...options });
|
||||||
return canResult;
|
if (!can) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return can;
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.permission = {
|
ctx.permission = {
|
||||||
@ -370,7 +409,7 @@ export class ACL extends EventEmitter {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async getActionParams(ctx) {
|
async getActionParams(ctx) {
|
||||||
const roleName = ctx.state.currentRole || 'anonymous';
|
const roleNames = ctx.state.currentRoles?.length ? ctx.state.currentRoles : 'anonymous';
|
||||||
const { resourceName: rawResourceName, actionName } = ctx.action;
|
const { resourceName: rawResourceName, actionName } = ctx.action;
|
||||||
|
|
||||||
let resourceName = rawResourceName;
|
let resourceName = rawResourceName;
|
||||||
@ -386,11 +425,11 @@ export class ACL extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.can = (options: Omit<CanArgs, 'role'>) => {
|
ctx.can = (options: Omit<CanArgs, 'role'>) => {
|
||||||
const can = this.can({ role: roleName, ...options });
|
const can = this.can({ roles: roleNames, ...options });
|
||||||
if (!can) {
|
if (can) {
|
||||||
return null;
|
return lodash.cloneDeep(can);
|
||||||
}
|
}
|
||||||
return lodash.cloneDeep(can);
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.permission = {
|
ctx.permission = {
|
||||||
@ -421,6 +460,23 @@ export class ACL extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查 $or 条件中的 createdById
|
||||||
|
if (params?.filter?.$or?.length) {
|
||||||
|
const checkCreatedById = (items) => {
|
||||||
|
return items.some(
|
||||||
|
(x) =>
|
||||||
|
'createdById' in x || x.$or?.some((y) => 'createdById' in y) || x.$and?.some((y) => 'createdById' in y),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (checkCreatedById(params.filter.$or)) {
|
||||||
|
const collection = ctx.db.getCollection(resourceName);
|
||||||
|
if (!collection || !collection.getField('createdById')) {
|
||||||
|
throw new NoPermissionError('createdById field not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,3 +14,4 @@ export * from './acl-resource';
|
|||||||
export * from './acl-role';
|
export * from './acl-role';
|
||||||
export * from './skip-middleware';
|
export * from './skip-middleware';
|
||||||
export * from './errors';
|
export * from './errors';
|
||||||
|
export * from './utils';
|
||||||
|
213
packages/core/acl/src/utils/acl-role.ts
Normal file
213
packages/core/acl/src/utils/acl-role.ts
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
/**
|
||||||
|
* 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 { assign } from '@nocobase/utils';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { ACLRole } from '../acl-role';
|
||||||
|
|
||||||
|
export function mergeRole(roles: ACLRole[]) {
|
||||||
|
const result: Record<string, any> = {
|
||||||
|
roles: [],
|
||||||
|
strategy: {},
|
||||||
|
actions: null,
|
||||||
|
snippets: [],
|
||||||
|
resources: null,
|
||||||
|
};
|
||||||
|
const allSnippets: string[][] = [];
|
||||||
|
for (const role of roles) {
|
||||||
|
const jsonRole = role.toJSON();
|
||||||
|
result.roles = mergeRoleNames(result.roles, jsonRole.role);
|
||||||
|
result.strategy = mergeRoleStrategy(result.strategy, jsonRole.strategy);
|
||||||
|
result.actions = mergeRoleActions(result.actions, jsonRole.actions);
|
||||||
|
result.resources = mergeRoleResources(result.resources, [...role.resources.keys()]);
|
||||||
|
if (_.isArray(jsonRole.snippets)) {
|
||||||
|
allSnippets.push(jsonRole.snippets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.snippets = mergeRoleSnippets(allSnippets);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRoleNames(sourceRoleNames, newRoleName) {
|
||||||
|
return newRoleName ? sourceRoleNames.concat(newRoleName) : sourceRoleNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRoleStrategy(sourceStrategy, newStrategy) {
|
||||||
|
if (!newStrategy) {
|
||||||
|
return sourceStrategy;
|
||||||
|
}
|
||||||
|
if (_.isArray(newStrategy.actions)) {
|
||||||
|
if (!sourceStrategy.actions) {
|
||||||
|
sourceStrategy.actions = newStrategy.actions;
|
||||||
|
} else {
|
||||||
|
const actions = sourceStrategy.actions.concat(newStrategy.actions);
|
||||||
|
return {
|
||||||
|
...sourceStrategy,
|
||||||
|
actions: [...new Set(actions)],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sourceStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRoleActions(sourceActions, newActions) {
|
||||||
|
if (_.isEmpty(sourceActions)) return newActions;
|
||||||
|
if (_.isEmpty(newActions)) return sourceActions;
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
[...new Set(Reflect.ownKeys(sourceActions).concat(Reflect.ownKeys(newActions)))].forEach((key) => {
|
||||||
|
if (_.has(sourceActions, key) && _.has(newActions, key)) {
|
||||||
|
result[key] = mergeAclActionParams(sourceActions[key], newActions[key]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result[key] = _.has(sourceActions, key) ? sourceActions[key] : newActions[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRoleSnippets(allRoleSnippets: string[][]): string[] {
|
||||||
|
if (!allRoleSnippets.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const allSnippets = allRoleSnippets.flat();
|
||||||
|
const isExclusion = (value) => value.startsWith('!');
|
||||||
|
const includes = new Set(allSnippets.filter((x) => !isExclusion(x)));
|
||||||
|
const excludes = new Set(allSnippets.filter(isExclusion));
|
||||||
|
|
||||||
|
// 统计 xxx.* 在多少个角色中存在
|
||||||
|
const domainRoleMap = new Map<string, Set<number>>();
|
||||||
|
allRoleSnippets.forEach((roleSnippets, i) => {
|
||||||
|
roleSnippets
|
||||||
|
.filter((x) => x.endsWith('.*') && !isExclusion(x))
|
||||||
|
.forEach((include) => {
|
||||||
|
const domain = include.slice(0, -1);
|
||||||
|
if (!domainRoleMap.has(domain)) {
|
||||||
|
domainRoleMap.set(domain, new Set());
|
||||||
|
}
|
||||||
|
domainRoleMap.get(domain).add(i);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理黑名单交集(只有所有角色都有 `!xxx` 才保留)
|
||||||
|
const excludesSet = new Set<string>();
|
||||||
|
for (const snippet of excludes) {
|
||||||
|
if (allRoleSnippets.every((x) => x.includes(snippet))) {
|
||||||
|
excludesSet.add(snippet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [domain, indexes] of domainRoleMap.entries()) {
|
||||||
|
const fullDomain = `${domain}.*`;
|
||||||
|
|
||||||
|
// xxx.* 存在时,覆盖 !xxx.*
|
||||||
|
if (includes.has(fullDomain)) {
|
||||||
|
excludesSet.delete(`!${fullDomain}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算 !xxx.yyy,当所有 xxx.* 角色都包含 !xxx.yyy 时才保留
|
||||||
|
for (const roleIndex of indexes) {
|
||||||
|
for (const exclude of allRoleSnippets[roleIndex]) {
|
||||||
|
if (exclude.startsWith(`!${domain}`) && exclude !== `!${fullDomain}`) {
|
||||||
|
if ([...indexes].every((i) => allRoleSnippets[i].includes(exclude))) {
|
||||||
|
excludesSet.add(exclude);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保 !xxx.yyy 只有在 xxx.* 存在时才有效,同时解决 [xxx] 和 [!xxx] 冲突
|
||||||
|
if (includes.size > 0) {
|
||||||
|
for (const x of [...excludesSet]) {
|
||||||
|
const exactMatch = x.slice(1);
|
||||||
|
const segments = exactMatch.split('.');
|
||||||
|
if (segments.length > 1 && segments[1] !== '*') {
|
||||||
|
const parentDomain = segments[0] + '.*';
|
||||||
|
if (!includes.has(parentDomain)) {
|
||||||
|
excludesSet.delete(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...includes, ...excludesSet];
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRoleResources(sourceResources, newResources) {
|
||||||
|
if (sourceResources === null) {
|
||||||
|
return newResources;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...new Set(sourceResources.concat(newResources))];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeAclActionParams(sourceParams, targetParams) {
|
||||||
|
if (_.isEmpty(sourceParams) || _.isEmpty(targetParams)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// source 和 target 其中之一没有 fields 字段时, 最终希望没有此字段
|
||||||
|
removeUnmatchedParams(sourceParams, targetParams, ['fields', 'whitelist', 'appends']);
|
||||||
|
|
||||||
|
const andMerge = (x, y) => {
|
||||||
|
if (_.isEmpty(x) || _.isEmpty(y)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return _.uniq(x.concat(y)).filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergedParams = assign(targetParams, sourceParams, {
|
||||||
|
own: (x, y) => x || y,
|
||||||
|
filter: (x, y) => {
|
||||||
|
if (_.isEmpty(x) || _.isEmpty(y)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const xHasOr = _.has(x, '$or'),
|
||||||
|
yHasOr = _.has(y, '$or');
|
||||||
|
let $or = [x, y];
|
||||||
|
if (xHasOr && !yHasOr) {
|
||||||
|
$or = [...x.$or, y];
|
||||||
|
} else if (!xHasOr && yHasOr) {
|
||||||
|
$or = [x, ...y.$or];
|
||||||
|
} else if (xHasOr && yHasOr) {
|
||||||
|
$or = [...x.$or, ...y.$or];
|
||||||
|
}
|
||||||
|
|
||||||
|
return { $or: _.uniqWith($or, _.isEqual) };
|
||||||
|
},
|
||||||
|
fields: andMerge,
|
||||||
|
whitelist: andMerge,
|
||||||
|
appends: 'union',
|
||||||
|
});
|
||||||
|
removeEmptyParams(mergedParams);
|
||||||
|
return mergedParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeEmptyParams(params) {
|
||||||
|
if (!_.isObject(params)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
if (_.isEmpty(params[key])) {
|
||||||
|
delete params[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeUnmatchedParams(source, target, keys: string[]) {
|
||||||
|
for (const key of keys) {
|
||||||
|
if (_.has(source, key) && !_.has(target, key)) {
|
||||||
|
delete source[key];
|
||||||
|
}
|
||||||
|
if (!_.has(source, key) && _.has(target, key)) {
|
||||||
|
delete target[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
packages/core/acl/src/utils/index.ts
Normal file
10
packages/core/acl/src/utils/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './acl-role';
|
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/actions",
|
"name": "@nocobase/actions",
|
||||||
"version": "1.7.0-beta.1",
|
"version": "1.7.0-alpha.10",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/cache": "1.7.0-beta.1",
|
"@nocobase/cache": "1.7.0-alpha.10",
|
||||||
"@nocobase/database": "1.7.0-beta.1",
|
"@nocobase/database": "1.7.0-alpha.10",
|
||||||
"@nocobase/resourcer": "1.7.0-beta.1"
|
"@nocobase/resourcer": "1.7.0-alpha.10"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/app",
|
"name": "@nocobase/app",
|
||||||
"version": "1.7.0-beta.1",
|
"version": "1.7.0-alpha.10",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/database": "1.7.0-beta.1",
|
"@nocobase/database": "1.7.0-alpha.10",
|
||||||
"@nocobase/preset-nocobase": "1.7.0-beta.1",
|
"@nocobase/preset-nocobase": "1.7.0-alpha.10",
|
||||||
"@nocobase/server": "1.7.0-beta.1"
|
"@nocobase/server": "1.7.0-alpha.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nocobase/client": "1.7.0-beta.1"
|
"@nocobase/client": "1.7.0-alpha.10"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mockDatabase } from '@nocobase/database';
|
import { createMockDatabase, mockDatabase } from '@nocobase/database';
|
||||||
import { uid } from '@nocobase/utils';
|
import { uid } from '@nocobase/utils';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import execa from 'execa';
|
import execa from 'execa';
|
||||||
@ -64,7 +64,7 @@ const createDatabase = async () => {
|
|||||||
if (process.env.DB_DIALECT === 'sqlite') {
|
if (process.env.DB_DIALECT === 'sqlite') {
|
||||||
return 'nocobase';
|
return 'nocobase';
|
||||||
}
|
}
|
||||||
const db = mockDatabase();
|
const db = await createMockDatabase();
|
||||||
const name = `d_${uid()}`;
|
const name = `d_${uid()}`;
|
||||||
await db.sequelize.query(`CREATE DATABASE ${name}`);
|
await db.sequelize.query(`CREATE DATABASE ${name}`);
|
||||||
await db.close();
|
await db.close();
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/auth",
|
"name": "@nocobase/auth",
|
||||||
"version": "1.7.0-beta.1",
|
"version": "1.7.0-alpha.10",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/actions": "1.7.0-beta.1",
|
"@nocobase/actions": "1.7.0-alpha.10",
|
||||||
"@nocobase/cache": "1.7.0-beta.1",
|
"@nocobase/cache": "1.7.0-alpha.10",
|
||||||
"@nocobase/database": "1.7.0-beta.1",
|
"@nocobase/database": "1.7.0-alpha.10",
|
||||||
"@nocobase/resourcer": "1.7.0-beta.1",
|
"@nocobase/resourcer": "1.7.0-alpha.10",
|
||||||
"@nocobase/utils": "1.7.0-beta.1",
|
"@nocobase/utils": "1.7.0-alpha.10",
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^8.5.8",
|
||||||
"jsonwebtoken": "^8.5.1"
|
"jsonwebtoken": "^8.5.1"
|
||||||
},
|
},
|
||||||
|
@ -22,7 +22,7 @@ describe('middleware', () => {
|
|||||||
app = await createMockServer({
|
app = await createMockServer({
|
||||||
registerActions: true,
|
registerActions: true,
|
||||||
acl: true,
|
acl: true,
|
||||||
plugins: ['users', 'auth', 'acl', 'field-sort', 'data-source-manager', 'error-handler'],
|
plugins: ['users', 'auth', 'acl', 'field-sort', 'data-source-manager', 'error-handler', 'system-settings'],
|
||||||
});
|
});
|
||||||
|
|
||||||
// app.plugin(ApiKeysPlugin);
|
// app.plugin(ApiKeysPlugin);
|
||||||
|
@ -267,6 +267,24 @@ export class BaseAuth extends Auth {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async signNewToken(userId: number) {
|
||||||
|
const tokenInfo = await this.tokenController.add({ userId });
|
||||||
|
const expiresIn = Math.floor((await this.tokenController.getConfig()).tokenExpirationTime / 1000);
|
||||||
|
const token = this.jwt.sign(
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
temp: true,
|
||||||
|
iat: Math.floor(tokenInfo.issuedTime / 1000),
|
||||||
|
signInTime: tokenInfo.signInTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jwtid: tokenInfo.jti,
|
||||||
|
expiresIn,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
async signIn() {
|
async signIn() {
|
||||||
let user: Model;
|
let user: Model;
|
||||||
try {
|
try {
|
||||||
@ -282,20 +300,7 @@ export class BaseAuth extends Auth {
|
|||||||
code: AuthErrorCode.NOT_EXIST_USER,
|
code: AuthErrorCode.NOT_EXIST_USER,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const tokenInfo = await this.tokenController.add({ userId: user.id });
|
const token = await this.signNewToken(user.id);
|
||||||
const expiresIn = Math.floor((await this.tokenController.getConfig()).tokenExpirationTime / 1000);
|
|
||||||
const token = this.jwt.sign(
|
|
||||||
{
|
|
||||||
userId: user.id,
|
|
||||||
temp: true,
|
|
||||||
iat: Math.floor(tokenInfo.issuedTime / 1000),
|
|
||||||
signInTime: tokenInfo.signInTime,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
jwtid: tokenInfo.jti,
|
|
||||||
expiresIn,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
token,
|
token,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/build",
|
"name": "@nocobase/build",
|
||||||
"version": "1.7.0-beta.1",
|
"version": "1.7.0-alpha.10",
|
||||||
"description": "Library build tool based on rollup.",
|
"description": "Library build tool based on rollup.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
|
4
packages/core/cache/package.json
vendored
4
packages/core/cache/package.json
vendored
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/cache",
|
"name": "@nocobase/cache",
|
||||||
"version": "1.7.0-beta.1",
|
"version": "1.7.0-alpha.10",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/lock-manager": "1.6.0-alpha.6",
|
"@nocobase/lock-manager": "1.7.0-alpha.10",
|
||||||
"bloom-filters": "^3.0.1",
|
"bloom-filters": "^3.0.1",
|
||||||
"cache-manager": "^5.2.4",
|
"cache-manager": "^5.2.4",
|
||||||
"cache-manager-redis-yet": "^4.1.2"
|
"cache-manager-redis-yet": "^4.1.2"
|
||||||
|
@ -17,7 +17,7 @@ server {
|
|||||||
server_name _;
|
server_name _;
|
||||||
root {{cwd}}/node_modules/@nocobase/app/dist/client;
|
root {{cwd}}/node_modules/@nocobase/app/dist/client;
|
||||||
index index.html;
|
index index.html;
|
||||||
client_max_body_size 1000M;
|
client_max_body_size 0;
|
||||||
access_log /var/log/nginx/nocobase.log apm;
|
access_log /var/log/nginx/nocobase.log apm;
|
||||||
|
|
||||||
gzip on;
|
gzip on;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/cli",
|
"name": "@nocobase/cli",
|
||||||
"version": "1.7.0-beta.1",
|
"version": "1.7.0-alpha.10",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./src/index.js",
|
"main": "./src/index.js",
|
||||||
@ -8,7 +8,7 @@
|
|||||||
"nocobase": "./bin/index.js"
|
"nocobase": "./bin/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/app": "1.7.0-beta.1",
|
"@nocobase/app": "1.7.0-alpha.10",
|
||||||
"@nocobase/license-kit": "^0.2.3",
|
"@nocobase/license-kit": "^0.2.3",
|
||||||
"@types/fs-extra": "^11.0.1",
|
"@types/fs-extra": "^11.0.1",
|
||||||
"@umijs/utils": "3.5.20",
|
"@umijs/utils": "3.5.20",
|
||||||
@ -26,7 +26,7 @@
|
|||||||
"tsx": "^4.19.0"
|
"tsx": "^4.19.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nocobase/devtools": "1.7.0-beta.1"
|
"@nocobase/devtools": "1.7.0-alpha.10"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -18,6 +18,7 @@ module.exports = (cli) => {
|
|||||||
generateAppDir();
|
generateAppDir();
|
||||||
require('./global')(cli);
|
require('./global')(cli);
|
||||||
require('./create-nginx-conf')(cli);
|
require('./create-nginx-conf')(cli);
|
||||||
|
require('./locale')(cli);
|
||||||
require('./build')(cli);
|
require('./build')(cli);
|
||||||
require('./tar')(cli);
|
require('./tar')(cli);
|
||||||
require('./dev')(cli);
|
require('./dev')(cli);
|
||||||
|
81
packages/core/cli/src/commands/locale.js
Normal file
81
packages/core/cli/src/commands/locale.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Command } = require('commander');
|
||||||
|
const fg = require('fast-glob');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const deepmerge = require('deepmerge');
|
||||||
|
const { getCronstrueLocale } = require('./locale/cronstrue');
|
||||||
|
const { getReactJsCron } = require('./locale/react-js-cron');
|
||||||
|
|
||||||
|
function sortJSON(json) {
|
||||||
|
if (Array.isArray(json)) {
|
||||||
|
return json.map(sortJSON);
|
||||||
|
} else if (typeof json === 'object' && json !== null) {
|
||||||
|
const sortedKeys = Object.keys(json).sort();
|
||||||
|
const sortedObject = {};
|
||||||
|
sortedKeys.forEach((key) => {
|
||||||
|
sortedObject[key] = sortJSON(json[key]);
|
||||||
|
});
|
||||||
|
return sortedObject;
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Command} cli
|
||||||
|
*/
|
||||||
|
module.exports = (cli) => {
|
||||||
|
const locale = cli.command('locale');
|
||||||
|
locale.command('generate').action(async (options) => {
|
||||||
|
const cwd = path.resolve(process.cwd(), 'node_modules', '@nocobase');
|
||||||
|
const files = await fg('./*/src/locale/*.json', {
|
||||||
|
cwd,
|
||||||
|
});
|
||||||
|
let locales = {};
|
||||||
|
await fs.mkdirp(path.resolve(process.cwd(), 'storage/locales'));
|
||||||
|
for (const file of files) {
|
||||||
|
const locale = path.basename(file, '.json');
|
||||||
|
const pkg = path.basename(path.dirname(path.dirname(path.dirname(file))));
|
||||||
|
_.set(locales, [locale.replace(/_/g, '-'), `@nocobase/${pkg}`], await fs.readJSON(path.resolve(cwd, file)));
|
||||||
|
if (locale.includes('_')) {
|
||||||
|
await fs.rename(
|
||||||
|
path.resolve(cwd, file),
|
||||||
|
path.resolve(cwd, path.dirname(file), `${locale.replace(/_/g, '-')}.json`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const zhCN = locales['zh-CN'];
|
||||||
|
const enUS = locales['en-US'];
|
||||||
|
for (const key1 in zhCN) {
|
||||||
|
for (const key2 in zhCN[key1]) {
|
||||||
|
if (!_.get(enUS, [key1, key2])) {
|
||||||
|
_.set(enUS, [key1, key2], key2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const locale of Object.keys(locales)) {
|
||||||
|
locales[locale] = deepmerge(enUS, locales[locale]);
|
||||||
|
locales[locale]['cronstrue'] = getCronstrueLocale(locale);
|
||||||
|
locales[locale]['react-js-cron'] = getReactJsCron(locale);
|
||||||
|
}
|
||||||
|
locales = sortJSON(locales);
|
||||||
|
for (const locale of Object.keys(locales)) {
|
||||||
|
await fs.writeFile(
|
||||||
|
path.resolve(process.cwd(), 'storage/locales', `${locale}.json`),
|
||||||
|
JSON.stringify(sortJSON(locales[locale]), null, 2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
locale.command('sync').action(async (options) => {});
|
||||||
|
};
|
122
packages/core/cli/src/commands/locale/cronstrue.js
Normal file
122
packages/core/cli/src/commands/locale/cronstrue.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/**
|
||||||
|
* 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 methods = [
|
||||||
|
'atX0SecondsPastTheMinuteGt20',
|
||||||
|
'atX0MinutesPastTheHourGt20',
|
||||||
|
'commaMonthX0ThroughMonthX1',
|
||||||
|
'commaYearX0ThroughYearX1',
|
||||||
|
'use24HourTimeFormatByDefault',
|
||||||
|
'anErrorOccuredWhenGeneratingTheExpressionD',
|
||||||
|
'everyMinute',
|
||||||
|
'everyHour',
|
||||||
|
'atSpace',
|
||||||
|
'everyMinuteBetweenX0AndX1',
|
||||||
|
'at',
|
||||||
|
'spaceAnd',
|
||||||
|
'everySecond',
|
||||||
|
'everyX0Seconds',
|
||||||
|
'secondsX0ThroughX1PastTheMinute',
|
||||||
|
'atX0SecondsPastTheMinute',
|
||||||
|
'everyX0Minutes',
|
||||||
|
'minutesX0ThroughX1PastTheHour',
|
||||||
|
'atX0MinutesPastTheHour',
|
||||||
|
'everyX0Hours',
|
||||||
|
'betweenX0AndX1',
|
||||||
|
'atX0',
|
||||||
|
'commaEveryDay',
|
||||||
|
'commaEveryX0DaysOfTheWeek',
|
||||||
|
'commaX0ThroughX1',
|
||||||
|
'commaAndX0ThroughX1',
|
||||||
|
'first',
|
||||||
|
'second',
|
||||||
|
'third',
|
||||||
|
'fourth',
|
||||||
|
'fifth',
|
||||||
|
'commaOnThe',
|
||||||
|
'spaceX0OfTheMonth',
|
||||||
|
'lastDay',
|
||||||
|
'commaOnTheLastX0OfTheMonth',
|
||||||
|
'commaOnlyOnX0',
|
||||||
|
'commaAndOnX0',
|
||||||
|
'commaEveryX0Months',
|
||||||
|
'commaOnlyInX0',
|
||||||
|
'commaOnTheLastDayOfTheMonth',
|
||||||
|
'commaOnTheLastWeekdayOfTheMonth',
|
||||||
|
'commaDaysBeforeTheLastDayOfTheMonth',
|
||||||
|
'firstWeekday',
|
||||||
|
'weekdayNearestDayX0',
|
||||||
|
'commaOnTheX0OfTheMonth',
|
||||||
|
'commaEveryX0Days',
|
||||||
|
'commaBetweenDayX0AndX1OfTheMonth',
|
||||||
|
'commaOnDayX0OfTheMonth',
|
||||||
|
'commaEveryHour',
|
||||||
|
'commaEveryX0Years',
|
||||||
|
'commaStartingX0',
|
||||||
|
'daysOfTheWeek',
|
||||||
|
'monthsOfTheYear',
|
||||||
|
];
|
||||||
|
|
||||||
|
const langs = {
|
||||||
|
af: 'af',
|
||||||
|
ar: 'ar',
|
||||||
|
be: 'be',
|
||||||
|
ca: 'ca',
|
||||||
|
cs: 'cs',
|
||||||
|
da: 'da',
|
||||||
|
de: 'de',
|
||||||
|
'en-US': 'en',
|
||||||
|
es: 'es',
|
||||||
|
fa: 'fa',
|
||||||
|
fi: 'fi',
|
||||||
|
fr: 'fr',
|
||||||
|
he: 'he',
|
||||||
|
hu: 'hu',
|
||||||
|
id: 'id',
|
||||||
|
it: 'it',
|
||||||
|
'ja-JP': 'ja',
|
||||||
|
ko: 'ko',
|
||||||
|
nb: 'nb',
|
||||||
|
nl: 'nl',
|
||||||
|
pl: 'pl',
|
||||||
|
pt_BR: 'pt_BR',
|
||||||
|
pt_PT: 'pt_PT',
|
||||||
|
ro: 'ro',
|
||||||
|
'ru-RU': 'ru',
|
||||||
|
sk: 'sk',
|
||||||
|
sl: 'sl',
|
||||||
|
sv: 'sv',
|
||||||
|
sw: 'sw',
|
||||||
|
'th-TH': 'th',
|
||||||
|
'tr-TR': 'tr',
|
||||||
|
uk: 'uk',
|
||||||
|
'zh-CN': 'zh_CN',
|
||||||
|
'zh-TW': 'zh_TW',
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getCronstrueLocale = (lang) => {
|
||||||
|
const lng = langs[lang] || 'en';
|
||||||
|
const Locale = require(`cronstrue/locales/${lng}`);
|
||||||
|
let locale;
|
||||||
|
if (Locale?.default) {
|
||||||
|
locale = Locale.default.locales[lng];
|
||||||
|
} else {
|
||||||
|
const L = Locale[lng];
|
||||||
|
locale = new L();
|
||||||
|
}
|
||||||
|
const items = {};
|
||||||
|
for (const method of methods) {
|
||||||
|
try {
|
||||||
|
items[method] = locale[method]();
|
||||||
|
} catch (error) {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
};
|
@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"everyText": "every",
|
||||||
|
"emptyMonths": "every month",
|
||||||
|
"emptyMonthDays": "every day of the month",
|
||||||
|
"emptyMonthDaysShort": "day of the month",
|
||||||
|
"emptyWeekDays": "every day of the week",
|
||||||
|
"emptyWeekDaysShort": "day of the week",
|
||||||
|
"emptyHours": "every hour",
|
||||||
|
"emptyMinutes": "every minute",
|
||||||
|
"emptyMinutesForHourPeriod": "every",
|
||||||
|
"yearOption": "year",
|
||||||
|
"monthOption": "month",
|
||||||
|
"weekOption": "week",
|
||||||
|
"dayOption": "day",
|
||||||
|
"hourOption": "hour",
|
||||||
|
"minuteOption": "minute",
|
||||||
|
"rebootOption": "reboot",
|
||||||
|
"prefixPeriod": "Every",
|
||||||
|
"prefixMonths": "in",
|
||||||
|
"prefixMonthDays": "on",
|
||||||
|
"prefixWeekDays": "on",
|
||||||
|
"prefixWeekDaysForMonthAndYearPeriod": "and",
|
||||||
|
"prefixHours": "at",
|
||||||
|
"prefixMinutes": ":",
|
||||||
|
"prefixMinutesForHourPeriod": "at",
|
||||||
|
"suffixMinutesForHourPeriod": "minute(s)",
|
||||||
|
"errorInvalidCron": "Invalid cron expression",
|
||||||
|
"clearButtonText": "Clear",
|
||||||
|
"weekDays": [
|
||||||
|
"Sunday",
|
||||||
|
"Monday",
|
||||||
|
"Tuesday",
|
||||||
|
"Wednesday",
|
||||||
|
"Thursday",
|
||||||
|
"Friday",
|
||||||
|
"Saturday"
|
||||||
|
],
|
||||||
|
"months": [
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December"
|
||||||
|
],
|
||||||
|
"altWeekDays": [
|
||||||
|
"SUN",
|
||||||
|
"MON",
|
||||||
|
"TUE",
|
||||||
|
"WED",
|
||||||
|
"THU",
|
||||||
|
"FRI",
|
||||||
|
"SAT"
|
||||||
|
],
|
||||||
|
"altMonths": [
|
||||||
|
"JAN",
|
||||||
|
"FEB",
|
||||||
|
"MAR",
|
||||||
|
"APR",
|
||||||
|
"MAY",
|
||||||
|
"JUN",
|
||||||
|
"JUL",
|
||||||
|
"AUG",
|
||||||
|
"SEP",
|
||||||
|
"OCT",
|
||||||
|
"NOV",
|
||||||
|
"DEC"
|
||||||
|
]
|
||||||
|
}
|
17
packages/core/cli/src/commands/locale/react-js-cron/index.js
vendored
Normal file
17
packages/core/cli/src/commands/locale/react-js-cron/index.js
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.getReactJsCron = (lang) => {
|
||||||
|
const langs = {
|
||||||
|
'en-US': require('./en-US.json'),
|
||||||
|
'zh-CN': require('./zh-CN.json'),
|
||||||
|
'z-TW': require('./zh-TW.json'),
|
||||||
|
}
|
||||||
|
return langs[lang] || langs['en-US'];
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"everyText": "每",
|
||||||
|
"emptyMonths": "每月",
|
||||||
|
"emptyMonthDays": "每日(月)",
|
||||||
|
"emptyMonthDaysShort": "每日",
|
||||||
|
"emptyWeekDays": "每天(周)",
|
||||||
|
"emptyWeekDaysShort": "每天(周)",
|
||||||
|
"emptyHours": "每小时",
|
||||||
|
"emptyMinutes": "每分钟",
|
||||||
|
"emptyMinutesForHourPeriod": "每",
|
||||||
|
"yearOption": "年",
|
||||||
|
"monthOption": "月",
|
||||||
|
"weekOption": "周",
|
||||||
|
"dayOption": "天",
|
||||||
|
"hourOption": "小时",
|
||||||
|
"minuteOption": "分钟",
|
||||||
|
"rebootOption": "重启",
|
||||||
|
"prefixPeriod": "每",
|
||||||
|
"prefixMonths": "的",
|
||||||
|
"prefixMonthDays": "的",
|
||||||
|
"prefixWeekDays": "的",
|
||||||
|
"prefixWeekDaysForMonthAndYearPeriod": "或者",
|
||||||
|
"prefixHours": "的",
|
||||||
|
"prefixMinutes": ":",
|
||||||
|
"prefixMinutesForHourPeriod": "的",
|
||||||
|
"suffixMinutesForHourPeriod": "分钟",
|
||||||
|
"errorInvalidCron": "不符合 cron 规则的表达式",
|
||||||
|
"clearButtonText": "清空",
|
||||||
|
"weekDays": ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||||
|
"months": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
|
||||||
|
"altWeekDays": ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
|
||||||
|
"altMonths": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"everyText": "每",
|
||||||
|
"emptyMonths": "每月",
|
||||||
|
"emptyMonthDays": "每日(月)",
|
||||||
|
"emptyMonthDaysShort": "每日",
|
||||||
|
"emptyWeekDays": "每天(週)",
|
||||||
|
"emptyWeekDaysShort": "每天(週)",
|
||||||
|
"emptyHours": "每小時",
|
||||||
|
"emptyMinutes": "每分鐘",
|
||||||
|
"emptyMinutesForHourPeriod": "每",
|
||||||
|
"yearOption": "年",
|
||||||
|
"monthOption": "月",
|
||||||
|
"weekOption": "週",
|
||||||
|
"dayOption": "天",
|
||||||
|
"hourOption": "小時",
|
||||||
|
"minuteOption": "分鐘",
|
||||||
|
"rebootOption": "重啟",
|
||||||
|
"prefixPeriod": "每",
|
||||||
|
"prefixMonths": "的",
|
||||||
|
"prefixMonthDays": "的",
|
||||||
|
"prefixWeekDays": "的",
|
||||||
|
"prefixWeekDaysForMonthAndYearPeriod": "或者",
|
||||||
|
"prefixHours": "的",
|
||||||
|
"prefixMinutes": ":",
|
||||||
|
"prefixMinutesForHourPeriod": "的",
|
||||||
|
"suffixMinutesForHourPeriod": "分鐘",
|
||||||
|
"errorInvalidCron": "不符合 cron 規則的表示式",
|
||||||
|
"clearButtonText": "清空",
|
||||||
|
"weekDays": ["週日", "週一", "週二", "週三", "週四", "週五", "週六"],
|
||||||
|
"months": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
|
||||||
|
"altWeekDays": ["週日", "週一", "週二", "週三", "週四", "週五", "週六"],
|
||||||
|
"altMonths": ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]
|
||||||
|
}
|
@ -462,6 +462,8 @@ exports.initEnv = function initEnv() {
|
|||||||
process.env.SOCKET_PATH = generateGatewayPath();
|
process.env.SOCKET_PATH = generateGatewayPath();
|
||||||
fs.mkdirpSync(dirname(process.env.SOCKET_PATH), { force: true, recursive: true });
|
fs.mkdirpSync(dirname(process.env.SOCKET_PATH), { force: true, recursive: true });
|
||||||
fs.mkdirpSync(process.env.PM2_HOME, { force: true, recursive: true });
|
fs.mkdirpSync(process.env.PM2_HOME, { force: true, recursive: true });
|
||||||
|
const pkgDir = resolve(process.cwd(), 'storage/plugins', '@nocobase/plugin-multi-app-manager');
|
||||||
|
fs.existsSync(pkgDir) && fs.rmdirSync(pkgDir, { force: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.generatePlugins = function () {
|
exports.generatePlugins = function () {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/client",
|
"name": "@nocobase/client",
|
||||||
"version": "1.7.0-beta.1",
|
"version": "1.7.0-alpha.10",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "es/index.mjs",
|
"module": "es/index.mjs",
|
||||||
@ -17,7 +17,7 @@
|
|||||||
"@dnd-kit/modifiers": "^6.0.0",
|
"@dnd-kit/modifiers": "^6.0.0",
|
||||||
"@dnd-kit/sortable": "^6.0.0",
|
"@dnd-kit/sortable": "^6.0.0",
|
||||||
"@emotion/css": "^11.7.1",
|
"@emotion/css": "^11.7.1",
|
||||||
"@formily/antd-v5": "1.1.9",
|
"@formily/antd-v5": "1.2.3",
|
||||||
"@formily/core": "^2.2.27",
|
"@formily/core": "^2.2.27",
|
||||||
"@formily/grid": "^2.2.27",
|
"@formily/grid": "^2.2.27",
|
||||||
"@formily/json-schema": "^2.2.27",
|
"@formily/json-schema": "^2.2.27",
|
||||||
@ -27,11 +27,11 @@
|
|||||||
"@formily/reactive-react": "^2.2.27",
|
"@formily/reactive-react": "^2.2.27",
|
||||||
"@formily/shared": "^2.2.27",
|
"@formily/shared": "^2.2.27",
|
||||||
"@formily/validator": "^2.2.27",
|
"@formily/validator": "^2.2.27",
|
||||||
"@nocobase/evaluators": "1.7.0-beta.1",
|
"@nocobase/evaluators": "1.7.0-alpha.10",
|
||||||
"@nocobase/sdk": "1.7.0-beta.1",
|
"@nocobase/sdk": "1.7.0-alpha.10",
|
||||||
"@nocobase/utils": "1.7.0-beta.1",
|
"@nocobase/utils": "1.7.0-alpha.10",
|
||||||
"ahooks": "^3.7.2",
|
"ahooks": "^3.7.2",
|
||||||
"antd": "5.12.8",
|
"antd": "5.24.2",
|
||||||
"antd-style": "3.7.1",
|
"antd-style": "3.7.1",
|
||||||
"axios": "^1.7.0",
|
"axios": "^1.7.0",
|
||||||
"bignumber.js": "^9.1.2",
|
"bignumber.js": "^9.1.2",
|
||||||
|
@ -74,6 +74,7 @@ export const ACLRolesCheckProvider = (props) => {
|
|||||||
url: 'roles:check',
|
url: 'roles:check',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
manual: !api.auth.token,
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
if (!data?.data?.snippets.includes('ui.*')) {
|
if (!data?.data?.snippets.includes('ui.*')) {
|
||||||
setDesignable(false);
|
setDesignable(false);
|
||||||
@ -102,6 +103,11 @@ export const useRoleRecheck = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCurrentRoleMode = () => {
|
||||||
|
const ctx = useContext(ACLContext);
|
||||||
|
return ctx?.data?.data?.roleMode;
|
||||||
|
};
|
||||||
|
|
||||||
export const useACLContext = () => {
|
export const useACLContext = () => {
|
||||||
return useContext(ACLContext);
|
return useContext(ACLContext);
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Checkbox, message, Table } from 'antd';
|
import { Checkbox, message, Table, TableProps } from 'antd';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import React, { createContext, useContext, useMemo, useState } from 'react';
|
import React, { createContext, useContext, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -102,44 +102,46 @@ export const SettingsCenterConfigure = () => {
|
|||||||
expandable={{
|
expandable={{
|
||||||
defaultExpandAllRows: true,
|
defaultExpandAllRows: true,
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={
|
||||||
{
|
[
|
||||||
dataIndex: 'title',
|
{
|
||||||
title: t('Plugin name'),
|
dataIndex: 'title',
|
||||||
render: (value) => {
|
title: t('Plugin name'),
|
||||||
return compile(value);
|
render: (value) => {
|
||||||
|
return compile(value);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
dataIndex: 'accessible',
|
||||||
dataIndex: 'accessible',
|
title: (
|
||||||
title: (
|
<>
|
||||||
<>
|
<Checkbox
|
||||||
<Checkbox
|
checked={allChecked}
|
||||||
checked={allChecked}
|
onChange={async () => {
|
||||||
onChange={async () => {
|
const values = allAclSnippets.map((v) => '!' + v);
|
||||||
const values = allAclSnippets.map((v) => '!' + v);
|
if (!allChecked) {
|
||||||
if (!allChecked) {
|
await resource.remove({
|
||||||
await resource.remove({
|
values,
|
||||||
values,
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
await resource.add({
|
||||||
await resource.add({
|
values,
|
||||||
values,
|
});
|
||||||
});
|
}
|
||||||
}
|
refresh();
|
||||||
refresh();
|
message.success(t('Saved successfully'));
|
||||||
message.success(t('Saved successfully'));
|
}}
|
||||||
}}
|
/>{' '}
|
||||||
/>{' '}
|
{t('Accessible')}
|
||||||
{t('Accessible')}
|
</>
|
||||||
</>
|
),
|
||||||
),
|
render: (_, record) => {
|
||||||
render: (_, record) => {
|
const checked = !snippets.includes('!' + record.aclSnippet);
|
||||||
const checked = !snippets.includes('!' + record.aclSnippet);
|
return <Checkbox checked={checked} onChange={() => handleChange(checked, record)} />;
|
||||||
return <Checkbox checked={checked} onChange={() => handleChange(checked, record)} />;
|
},
|
||||||
},
|
},
|
||||||
},
|
] as TableProps['columns']
|
||||||
]}
|
}
|
||||||
dataSource={settings
|
dataSource={settings
|
||||||
.filter((v) => {
|
.filter((v) => {
|
||||||
return v.isTopLevel !== false;
|
return v.isTopLevel !== false;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Checkbox, message, Table } from 'antd';
|
import { Checkbox, message, Table, TableProps } from 'antd';
|
||||||
import { uniq } from 'lodash';
|
import { uniq } from 'lodash';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -121,40 +121,42 @@ export const MenuConfigure = () => {
|
|||||||
expandable={{
|
expandable={{
|
||||||
defaultExpandAllRows: true,
|
defaultExpandAllRows: true,
|
||||||
}}
|
}}
|
||||||
columns={[
|
columns={
|
||||||
{
|
[
|
||||||
dataIndex: 'title',
|
{
|
||||||
title: t('Menu item title'),
|
dataIndex: 'title',
|
||||||
},
|
title: t('Menu item title'),
|
||||||
{
|
|
||||||
dataIndex: 'accessible',
|
|
||||||
title: (
|
|
||||||
<>
|
|
||||||
<Checkbox
|
|
||||||
checked={allChecked}
|
|
||||||
onChange={async (value) => {
|
|
||||||
if (allChecked) {
|
|
||||||
await resource.set({
|
|
||||||
values: [],
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await resource.set({
|
|
||||||
values: allUids,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
refresh();
|
|
||||||
message.success(t('Saved successfully'));
|
|
||||||
}}
|
|
||||||
/>{' '}
|
|
||||||
{t('Accessible')}
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
render: (_, schema) => {
|
|
||||||
const checked = uids.includes(schema.uid);
|
|
||||||
return <Checkbox checked={checked} onChange={() => handleChange(checked, schema)} />;
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
]}
|
dataIndex: 'accessible',
|
||||||
|
title: (
|
||||||
|
<>
|
||||||
|
<Checkbox
|
||||||
|
checked={allChecked}
|
||||||
|
onChange={async (value) => {
|
||||||
|
if (allChecked) {
|
||||||
|
await resource.set({
|
||||||
|
values: [],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await resource.set({
|
||||||
|
values: allUids,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
refresh();
|
||||||
|
message.success(t('Saved successfully'));
|
||||||
|
}}
|
||||||
|
/>{' '}
|
||||||
|
{t('Accessible')}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
render: (_, schema: { uid: string }) => {
|
||||||
|
const checked = uids.includes(schema.uid);
|
||||||
|
return <Checkbox checked={checked} onChange={() => handleChange(checked, schema)} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as TableProps['columns']
|
||||||
|
}
|
||||||
dataSource={translateTitle(items)}
|
dataSource={translateTitle(items)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
import { FormItem, FormLayout } from '@formily/antd-v5';
|
import { FormItem, FormLayout } from '@formily/antd-v5';
|
||||||
import { ArrayField } from '@formily/core';
|
import { ArrayField } from '@formily/core';
|
||||||
import { connect, useField, useForm } from '@formily/react';
|
import { connect, useField, useForm } from '@formily/react';
|
||||||
import { Checkbox, Table, Tag } from 'antd';
|
import { Checkbox, Table, Tag, TableProps } from 'antd';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -105,48 +105,50 @@ export const RolesResourcesActions = connect((props) => {
|
|||||||
className={antTableCell}
|
className={antTableCell}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
columns={[
|
columns={
|
||||||
{
|
[
|
||||||
dataIndex: 'displayName',
|
{
|
||||||
title: t('Action display name'),
|
dataIndex: 'displayName',
|
||||||
render: (value) => compile(value),
|
title: t('Action display name'),
|
||||||
},
|
render: (value) => compile(value),
|
||||||
{
|
},
|
||||||
dataIndex: 'onNewRecord',
|
{
|
||||||
title: t('Action type'),
|
dataIndex: 'onNewRecord',
|
||||||
render: (onNewRecord) =>
|
title: t('Action type'),
|
||||||
onNewRecord ? (
|
render: (onNewRecord) =>
|
||||||
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
onNewRecord ? (
|
||||||
) : (
|
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
||||||
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
) : (
|
||||||
),
|
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
||||||
},
|
),
|
||||||
{
|
},
|
||||||
dataIndex: 'enabled',
|
{
|
||||||
title: t('Allow'),
|
dataIndex: 'enabled',
|
||||||
render: (enabled, action) => (
|
title: t('Allow'),
|
||||||
<Checkbox
|
render: (enabled, action) => (
|
||||||
checked={enabled}
|
<Checkbox
|
||||||
onChange={() => {
|
checked={enabled}
|
||||||
toggleAction(action.name);
|
onChange={() => {
|
||||||
}}
|
toggleAction(action.name);
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dataIndex: 'scope',
|
|
||||||
title: t('Data scope'),
|
|
||||||
render: (value, action) =>
|
|
||||||
!action.onNewRecord && (
|
|
||||||
<ScopeSelect
|
|
||||||
value={value}
|
|
||||||
onChange={(scope) => {
|
|
||||||
setScope(action.name, scope);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]}
|
{
|
||||||
|
dataIndex: 'scope',
|
||||||
|
title: t('Data scope'),
|
||||||
|
render: (value, action) =>
|
||||||
|
!action.onNewRecord && (
|
||||||
|
<ScopeSelect
|
||||||
|
value={value}
|
||||||
|
onChange={(scope) => {
|
||||||
|
setScope(action.name, scope);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] as TableProps['columns']
|
||||||
|
}
|
||||||
dataSource={availableActions?.map((item) => {
|
dataSource={availableActions?.map((item) => {
|
||||||
let enabled = false;
|
let enabled = false;
|
||||||
let scope = null;
|
let scope = null;
|
||||||
@ -169,60 +171,62 @@ export const RolesResourcesActions = connect((props) => {
|
|||||||
className={antTableCell}
|
className={antTableCell}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
dataSource={fieldPermissions}
|
dataSource={fieldPermissions}
|
||||||
columns={[
|
columns={
|
||||||
{
|
[
|
||||||
dataIndex: ['uiSchema', 'title'],
|
{
|
||||||
title: t('Field display name'),
|
dataIndex: ['uiSchema', 'title'],
|
||||||
render: (value) => compile(value),
|
title: t('Field display name'),
|
||||||
},
|
render: (value) => compile(value),
|
||||||
...availableActionsWithFields.map((action) => {
|
},
|
||||||
const checked = allChecked?.[action.name];
|
...availableActionsWithFields.map((action) => {
|
||||||
return {
|
const checked = allChecked?.[action.name];
|
||||||
dataIndex: action.name,
|
return {
|
||||||
title: (
|
dataIndex: action.name,
|
||||||
<>
|
title: (
|
||||||
|
<>
|
||||||
|
<Checkbox
|
||||||
|
checked={checked}
|
||||||
|
onChange={() => {
|
||||||
|
const item = actionMap[action.name] || {
|
||||||
|
name: action.name,
|
||||||
|
};
|
||||||
|
if (checked) {
|
||||||
|
item.fields = [];
|
||||||
|
} else {
|
||||||
|
item.fields = collectionFields?.map?.((item) => item.name);
|
||||||
|
}
|
||||||
|
actionMap[action.name] = item;
|
||||||
|
onChange(Object.values(actionMap));
|
||||||
|
}}
|
||||||
|
/>{' '}
|
||||||
|
{compile(action.displayName)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
render: (checked, field) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={checked}
|
checked={checked}
|
||||||
|
aria-label={`${action.name}_checkbox`}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
const item = actionMap[action.name] || {
|
const item = actionMap[action.name] || {
|
||||||
name: action.name,
|
name: action.name,
|
||||||
};
|
};
|
||||||
|
const fields: string[] = item.fields || [];
|
||||||
if (checked) {
|
if (checked) {
|
||||||
item.fields = [];
|
const index = fields.indexOf(field.name);
|
||||||
|
fields.splice(index, 1);
|
||||||
} else {
|
} else {
|
||||||
item.fields = collectionFields?.map?.((item) => item.name);
|
fields.push(field.name);
|
||||||
}
|
}
|
||||||
|
item.fields = fields;
|
||||||
actionMap[action.name] = item;
|
actionMap[action.name] = item;
|
||||||
onChange(Object.values(actionMap));
|
onChange(Object.values(actionMap));
|
||||||
}}
|
}}
|
||||||
/>{' '}
|
/>
|
||||||
{compile(action.displayName)}
|
),
|
||||||
</>
|
};
|
||||||
),
|
}),
|
||||||
render: (checked, field) => (
|
] as TableProps['columns']
|
||||||
<Checkbox
|
}
|
||||||
checked={checked}
|
|
||||||
aria-label={`${action.name}_checkbox`}
|
|
||||||
onChange={() => {
|
|
||||||
const item = actionMap[action.name] || {
|
|
||||||
name: action.name,
|
|
||||||
};
|
|
||||||
const fields: string[] = item.fields || [];
|
|
||||||
if (checked) {
|
|
||||||
const index = fields.indexOf(field.name);
|
|
||||||
fields.splice(index, 1);
|
|
||||||
} else {
|
|
||||||
fields.push(field.name);
|
|
||||||
}
|
|
||||||
item.fields = fields;
|
|
||||||
actionMap[action.name] = item;
|
|
||||||
onChange(Object.values(actionMap));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormLayout>
|
</FormLayout>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import { ArrayField } from '@formily/core';
|
import { ArrayField } from '@formily/core';
|
||||||
import { connect, useField } from '@formily/react';
|
import { connect, useField } from '@formily/react';
|
||||||
import { Checkbox, Select, Table, Tag } from 'antd';
|
import { Checkbox, Select, Table, Tag, TableProps } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCompile } from '../..';
|
import { useCompile } from '../..';
|
||||||
@ -55,62 +55,64 @@ export const StrategyActions = connect((props) => {
|
|||||||
size={'small'}
|
size={'small'}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
rowKey={'name'}
|
rowKey={'name'}
|
||||||
columns={[
|
columns={
|
||||||
{
|
[
|
||||||
dataIndex: 'displayName',
|
{
|
||||||
title: t('Action display name'),
|
dataIndex: 'displayName',
|
||||||
render: (value) => compile(value),
|
title: t('Action display name'),
|
||||||
},
|
render: (value) => compile(value),
|
||||||
{
|
},
|
||||||
dataIndex: 'onNewRecord',
|
{
|
||||||
title: t('Action type'),
|
dataIndex: 'onNewRecord',
|
||||||
render: (onNewRecord) =>
|
title: t('Action type'),
|
||||||
onNewRecord ? (
|
render: (onNewRecord) =>
|
||||||
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
onNewRecord ? (
|
||||||
) : (
|
<Tag color={'green'}>{t('Action on new records')}</Tag>
|
||||||
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
) : (
|
||||||
),
|
<Tag color={'geekblue'}>{t('Action on existing records')}</Tag>
|
||||||
},
|
),
|
||||||
{
|
},
|
||||||
dataIndex: 'enabled',
|
{
|
||||||
title: t('Allow'),
|
dataIndex: 'enabled',
|
||||||
render: (enabled, action) => (
|
title: t('Allow'),
|
||||||
<Checkbox
|
render: (enabled, action) => (
|
||||||
checked={enabled}
|
<Checkbox
|
||||||
aria-label={`${action.name}_checkbox`}
|
checked={enabled}
|
||||||
onChange={(e) => {
|
aria-label={`${action.name}_checkbox`}
|
||||||
if (enabled) {
|
onChange={(e) => {
|
||||||
delete scopes[action.name];
|
if (enabled) {
|
||||||
} else {
|
delete scopes[action.name];
|
||||||
scopes[action.name] = 'all';
|
} else {
|
||||||
}
|
scopes[action.name] = 'all';
|
||||||
onChange(toFieldValue(scopes));
|
}
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dataIndex: 'scope',
|
|
||||||
title: t('Data scope'),
|
|
||||||
render: (scope, action) =>
|
|
||||||
!action.onNewRecord && (
|
|
||||||
<Select
|
|
||||||
data-testid="select-data-scope"
|
|
||||||
popupMatchSelectWidth={false}
|
|
||||||
size={'small'}
|
|
||||||
value={scope}
|
|
||||||
options={[
|
|
||||||
{ label: t('All records'), value: 'all' },
|
|
||||||
{ label: t('Own records'), value: 'own' },
|
|
||||||
]}
|
|
||||||
onChange={(value) => {
|
|
||||||
scopes[action.name] = value;
|
|
||||||
onChange(toFieldValue(scopes));
|
onChange(toFieldValue(scopes));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]}
|
{
|
||||||
|
dataIndex: 'scope',
|
||||||
|
title: t('Data scope'),
|
||||||
|
render: (scope, action) =>
|
||||||
|
!action.onNewRecord && (
|
||||||
|
<Select
|
||||||
|
data-testid="select-data-scope"
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
|
size={'small'}
|
||||||
|
value={scope}
|
||||||
|
options={[
|
||||||
|
{ label: t('All records'), value: 'all' },
|
||||||
|
{ label: t('Own records'), value: 'own' },
|
||||||
|
]}
|
||||||
|
onChange={(value) => {
|
||||||
|
scopes[action.name] = value;
|
||||||
|
onChange(toFieldValue(scopes));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] as TableProps['columns']
|
||||||
|
}
|
||||||
dataSource={availableActions?.map((item) => {
|
dataSource={availableActions?.map((item) => {
|
||||||
let scope = 'all';
|
let scope = 'all';
|
||||||
let enabled = false;
|
let enabled = false;
|
||||||
|
@ -139,7 +139,19 @@ export class APIClient extends APIClientSDK {
|
|||||||
if (typeof error?.response?.data === 'string') {
|
if (typeof error?.response?.data === 'string') {
|
||||||
const tempElement = document.createElement('div');
|
const tempElement = document.createElement('div');
|
||||||
tempElement.innerHTML = error?.response?.data;
|
tempElement.innerHTML = error?.response?.data;
|
||||||
return [{ message: tempElement.textContent || tempElement.innerText }];
|
let message = tempElement.textContent || tempElement.innerText;
|
||||||
|
if (message.includes('Error occurred while trying')) {
|
||||||
|
message = 'The application may be starting up. Please try again later.';
|
||||||
|
return [{ code: 'APP_WARNING', message }];
|
||||||
|
}
|
||||||
|
if (message.includes('502 Bad Gateway')) {
|
||||||
|
message = 'The application may be starting up. Please try again later.';
|
||||||
|
return [{ code: 'APP_WARNING', message }];
|
||||||
|
}
|
||||||
|
return [{ message }];
|
||||||
|
}
|
||||||
|
if (error?.response?.data?.error) {
|
||||||
|
return [error?.response?.data?.error];
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
error?.response?.data?.errors ||
|
error?.response?.data?.errors ||
|
||||||
|
@ -108,6 +108,7 @@ export class Application {
|
|||||||
public name: string;
|
public name: string;
|
||||||
public favicon: string;
|
public favicon: string;
|
||||||
public globalVars: Record<string, any> = {};
|
public globalVars: Record<string, any> = {};
|
||||||
|
public globalVarCtxs: Record<string, any> = {};
|
||||||
public jsonLogic: JsonLogic;
|
public jsonLogic: JsonLogic;
|
||||||
loading = true;
|
loading = true;
|
||||||
maintained = false;
|
maintained = false;
|
||||||
@ -350,23 +351,9 @@ export class Application {
|
|||||||
setTimeout(() => resolve(null), 1000);
|
setTimeout(() => resolve(null), 1000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const toError = (error) => {
|
|
||||||
if (typeof error?.response?.data === 'string') {
|
|
||||||
const tempElement = document.createElement('div');
|
|
||||||
tempElement.innerHTML = error?.response?.data;
|
|
||||||
return { message: tempElement.textContent || tempElement.innerText };
|
|
||||||
}
|
|
||||||
if (error?.response?.data?.error) {
|
|
||||||
return error?.response?.data?.error;
|
|
||||||
}
|
|
||||||
if (error?.response?.data?.errors?.[0]) {
|
|
||||||
return error?.response?.data?.errors?.[0];
|
|
||||||
}
|
|
||||||
return { message: error?.message };
|
|
||||||
};
|
|
||||||
this.error = {
|
this.error = {
|
||||||
code: 'LOAD_ERROR',
|
code: 'LOAD_ERROR',
|
||||||
...toError(error),
|
...this.apiClient.toErrMessages(error)?.[0],
|
||||||
};
|
};
|
||||||
console.error(error, this.error);
|
console.error(error, this.error);
|
||||||
}
|
}
|
||||||
@ -508,13 +495,20 @@ export class Application {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addGlobalVar(key: string, value: any) {
|
addGlobalVar(key: string, value: any, varCtx?: any) {
|
||||||
set(this.globalVars, key, value);
|
set(this.globalVars, key, value);
|
||||||
|
if (varCtx) {
|
||||||
|
set(this.globalVarCtxs, key, varCtx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getGlobalVar(key) {
|
getGlobalVar(key) {
|
||||||
return get(this.globalVars, key);
|
return get(this.globalVars, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGlobalVarCtx(key) {
|
||||||
|
return get(this.globalVarCtxs, key);
|
||||||
|
}
|
||||||
addUserCenterSettingsItem(item: SchemaSettingsItemType & { aclSnippet?: string }) {
|
addUserCenterSettingsItem(item: SchemaSettingsItemType & { aclSnippet?: string }) {
|
||||||
const useVisibleProp = item.useVisible || (() => true);
|
const useVisibleProp = item.useVisible || (() => true);
|
||||||
const useVisible = () => {
|
const useVisible = () => {
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import { get, set } from 'lodash';
|
import { get, set } from 'lodash';
|
||||||
import React, { ComponentType, createContext, useContext } from 'react';
|
import React, { ComponentType, createContext, useContext } from 'react';
|
||||||
|
import { matchRoutes } from 'react-router';
|
||||||
import {
|
import {
|
||||||
BrowserRouterProps,
|
BrowserRouterProps,
|
||||||
createBrowserRouter,
|
createBrowserRouter,
|
||||||
@ -42,6 +43,7 @@ export type RouterOptions = (HashRouterOptions | BrowserRouterOptions | MemoryRo
|
|||||||
export type ComponentTypeAndString<T = any> = ComponentType<T> | string;
|
export type ComponentTypeAndString<T = any> = ComponentType<T> | string;
|
||||||
export interface RouteType extends Omit<RouteObject, 'children' | 'Component'> {
|
export interface RouteType extends Omit<RouteObject, 'children' | 'Component'> {
|
||||||
Component?: ComponentTypeAndString;
|
Component?: ComponentTypeAndString;
|
||||||
|
skipAuthCheck?: boolean;
|
||||||
}
|
}
|
||||||
export type RenderComponentType = (Component: ComponentTypeAndString, props?: any) => React.ReactNode;
|
export type RenderComponentType = (Component: ComponentTypeAndString, props?: any) => React.ReactNode;
|
||||||
|
|
||||||
@ -134,6 +136,18 @@ export class RouterManager {
|
|||||||
this.options.basename = basename;
|
this.options.basename = basename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
matchRoutes(pathname: string) {
|
||||||
|
const routes = Object.values(this.routes);
|
||||||
|
// @ts-ignore
|
||||||
|
return matchRoutes<RouteType>(routes, pathname, this.basename);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSkippedAuthCheckRoute(pathname: string) {
|
||||||
|
const matchedRoutes = this.matchRoutes(pathname);
|
||||||
|
return matchedRoutes.some((match) => {
|
||||||
|
return match?.route?.skipAuthCheck === true;
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -30,7 +30,7 @@ describe('Router', () => {
|
|||||||
let router: RouterManager;
|
let router: RouterManager;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
router = new RouterManager({ type: 'memory', initialEntries: ['/'] }, app);
|
router = new RouterManager({ type: 'memory', initialEntries: ['/'], basename: '/nocobase/apps/test1' }, app);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('basic', () => {
|
it('basic', () => {
|
||||||
@ -132,6 +132,38 @@ describe('Router', () => {
|
|||||||
router.add('test', route);
|
router.add('test', route);
|
||||||
expect(router.getRoutesTree()).toEqual([{ path: '/', element: <Hello />, children: undefined }]);
|
expect(router.getRoutesTree()).toEqual([{ path: '/', element: <Hello />, children: undefined }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('add skipAuthCheck route', () => {
|
||||||
|
router.add('skip-auth-check', { path: '/skip-auth-check', Component: 'Hello', skipAuthCheck: true });
|
||||||
|
router.add('not-skip-auth-check', { path: '/not-skip-auth-check', Component: 'Hello' });
|
||||||
|
|
||||||
|
const RouterComponent = router.getRouterComponent();
|
||||||
|
const BaseLayout: FC = (props) => {
|
||||||
|
return <div>BaseLayout {props.children}</div>;
|
||||||
|
};
|
||||||
|
render(<RouterComponent BaseLayout={BaseLayout} />);
|
||||||
|
router.navigate('/skip-auth-check');
|
||||||
|
const state = router.state;
|
||||||
|
const { pathname, search } = state.location;
|
||||||
|
const isSkipedAuthCheck = router.isSkippedAuthCheckRoute(pathname);
|
||||||
|
expect(isSkipedAuthCheck).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add not skipAuthCheck route', () => {
|
||||||
|
router.add('skip-auth-check', { path: '/skip-auth-check', Component: 'Hello', skipAuthCheck: true });
|
||||||
|
router.add('not-skip-auth-check', { path: '/not-skip-auth-check', Component: 'Hello' });
|
||||||
|
|
||||||
|
const RouterComponent = router.getRouterComponent();
|
||||||
|
const BaseLayout: FC = (props) => {
|
||||||
|
return <div>BaseLayout {props.children}</div>;
|
||||||
|
};
|
||||||
|
render(<RouterComponent BaseLayout={BaseLayout} />);
|
||||||
|
router.navigate('/not-skip-auth-check');
|
||||||
|
const state = router.state;
|
||||||
|
const { pathname, search } = state.location;
|
||||||
|
const isSkipedAuthCheck = router.isSkippedAuthCheckRoute(pathname);
|
||||||
|
expect(isSkipedAuthCheck).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('remove', () => {
|
describe('remove', () => {
|
||||||
|
@ -11,10 +11,11 @@ import React, { FC } from 'react';
|
|||||||
import { MainComponent } from './MainComponent';
|
import { MainComponent } from './MainComponent';
|
||||||
|
|
||||||
const Loading: FC = () => <div>Loading...</div>;
|
const Loading: FC = () => <div>Loading...</div>;
|
||||||
const AppError: FC<{ error: Error }> = ({ error }) => {
|
const AppError: FC<{ error: Error & { title?: string } }> = ({ error }) => {
|
||||||
|
const title = error?.title || 'App Error';
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>App Error</div>
|
<div>{title}</div>
|
||||||
{error?.message}
|
{error?.message}
|
||||||
{process.env.__TEST__ && error?.stack}
|
{process.env.__TEST__ && error?.stack}
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,3 +12,4 @@ export * from './useAppSpin';
|
|||||||
export * from './usePlugin';
|
export * from './usePlugin';
|
||||||
export * from './useRouter';
|
export * from './useRouter';
|
||||||
export * from './useGlobalVariable';
|
export * from './useGlobalVariable';
|
||||||
|
export * from './useAclSnippets';
|
||||||
|
@ -29,3 +29,22 @@ export const useGlobalVariable = (key: string) => {
|
|||||||
|
|
||||||
return variable;
|
return variable;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGlobalVariableCtx = (key: string) => {
|
||||||
|
const app = useApp();
|
||||||
|
|
||||||
|
const variable = useMemo(() => {
|
||||||
|
return app?.getGlobalVarCtx?.(key);
|
||||||
|
}, [app, key]);
|
||||||
|
|
||||||
|
if (isFunction(variable)) {
|
||||||
|
try {
|
||||||
|
return variable();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error calling global variable function for key: ${key}`, error);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return variable;
|
||||||
|
};
|
||||||
|
@ -63,7 +63,7 @@ export const SchemaInitializerItem = memo(
|
|||||||
className: className,
|
className: className,
|
||||||
label: children || compile(title),
|
label: children || compile(title),
|
||||||
onClick: (info) => {
|
onClick: (info) => {
|
||||||
if (info.key !== name) return;
|
if (disabled || info.key !== name) return;
|
||||||
if (closeInitializerMenuWhenClick) {
|
if (closeInitializerMenuWhenClick) {
|
||||||
setVisible?.(false);
|
setVisible?.(false);
|
||||||
}
|
}
|
||||||
@ -73,10 +73,10 @@ export const SchemaInitializerItem = memo(
|
|||||||
children: childrenItems,
|
children: childrenItems,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [name, style, className, children, title, onClick, icon, childrenItems]);
|
}, [name, disabled, style, className, children, title, onClick, icon, childrenItems]);
|
||||||
|
|
||||||
if (items && items.length > 0) {
|
if (items && items.length > 0) {
|
||||||
return <SchemaInitializerMenu items={menuItems}></SchemaInitializerMenu>;
|
return <SchemaInitializerMenu disabled={disabled} items={menuItems}></SchemaInitializerMenu>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -12,7 +12,6 @@ import { useField, useFieldSchema } from '@formily/react';
|
|||||||
import { useUpdate } from 'ahooks';
|
import { useUpdate } from 'ahooks';
|
||||||
import { Spin } from 'antd';
|
import { Spin } from 'antd';
|
||||||
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
|
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
|
||||||
import { useCollectionManager_deprecated } from '../collection-manager';
|
|
||||||
import { useCollection, useCollectionRecordData } from '../data-source';
|
import { useCollection, useCollectionRecordData } from '../data-source';
|
||||||
import { useCollectionParentRecord } from '../data-source/collection-record/CollectionRecordProvider';
|
import { useCollectionParentRecord } from '../data-source/collection-record/CollectionRecordProvider';
|
||||||
import { withDynamicSchemaProps } from '../hoc/withDynamicSchemaProps';
|
import { withDynamicSchemaProps } from '../hoc/withDynamicSchemaProps';
|
||||||
@ -103,8 +102,7 @@ const useCompatDetailsBlockParams = (props) => {
|
|||||||
export const DetailsBlockProvider = withDynamicSchemaProps((props) => {
|
export const DetailsBlockProvider = withDynamicSchemaProps((props) => {
|
||||||
const { params, parseVariableLoading } = useCompatDetailsBlockParams(props);
|
const { params, parseVariableLoading } = useCompatDetailsBlockParams(props);
|
||||||
const record = useCollectionRecordData();
|
const record = useCollectionRecordData();
|
||||||
const { association, dataSource, action } = props;
|
const { association, action } = props;
|
||||||
const { getCollection } = useCollectionManager_deprecated(dataSource);
|
|
||||||
const { __collection } = record || {};
|
const { __collection } = record || {};
|
||||||
const { designable } = useDesignable();
|
const { designable } = useDesignable();
|
||||||
const collectionName = props.collection;
|
const collectionName = props.collection;
|
||||||
|
@ -62,6 +62,7 @@ interface Props {
|
|||||||
children?: any;
|
children?: any;
|
||||||
expandFlag?: boolean;
|
expandFlag?: boolean;
|
||||||
dragSortBy?: string;
|
dragSortBy?: string;
|
||||||
|
enableIndexÏColumn?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InternalTableBlockProvider = (props: Props) => {
|
const InternalTableBlockProvider = (props: Props) => {
|
||||||
@ -74,6 +75,7 @@ const InternalTableBlockProvider = (props: Props) => {
|
|||||||
expandFlag: propsExpandFlag = false,
|
expandFlag: propsExpandFlag = false,
|
||||||
fieldNames,
|
fieldNames,
|
||||||
collection,
|
collection,
|
||||||
|
enableIndexÏColumn,
|
||||||
} = props;
|
} = props;
|
||||||
const field: any = useField();
|
const field: any = useField();
|
||||||
const { resource, service } = useBlockRequestContext();
|
const { resource, service } = useBlockRequestContext();
|
||||||
@ -131,6 +133,7 @@ const InternalTableBlockProvider = (props: Props) => {
|
|||||||
allIncludesChildren,
|
allIncludesChildren,
|
||||||
setExpandFlag: setExpandFlagValue,
|
setExpandFlag: setExpandFlagValue,
|
||||||
heightProps,
|
heightProps,
|
||||||
|
enableIndexÏColumn,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
allIncludesChildren,
|
allIncludesChildren,
|
||||||
@ -146,6 +149,7 @@ const InternalTableBlockProvider = (props: Props) => {
|
|||||||
service,
|
service,
|
||||||
setExpandFlagValue,
|
setExpandFlagValue,
|
||||||
showIndex,
|
showIndex,
|
||||||
|
enableIndexÏColumn,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ export function useCollectValuesToSubmit() {
|
|||||||
if (parsedValue !== null && parsedValue !== undefined) {
|
if (parsedValue !== null && parsedValue !== undefined) {
|
||||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||||
}
|
}
|
||||||
} else if (value != null && value !== '') {
|
} else if (value !== '') {
|
||||||
assignedValues[key] = value;
|
assignedValues[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -203,6 +203,12 @@ export function useCollectValuesToSubmit() {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function interpolateVariables(str: string, scope: Record<string, any>): string {
|
||||||
|
return str.replace(/\{\{\s*([a-zA-Z0-9_$-.]+?)\s*\}\}/g, (_, key) => {
|
||||||
|
return scope[key] !== undefined ? String(scope[key]) : '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const useCreateActionProps = () => {
|
export const useCreateActionProps = () => {
|
||||||
const filterByTk = useFilterByTk();
|
const filterByTk = useFilterByTk();
|
||||||
const record = useCollectionRecord();
|
const record = useCollectionRecord();
|
||||||
@ -219,11 +225,20 @@ export const useCreateActionProps = () => {
|
|||||||
const collectValues = useCollectValuesToSubmit();
|
const collectValues = useCollectValuesToSubmit();
|
||||||
const action = record.isNew ? actionField.componentProps.saveMode || 'create' : 'update';
|
const action = record.isNew ? actionField.componentProps.saveMode || 'create' : 'update';
|
||||||
const filterKeys = actionField.componentProps.filterKeys?.checked || [];
|
const filterKeys = actionField.componentProps.filterKeys?.checked || [];
|
||||||
|
const localVariables = useLocalVariables();
|
||||||
|
const variables = useVariables();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async onClick() {
|
async onClick() {
|
||||||
const { onSuccess, skipValidator, triggerWorkflows } = actionSchema?.['x-action-settings'] ?? {};
|
const { onSuccess, skipValidator, triggerWorkflows } = actionSchema?.['x-action-settings'] ?? {};
|
||||||
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
|
const {
|
||||||
|
manualClose,
|
||||||
|
redirecting,
|
||||||
|
redirectTo: rawRedirectTo,
|
||||||
|
successMessage,
|
||||||
|
actionAfterSuccess,
|
||||||
|
} = onSuccess || {};
|
||||||
|
|
||||||
if (!skipValidator) {
|
if (!skipValidator) {
|
||||||
await form.submit();
|
await form.submit();
|
||||||
}
|
}
|
||||||
@ -241,6 +256,15 @@ export const useCreateActionProps = () => {
|
|||||||
: undefined,
|
: undefined,
|
||||||
updateAssociationValues,
|
updateAssociationValues,
|
||||||
});
|
});
|
||||||
|
let redirectTo = rawRedirectTo;
|
||||||
|
if (rawRedirectTo) {
|
||||||
|
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
|
||||||
|
variables,
|
||||||
|
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(data?.data?.data, {}) }],
|
||||||
|
});
|
||||||
|
redirectTo = interpolateVariables(exp, expScope);
|
||||||
|
}
|
||||||
|
|
||||||
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
|
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
|
||||||
setVisible?.(false);
|
setVisible?.(false);
|
||||||
}
|
}
|
||||||
@ -338,7 +362,7 @@ export const useAssociationCreateActionProps = () => {
|
|||||||
if (parsedValue) {
|
if (parsedValue) {
|
||||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||||
}
|
}
|
||||||
} else if (value != null && value !== '') {
|
} else if (value !== '') {
|
||||||
assignedValues[key] = value;
|
assignedValues[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -468,6 +492,10 @@ const useDoFilter = () => {
|
|||||||
block.defaultFilter,
|
block.defaultFilter,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (_.isEmpty(storedFilter[uid])) {
|
||||||
|
block.clearSelection?.();
|
||||||
|
}
|
||||||
|
|
||||||
if (doNothingWhenFilterIsEmpty && _.isEmpty(storedFilter[uid])) {
|
if (doNothingWhenFilterIsEmpty && _.isEmpty(storedFilter[uid])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -584,7 +612,13 @@ export const useCustomizeUpdateActionProps = () => {
|
|||||||
skipValidator,
|
skipValidator,
|
||||||
triggerWorkflows,
|
triggerWorkflows,
|
||||||
} = actionSchema?.['x-action-settings'] ?? {};
|
} = actionSchema?.['x-action-settings'] ?? {};
|
||||||
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
|
const {
|
||||||
|
manualClose,
|
||||||
|
redirecting,
|
||||||
|
redirectTo: rawRedirectTo,
|
||||||
|
successMessage,
|
||||||
|
actionAfterSuccess,
|
||||||
|
} = onSuccess || {};
|
||||||
const assignedValues = {};
|
const assignedValues = {};
|
||||||
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
||||||
const value = originalAssignedValues[key];
|
const value = originalAssignedValues[key];
|
||||||
@ -601,7 +635,7 @@ export const useCustomizeUpdateActionProps = () => {
|
|||||||
if (parsedValue) {
|
if (parsedValue) {
|
||||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||||
}
|
}
|
||||||
} else if (value != null && value !== '') {
|
} else if (value !== '') {
|
||||||
assignedValues[key] = value;
|
assignedValues[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -610,7 +644,7 @@ export const useCustomizeUpdateActionProps = () => {
|
|||||||
if (skipValidator === false) {
|
if (skipValidator === false) {
|
||||||
await form.submit();
|
await form.submit();
|
||||||
}
|
}
|
||||||
await resource.update({
|
const result = await resource.update({
|
||||||
filterByTk,
|
filterByTk,
|
||||||
values: { ...assignedValues },
|
values: { ...assignedValues },
|
||||||
// TODO(refactor): should change to inject by plugin
|
// TODO(refactor): should change to inject by plugin
|
||||||
@ -618,6 +652,16 @@ export const useCustomizeUpdateActionProps = () => {
|
|||||||
? triggerWorkflows.map((row) => [row.workflowKey, row.context].filter(Boolean).join('!')).join(',')
|
? triggerWorkflows.map((row) => [row.workflowKey, row.context].filter(Boolean).join('!')).join(',')
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let redirectTo = rawRedirectTo;
|
||||||
|
if (rawRedirectTo) {
|
||||||
|
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
|
||||||
|
variables,
|
||||||
|
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(result?.data?.data?.[0], {}) }],
|
||||||
|
});
|
||||||
|
redirectTo = interpolateVariables(exp, expScope);
|
||||||
|
}
|
||||||
|
|
||||||
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
|
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
|
||||||
setVisible?.(false);
|
setVisible?.(false);
|
||||||
}
|
}
|
||||||
@ -704,7 +748,7 @@ export const useCustomizeBulkUpdateActionProps = () => {
|
|||||||
if (parsedValue) {
|
if (parsedValue) {
|
||||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||||
}
|
}
|
||||||
} else if (value != null && value !== '') {
|
} else if (value !== '') {
|
||||||
assignedValues[key] = value;
|
assignedValues[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -909,7 +953,13 @@ export const useUpdateActionProps = () => {
|
|||||||
skipValidator,
|
skipValidator,
|
||||||
triggerWorkflows,
|
triggerWorkflows,
|
||||||
} = actionSchema?.['x-action-settings'] ?? {};
|
} = actionSchema?.['x-action-settings'] ?? {};
|
||||||
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
|
const {
|
||||||
|
manualClose,
|
||||||
|
redirecting,
|
||||||
|
redirectTo: rawRedirectTo,
|
||||||
|
successMessage,
|
||||||
|
actionAfterSuccess,
|
||||||
|
} = onSuccess || {};
|
||||||
const assignedValues = {};
|
const assignedValues = {};
|
||||||
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
||||||
const value = originalAssignedValues[key];
|
const value = originalAssignedValues[key];
|
||||||
@ -926,7 +976,7 @@ export const useUpdateActionProps = () => {
|
|||||||
if (parsedValue) {
|
if (parsedValue) {
|
||||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||||
}
|
}
|
||||||
} else if (value != null && value !== '') {
|
} else if (value !== '') {
|
||||||
assignedValues[key] = value;
|
assignedValues[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -948,7 +998,7 @@ export const useUpdateActionProps = () => {
|
|||||||
actionField.data = field.data || {};
|
actionField.data = field.data || {};
|
||||||
actionField.data.loading = true;
|
actionField.data.loading = true;
|
||||||
try {
|
try {
|
||||||
await resource.update({
|
const result = await resource.update({
|
||||||
filterByTk,
|
filterByTk,
|
||||||
values: {
|
values: {
|
||||||
...values,
|
...values,
|
||||||
@ -967,6 +1017,15 @@ export const useUpdateActionProps = () => {
|
|||||||
if (callBack) {
|
if (callBack) {
|
||||||
callBack?.();
|
callBack?.();
|
||||||
}
|
}
|
||||||
|
let redirectTo = rawRedirectTo;
|
||||||
|
if (rawRedirectTo) {
|
||||||
|
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
|
||||||
|
variables,
|
||||||
|
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(result?.data?.data?.[0], {}) }],
|
||||||
|
});
|
||||||
|
redirectTo = interpolateVariables(exp, expScope);
|
||||||
|
}
|
||||||
|
|
||||||
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
|
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
|
||||||
setVisible?.(false);
|
setVisible?.(false);
|
||||||
}
|
}
|
||||||
@ -1153,6 +1212,7 @@ export const useDetailsPaginationProps = () => {
|
|||||||
current: ctx.service?.data?.meta?.page || 1,
|
current: ctx.service?.data?.meta?.page || 1,
|
||||||
pageSize: 1,
|
pageSize: 1,
|
||||||
showSizeChanger: false,
|
showSizeChanger: false,
|
||||||
|
align: 'center',
|
||||||
async onChange(page) {
|
async onChange(page) {
|
||||||
const params = ctx.service?.params?.[0];
|
const params = ctx.service?.params?.[0];
|
||||||
ctx.service.run({ ...params, page });
|
ctx.service.run({ ...params, page });
|
||||||
@ -1178,6 +1238,7 @@ export const useDetailsPaginationProps = () => {
|
|||||||
total: count,
|
total: count,
|
||||||
pageSize: 1,
|
pageSize: 1,
|
||||||
showSizeChanger: false,
|
showSizeChanger: false,
|
||||||
|
align: 'center',
|
||||||
async onChange(page) {
|
async onChange(page) {
|
||||||
const params = ctx.service?.params?.[0];
|
const params = ctx.service?.params?.[0];
|
||||||
ctx.service.run({ ...params, page });
|
ctx.service.run({ ...params, page });
|
||||||
@ -1349,6 +1410,7 @@ export const useAssociationFilterBlockProps = () => {
|
|||||||
[filterKey]: value,
|
[filterKey]: value,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
block.clearSelection?.();
|
||||||
if (block.dataLoadingMode === 'manual') {
|
if (block.dataLoadingMode === 'manual') {
|
||||||
return block.clearData();
|
return block.clearData();
|
||||||
}
|
}
|
||||||
@ -1442,6 +1504,8 @@ async function doReset({
|
|||||||
const target = targets.find((target) => target.uid === block.uid);
|
const target = targets.find((target) => target.uid === block.uid);
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
|
|
||||||
|
block.clearSelection?.();
|
||||||
|
|
||||||
if (block.dataLoadingMode === 'manual') {
|
if (block.dataLoadingMode === 'manual') {
|
||||||
return block.clearData();
|
return block.clearData();
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import { useCollectionManager_deprecated } from '../hooks';
|
|||||||
import useDialect from '../hooks/useDialect';
|
import useDialect from '../hooks/useDialect';
|
||||||
import * as components from './components';
|
import * as components from './components';
|
||||||
import { useFieldInterfaceOptions } from './interfaces';
|
import { useFieldInterfaceOptions } from './interfaces';
|
||||||
|
import { ItemType, MenuItemType } from 'antd/es/menu/interface';
|
||||||
|
|
||||||
const getSchema = (schema: CollectionFieldInterface, record: any, compile) => {
|
const getSchema = (schema: CollectionFieldInterface, record: any, compile) => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
@ -231,7 +232,7 @@ export const AddFieldAction = (props) => {
|
|||||||
}, [getTemplate, record]);
|
}, [getTemplate, record]);
|
||||||
const items = useMemo<MenuProps['items']>(() => {
|
const items = useMemo<MenuProps['items']>(() => {
|
||||||
return getFieldOptions()
|
return getFieldOptions()
|
||||||
.map((option) => {
|
.map((option): ItemType & { title: string; children?: ItemType[] } => {
|
||||||
if (option?.children?.length === 0) {
|
if (option?.children?.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -157,6 +157,8 @@ export const useCollectionFilterOptions = (collection: any, dataSource?: string)
|
|||||||
const option = {
|
const option = {
|
||||||
name: field.name,
|
name: field.name,
|
||||||
title: field?.uiSchema?.title || field.name,
|
title: field?.uiSchema?.title || field.name,
|
||||||
|
label: field?.uiSchema?.title || field.name,
|
||||||
|
value: field.name,
|
||||||
schema: field?.uiSchema,
|
schema: field?.uiSchema,
|
||||||
operators:
|
operators:
|
||||||
operators?.filter?.((operator) => {
|
operators?.filter?.((operator) => {
|
||||||
|
@ -62,6 +62,12 @@ export class InputFieldInterface extends CollectionFieldInterface {
|
|||||||
hasDefaultValue = true;
|
hasDefaultValue = true;
|
||||||
properties = {
|
properties = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
|
trim: {
|
||||||
|
type: 'boolean',
|
||||||
|
'x-content': '{{t("Automatically remove heading and tailing spaces")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
},
|
||||||
layout: {
|
layout: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: '{{t("Index")}}',
|
title: '{{t("Index")}}',
|
||||||
|
@ -129,12 +129,12 @@ export const enumType = [
|
|||||||
label: '{{t("is")}}',
|
label: '{{t("is")}}',
|
||||||
value: '$eq',
|
value: '$eq',
|
||||||
selected: true,
|
selected: true,
|
||||||
schema: { 'x-component': 'Select' },
|
schema: { 'x-component': 'Select', 'x-component-props': { mode: null } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '{{t("is not")}}',
|
label: '{{t("is not")}}',
|
||||||
value: '$ne',
|
value: '$ne',
|
||||||
schema: { 'x-component': 'Select' },
|
schema: { 'x-component': 'Select', 'x-component-props': { mode: null } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '{{t("is any of")}}',
|
label: '{{t("is any of")}}',
|
||||||
|
@ -28,8 +28,15 @@ export class TextareaFieldInterface extends CollectionFieldInterface {
|
|||||||
};
|
};
|
||||||
availableTypes = ['text', 'json', 'string'];
|
availableTypes = ['text', 'json', 'string'];
|
||||||
hasDefaultValue = true;
|
hasDefaultValue = true;
|
||||||
|
titleUsable = true;
|
||||||
properties = {
|
properties = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
|
trim: {
|
||||||
|
type: 'boolean',
|
||||||
|
'x-content': '{{t("Automatically remove heading and tailing spaces")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
schemaInitialize(schema: ISchema, { block }) {
|
schemaInitialize(schema: ISchema, { block }) {
|
||||||
if (['Table', 'Kanban'].includes(block)) {
|
if (['Table', 'Kanban'].includes(block)) {
|
||||||
|
@ -96,7 +96,7 @@ export const PresetFields = observer(
|
|||||||
rowSelection={{
|
rowSelection={{
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
getCheckboxProps: (record) => ({
|
getCheckboxProps: (record: { name: string }) => ({
|
||||||
name: record.name,
|
name: record.name,
|
||||||
disabled: props?.disabled || props?.presetFieldsDisabledIncludes?.includes?.(record.name),
|
disabled: props?.disabled || props?.presetFieldsDisabledIncludes?.includes?.(record.name),
|
||||||
}),
|
}),
|
||||||
|
@ -18,7 +18,14 @@ export interface SelectWithTitleProps {
|
|||||||
onChange?: (...args: any[]) => void;
|
onChange?: (...args: any[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectWithTitle({ title, defaultValue, onChange, options, fieldNames }: SelectWithTitleProps) {
|
export function SelectWithTitle({
|
||||||
|
title,
|
||||||
|
defaultValue,
|
||||||
|
onChange,
|
||||||
|
options,
|
||||||
|
fieldNames,
|
||||||
|
...others
|
||||||
|
}: SelectWithTitleProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const timerRef = useRef<any>(null);
|
const timerRef = useRef<any>(null);
|
||||||
return (
|
return (
|
||||||
@ -36,6 +43,7 @@ export function SelectWithTitle({ title, defaultValue, onChange, options, fieldN
|
|||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
<Select
|
<Select
|
||||||
|
{...others}
|
||||||
open={open}
|
open={open}
|
||||||
data-testid={`select-${title}`}
|
data-testid={`select-${title}`}
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
|
@ -37,6 +37,7 @@ export const CSSVariableProvider = ({ children }) => {
|
|||||||
document.body.style.setProperty('--colorWarningBg', token.colorWarningBg);
|
document.body.style.setProperty('--colorWarningBg', token.colorWarningBg);
|
||||||
document.body.style.setProperty('--colorWarningBorder', token.colorWarningBorder);
|
document.body.style.setProperty('--colorWarningBorder', token.colorWarningBorder);
|
||||||
document.body.style.setProperty('--colorText', token.colorText);
|
document.body.style.setProperty('--colorText', token.colorText);
|
||||||
|
document.body.style.setProperty('--colorTextHeaderMenu', token.colorTextHeaderMenu);
|
||||||
document.body.style.setProperty('--colorPrimaryText', token.colorPrimaryText);
|
document.body.style.setProperty('--colorPrimaryText', token.colorPrimaryText);
|
||||||
document.body.style.setProperty('--colorPrimaryTextActive', token.colorPrimaryTextActive);
|
document.body.style.setProperty('--colorPrimaryTextActive', token.colorPrimaryTextActive);
|
||||||
document.body.style.setProperty('--colorPrimaryTextHover', token.colorPrimaryTextHover);
|
document.body.style.setProperty('--colorPrimaryTextHover', token.colorPrimaryTextHover);
|
||||||
@ -46,6 +47,7 @@ export const CSSVariableProvider = ({ children }) => {
|
|||||||
document.body.style.setProperty('--colorBgScrollBarActive', colorBgScrollBarActive);
|
document.body.style.setProperty('--colorBgScrollBarActive', colorBgScrollBarActive);
|
||||||
document.body.style.setProperty('--colorSettings', token.colorSettings || defaultTheme.token.colorSettings);
|
document.body.style.setProperty('--colorSettings', token.colorSettings || defaultTheme.token.colorSettings);
|
||||||
document.body.style.setProperty('--colorBgSettingsHover', token.colorBgSettingsHover);
|
document.body.style.setProperty('--colorBgSettingsHover', token.colorBgSettingsHover);
|
||||||
|
document.body.style.setProperty('--colorTemplateBgSettingsHover', token.colorTemplateBgSettingsHover);
|
||||||
document.body.style.setProperty('--colorBorderSettingsHover', token.colorBorderSettingsHover);
|
document.body.style.setProperty('--colorBorderSettingsHover', token.colorBorderSettingsHover);
|
||||||
document.body.style.setProperty('--colorBgMenuItemSelected', token.colorBgHeaderMenuActive);
|
document.body.style.setProperty('--colorBgMenuItemSelected', token.colorBgHeaderMenuActive);
|
||||||
|
|
||||||
@ -59,6 +61,7 @@ export const CSSVariableProvider = ({ children }) => {
|
|||||||
token.colorBgContainer,
|
token.colorBgContainer,
|
||||||
token.colorBgLayout,
|
token.colorBgLayout,
|
||||||
token.colorBgSettingsHover,
|
token.colorBgSettingsHover,
|
||||||
|
token.colorTemplateBgSettingsHover,
|
||||||
token.colorBorderSettingsHover,
|
token.colorBorderSettingsHover,
|
||||||
token.colorInfoBg,
|
token.colorInfoBg,
|
||||||
token.colorInfoBorder,
|
token.colorInfoBorder,
|
||||||
@ -75,6 +78,7 @@ export const CSSVariableProvider = ({ children }) => {
|
|||||||
token.marginXS,
|
token.marginXS,
|
||||||
token.paddingContentVerticalSM,
|
token.paddingContentVerticalSM,
|
||||||
token.sizeXXL,
|
token.sizeXXL,
|
||||||
|
token.colorTextHeaderMenu,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
|
@ -83,6 +83,8 @@ const CollectionFieldInternalField_deprecated: React.FC = (props: Props) => {
|
|||||||
setRequired(field, fieldSchema, uiSchema);
|
setRequired(field, fieldSchema, uiSchema);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
field.dataSource = uiSchema.enum;
|
field.dataSource = uiSchema.enum;
|
||||||
|
field.data = field.data || {};
|
||||||
|
field.data.dataSource = uiSchema?.enum;
|
||||||
const originalProps = compile(uiSchema['x-component-props']) || {};
|
const originalProps = compile(uiSchema['x-component-props']) || {};
|
||||||
field.componentProps = merge(originalProps, field.componentProps || {}, dynamicProps || {});
|
field.componentProps = merge(originalProps, field.componentProps || {}, dynamicProps || {});
|
||||||
}, [uiSchemaOrigin]);
|
}, [uiSchemaOrigin]);
|
||||||
@ -102,12 +104,33 @@ const CollectionFieldInternalField = (props) => {
|
|||||||
const dynamicProps = useDynamicComponentProps(uiSchema?.['x-use-component-props'], props);
|
const dynamicProps = useDynamicComponentProps(uiSchema?.['x-use-component-props'], props);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// There seems to be a bug in formily where after setting a field to readPretty, switching to editable,
|
/**
|
||||||
// then back to readPretty, and refreshing the page, the field remains in editable state. The expected state is readPretty.
|
* There seems to be a bug in formily where after setting a field to readPretty, switching to editable,
|
||||||
// This code is meant to fix this issue.
|
* then back to readPretty, and refreshing the page, the field remains in editable state. The expected state is readPretty.
|
||||||
|
* This code is meant to fix this issue.
|
||||||
|
*/
|
||||||
if (fieldSchema['x-read-pretty'] === true && !field.readPretty) {
|
if (fieldSchema['x-read-pretty'] === true && !field.readPretty) {
|
||||||
field.readPretty = true;
|
field.readPretty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This solves the issue: After creating a form and setting a field to "read-only", the field remains editable when refreshing the page and reopening the dialog.
|
||||||
|
*
|
||||||
|
* Note: This might be a bug in Formily
|
||||||
|
* When both x-disabled and x-read-pretty exist in the Schema:
|
||||||
|
* - If x-disabled appears before x-read-pretty in the Schema JSON, the disabled state becomes ineffective
|
||||||
|
* - The reason is that during field instance initialization, field.disabled is set before field.readPretty, which causes the pattern value to be changed to 'editable'
|
||||||
|
* - This issue is related to the order of JSON fields, which might return different orders in different environments (databases), thus making the issue inconsistent to reproduce
|
||||||
|
*
|
||||||
|
* Reference to Formily source code:
|
||||||
|
* 1. Setting readPretty may cause pattern to be changed to 'editable': https://github.com/alibaba/formily/blob/d4bb96c40e7918210b1bd7d57b8fadee0cfe4b26/packages/core/src/models/BaseField.ts#L208-L224
|
||||||
|
* 2. The execution order of the each method depends on the order of JSON fields: https://github.com/alibaba/formily/blob/123d536b6076196e00b4e02ee160d72480359f54/packages/json-schema/src/schema.ts#L486-L519
|
||||||
|
*/
|
||||||
|
if (fieldSchema['x-disabled'] === true) {
|
||||||
|
field.disabled = true;
|
||||||
|
}
|
||||||
|
field.data = field.data || {};
|
||||||
|
field.data.dataSource = uiSchema?.enum;
|
||||||
}, [field, fieldSchema]);
|
}, [field, fieldSchema]);
|
||||||
|
|
||||||
if (!uiSchema) return null;
|
if (!uiSchema) return null;
|
||||||
|
@ -19,6 +19,7 @@ import { mergeFilter, useAssociatedFields } from './utils';
|
|||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import React, { createContext, useCallback, useEffect, useMemo, useRef } from 'react';
|
import React, { createContext, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
|
import { useAllDataBlocks } from '../schema-component/antd/page/AllDataBlocksProvider';
|
||||||
|
|
||||||
enum FILTER_OPERATOR {
|
enum FILTER_OPERATOR {
|
||||||
AND = '$and',
|
AND = '$and',
|
||||||
@ -54,6 +55,8 @@ export interface DataBlock {
|
|||||||
clearFilter: (uid: string) => void;
|
clearFilter: (uid: string) => void;
|
||||||
/** 将数据区块的数据置为空 */
|
/** 将数据区块的数据置为空 */
|
||||||
clearData: () => void;
|
clearData: () => void;
|
||||||
|
/** 清除表格的选中项 */
|
||||||
|
clearSelection?: () => void;
|
||||||
/** 数据区块表中所有的关系字段 */
|
/** 数据区块表中所有的关系字段 */
|
||||||
associatedFields?: CollectionFieldOptions_deprecated[];
|
associatedFields?: CollectionFieldOptions_deprecated[];
|
||||||
/** 数据区块表中所有的外键字段 */
|
/** 数据区块表中所有的外键字段 */
|
||||||
@ -69,6 +72,10 @@ export interface DataBlock {
|
|||||||
* manual: 只有当点击了筛选按钮,才会请求数据
|
* manual: 只有当点击了筛选按钮,才会请求数据
|
||||||
*/
|
*/
|
||||||
dataLoadingMode?: 'auto' | 'manual';
|
dataLoadingMode?: 'auto' | 'manual';
|
||||||
|
/** 让整个区块悬浮起来 */
|
||||||
|
highlightBlock: () => void;
|
||||||
|
/** 取消悬浮 */
|
||||||
|
unhighlightBlock: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilterContextValue {
|
interface FilterContextValue {
|
||||||
@ -122,7 +129,7 @@ export const DataBlockCollector = ({
|
|||||||
const field = useField();
|
const field = useField();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const associatedFields = useAssociatedFields();
|
const associatedFields = useAssociatedFields();
|
||||||
const container = useRef(null);
|
const container = useRef<HTMLDivElement | null>(null);
|
||||||
const dataLoadingMode = useDataLoadingMode();
|
const dataLoadingMode = useDataLoadingMode();
|
||||||
|
|
||||||
const shouldApplyFilter =
|
const shouldApplyFilter =
|
||||||
@ -165,16 +172,49 @@ export const DataBlockCollector = ({
|
|||||||
clearData() {
|
clearData() {
|
||||||
this.service.mutate(undefined);
|
this.service.mutate(undefined);
|
||||||
},
|
},
|
||||||
|
clearSelection() {
|
||||||
|
if (field) {
|
||||||
|
field.data?.clearSelectedRowKeys?.();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
highlightBlock() {
|
||||||
|
const dom = container.current;
|
||||||
|
|
||||||
|
if (!dom) return;
|
||||||
|
|
||||||
|
const designer = dom.querySelector('.ant-nb-schema-toolbar');
|
||||||
|
if (designer) {
|
||||||
|
designer.classList.remove(process.env.__E2E__ ? 'hidden-e2e' : 'hidden');
|
||||||
|
}
|
||||||
|
dom.style.boxShadow = '0 3px 12px rgba(0, 0, 0, 0.15)';
|
||||||
|
dom.style.transition = 'box-shadow 0.3s ease, transform 0.2s ease';
|
||||||
|
dom.scrollIntoView?.({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'start',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unhighlightBlock() {
|
||||||
|
const dom = container.current;
|
||||||
|
|
||||||
|
if (!dom) return;
|
||||||
|
|
||||||
|
const designer = dom.querySelector('.ant-nb-schema-toolbar');
|
||||||
|
if (designer) {
|
||||||
|
designer.classList.add(process.env.__E2E__ ? 'hidden-e2e' : 'hidden');
|
||||||
|
}
|
||||||
|
dom.style.boxShadow = 'none';
|
||||||
|
dom.style.transition = 'box-shadow 0.3s ease, transform 0.2s ease';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
associatedFields,
|
associatedFields,
|
||||||
collection,
|
collection,
|
||||||
dataLoadingMode,
|
dataLoadingMode,
|
||||||
field?.componentProps?.title,
|
|
||||||
fieldSchema,
|
fieldSchema,
|
||||||
params?.filter,
|
params?.filter,
|
||||||
recordDataBlocks,
|
recordDataBlocks,
|
||||||
getDataBlockRequest,
|
getDataBlockRequest,
|
||||||
|
field,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -190,12 +230,14 @@ export const DataBlockCollector = ({
|
|||||||
*/
|
*/
|
||||||
export const useFilterBlock = () => {
|
export const useFilterBlock = () => {
|
||||||
const ctx = React.useContext(FilterContext);
|
const ctx = React.useContext(FilterContext);
|
||||||
|
const allDataBlocksCtx = useAllDataBlocks();
|
||||||
|
|
||||||
// 有可能存在页面没有提供 FilterBlockProvider 的情况,比如内部使用的数据表管理页面
|
// 有可能存在页面没有提供 FilterBlockProvider 的情况,比如内部使用的数据表管理页面
|
||||||
const getDataBlocks = useCallback<() => DataBlock[]>(() => ctx?.getDataBlocks() || [], [ctx]);
|
const getDataBlocks = useCallback<() => DataBlock[]>(() => ctx?.getDataBlocks() || [], [ctx]);
|
||||||
|
|
||||||
const recordDataBlocks = useCallback(
|
const recordDataBlocks = useCallback(
|
||||||
(block: DataBlock) => {
|
(block: DataBlock) => {
|
||||||
|
allDataBlocksCtx.recordDataBlocks(block);
|
||||||
const existingBlock = ctx?.getDataBlocks().find((item) => item.uid === block.uid);
|
const existingBlock = ctx?.getDataBlocks().find((item) => item.uid === block.uid);
|
||||||
|
|
||||||
if (existingBlock) {
|
if (existingBlock) {
|
||||||
@ -211,6 +253,7 @@ export const useFilterBlock = () => {
|
|||||||
|
|
||||||
const removeDataBlock = useCallback(
|
const removeDataBlock = useCallback(
|
||||||
(uid: string) => {
|
(uid: string) => {
|
||||||
|
allDataBlocksCtx.removeDataBlock(uid);
|
||||||
if (ctx?.getDataBlocks().every((item) => item.uid !== uid)) return;
|
if (ctx?.getDataBlocks().every((item) => item.uid !== uid)) return;
|
||||||
ctx?.setDataBlocks((prev) => prev.filter((item) => item.uid !== uid));
|
ctx?.setDataBlocks((prev) => prev.filter((item) => item.uid !== uid));
|
||||||
},
|
},
|
||||||
|
47
packages/core/client/src/filter-provider/highlightBlock.ts
Normal file
47
packages/core/client/src/filter-provider/highlightBlock.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
let container: HTMLElement | null = null;
|
||||||
|
|
||||||
|
export const highlightBlock = (clonedBlockDom: HTMLElement, boxRect: DOMRect) => {
|
||||||
|
if (!container) {
|
||||||
|
container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
container.style.position = 'absolute';
|
||||||
|
container.style.transition = 'opacity 0.3s ease';
|
||||||
|
container.style.pointerEvents = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(clonedBlockDom);
|
||||||
|
container.style.opacity = '1';
|
||||||
|
container.style.width = `${boxRect.width}px`;
|
||||||
|
container.style.height = `${boxRect.height}px`;
|
||||||
|
container.style.top = `${boxRect.top}px`;
|
||||||
|
container.style.left = `${boxRect.left}px`;
|
||||||
|
container.style.zIndex = '2000';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unhighlightBlock = () => {
|
||||||
|
if (container) {
|
||||||
|
container.style.opacity = '0';
|
||||||
|
container.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startScrollEndTracking = (dom: HTMLElement & { _prevRect?: DOMRect; _timer?: any }, callback: () => void) => {
|
||||||
|
dom._timer = setInterval(() => {
|
||||||
|
const prevRect = dom._prevRect;
|
||||||
|
const currentRect = dom.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (!prevRect || currentRect.top !== prevRect.top) {
|
||||||
|
dom._prevRect = currentRect;
|
||||||
|
} else {
|
||||||
|
clearInterval(dom._timer);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopScrollEndTracking = (dom: HTMLElement & { _timer?: any }) => {
|
||||||
|
if (dom._timer) {
|
||||||
|
clearInterval(dom._timer);
|
||||||
|
dom._timer = null;
|
||||||
|
}
|
||||||
|
}
|
@ -215,7 +215,7 @@ export const useFilterAPI = () => {
|
|||||||
// 保留原有的 filter
|
// 保留原有的 filter
|
||||||
const storedFilter = block.service.params?.[1]?.filters || {};
|
const storedFilter = block.service.params?.[1]?.filters || {};
|
||||||
|
|
||||||
if (value !== undefined) {
|
if (value != null) {
|
||||||
storedFilter[uid] = {
|
storedFilter[uid] = {
|
||||||
$and: [
|
$and: [
|
||||||
{
|
{
|
||||||
@ -226,7 +226,11 @@ export const useFilterAPI = () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
block.clearSelection?.();
|
||||||
delete storedFilter[uid];
|
delete storedFilter[uid];
|
||||||
|
if (block.dataLoadingMode === 'manual') {
|
||||||
|
return block.clearData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergedFilter = mergeFilter([
|
const mergedFilter = mergeFilter([
|
||||||
|
@ -48,6 +48,7 @@ interface INocoBaseRecursionFieldProps extends IRecursionFieldProps {
|
|||||||
* Whether to use Formily Field class - performance will be reduced but provides better compatibility with Formily
|
* Whether to use Formily Field class - performance will be reduced but provides better compatibility with Formily
|
||||||
*/
|
*/
|
||||||
isUseFormilyField?: boolean;
|
isUseFormilyField?: boolean;
|
||||||
|
parentSchema?: Schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollectionFieldUISchemaContext = React.createContext<CollectionFieldOptions>({});
|
const CollectionFieldUISchemaContext = React.createContext<CollectionFieldOptions>({});
|
||||||
@ -266,6 +267,7 @@ export const NocoBaseRecursionField: ReactFC<INocoBaseRecursionFieldProps> = Rea
|
|||||||
values,
|
values,
|
||||||
isUseFormilyField = true,
|
isUseFormilyField = true,
|
||||||
uiSchema,
|
uiSchema,
|
||||||
|
parentSchema,
|
||||||
} = props;
|
} = props;
|
||||||
const basePath = useBasePath(props);
|
const basePath = useBasePath(props);
|
||||||
const newFieldSchemaRef = useRef(null);
|
const newFieldSchemaRef = useRef(null);
|
||||||
@ -279,6 +281,14 @@ export const NocoBaseRecursionField: ReactFC<INocoBaseRecursionFieldProps> = Rea
|
|||||||
|
|
||||||
const fieldSchema: Schema = newFieldSchemaRef.current || oldFieldSchema;
|
const fieldSchema: Schema = newFieldSchemaRef.current || oldFieldSchema;
|
||||||
|
|
||||||
|
// Establish connection with the Schema tree
|
||||||
|
if (!fieldSchema.parent && parentSchema) {
|
||||||
|
fieldSchema.parent = parentSchema;
|
||||||
|
if (!fieldSchema.parent?.properties?.[fieldSchema.name] && fieldSchema.name) {
|
||||||
|
_.set(fieldSchema.parent, `properties.${fieldSchema.name}`, fieldSchema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const refresh = useCallback(() => {
|
const refresh = useCallback(() => {
|
||||||
const parent = fieldSchema.parent;
|
const parent = fieldSchema.parent;
|
||||||
newFieldSchemaRef.current = new Schema(fieldSchema.toJSON(), parent);
|
newFieldSchemaRef.current = new Schema(fieldSchema.toJSON(), parent);
|
||||||
|
@ -24,11 +24,13 @@ const defaultTheme: ThemeConfig = {
|
|||||||
// UI 配置组件
|
// UI 配置组件
|
||||||
colorSettings: '#F18B62',
|
colorSettings: '#F18B62',
|
||||||
colorBgSettingsHover: 'rgba(241, 139, 98, 0.06)',
|
colorBgSettingsHover: 'rgba(241, 139, 98, 0.06)',
|
||||||
|
colorTemplateBgSettingsHover: 'rgba(98, 200, 241, 0.06)', // 默认为colorBgSettingsHover的互补色
|
||||||
colorBorderSettingsHover: 'rgba(241, 139, 98, 0.3)',
|
colorBorderSettingsHover: 'rgba(241, 139, 98, 0.3)',
|
||||||
|
|
||||||
// 动画相关
|
// 动画相关
|
||||||
motionUnit: 0.03,
|
motionUnit: 0.03,
|
||||||
motion: !process.env.__E2E__,
|
// ant design 升级到5.24.2后,Modal.confirm在E2E中如果关闭动画,会出现ant-modal-mask不销毁的问题
|
||||||
|
// motion: !process.env.__E2E__,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,6 +30,8 @@ export interface CustomToken extends AliasToken {
|
|||||||
colorSettings: string;
|
colorSettings: string;
|
||||||
/** 鼠标悬浮时显示的背景色 */
|
/** 鼠标悬浮时显示的背景色 */
|
||||||
colorBgSettingsHover: string;
|
colorBgSettingsHover: string;
|
||||||
|
/** 鼠标悬浮模板区块时显示的背景色 */
|
||||||
|
colorTemplateBgSettingsHover: string;
|
||||||
/** 鼠标悬浮时显示的边框色 */
|
/** 鼠标悬浮时显示的边框色 */
|
||||||
colorBorderSettingsHover: string;
|
colorBorderSettingsHover: string;
|
||||||
|
|
||||||
|
12
packages/core/client/src/i18n/constant.ts
Normal file
12
packages/core/client/src/i18n/constant.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* 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 NAMESPACE_UI_SCHEMA = 'ui-schema-storage';
|
||||||
|
|
||||||
|
export { NAMESPACE_UI_SCHEMA };
|
@ -8,3 +8,4 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './i18n';
|
export * from './i18n';
|
||||||
|
export * from './constant';
|
||||||
|
891
packages/core/client/src/locale/de-DE.json
Normal file
891
packages/core/client/src/locale/de-DE.json
Normal file
@ -0,0 +1,891 @@
|
|||||||
|
{
|
||||||
|
"Display <1><0>10</0><1>20</1><2>50</2><3>100</3></1> items per page": "<1><0>10</0><1>20</1><2>50</2><3>100</3></1> Einträge pro Seite anzeigen",
|
||||||
|
"Meet <1><0>All</0><1>Any</1></1> conditions in the group": "<1><0>Alle</0><1>Beliebige</1></1> Bedingungen in der Gruppe erfüllen",
|
||||||
|
"Open in<1><0>Modal</0><1>Drawer</1><2>Window</2></1>": "Öffnen in<1><0>Modal</0><1>Seitenleiste</1><2>Fenster</2></1>",
|
||||||
|
"{{count}} filter items": "{{count}} Filterelemente",
|
||||||
|
"{{count}} more items": "{{count}} weitere Einträge",
|
||||||
|
"Total {{count}} items": "Insgesamt {{count}} Einträge",
|
||||||
|
"Today": "Heute",
|
||||||
|
"Yesterday": "Gestern",
|
||||||
|
"Tomorrow": "Morgen",
|
||||||
|
"Month": "Monat",
|
||||||
|
"Week": "Woche",
|
||||||
|
"This week": "Diese Woche",
|
||||||
|
"This month": "Dieser Monat",
|
||||||
|
"This year": "Dieses Jahr",
|
||||||
|
"Next year": "Nächstes Jahr",
|
||||||
|
"Last week": "Letzte Woche",
|
||||||
|
"Next week": "Nächste Woche",
|
||||||
|
"Last month": "Letzter Monat",
|
||||||
|
"Next month": "Nächster Monat",
|
||||||
|
"Last quarter": "Letztes Quartal",
|
||||||
|
"This quarter": "Dieses Quartal",
|
||||||
|
"Next quarter": "Nächstes Quartal",
|
||||||
|
"Last year": "Letztes Jahr",
|
||||||
|
"Last 7 days": "Letzte 7 Tage",
|
||||||
|
"Last 30 days": "Letzte 30 Tage",
|
||||||
|
"Last 90 days": "Letzte 90 Tage",
|
||||||
|
"Next 7 days": "Nächste 7 Tage",
|
||||||
|
"Next 30 days": "Nächste 30 Tage",
|
||||||
|
"Next 90 days": "Nächste 90 Tage",
|
||||||
|
"Work week": "Arbeitswoche",
|
||||||
|
"Day": "Tag",
|
||||||
|
"Agenda": "Agenda",
|
||||||
|
"Date": "Datum",
|
||||||
|
"Time": "Zeit",
|
||||||
|
"Event": "Ereignis",
|
||||||
|
"None": "Keine",
|
||||||
|
"Unconnected": "Nicht verbunden",
|
||||||
|
"System settings": "Systemeinstellungen",
|
||||||
|
"System title": "Systemtitel",
|
||||||
|
"Settings": "Einstellungen",
|
||||||
|
"Logo": "Logo",
|
||||||
|
"Add menu item": "Menüpunkt hinzufügen",
|
||||||
|
"Page": "Seite",
|
||||||
|
"Name": "Name",
|
||||||
|
"Icon": "Symbol",
|
||||||
|
"Group": "Gruppe",
|
||||||
|
"Link": "Link",
|
||||||
|
"Tab": "Tab",
|
||||||
|
"Save conditions": "Bedingungen speichern",
|
||||||
|
"Edit menu item": "Menüpunkt bearbeiten",
|
||||||
|
"Move to": "Verschieben nach",
|
||||||
|
"Insert left": "Links einfügen",
|
||||||
|
"Insert right": "Rechts einfügen",
|
||||||
|
"Insert inner": "Innen einfügen",
|
||||||
|
"Delete": "Löschen",
|
||||||
|
"Disassociate": "Trennen",
|
||||||
|
"Disassociate record": "Datensatz trennen",
|
||||||
|
"Are you sure you want to disassociate it?": "Sind Sie sicher, dass Sie die Verbindung trennen möchten?",
|
||||||
|
"UI editor": "UI-Editor",
|
||||||
|
"Collection": "Sammlung",
|
||||||
|
"Collection selector": "Sammlungsauswahl",
|
||||||
|
"Providing certain collections as options for users, typically used in polymorphic or inheritance scenarios": "Bestimmte Sammlungen als Optionen für Benutzer bereitstellen, typischerweise verwendet in polymorphen oder Vererbungsszenarien",
|
||||||
|
"Collections & Fields": "Sammlungen & Felder",
|
||||||
|
"All collections": "Alle Sammlungen",
|
||||||
|
"Add category": "Kategorie hinzufügen",
|
||||||
|
"Enable child collections": "Untersammlungen aktivieren",
|
||||||
|
"Allow adding records to the current collection": "Hinzufügen von Datensätzen zur aktuellen Sammlung erlauben",
|
||||||
|
"Delete category": "Kategorie löschen",
|
||||||
|
"Edit category": "Kategorie bearbeiten",
|
||||||
|
"Collection category": "Sammlungskategorie",
|
||||||
|
"Collection template": "Sammlungsvorlage",
|
||||||
|
"Sort": "Sortieren",
|
||||||
|
"Categories": "Kategorien",
|
||||||
|
"Visible": "Sichtbar",
|
||||||
|
"Read only": "Nur lesen",
|
||||||
|
"Easy reading": "Leicht lesbar",
|
||||||
|
"Hidden": "Versteckt",
|
||||||
|
"Hidden(reserved value)": "Versteckt (reservierter Wert)",
|
||||||
|
"Not required": "Nicht erforderlich",
|
||||||
|
"Value": "Wert",
|
||||||
|
"Disabled": "Deaktiviert",
|
||||||
|
"Enabled": "Aktiviert",
|
||||||
|
"Problematic": "Problematisch",
|
||||||
|
"Setting": "Einstellung",
|
||||||
|
"On": "Ein",
|
||||||
|
"Off": "Aus",
|
||||||
|
"Empty": "Leer",
|
||||||
|
"Linkage rule": "Verknüpfungsregel",
|
||||||
|
"Linkage rules": "Verknüpfungsregeln",
|
||||||
|
"Condition": "Bedingung",
|
||||||
|
"Properties": "Eigenschaften",
|
||||||
|
"Add linkage rule": "Verknüpfungsregel hinzufügen",
|
||||||
|
"Add property": "Eigenschaft hinzufügen",
|
||||||
|
"Category name": "Kategoriename",
|
||||||
|
"Roles & Permissions": "Rollen & Berechtigungen",
|
||||||
|
"Edit profile": "Profil bearbeiten",
|
||||||
|
"Change password": "Passwort ändern",
|
||||||
|
"Old password": "Altes Passwort",
|
||||||
|
"New password": "Neues Passwort",
|
||||||
|
"Switch role": "Rolle wechseln",
|
||||||
|
"Super admin": "Superadministrator",
|
||||||
|
"Language": "Sprache",
|
||||||
|
"Allow sign up": "Registrierung erlauben",
|
||||||
|
"Enable SMS authentication": "SMS-Authentifizierung aktivieren",
|
||||||
|
"Sign out": "Abmelden",
|
||||||
|
"Cancel": "Abbrechen",
|
||||||
|
"Submit": "Absenden",
|
||||||
|
"Close": "Schließen",
|
||||||
|
"Set the data scope": "Datenbereich festlegen",
|
||||||
|
"Set data loading mode": "Datenladungsmodus festlegen",
|
||||||
|
"Load all data when filter is empty": "Alle Daten laden, wenn der Filter leer ist",
|
||||||
|
"Do not load data when filter is empty": "Keine Daten laden, wenn der Filter leer ist",
|
||||||
|
"Data loading mode": "Datenladungsmodus",
|
||||||
|
"Data blocks": "Datenblöcke",
|
||||||
|
"Filter blocks": "Filterblöcke",
|
||||||
|
"Table": "Tabelle",
|
||||||
|
"Table OID(Inheritance)": "Tabellen-OID (Vererbung)",
|
||||||
|
"Form": "Formular",
|
||||||
|
"List": "Liste",
|
||||||
|
"Grid Card": "Rasterkarte",
|
||||||
|
"pixels": "Pixel",
|
||||||
|
"Screen size": "Bildschirmgröße",
|
||||||
|
"Display title": "Titel anzeigen",
|
||||||
|
"Set the count of columns displayed in a row": "Anzahl der Spalten in einer Zeile festlegen",
|
||||||
|
"Column": "Spalte",
|
||||||
|
"Phone device": "Mobiltelefon",
|
||||||
|
"Tablet device": "Tablet",
|
||||||
|
"Desktop device": "Desktop",
|
||||||
|
"Large screen device": "Großer Bildschirm",
|
||||||
|
"Collapse": "Einklappen",
|
||||||
|
"Select data source": "Datenquelle auswählen",
|
||||||
|
"Calendar": "Kalender",
|
||||||
|
"Delete events": "Ereignisse löschen",
|
||||||
|
"This event": "Dieses Ereignis",
|
||||||
|
"This and following events": "Dieses und folgende Ereignisse",
|
||||||
|
"All events": "Alle Ereignisse",
|
||||||
|
"Delete this event?": "Dieses Ereignis löschen?",
|
||||||
|
"Delete Event": "Ereignis löschen",
|
||||||
|
"Kanban": "Kanban",
|
||||||
|
"Gantt": "Gantt",
|
||||||
|
"Create gantt block": "Gantt-Block erstellen",
|
||||||
|
"Progress field": "Fortschrittsfeld",
|
||||||
|
"Time scale": "Zeitskala",
|
||||||
|
"Hour": "Stunde",
|
||||||
|
"Quarter of day": "Viertel des Tages",
|
||||||
|
"Half of day": "Halber Tag",
|
||||||
|
"Year": "Jahr",
|
||||||
|
"QuarterYear": "Jahresquartal",
|
||||||
|
"Select grouping field": "Gruppierungsfeld auswählen",
|
||||||
|
"Media": "Medien",
|
||||||
|
"Markdown": "Markdown",
|
||||||
|
"Wysiwyg": "Wysiwyg",
|
||||||
|
"Chart blocks": "Diagrammblöcke",
|
||||||
|
"Column chart": "Säulendiagramm",
|
||||||
|
"Bar chart": "Balkendiagramm",
|
||||||
|
"Line chart": "Liniendiagramm",
|
||||||
|
"Pie chart": "Kreisdiagramm",
|
||||||
|
"Area chart": "Flächendiagramm",
|
||||||
|
"Other chart": "Anderes Diagramm",
|
||||||
|
"Other blocks": "Andere Blöcke",
|
||||||
|
"In configuration": "In Konfiguration",
|
||||||
|
"Chart title": "Diagrammtitel",
|
||||||
|
"Chart type": "Diagrammtyp",
|
||||||
|
"Chart config": "Diagrammkonfiguration",
|
||||||
|
"Templates": "Vorlagen",
|
||||||
|
"Select template": "Vorlage auswählen",
|
||||||
|
"Action logs": "Aktionslogs",
|
||||||
|
"Create template": "Vorlage erstellen",
|
||||||
|
"Edit markdown": "Markdown bearbeiten",
|
||||||
|
"Add block": "Block hinzufügen",
|
||||||
|
"Add new": "Neu hinzufügen",
|
||||||
|
"Add record": "Datensatz hinzufügen",
|
||||||
|
"Add child": "Kind hinzufügen",
|
||||||
|
"Collapse all": "Alle einklappen",
|
||||||
|
"Expand all": "Alle ausklappen",
|
||||||
|
"Expand/Collapse": "Erweitern/Einklappen",
|
||||||
|
"Default collapse": "Standardmäßig eingeklappt",
|
||||||
|
"Tree table": "Baumtabelle",
|
||||||
|
"Custom field display name": "Benutzerdefinierter Feldanzeigename",
|
||||||
|
"Display fields": "Anzeigefelder der Sammlung",
|
||||||
|
"Edit record": "Datensatz bearbeiten",
|
||||||
|
"Delete menu item": "Menüpunkt löschen",
|
||||||
|
"Add page": "Seite hinzufügen",
|
||||||
|
"Add group": "Gruppe hinzufügen",
|
||||||
|
"Add link": "Link hinzufügen",
|
||||||
|
"Insert above": "Oben einfügen",
|
||||||
|
"Insert below": "Unten einfügen",
|
||||||
|
"Save": "Speichern",
|
||||||
|
"Delete block": "Block löschen",
|
||||||
|
"Are you sure you want to delete it?": "Sind Sie sicher, dass Sie es löschen möchten?",
|
||||||
|
"This is a demo text, **supports Markdown syntax**.": "Dies ist ein Beispieltext, **unterstützt Markdown-Syntax**.",
|
||||||
|
"Filter": "Filter",
|
||||||
|
"Connect data blocks": "Datenblöcke verbinden",
|
||||||
|
"Action type": "Aktionstyp",
|
||||||
|
"Actions": "Aktionen",
|
||||||
|
"Insert": "Einfügen",
|
||||||
|
"Insert if not exists": "Einfügen, wenn nicht vorhanden",
|
||||||
|
"Insert if not exists, or update": "Einfügen, wenn nicht vorhanden, sonst aktualisieren",
|
||||||
|
"Determine whether a record exists by the following fields": "Bestimmen Sie, ob ein Datensatz anhand der folgenden Felder existiert",
|
||||||
|
"Update": "Aktualisieren",
|
||||||
|
"Update record": "Datensatz aktualisieren",
|
||||||
|
"View": "Ansicht",
|
||||||
|
"View record": "Datensatz ansehen",
|
||||||
|
"Refresh": "Aktualisieren",
|
||||||
|
"Data changes": "Datenänderungen",
|
||||||
|
"Field name": "Feldname",
|
||||||
|
"Before change": "Vor der Änderung",
|
||||||
|
"After change": "Nach der Änderung",
|
||||||
|
"Delete record": "Datensatz löschen",
|
||||||
|
"Delete collection": "Sammlung löschen",
|
||||||
|
"Create collection": "Sammlung erstellen",
|
||||||
|
"Collection display name": "Anzeigename der Sammlung",
|
||||||
|
"Collection name": "Sammlungsname",
|
||||||
|
"Inherits": "Erbt von",
|
||||||
|
"Primary key, unique identifier, self growth": "Primärschlüssel, eindeutiger Bezeichner, automatische Erhöhung",
|
||||||
|
"Store the creation user of each record": "Speichert den Erstellungsbenutzer jedes Datensatzes",
|
||||||
|
"Store the last update user of each record": "Speichert den letzten Aktualisierungsbenutzer jedes Datensatzes",
|
||||||
|
"Store the creation time of each record": "Speichert die Erstellungszeit jedes Datensatzes",
|
||||||
|
"Store the last update time of each record": "Speichert die letzte Aktualisierungszeit jedes Datensatzes",
|
||||||
|
"More options": "Weitere Optionen",
|
||||||
|
"Records can be sorted": "Datensätze können sortiert werden",
|
||||||
|
"Calendar collection": "Kalendersammlung",
|
||||||
|
"General collection": "Allgemeine Sammlung",
|
||||||
|
"Connect to database view": "Mit Datenbankansicht verbinden",
|
||||||
|
"Sync from database": "Von Datenbank synchronisieren",
|
||||||
|
"Source collections": "Quellsammlungen",
|
||||||
|
"Field source": "Feldquelle",
|
||||||
|
"Preview": "Vorschau",
|
||||||
|
"Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "Zufällig generiert und kann geändert werden. Unterstützt Buchstaben, Zahlen und Unterstriche, muss mit einem Buchstaben beginnen.",
|
||||||
|
"Edit": "Bearbeiten",
|
||||||
|
"Edit collection": "Sammlung bearbeiten",
|
||||||
|
"Configure fields": "Felder konfigurieren",
|
||||||
|
"Configure columns": "Spalten konfigurieren",
|
||||||
|
"Edit field": "Feld bearbeiten",
|
||||||
|
"Override": "Überschreiben",
|
||||||
|
"Override field": "Feld überschreiben",
|
||||||
|
"Configure fields of {{title}}": "Felder von {{title}} konfigurieren",
|
||||||
|
"Association fields filter": "Filter für Verknüpfungsfelder",
|
||||||
|
"PK & FK fields": "PK & FK Felder",
|
||||||
|
"Association fields": "Verknüpfungsfelder",
|
||||||
|
"Choices fields": "Auswahlfelder",
|
||||||
|
"System fields": "Systemfelder",
|
||||||
|
"General fields": "Allgemeine Felder",
|
||||||
|
"Inherited fields": "Geerbte Felder",
|
||||||
|
"Parent collection fields": "Felder der übergeordneten Sammlung",
|
||||||
|
"Basic": "Grundlegend",
|
||||||
|
"Single line text": "Einzeiliger Text",
|
||||||
|
"Long text": "Langer Text",
|
||||||
|
"Phone": "Telefon",
|
||||||
|
"Email": "E-Mail",
|
||||||
|
"Number": "Zahl",
|
||||||
|
"Integer": "Ganzzahl",
|
||||||
|
"Percent": "Prozent",
|
||||||
|
"Password": "Passwort",
|
||||||
|
"Advanced type": "Erweitert",
|
||||||
|
"Formula": "Formel",
|
||||||
|
"Formula description": "Berechnet einen Wert in jedem Datensatz basierend auf anderen Feldern im selben Datensatz.",
|
||||||
|
"Choices": "Auswahlmöglichkeiten",
|
||||||
|
"Checkbox": "Kontrollkästchen",
|
||||||
|
"Single select": "Einzelauswahl",
|
||||||
|
"Multiple select": "Mehrfachauswahl",
|
||||||
|
"Radio group": "Radiogruppe",
|
||||||
|
"Checkbox group": "Kontrollkästchengruppe",
|
||||||
|
"China region": "China-Region",
|
||||||
|
"Date & Time": "Datum & Zeit",
|
||||||
|
"Datetime": "Datum/Zeit",
|
||||||
|
"Relation": "Beziehung",
|
||||||
|
"Link to": "Verknüpfen mit",
|
||||||
|
"Link to description": "Wird verwendet, um Sammlungsbeziehungen schnell zu erstellen und ist mit den meisten gängigen Szenarien kompatibel. Geeignet für Nicht-Entwickler. Als Feld dargestellt, ist es eine Dropdown-Auswahl zur Auswahl von Datensätzen aus der Zielsammlung. Nach der Erstellung werden gleichzeitig die zugehörigen Felder der aktuellen Sammlung in der Zielsammlung generiert.",
|
||||||
|
"Sub-table": "Untertabelle",
|
||||||
|
"Sub-details": "Unterdetails",
|
||||||
|
"Sub-form(Popover)": "Unterformular (Popover)",
|
||||||
|
"System info": "Systeminformationen",
|
||||||
|
"Created at": "Erstellt am",
|
||||||
|
"Last updated at": "Zuletzt aktualisiert am",
|
||||||
|
"Created by": "Erstellt von",
|
||||||
|
"Last updated by": "Zuletzt aktualisiert von",
|
||||||
|
"Add field": "Feld hinzufügen",
|
||||||
|
"Field display name": "Feldanzeigename",
|
||||||
|
"Field type": "Feldtyp",
|
||||||
|
"Field interface": "Feldschnittstelle",
|
||||||
|
"Date format": "Datumsformat",
|
||||||
|
"Year/Month/Day": "Jahr/Monat/Tag",
|
||||||
|
"Year-Month-Day": "Jahr-Monat-Tag",
|
||||||
|
"Day/Month/Year": "Tag/Monat/Jahr",
|
||||||
|
"Show time": "Zeit anzeigen",
|
||||||
|
"Time format": "Zeitformat",
|
||||||
|
"12 hour": "12 Stunden",
|
||||||
|
"24 hour": "24 Stunden",
|
||||||
|
"Relationship type": "Beziehungstyp",
|
||||||
|
"Inverse relationship type": "Inverse Beziehungstyp",
|
||||||
|
"Source collection": "Quellsammlung",
|
||||||
|
"Source key": "Quellschlüssel",
|
||||||
|
"Target collection": "Zielsammlung",
|
||||||
|
"Through collection": "Zwischensammlung",
|
||||||
|
"Target key": "Zielschlüssel",
|
||||||
|
"Foreign key": "Fremdschlüssel",
|
||||||
|
"One to one": "Eins zu Eins",
|
||||||
|
"One to many": "Eins zu Viele",
|
||||||
|
"Many to one": "Viele zu Eins",
|
||||||
|
"Many to many": "Viele zu Viele",
|
||||||
|
"Foreign key 1": "Fremdschlüssel 1",
|
||||||
|
"Foreign key 2": "Fremdschlüssel 2",
|
||||||
|
"One to one description": "Wird verwendet, um eine Eins-zu-Eins-Beziehung zu erstellen. Zum Beispiel hat ein Benutzer ein Profil.",
|
||||||
|
"One to many description": "Wird verwendet, um eine Eins-zu-Viele-Beziehung zu erstellen. Zum Beispiel hat ein Land viele Städte und eine Stadt kann nur in einem Land sein. Als Feld dargestellt, ist es eine Untertabelle, die die Datensätze der zugehörigen Sammlung anzeigt. Bei der Erstellung wird automatisch ein Viele-zu-Eins-Feld in der zugehörigen Sammlung generiert.",
|
||||||
|
"Many to one description": "Wird verwendet, um Viele-zu-Eins-Beziehungen zu erstellen. Zum Beispiel kann eine Stadt nur zu einem Land gehören und ein Land kann viele Städte haben. Als Feld dargestellt, ist es eine Dropdown-Auswahl zur Auswahl eines Datensatzes aus der zugehörigen Sammlung. Nach der Erstellung wird automatisch ein Eins-zu-Viele-Feld in der zugehörigen Sammlung generiert.",
|
||||||
|
"Many to many description": "Wird verwendet, um Viele-zu-Viele-Beziehungen zu erstellen. Zum Beispiel hat ein Schüler viele Lehrer und ein Lehrer hat viele Schüler. Als Feld dargestellt, ist es eine Dropdown-Auswahl zur Auswahl von Datensätzen aus der zugehörigen Sammlung.",
|
||||||
|
"Generated automatically if left blank": "Wird automatisch generiert, wenn leer gelassen",
|
||||||
|
"Display association fields": "Verknüpfungsfelder anzeigen",
|
||||||
|
"Display field title": "Feldtitel anzeigen",
|
||||||
|
"Field component": "Feldkomponente",
|
||||||
|
"Allow multiple": "Mehrere erlauben",
|
||||||
|
"Quick upload": "Schnelles Hochladen",
|
||||||
|
"Select file": "Datei auswählen",
|
||||||
|
"Subtable": "Untertabelle",
|
||||||
|
"Sub-form": "Unterformular",
|
||||||
|
"Field mode": "Feldmodus",
|
||||||
|
"Allow add new data": "Hinzufügen neuer Daten erlauben",
|
||||||
|
"Record picker": "Datensatzauswahl",
|
||||||
|
"Toggles the subfield mode": "Schaltet den Unterfeld-Modus um",
|
||||||
|
"Selector mode": "Auswahlmodus",
|
||||||
|
"Subtable mode": "Untertabellenmodus",
|
||||||
|
"Subform mode": "Unterformularmodus",
|
||||||
|
"Edit block title": "Blocktitel bearbeiten",
|
||||||
|
"Block title": "Blocktitel",
|
||||||
|
"Pattern": "Muster",
|
||||||
|
"Operator": "Operator",
|
||||||
|
"Editable": "Bearbeitbar",
|
||||||
|
"Readonly": "Schreibgeschützt",
|
||||||
|
"Easy-reading": "Leicht lesbar",
|
||||||
|
"Add filter": "Filter hinzufügen",
|
||||||
|
"Add filter group": "Filtergruppe hinzufügen",
|
||||||
|
"Comparision": "Vergleich",
|
||||||
|
"is": "ist",
|
||||||
|
"is not": "ist nicht",
|
||||||
|
"contains": "enthält",
|
||||||
|
"does not contain": "enthält nicht",
|
||||||
|
"starts with": "beginnt mit",
|
||||||
|
"not starts with": "beginnt nicht mit",
|
||||||
|
"ends with": "endet mit",
|
||||||
|
"not ends with": "endet nicht mit",
|
||||||
|
"is empty": "ist leer",
|
||||||
|
"is not empty": "ist nicht leer",
|
||||||
|
"Edit chart": "Diagramm bearbeiten",
|
||||||
|
"Add text": "Text hinzufügen",
|
||||||
|
"Filterable fields": "Filterbare Felder",
|
||||||
|
"Edit button": "Schaltfläche bearbeiten",
|
||||||
|
"Hide": "Ausblenden",
|
||||||
|
"Enable actions": "Aktionen aktivieren",
|
||||||
|
"Import": "Importieren",
|
||||||
|
"Export": "Exportieren",
|
||||||
|
"Customize": "Anpassen",
|
||||||
|
"Custom": "Benutzerdefiniert",
|
||||||
|
"Function": "Funktion",
|
||||||
|
"Popup form": "Popup-Formular",
|
||||||
|
"Flexible popup": "Flexibles Popup",
|
||||||
|
"Configure actions": "Aktionen konfigurieren",
|
||||||
|
"Display order number": "Bestellnummer anzeigen",
|
||||||
|
"Enable drag and drop sorting": "Drag & Drop-Sortierung aktivieren",
|
||||||
|
"Triggered when the row is clicked": "Wird ausgelöst, wenn auf die Zeile geklickt wird",
|
||||||
|
"Add tab": "Tab hinzufügen",
|
||||||
|
"Disable tabs": "Tabs deaktivieren",
|
||||||
|
"Details": "Details",
|
||||||
|
"Edit form": "Formular bearbeiten",
|
||||||
|
"Create form": "Formular erstellen",
|
||||||
|
"Form (Edit)": "Formular (Bearbeiten)",
|
||||||
|
"Form (Add new)": "Formular (Neu hinzufügen)",
|
||||||
|
"Edit tab": "Tab bearbeiten",
|
||||||
|
"Relationship blocks": "Beziehungsblöcke",
|
||||||
|
"Select record": "Datensatz auswählen",
|
||||||
|
"Display name": "Anzeigename",
|
||||||
|
"Select icon": "Symbol auswählen",
|
||||||
|
"Custom column name": "Benutzerdefinierter Spaltenname",
|
||||||
|
"Edit description": "Beschreibung bearbeiten",
|
||||||
|
"Required": "Erforderlich",
|
||||||
|
"Unique": "Eindeutig",
|
||||||
|
"Primary": "Primär",
|
||||||
|
"Auto increment": "Automatische Erhöhung",
|
||||||
|
"Label field": "Beschriftungsfeld",
|
||||||
|
"Default is the ID field": "Standard ist das ID-Feld",
|
||||||
|
"Set default sorting rules": "Standardsortierregeln festlegen",
|
||||||
|
"Set validation rules": "Validierungsregeln festlegen",
|
||||||
|
"Max length": "Maximale Länge",
|
||||||
|
"Min length": "Minimale Länge",
|
||||||
|
"Maximum": "Maximum",
|
||||||
|
"Minimum": "Minimum",
|
||||||
|
"Max length must greater than min length": "Maximale Länge muss größer als minimale Länge sein",
|
||||||
|
"Min length must less than max length": "Minimale Länge muss kleiner als maximale Länge sein",
|
||||||
|
"Maximum must greater than minimum": "Maximum muss größer als Minimum sein",
|
||||||
|
"Minimum must less than maximum": "Minimum muss kleiner als Maximum sein",
|
||||||
|
"Validation rule": "Validierungsregel",
|
||||||
|
"Add validation rule": "Validierungsregel hinzufügen",
|
||||||
|
"Format": "Format",
|
||||||
|
"Regular expression": "Regulärer Ausdruck",
|
||||||
|
"Error message": "Fehlermeldung",
|
||||||
|
"Length": "Länge",
|
||||||
|
"The field value cannot be greater than ": "Der Feldwert darf nicht größer sein als ",
|
||||||
|
"The field value cannot be less than ": "Der Feldwert darf nicht kleiner sein als ",
|
||||||
|
"The field value is not an integer number": "Der Feldwert ist keine ganze Zahl",
|
||||||
|
"Set default value": "Standardwert festlegen",
|
||||||
|
"Default value": "Standardwert",
|
||||||
|
"is before": "ist vor",
|
||||||
|
"is after": "ist nach",
|
||||||
|
"is on or after": "ist am oder nach",
|
||||||
|
"is on or before": "ist am oder vor",
|
||||||
|
"is between": "ist zwischen",
|
||||||
|
"Upload": "Hochladen",
|
||||||
|
"Select level": "Ebene auswählen",
|
||||||
|
"Province": "Provinz",
|
||||||
|
"City": "Stadt",
|
||||||
|
"Area": "Gebiet",
|
||||||
|
"Street": "Straße",
|
||||||
|
"Village": "Dorf",
|
||||||
|
"Must select to the last level": "Muss bis zur letzten Ebene ausgewählt werden",
|
||||||
|
"Move {{title}} to": "{{title}} verschieben nach",
|
||||||
|
"Target position": "Zielposition",
|
||||||
|
"After": "Nach",
|
||||||
|
"Before": "Vor",
|
||||||
|
"Add {{type}} before \"{{title}}\"": "{{type}} vor \"{{title}}\" hinzufügen",
|
||||||
|
"Add {{type}} after \"{{title}}\"": "{{type}} nach \"{{title}}\" hinzufügen",
|
||||||
|
"Add {{type}} in \"{{title}}\"": "{{type}} in \"{{title}}\" hinzufügen",
|
||||||
|
"Original name": "Ursprünglicher Name",
|
||||||
|
"Custom name": "Benutzerdefinierter Name",
|
||||||
|
"Custom Title": "Benutzerdefinierter Titel",
|
||||||
|
"Options": "Optionen",
|
||||||
|
"Option value": "Optionswert",
|
||||||
|
"Option label": "Optionsbezeichnung",
|
||||||
|
"Color": "Farbe",
|
||||||
|
"Background Color": "Hintergrundfarbe",
|
||||||
|
"Text Align": "Textausrichtung",
|
||||||
|
"Add option": "Option hinzufügen",
|
||||||
|
"Related collection": "Zugehörige Sammlung",
|
||||||
|
"Allow linking to multiple records": "Verknüpfung mit mehreren Datensätzen erlauben",
|
||||||
|
"Allow uploading multiple files": "Hochladen mehrerer Dateien erlauben",
|
||||||
|
"Configure calendar": "Kalender konfigurieren",
|
||||||
|
"Title field": "Titelfeld",
|
||||||
|
"Custom title": "Benutzerdefinierter Titel",
|
||||||
|
"Daily": "Täglich",
|
||||||
|
"Weekly": "Wöchentlich",
|
||||||
|
"Monthly": "Monatlich",
|
||||||
|
"Yearly": "Jährlich",
|
||||||
|
"Repeats": "Wiederholungen",
|
||||||
|
"Show lunar": "Mondkalender anzeigen",
|
||||||
|
"Start date field": "Startdatumsfeld",
|
||||||
|
"End date field": "Enddatumsfeld",
|
||||||
|
"Navigate": "Navigieren",
|
||||||
|
"Title": "Titel",
|
||||||
|
"Description": "Beschreibung",
|
||||||
|
"Select view": "Ansicht auswählen",
|
||||||
|
"Reset": "Zurücksetzen",
|
||||||
|
"Importable fields": "Importierbare Felder",
|
||||||
|
"Exportable fields": "Exportierbare Felder",
|
||||||
|
"Saved successfully": "Erfolgreich gespeichert",
|
||||||
|
"Nickname": "Spitzname",
|
||||||
|
"Sign in": "Anmelden",
|
||||||
|
"Sign in via account": "Über Konto anmelden",
|
||||||
|
"Sign in via phone": "Über Telefon anmelden",
|
||||||
|
"Create an account": "Konto erstellen",
|
||||||
|
"Sign up": "Registrieren",
|
||||||
|
"Confirm password": "Passwort bestätigen",
|
||||||
|
"Log in with an existing account": "Mit einem bestehenden Konto anmelden",
|
||||||
|
"Signed up successfully. It will jump to the login page.": "Registrierung erfolgreich. Sie werden zur Anmeldeseite weitergeleitet.",
|
||||||
|
"Password mismatch": "Passwörter stimmen nicht überein",
|
||||||
|
"Users": "Benutzer",
|
||||||
|
"Verification code": "Bestätigungscode",
|
||||||
|
"Send code": "Code senden",
|
||||||
|
"Retry after {{count}} seconds": "Wiederholen nach {{count}} Sekunden",
|
||||||
|
"Roles": "Rollen",
|
||||||
|
"Add role": "Rolle hinzufügen",
|
||||||
|
"Role name": "Rollenname",
|
||||||
|
"Configure": "Konfigurieren",
|
||||||
|
"Configure permissions": "Berechtigungen konfigurieren",
|
||||||
|
"Edit role": "Rolle bearbeiten",
|
||||||
|
"Action permissions": "Aktionsberechtigungen",
|
||||||
|
"Menu permissions": "Menüberechtigungen",
|
||||||
|
"Menu item name": "Menüpunktname",
|
||||||
|
"Allow access": "Zugriff erlauben",
|
||||||
|
"Action name": "Aktionsname",
|
||||||
|
"Allow action": "Aktion erlauben",
|
||||||
|
"Action scope": "Aktionsbereich",
|
||||||
|
"Operate on new data": "Mit neuen Daten arbeiten",
|
||||||
|
"Operate on existing data": "Mit vorhandenen Daten arbeiten",
|
||||||
|
"Yes": "Ja",
|
||||||
|
"No": "Nein",
|
||||||
|
"Red": "Rot",
|
||||||
|
"Magenta": "Magenta",
|
||||||
|
"Volcano": "Vulkan",
|
||||||
|
"Orange": "Orange",
|
||||||
|
"Gold": "Gold",
|
||||||
|
"Lime": "Limette",
|
||||||
|
"Green": "Grün",
|
||||||
|
"Cyan": "Cyan",
|
||||||
|
"Blue": "Blau",
|
||||||
|
"Geek blue": "Geek-Blau",
|
||||||
|
"Purple": "Lila",
|
||||||
|
"Default": "Standard",
|
||||||
|
"Add card": "Karte hinzufügen",
|
||||||
|
"edit title": "Titel bearbeiten",
|
||||||
|
"Turn pages": "Seiten umblättern",
|
||||||
|
"Others": "Andere",
|
||||||
|
"Other records": "Andere Datensätze",
|
||||||
|
"Save as template": "Als Vorlage speichern",
|
||||||
|
"Save as block template": "Als Blockvorlage speichern",
|
||||||
|
"Block templates": "Blockvorlagen",
|
||||||
|
"Block template": "Blockvorlage",
|
||||||
|
"Convert reference to duplicate": "Referenz in Duplikat umwandeln",
|
||||||
|
"Template name": "Vorlagenname",
|
||||||
|
"Block type": "Blocktyp",
|
||||||
|
"No blocks to connect": "Keine Blöcke zum Verbinden",
|
||||||
|
"Action column": "Aktionsspalte",
|
||||||
|
"Records per page": "Datensätze pro Seite",
|
||||||
|
"(Fields only)": "(Nur Felder)",
|
||||||
|
"Button title": "Schaltflächentitel",
|
||||||
|
"Button icon": "Schaltflächensymbol",
|
||||||
|
"Submitted successfully": "Erfolgreich übermittelt",
|
||||||
|
"Operation succeeded": "Operation erfolgreich",
|
||||||
|
"Operation failed": "Operation fehlgeschlagen",
|
||||||
|
"Open mode": "Öffnungsmodus",
|
||||||
|
"Popup size": "Popup-Größe",
|
||||||
|
"Small": "Klein",
|
||||||
|
"Middle": "Mittel",
|
||||||
|
"Large": "Groß",
|
||||||
|
"Size": "Größe",
|
||||||
|
"Oversized": "Übergroß",
|
||||||
|
"Auto": "Automatisch",
|
||||||
|
"Object Fit": "Objektanpassung",
|
||||||
|
"Cover": "Abdecken",
|
||||||
|
"Fill": "Füllen",
|
||||||
|
"Contain": "Enthalten",
|
||||||
|
"Scale Down": "Verkleinern",
|
||||||
|
"Menu item title": "Menüpunkttitel",
|
||||||
|
"Menu item icon": "Menüpunktsymbol",
|
||||||
|
"Target": "Ziel",
|
||||||
|
"Position": "Position",
|
||||||
|
"Insert before": "Davor einfügen",
|
||||||
|
"Insert after": "Danach einfügen",
|
||||||
|
"UI Editor": "UI-Editor",
|
||||||
|
"ASC": "Aufsteigend",
|
||||||
|
"DESC": "Absteigend",
|
||||||
|
"Add sort field": "Sortierfeld hinzufügen",
|
||||||
|
"ID": "ID",
|
||||||
|
"Identifier for program usage. Support letters, numbers and underscores, must start with an letter.": "Bezeichner für Programmnutzung. Unterstützt Buchstaben, Zahlen und Unterstriche, muss mit einem Buchstaben beginnen.",
|
||||||
|
"Drawer": "Seitenleiste",
|
||||||
|
"Dialog": "Dialog",
|
||||||
|
"Delete action": "Aktion löschen",
|
||||||
|
"Custom column title": "Benutzerdefinierter Spaltentitel",
|
||||||
|
"Column title": "Spaltentitel",
|
||||||
|
"Original title: ": "Ursprünglicher Titel: ",
|
||||||
|
"Delete table column": "Tabellenspalte löschen",
|
||||||
|
"Skip required validation": "Erforderliche Validierung überspringen",
|
||||||
|
"Form values": "Formularwerte",
|
||||||
|
"Fields values": "Feldwerte",
|
||||||
|
"The field has been deleted": "Das Feld wurde gelöscht",
|
||||||
|
"When submitting the following fields, the saved values are": "Beim Absenden der folgenden Felder sind die gespeicherten Werte",
|
||||||
|
"After successful submission": "Nach erfolgreicher Übermittlung",
|
||||||
|
"Then": "Dann",
|
||||||
|
"Stay on current page": "Auf aktueller Seite bleiben",
|
||||||
|
"Redirect to": "Weiterleiten zu",
|
||||||
|
"Save action": "Aktion speichern",
|
||||||
|
"Exists": "Existiert",
|
||||||
|
"Add condition": "Bedingung hinzufügen",
|
||||||
|
"Add condition group": "Bedingungsgruppe hinzufügen",
|
||||||
|
"exists": "existiert",
|
||||||
|
"not exists": "existiert nicht",
|
||||||
|
"Style": "Stil",
|
||||||
|
"=": "=",
|
||||||
|
"≠": "≠",
|
||||||
|
">": ">",
|
||||||
|
"≥": "≥",
|
||||||
|
"<": "<",
|
||||||
|
"≤": "≤",
|
||||||
|
"Role UID": "Rollen-UID",
|
||||||
|
"Precision": "Genauigkeit",
|
||||||
|
"Formula mode": "Formelmodus",
|
||||||
|
"Expression": "Ausdruck",
|
||||||
|
"Input +, -, *, /, ( ) to calculate, input @ to open field variables.": "Geben Sie +, -, *, /, ( ) zum Berechnen ein, geben Sie @ ein, um Feldvariablen zu öffnen.",
|
||||||
|
"Formula error.": "Formelfehler.",
|
||||||
|
"Rich Text": "Rich Text",
|
||||||
|
"Junction collection": "Verbindungssammlung",
|
||||||
|
"Leave it blank, unless you need a custom intermediate table": "Lassen Sie es leer, es sei denn, Sie benötigen eine benutzerdefinierte Zwischentabelle",
|
||||||
|
"Fields": "Felder",
|
||||||
|
"Edit field title": "Feldtitel bearbeiten",
|
||||||
|
"Field title": "Feldtitel",
|
||||||
|
"Original field title: ": "Ursprünglicher Feldtitel: ",
|
||||||
|
"Edit tooltip": "Tooltip bearbeiten",
|
||||||
|
"Delete field": "Feld löschen",
|
||||||
|
"Select collection": "Sammlung auswählen",
|
||||||
|
"Blank block": "Leerer Block",
|
||||||
|
"Duplicate template": "Vorlage duplizieren",
|
||||||
|
"Reference template": "Referenzvorlage",
|
||||||
|
"Create calendar block": "Kalenderblock erstellen",
|
||||||
|
"Create kanban block": "Kanban-Block erstellen",
|
||||||
|
"Grouping field": "Gruppierungsfeld",
|
||||||
|
"Single select and radio fields can be used as the grouping field": "Einzelauswahl- und Radiofelder können als Gruppierungsfeld verwendet werden",
|
||||||
|
"Tab name": "Tab-Name",
|
||||||
|
"Current record blocks": "Blöcke des aktuellen Datensatzes",
|
||||||
|
"Popup message": "Popup-Nachricht",
|
||||||
|
"Delete role": "Rolle löschen",
|
||||||
|
"Role display name": "Rollenanzeigename",
|
||||||
|
"Default role": "Standardrolle",
|
||||||
|
"All collections use general action permissions by default; permission configured individually will override the default one.": "Alle Sammlungen verwenden standardmäßig allgemeine Aktionsberechtigungen; individuell konfigurierte Berechtigungen überschreiben die Standardeinstellung.",
|
||||||
|
"Allows configuration of the whole system, including UI, collections, permissions, etc.": "Ermöglicht die Konfiguration des gesamten Systems, einschließlich UI, Sammlungen, Berechtigungen usw.",
|
||||||
|
"New menu items are allowed to be accessed by default.": "Neue Menüpunkte dürfen standardmäßig zugegriffen werden.",
|
||||||
|
"Global permissions": "Globale Berechtigungen",
|
||||||
|
"General permissions": "Allgemeine Berechtigungen",
|
||||||
|
"Global action permissions": "Globale Aktionsberechtigungen",
|
||||||
|
"General action permissions": "Allgemeine Aktionsberechtigungen",
|
||||||
|
"Plugin settings permissions": "Plugin-Einstellungsberechtigungen",
|
||||||
|
"Allow to desgin pages": "Erlauben, Seiten zu gestalten",
|
||||||
|
"Allow to manage plugins": "Erlauben, Plugins zu verwalten",
|
||||||
|
"Allow to configure plugins": "Erlauben, Plugins zu konfigurieren",
|
||||||
|
"Allows to configure interface": "Erlaubt die Konfiguration der Schnittstelle",
|
||||||
|
"Allows to install, activate, disable plugins": "Erlaubt das Installieren, Aktivieren und Deaktivieren von Plugins",
|
||||||
|
"Allows to configure plugins": "Erlaubt die Konfiguration von Plugins",
|
||||||
|
"Action display name": "Anzeigeame der Aktion",
|
||||||
|
"Allow": "Erlauben",
|
||||||
|
"Data scope": "Datenbereich",
|
||||||
|
"Action on new records": "Aktion für neue Datensätze",
|
||||||
|
"Action on existing records": "Aktion für bestehende Datensätze",
|
||||||
|
"All records": "Alle Datensätze",
|
||||||
|
"Own records": "Eigene Datensätze",
|
||||||
|
"Permission policy": "Berechtigungsrichtlinie",
|
||||||
|
"Individual": "Individuell",
|
||||||
|
"General": "Allgemein",
|
||||||
|
"Accessible": "Zugänglich",
|
||||||
|
"Configure permission": "Berechtigung konfigurieren",
|
||||||
|
"Action permission": "Aktionsberechtigung",
|
||||||
|
"Field permission": "Feldberechtigung",
|
||||||
|
"Scope name": "Bereichsname",
|
||||||
|
"Unsaved changes": "Ungespeicherte Änderungen",
|
||||||
|
"Are you sure you don't want to save?": "Sind Sie sicher, dass Sie nicht speichern möchten?",
|
||||||
|
"Dragging": "Ziehen",
|
||||||
|
"Popup": "Popup",
|
||||||
|
"Trigger workflow": "Workflow auslösen",
|
||||||
|
"Request API": "API anfragen",
|
||||||
|
"Assign field values": "Feldwerte zuweisen",
|
||||||
|
"Constant value": "Konstanter Wert",
|
||||||
|
"Dynamic value": "Dynamischer Wert",
|
||||||
|
"Current user": "Aktueller Benutzer",
|
||||||
|
"Current role": "Aktuelle Rolle",
|
||||||
|
"Current record": "Aktueller Datensatz",
|
||||||
|
"Current collection": "Aktuelle Sammlung",
|
||||||
|
"Other collections": "Andere Sammlungen",
|
||||||
|
"Current popup record": "Aktueller Popup-Datensatz",
|
||||||
|
"Parent popup record": "Übergeordneter Popup-Datensatz",
|
||||||
|
"Associated records": "Zugehörige Datensätze",
|
||||||
|
"Parent record": "Übergeordneter Datensatz",
|
||||||
|
"Current time": "Aktuelle Zeit",
|
||||||
|
"System variables": "Systemvariablen",
|
||||||
|
"Date variables": "Datumsvariablen",
|
||||||
|
"Message popup close method": "Schließmethode für Popup-Nachrichten",
|
||||||
|
"Automatic close": "Automatisch schließen",
|
||||||
|
"Manually close": "Manuell schließen",
|
||||||
|
"After successful update": "Nach erfolgreicher Aktualisierung",
|
||||||
|
"Save record": "Datensatz speichern",
|
||||||
|
"Updated successfully": "Erfolgreich aktualisiert",
|
||||||
|
"After successful save": "Nach erfolgreichem Speichern",
|
||||||
|
"After clicking the custom button, the following field values will be assigned according to the following form.": "Nach dem Klicken auf die benutzerdefinierte Schaltfläche werden die folgenden Feldwerte gemäß dem folgenden Formular zugewiesen.",
|
||||||
|
"After clicking the custom button, the following fields of the current record will be saved according to the following form.": "Nach dem Klicken auf die benutzerdefinierte Schaltfläche werden die folgenden Felder des aktuellen Datensatzes gemäß dem folgenden Formular gespeichert.",
|
||||||
|
"Button background color": "Schaltflächen-Hintergrundfarbe",
|
||||||
|
"Highlight": "Hervorheben",
|
||||||
|
"Danger red": "Gefahr-Rot",
|
||||||
|
"Custom request": "Benutzerdefinierte Anfrage",
|
||||||
|
"Request settings": "Anfrageeinstellungen",
|
||||||
|
"Request URL": "Anfrage-URL",
|
||||||
|
"Request method": "Anfragemethode",
|
||||||
|
"Request query parameters": "Anfrageparameter",
|
||||||
|
"Request headers": "Anfrageheader",
|
||||||
|
"Request body": "Anfragekörper",
|
||||||
|
"Request success": "Anfrage erfolgreich",
|
||||||
|
"Invalid JSON format": "Ungültiges JSON-Format",
|
||||||
|
"After successful request": "Nach erfolgreicher Anfrage",
|
||||||
|
"Add exportable field": "Exportierbares Feld hinzufügen",
|
||||||
|
"Audit logs": "Prüfprotokolle",
|
||||||
|
"Record ID": "Datensatz-ID",
|
||||||
|
"User": "Benutzer",
|
||||||
|
"Field": "Feld",
|
||||||
|
"Select": "Auswählen",
|
||||||
|
"Select field": "Feld auswählen",
|
||||||
|
"Field value changes": "Feldwertänderungen",
|
||||||
|
"One to one (has one)": "Eins zu Eins (hat ein)",
|
||||||
|
"One to one (belongs to)": "Eins zu Eins (gehört zu)",
|
||||||
|
"Use the same time zone (GMT) for all users": "Verwenden Sie die gleiche Zeitzone (GMT) für alle Benutzer",
|
||||||
|
"Province/city/area name": "Provinz/Stadt/Gebietsname",
|
||||||
|
"Enabled languages": "Aktivierte Sprachen",
|
||||||
|
"View all plugins": "Alle Plugins anzeigen",
|
||||||
|
"Print": "Drucken",
|
||||||
|
"Done": "Fertig",
|
||||||
|
"Sign up successfully, and automatically jump to the sign in page": "Registrierung erfolgreich, Sie werden automatisch zur Anmeldeseite weitergeleitet",
|
||||||
|
"File manager": "Dateimanager",
|
||||||
|
"ACL": "ACL",
|
||||||
|
"Collection manager": "Sammlungsmanager",
|
||||||
|
"Plugin manager": "Plugin-Manager",
|
||||||
|
"Local": "Lokal",
|
||||||
|
"Built-in": "Eingebaut",
|
||||||
|
"Marketplace": "Marktplatz",
|
||||||
|
"Add plugin": "Plugin hinzufügen",
|
||||||
|
"Plugin source": "Plugin-Quelle",
|
||||||
|
"Upgrade": "Aktualisieren",
|
||||||
|
"Plugin dependencies check failed": "Überprüfung der Plugin-Abhängigkeiten fehlgeschlagen",
|
||||||
|
"More details": "Weitere Details",
|
||||||
|
"Upload new version": "Neue Version hochladen",
|
||||||
|
"Version": "Version",
|
||||||
|
"Npm package": "NPM-Paket",
|
||||||
|
"Npm package name": "NPM-Paketname",
|
||||||
|
"Upload plugin": "Plugin hochladen",
|
||||||
|
"Official plugin": "Offizielles Plugin",
|
||||||
|
"Add type": "Typ hinzufügen",
|
||||||
|
"Changelog": "Änderungsprotokoll",
|
||||||
|
"Dependencies check": "Abhängigkeitsprüfung",
|
||||||
|
"Update plugin": "Plugin aktualisieren",
|
||||||
|
"Installing": "Installiere",
|
||||||
|
"The deletion was successful.": "Das Löschen war erfolgreich.",
|
||||||
|
"Plugin Zip File": "Plugin-ZIP-Datei",
|
||||||
|
"Compressed file url": "URL der komprimierten Datei",
|
||||||
|
"Last updated": "Zuletzt aktualisiert",
|
||||||
|
"PackageName": "Paketname",
|
||||||
|
"DisplayName": "Anzeigename",
|
||||||
|
"Readme": "Readme",
|
||||||
|
"Dependencies compatibility check": "Kompatibilitätsprüfung der Abhängigkeiten",
|
||||||
|
"Plugin dependencies check failed, you should change the dependent version to meet the version requirements.": "Die Überprüfung der Plugin-Abhängigkeiten ist fehlgeschlagen. Sie sollten die abhängige Version ändern, um die Versionsanforderungen zu erfüllen.",
|
||||||
|
"Version range": "Versionsbereich",
|
||||||
|
"Plugin's version": "Plugin-Version",
|
||||||
|
"Result": "Ergebnis",
|
||||||
|
"No CHANGELOG.md file": "Keine CHANGELOG.md-Datei",
|
||||||
|
"No README.md file": "Keine README.md-Datei",
|
||||||
|
"Homepage": "Startseite",
|
||||||
|
"Drag and drop the file here or click to upload, file size should not exceed 30M": "Ziehen Sie die Datei hierher oder klicken Sie zum Hochladen, die Dateigröße sollte 30M nicht überschreiten",
|
||||||
|
"Dependencies check failed, can't enable.": "Abhängigkeitsprüfung fehlgeschlagen, kann nicht aktiviert werden.",
|
||||||
|
"Plugin starting...": "Plugin wird gestartet...",
|
||||||
|
"Plugin stopping...": "Plugin wird gestoppt...",
|
||||||
|
"Are you sure to delete this plugin?": "Sind Sie sicher, dass Sie dieses Plugin löschen möchten?",
|
||||||
|
"Are you sure to disable this plugin?": "Sind Sie sicher, dass Sie dieses Plugin deaktivieren möchten?",
|
||||||
|
"re-download file": "Datei erneut herunterladen",
|
||||||
|
"Not enabled": "Nicht aktiviert",
|
||||||
|
"Search plugin": "Plugin suchen",
|
||||||
|
"Author": "Autor",
|
||||||
|
"Plugin loading failed. Please check the server logs.": "Plugin-Ladung fehlgeschlagen. Bitte überprüfen Sie die Serverprotokolle.",
|
||||||
|
"Coming soon...": "Demnächst verfügbar...",
|
||||||
|
"All plugin settings": "Alle Plugin-Einstellungen",
|
||||||
|
"Bookmark": "Lesezeichen",
|
||||||
|
"Manage all settings": "Alle Einstellungen verwalten",
|
||||||
|
"Create inverse field in the target collection": "Inverses Feld in der Zielsammlung erstellen",
|
||||||
|
"Inverse field name": "Name des inversen Feldes",
|
||||||
|
"Inverse field display name": "Anzeigename des inversen Feldes",
|
||||||
|
"Bulk update": "Massenaktualisierung",
|
||||||
|
"After successful bulk update": "Nach erfolgreicher Massenaktualisierung",
|
||||||
|
"Bulk edit": "Massenbearbeitung",
|
||||||
|
"Data will be updated": "Daten werden aktualisiert",
|
||||||
|
"Selected": "Ausgewählt",
|
||||||
|
"All": "Alle",
|
||||||
|
"Update selected data?": "Ausgewählte Daten aktualisieren?",
|
||||||
|
"Update all data?": "Alle Daten aktualisieren?",
|
||||||
|
"Remains the same": "Bleibt gleich",
|
||||||
|
"Changed to": "Geändert zu",
|
||||||
|
"Clear": "Löschen",
|
||||||
|
"Add attach": "Anhang hinzufügen",
|
||||||
|
"Please select the records to be updated": "Bitte wählen Sie die zu aktualisierenden Datensätze aus",
|
||||||
|
"Selector": "Selektor",
|
||||||
|
"Inner": "Innen",
|
||||||
|
"Search and select collection": "Sammlung suchen und auswählen",
|
||||||
|
"Please fill in the iframe URL": "Bitte geben Sie die iframe-URL ein",
|
||||||
|
"Fix block": "Block fixieren",
|
||||||
|
"Plugin name": "Plugin-Name",
|
||||||
|
"Plugin tab name": "Plugin-Tab-Name",
|
||||||
|
"AutoGenId": "Automatisch generiertes ID-Feld",
|
||||||
|
"CreatedBy": "Erstellt von",
|
||||||
|
"UpdatedBy": "Aktualisiert von",
|
||||||
|
"CreatedAt": "Erstellt am",
|
||||||
|
"UpdatedAt": "Aktualisiert am",
|
||||||
|
"Column width": "Spaltenbreite",
|
||||||
|
"Sortable": "Sortierbar",
|
||||||
|
"Enable link": "Link aktivieren",
|
||||||
|
"This is likely a NocoBase internals bug. Please open an issue at <1>here</1>": "Dies ist wahrscheinlich ein interner Fehler von NocoBase. Bitte öffnen Sie ein Problem <1>hier</1>",
|
||||||
|
"Render Failed": "Rendering fehlgeschlagen",
|
||||||
|
"App error": "App-Fehler",
|
||||||
|
"Feedback": "Feedback",
|
||||||
|
"Try again": "Erneut versuchen",
|
||||||
|
"Download logs": "Protokolle herunterladen",
|
||||||
|
"Data template": "Datenvorlage",
|
||||||
|
"Duplicate": "Duplizieren",
|
||||||
|
"Duplicating": "Dupliziere",
|
||||||
|
"Duplicate mode": "Duplikationsmodus",
|
||||||
|
"Quick duplicate": "Schnelles Duplizieren",
|
||||||
|
"Duplicate and continue": "Duplizieren und fortfahren",
|
||||||
|
"Please configure the duplicate fields": "Bitte konfigurieren Sie die Duplikatfelder",
|
||||||
|
"Add": "Hinzufügen",
|
||||||
|
"Add new mode": "Neuer Hinzufügungsmodus",
|
||||||
|
"Quick add": "Schnell hinzufügen",
|
||||||
|
"Modal add": "Modal hinzufügen",
|
||||||
|
"Save mode": "Speichermodus",
|
||||||
|
"First or create": "Zuerst oder erstellen",
|
||||||
|
"Update or create": "Aktualisieren oder erstellen",
|
||||||
|
"Find by the following fields": "Nach den folgenden Feldern suchen",
|
||||||
|
"Create": "Erstellen",
|
||||||
|
"Current form": "Aktuelles Formular",
|
||||||
|
"Current object": "Aktuelles Objekt",
|
||||||
|
"Linkage with form fields": "Verknüpfung mit Formularfeldern",
|
||||||
|
"Allow add new, update and delete actions": "Hinzufügen, Aktualisieren und Löschen erlauben",
|
||||||
|
"Date display format": "Datumsanzeigeformat",
|
||||||
|
"Assign data scope for the template": "Datenbereich für die Vorlage zuweisen",
|
||||||
|
"Table selected records": "Ausgewählte Tabellendatensätze",
|
||||||
|
"Tag": "Tag",
|
||||||
|
"Tag color field": "Tag-Farbfeld",
|
||||||
|
"Sync successfully": "Synchronisierung erfolgreich",
|
||||||
|
"Sync from form fields": "Von Formularfeldern synchronisieren",
|
||||||
|
"Select all": "Alle auswählen",
|
||||||
|
"Restart": "Neustart",
|
||||||
|
"Restart application": "Anwendung neu starten",
|
||||||
|
"Cascade Select": "Kaskadierte Auswahl",
|
||||||
|
"Execute": "Ausführen",
|
||||||
|
"Please use a valid SELECT or WITH AS statement": "Bitte verwenden Sie eine gültige SELECT- oder WITH AS-Anweisung",
|
||||||
|
"Please confirm the SQL statement first": "Bitte bestätigen Sie zuerst die SQL-Anweisung",
|
||||||
|
"Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "Automatisches Löschen von Objekten, die von der Sammlung abhängen (wie Ansichten), und wiederum aller Objekte, die von diesen Objekten abhängen",
|
||||||
|
"Sign in with another account": "Mit einem anderen Konto anmelden",
|
||||||
|
"Return to the main application": "Zurück zur Hauptanwendung",
|
||||||
|
"Permission deined": "Berechtigung verweigert",
|
||||||
|
"loading": "Lädt",
|
||||||
|
"name is required": "Name ist erforderlich",
|
||||||
|
"data source": "Datenquelle",
|
||||||
|
"Data source": "Datenquelle",
|
||||||
|
"DataSource": "Datenquelle",
|
||||||
|
"The {{type}} \"{{name}}\" may have been deleted. Please remove this {{blockType}}.": "Der {{type}} \"{{name}}\" wurde möglicherweise gelöscht. Bitte entfernen Sie diesen {{blockType}}.",
|
||||||
|
"Preset fields": "Voreingestellte Felder",
|
||||||
|
"Home page": "Startseite",
|
||||||
|
"Handbook": "Handbuch",
|
||||||
|
"License": "Lizenz",
|
||||||
|
"Generic properties": "Allgemeine Eigenschaften",
|
||||||
|
"Specific properties": "Spezifische Eigenschaften",
|
||||||
|
"Used for drag and drop sorting scenarios, supporting grouping sorting": "Wird für Drag & Drop-Sortierungsszenarien verwendet und unterstützt Gruppensortierung",
|
||||||
|
"Grouped sorting": "Gruppierte Sortierung",
|
||||||
|
"When a field is selected for grouping, it will be grouped first before sorting.": "Wenn ein Feld für die Gruppierung ausgewählt wird, wird es zuerst gruppiert, bevor es sortiert wird.",
|
||||||
|
"Departments": "Abteilungen",
|
||||||
|
"Main department": "Hauptabteilung",
|
||||||
|
"Department name": "Abteilungsname",
|
||||||
|
"Superior department": "Übergeordnete Abteilung",
|
||||||
|
"Owners": "Eigentümer",
|
||||||
|
"Plugin settings": "Plugin-Einstellungen",
|
||||||
|
"Menu": "Menü",
|
||||||
|
"Drag and drop sorting field": "Feld für Drag & Drop-Sortierung",
|
||||||
|
"This variable has been deprecated and can be replaced with \"Current form\"": "Diese Variable ist veraltet und kann durch \"Aktuelles Formular\" ersetzt werden",
|
||||||
|
"The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.": "Der Wert dieser Variable wird aus der Abfragezeichenfolge der Seiten-URL abgeleitet. Diese Variable kann nur normal verwendet werden, wenn die Seite eine Abfragezeichenfolge hat.",
|
||||||
|
"URL search params": "URL-Suchparameter",
|
||||||
|
"Expand All": "Alle erweitern",
|
||||||
|
"Search": "Suchen",
|
||||||
|
"Clear default value": "Standardwert löschen",
|
||||||
|
"Open in new window": "In neuem Fenster öffnen",
|
||||||
|
"Sorry, the page you visited does not exist.": "Entschuldigung, die von Ihnen besuchte Seite existiert nicht.",
|
||||||
|
"is none of": "ist keines von",
|
||||||
|
"is any of": "ist eines von",
|
||||||
|
"Plugin dependency version mismatch": "Versionsinkompatibilität der Plugin-Abhängigkeit",
|
||||||
|
"The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "Die aktuelle Abhängigkeitsversion des Plugins stimmt nicht mit der Version der Anwendung überein und funktioniert möglicherweise nicht ordnungsgemäß. Sind Sie sicher, dass Sie das Plugin weiterhin aktivieren möchten?",
|
||||||
|
"Allow multiple selection": "Mehrfachauswahl erlauben",
|
||||||
|
"Parent object": "Übergeordnetes Objekt",
|
||||||
|
"Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data": "Überspringt das Abrufen der Gesamtanzahl der Tabellendatensätze während der Paginierung, um das Laden zu beschleunigen. Es wird empfohlen, diese Option für Datentabellen mit einer großen Datenmenge zu aktivieren",
|
||||||
|
"Enable secondary confirmation": "Sekundäre Bestätigung aktivieren",
|
||||||
|
"Notification": "Benachrichtigung",
|
||||||
|
"Ellipsis overflow content": "Auslassungszeichen für Überlaufinhalt",
|
||||||
|
"Hide column": "Spalte ausblenden",
|
||||||
|
"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.": "Im Konfigurationsmodus wird die gesamte Spalte transparent. Im Nicht-Konfigurationsmodus wird die gesamte Spalte ausgeblendet. Auch wenn die gesamte Spalte ausgeblendet ist, werden ihre konfigurierten Standardwerte und andere Einstellungen weiterhin wirksam.",
|
||||||
|
"Unauthenticated. Please sign in to continue.": "Nicht authentifiziert. Bitte melden Sie sich an, um fortzufahren.",
|
||||||
|
"User not found. Please sign in again to continue.": "Benutzer nicht gefunden. Bitte melden Sie sich erneut an, um fortzufahren.",
|
||||||
|
"Your session has expired. Please sign in again.": "Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.",
|
||||||
|
"User password changed, please signin again.": "Benutzerpasswort geändert, bitte melden Sie sich erneut an.",
|
||||||
|
"Desktop routes": "Desktop-Routen",
|
||||||
|
"Route permissions": "Routenberechtigungen",
|
||||||
|
"New routes are allowed to be accessed by default": "Neue Routen dürfen standardmäßig zugegriffen werden",
|
||||||
|
"Route name": "Routenname",
|
||||||
|
"Mobile routes": "Mobile Routen",
|
||||||
|
"Show in menu": "Im Menü anzeigen",
|
||||||
|
"Hide in menu": "Im Menü ausblenden",
|
||||||
|
"Path": "Pfad",
|
||||||
|
"Type": "Typ",
|
||||||
|
"Access": "Zugriff",
|
||||||
|
"Routes": "Routen",
|
||||||
|
"Add child route": "Unterroute hinzufügen",
|
||||||
|
"Delete routes": "Routen löschen",
|
||||||
|
"Delete route": "Route löschen",
|
||||||
|
"Are you sure you want to hide these routes in menu?": "Sind Sie sicher, dass Sie diese Routen im Menü ausblenden möchten?",
|
||||||
|
"Are you sure you want to show these routes in menu?": "Sind Sie sicher, dass Sie diese Routen im Menü anzeigen möchten?",
|
||||||
|
"Are you sure you want to hide this menu?": "Sind Sie sicher, dass Sie dieses Menü ausblenden möchten?",
|
||||||
|
"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.": "Nach dem Ausblenden wird dieses Menü nicht mehr in der Menüleiste angezeigt. Um es wieder anzuzeigen, müssen Sie zur Routenverwaltungsseite gehen, um es zu konfigurieren.",
|
||||||
|
"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.",
|
||||||
|
"Refresh data blocks": "Aktualisieren Sie die Datenblöcke",
|
||||||
|
"Select data blocks to refresh": "Wählen Sie die Datenblöcke aus, die aktualisiert werden sollen.",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Nach erfolgreicher Übermittlung werden die ausgewählten Datenblöcke automatisch aktualisiert."
|
||||||
|
}
|
@ -164,6 +164,7 @@
|
|||||||
"Chart type": "Chart type",
|
"Chart type": "Chart type",
|
||||||
"Chart config": "Chart config",
|
"Chart config": "Chart config",
|
||||||
"Templates": "Templates",
|
"Templates": "Templates",
|
||||||
|
"Template": "Template",
|
||||||
"Select template": "Select template",
|
"Select template": "Select template",
|
||||||
"Action logs": "Action logs",
|
"Action logs": "Action logs",
|
||||||
"Create template": "Create template",
|
"Create template": "Create template",
|
||||||
@ -884,5 +885,12 @@
|
|||||||
"If selected, the page will display Tab pages.": "If selected, the page will display Tab pages.",
|
"If selected, the page will display Tab pages.": "If selected, the page will display Tab pages.",
|
||||||
"If selected, the route will be displayed in the menu.": "If selected, the route will be displayed in the menu.",
|
"If selected, the route will be displayed in the menu.": "If selected, the route will be displayed in the menu.",
|
||||||
"Are you sure you want to hide this tab?": "Are you sure you want to hide this tab?",
|
"Are you sure you want to hide this tab?": "Are you sure you want to hide this tab?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.",
|
||||||
|
"Deprecated": "Deprecated",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "The following old template features have been deprecated and will be removed in next version.",
|
||||||
|
"Full permissions": "Full permissions",
|
||||||
|
"Refresh data blocks": "Refresh data blocks",
|
||||||
|
"Select data blocks to refresh": "Select data blocks to refresh",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "After successful submission, the selected data blocks will be automatically refreshed."
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -148,6 +148,7 @@
|
|||||||
"Chart type": "Tipo del gráfico",
|
"Chart type": "Tipo del gráfico",
|
||||||
"Chart config": "Configuración del gráfico",
|
"Chart config": "Configuración del gráfico",
|
||||||
"Templates": "Plantillas",
|
"Templates": "Plantillas",
|
||||||
|
"Template": "Plantilla",
|
||||||
"Select template": "Seleccione plantilla",
|
"Select template": "Seleccione plantilla",
|
||||||
"Action logs": "Acción logs",
|
"Action logs": "Acción logs",
|
||||||
"Create template": "Crear plantilla",
|
"Create template": "Crear plantilla",
|
||||||
@ -801,5 +802,11 @@
|
|||||||
"If selected, the page will display Tab pages.": "Si se selecciona, la página mostrará páginas de pestañas.",
|
"If selected, the page will display Tab pages.": "Si se selecciona, la página mostrará páginas de pestañas.",
|
||||||
"If selected, the route will be displayed in the menu.": "Si se selecciona, la ruta se mostrará en el menú.",
|
"If selected, the route will be displayed in the menu.": "Si se selecciona, la ruta se mostrará en el menú.",
|
||||||
"Are you sure you want to hide this tab?": "¿Estás seguro de que quieres ocultar esta pestaña?",
|
"Are you sure you want to hide this tab?": "¿Estás seguro de que quieres ocultar esta pestaña?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Después de ocultar, esta pestaña ya no aparecerá en la barra de pestañas. Para mostrarla de nuevo, deberás ir a la página de gestión de rutas para configurarla."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Después de ocultar, esta pestaña ya no aparecerá en la barra de pestañas. Para mostrarla de nuevo, deberás ir a la página de gestión de rutas para configurarla.",
|
||||||
|
"Deprecated": "Obsoleto",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "Las siguientes características de plantilla antigua han quedado obsoletas y se eliminarán en la próxima versión.",
|
||||||
|
"Full permissions": "Todos los derechos",
|
||||||
|
"Refresh data blocks": "Actualizar bloques de datos",
|
||||||
|
"Select data blocks to refresh": "Actualizar bloques de datos",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Después de enviar correctamente, los bloques de datos seleccionados se actualizarán automáticamente."
|
||||||
}
|
}
|
||||||
|
@ -159,6 +159,7 @@
|
|||||||
"Chart type": "Type de graphique",
|
"Chart type": "Type de graphique",
|
||||||
"Chart config": "Configuration du graphique",
|
"Chart config": "Configuration du graphique",
|
||||||
"Templates": "Modèles",
|
"Templates": "Modèles",
|
||||||
|
"Template": "Modèle",
|
||||||
"Select template": "Sélectionner un modèle",
|
"Select template": "Sélectionner un modèle",
|
||||||
"Action logs": "Logs d'action",
|
"Action logs": "Logs d'action",
|
||||||
"Create template": "Créer un modèle",
|
"Create template": "Créer un modèle",
|
||||||
@ -821,5 +822,11 @@
|
|||||||
"If selected, the page will display Tab pages.": "Si sélectionné, la page affichera des onglets.",
|
"If selected, the page will display Tab pages.": "Si sélectionné, la page affichera des onglets.",
|
||||||
"If selected, the route will be displayed in the menu.": "Si sélectionné, la route sera affichée dans le menu.",
|
"If selected, the route will be displayed in the menu.": "Si sélectionné, la route sera affichée dans le menu.",
|
||||||
"Are you sure you want to hide this tab?": "Êtes-vous sûr de vouloir masquer cet onglet ?",
|
"Are you sure you want to hide this tab?": "Êtes-vous sûr de vouloir masquer cet onglet ?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Après avoir masqué, cette tab ne sera plus affichée dans la barre de tab. Pour la montrer à nouveau, vous devez vous rendre sur la page de gestion des routes pour la configurer."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Après avoir masqué, cette tab ne sera plus affichée dans la barre de tab. Pour la montrer à nouveau, vous devez vous rendre sur la page de gestion des routes pour la configurer.",
|
||||||
|
"Deprecated": "Déprécié",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "Les fonctionnalités des anciens modèles ont été dépréciées et seront supprimées dans la prochaine version.",
|
||||||
|
"Full permissions": "Tous les droits",
|
||||||
|
"Refresh data blocks": "Actualiser les blocs de données",
|
||||||
|
"Select data blocks to refresh": "Actualiser les blocs de données",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Après une soumission réussie, les blocs de données sélectionnés seront automatiquement actualisés."
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -145,6 +145,7 @@
|
|||||||
"Chart type": "チャートタイプ",
|
"Chart type": "チャートタイプ",
|
||||||
"Chart config": "チャート設定",
|
"Chart config": "チャート設定",
|
||||||
"Templates": "テンプレート",
|
"Templates": "テンプレート",
|
||||||
|
"Template": "テンプレート",
|
||||||
"Select template": "テンプレートを選択してください",
|
"Select template": "テンプレートを選択してください",
|
||||||
"Action logs": "操作履歴",
|
"Action logs": "操作履歴",
|
||||||
"Create template": "テンプレートを作成",
|
"Create template": "テンプレートを作成",
|
||||||
@ -1039,5 +1040,11 @@
|
|||||||
"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.": "非表示にすると、このタブはタブバーに表示されなくなります。再表示するには、ルート管理ページで設定する必要があります。",
|
||||||
|
"Deprecated": "非推奨",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "次の古いテンプレート機能は非推奨になり、次のバージョンで削除されます。",
|
||||||
|
"Full permissions": "すべての権限",
|
||||||
|
"Refresh data blocks": "データブロックを更新",
|
||||||
|
"Select data blocks to refresh": "データブロックを選択して更新",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "送信後、選択したデータブロックが自動的に更新されます。"
|
||||||
}
|
}
|
||||||
|
@ -183,6 +183,7 @@
|
|||||||
"Chart type": "차트 유형",
|
"Chart type": "차트 유형",
|
||||||
"Chart config": "차트 구성",
|
"Chart config": "차트 구성",
|
||||||
"Templates": "템플릿",
|
"Templates": "템플릿",
|
||||||
|
"Template": "템플릿",
|
||||||
"Select template": "템플릿 선택",
|
"Select template": "템플릿 선택",
|
||||||
"Action logs": "작업 로그",
|
"Action logs": "작업 로그",
|
||||||
"Create template": "템플릿 생성",
|
"Create template": "템플릿 생성",
|
||||||
@ -912,5 +913,11 @@
|
|||||||
"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.": "숨기면 이 탭은 탭 바에 더 이상 표시되지 않습니다. 다시 표시하려면 라우트 관리 페이지에서 설정해야 합니다.",
|
||||||
|
"Deprecated": "사용 중단됨",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "다음 오래된 템플릿 기능은 사용 중단되었으며 다음 버전에서 제거될 것입니다.",
|
||||||
|
"Full permissions": "모든 권한",
|
||||||
|
"Refresh data blocks": "데이터 블록 새로 고침",
|
||||||
|
"Select data blocks to refresh": "데이터 블록을 선택하여 새로 고침",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "전송 후, 선택한 데이터 블록이 자동으로 새로 고쳐집니다."
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Display <1><0>10</0><1>20</1><2>50</2><3>100</3></1> items per page": "Toon <1><0>10</0><1>20</1><2>50</2><3>100</3></1> items per pagina",
|
"Display <1><0>10</0><1>20</1><2>50</2><3>100</3></1> items per page": "Toon <1><0>10</0><1>20</1><2>50</2><3>100</3></1> items per pagina",
|
||||||
|
"Page number": "Paginanummer",
|
||||||
|
"Page size": "Paginagrootte",
|
||||||
"Meet <1><0>All</0><1>Any</1></1> conditions in the group": "Voldoe aan <1><0>Alle</0><1>Een</1></1> voorwaarde(n) in de groep",
|
"Meet <1><0>All</0><1>Any</1></1> conditions in the group": "Voldoe aan <1><0>Alle</0><1>Een</1></1> voorwaarde(n) in de groep",
|
||||||
"Open in<1><0>Modal</0><1>Drawer</1><2>Window</2></1>": "Open in<1><0>Modal</0><1>Drawer</1><2>Venster</2></1>",
|
"Open in<1><0>Modal</0><1>Drawer</1><2>Window</2></1>": "Open in<1><0>Modal</0><1>Drawer</1><2>Venster</2></1>",
|
||||||
"{{count}} filter items": "{{count}} filter items",
|
"{{count}} filter items": "{{count}} filter items",
|
||||||
@ -38,16 +40,22 @@
|
|||||||
"Unconnected": "Niet verbonden",
|
"Unconnected": "Niet verbonden",
|
||||||
"System settings": "Systeeminstellingen",
|
"System settings": "Systeeminstellingen",
|
||||||
"System title": "Systeemtitel",
|
"System title": "Systeemtitel",
|
||||||
|
"Setting" : "Instelling",
|
||||||
"Settings": "Instellingen",
|
"Settings": "Instellingen",
|
||||||
|
"Enable": "Inschakelen",
|
||||||
|
"Disable": "Uitschakelen",
|
||||||
|
"On": "Aan",
|
||||||
|
"Off": "Uit",
|
||||||
"Logo": "Logo",
|
"Logo": "Logo",
|
||||||
"Add menu item": "Voeg een menu item toe",
|
"Add menu item": "Menu-item toevoegen",
|
||||||
"Page": "Pagina",
|
"Page": "Pagina",
|
||||||
|
"Tab": "Tab",
|
||||||
"Name": "Naam",
|
"Name": "Naam",
|
||||||
"Icon": "Icoon",
|
"Icon": "Icoon",
|
||||||
"Group": "Groep",
|
"Group": "Groep",
|
||||||
"Link": "Link",
|
"Link": "Link",
|
||||||
"Save conditions": "Voorwaarden opslaan",
|
"Save conditions": "Voorwaarden opslaan",
|
||||||
"Edit menu item": "Edit menu item",
|
"Edit menu item": "Menu-item bewerken",
|
||||||
"Move to": "Verplaats naar",
|
"Move to": "Verplaats naar",
|
||||||
"Insert left": "Links invoegen",
|
"Insert left": "Links invoegen",
|
||||||
"Insert right": "Rechts invoegen",
|
"Insert right": "Rechts invoegen",
|
||||||
@ -55,7 +63,7 @@
|
|||||||
"Delete": "Verwijder",
|
"Delete": "Verwijder",
|
||||||
"Disassociate": "Loskoppelen",
|
"Disassociate": "Loskoppelen",
|
||||||
"Disassociate record": "Record loskoppelen",
|
"Disassociate record": "Record loskoppelen",
|
||||||
"Are you sure you want to disassociate it?": "Weet je zeker dat je het wilt loskoppelen?",
|
"Are you sure you want to disassociate it?": "Weet je zeker dat je het wil loskoppelen?",
|
||||||
"UI editor": "UI-editor",
|
"UI editor": "UI-editor",
|
||||||
"Collection": "Collectie",
|
"Collection": "Collectie",
|
||||||
"Collection selector": "Collectie selector",
|
"Collection selector": "Collectie selector",
|
||||||
@ -81,9 +89,6 @@
|
|||||||
"Disabled": "Uitgeschakeld",
|
"Disabled": "Uitgeschakeld",
|
||||||
"Enabled": "Ingeschakeld",
|
"Enabled": "Ingeschakeld",
|
||||||
"Problematic": "Problematisch",
|
"Problematic": "Problematisch",
|
||||||
"Setting": "Instelling",
|
|
||||||
"On": "Aan",
|
|
||||||
"Off": "Uit",
|
|
||||||
"Empty": "Leeg",
|
"Empty": "Leeg",
|
||||||
"Linkage rule": "Koppelingregel",
|
"Linkage rule": "Koppelingregel",
|
||||||
"Linkage rules": "Koppelingregels",
|
"Linkage rules": "Koppelingregels",
|
||||||
@ -168,7 +173,7 @@
|
|||||||
"Create template": "Sjabloon maken",
|
"Create template": "Sjabloon maken",
|
||||||
"Edit markdown": "Bewerk markdown",
|
"Edit markdown": "Bewerk markdown",
|
||||||
"Add block": "Blok toevoegen",
|
"Add block": "Blok toevoegen",
|
||||||
"Add new": "Nieuwe toevoegen",
|
"Add new": "Nieuw toevoegen",
|
||||||
"Add record": "Record toevoegen",
|
"Add record": "Record toevoegen",
|
||||||
"Add child": "Onderliggend toevoegen",
|
"Add child": "Onderliggend toevoegen",
|
||||||
"Collapse all": "Alles inklappen",
|
"Collapse all": "Alles inklappen",
|
||||||
@ -177,7 +182,7 @@
|
|||||||
"Default collapse": "Standaard inklappen",
|
"Default collapse": "Standaard inklappen",
|
||||||
"Tree table": "Boomtabel",
|
"Tree table": "Boomtabel",
|
||||||
"Custom field display name": "Aangepaste veldweergavenaam",
|
"Custom field display name": "Aangepaste veldweergavenaam",
|
||||||
"Display fields": "Display collection fields",
|
"Display fields": "Toon collectie velden",
|
||||||
"Edit record": "Record bewerken",
|
"Edit record": "Record bewerken",
|
||||||
"Delete menu item": "Menu-item verwijderen",
|
"Delete menu item": "Menu-item verwijderen",
|
||||||
"Add page": "Pagina toevoegen",
|
"Add page": "Pagina toevoegen",
|
||||||
@ -187,8 +192,8 @@
|
|||||||
"Insert below": "Onder invoegen",
|
"Insert below": "Onder invoegen",
|
||||||
"Save": "Opslaan",
|
"Save": "Opslaan",
|
||||||
"Delete block": "Blok verwijderen",
|
"Delete block": "Blok verwijderen",
|
||||||
"Are you sure you want to delete it?": "Weet je zeker dat je het wilt verwijderen?",
|
"Are you sure you want to delete it?": "Weet je zeker dat je het wil verwijderen?",
|
||||||
"This is a demo text, **supports Markdown syntax**.": "This is a demo text, **supports Markdown syntax**.",
|
"This is a demo text, **supports Markdown syntax**.": "Dit is een demo tekst, **ondersteunt Markdown syntax**.",
|
||||||
"Filter": "Filter",
|
"Filter": "Filter",
|
||||||
"Connect data blocks": "Verbind gegevensblokken",
|
"Connect data blocks": "Verbind gegevensblokken",
|
||||||
"Action type": "Actietype",
|
"Action type": "Actietype",
|
||||||
@ -228,12 +233,12 @@
|
|||||||
"Preview": "Voorbeeld",
|
"Preview": "Voorbeeld",
|
||||||
"Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "Willekeurig gegenereerd en kan worden aangepast. Ondersteunt letters, cijfers en onderstrepingstekens, moet beginnen met een letter.",
|
"Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.": "Willekeurig gegenereerd en kan worden aangepast. Ondersteunt letters, cijfers en onderstrepingstekens, moet beginnen met een letter.",
|
||||||
"Edit": "Bewerken",
|
"Edit": "Bewerken",
|
||||||
"Edit collection": "Bewerk verzameling",
|
"Edit collection": "Collectie bewerken",
|
||||||
"Configure fields": "Configureer velden",
|
"Configure fields": "Velden configureren",
|
||||||
"Configure columns": "Configureer kolommen",
|
"Configure columns": "Kolommen configureren",
|
||||||
"Edit field": "Bewerk veld",
|
"Edit field": "Veld bewerken",
|
||||||
"Override": "Overschrijven",
|
"Override": "Overschrijven",
|
||||||
"Override field": "Overschrijf veld",
|
"Override field": "Veld overschrijven",
|
||||||
"Configure fields of {{title}}": "Configureer velden van {{title}}",
|
"Configure fields of {{title}}": "Configureer velden van {{title}}",
|
||||||
"Association fields filter": "Filter voor associatievelden",
|
"Association fields filter": "Filter voor associatievelden",
|
||||||
"PK & FK fields": "PK & FK velden",
|
"PK & FK fields": "PK & FK velden",
|
||||||
@ -242,7 +247,7 @@
|
|||||||
"System fields": "Systeemvelden",
|
"System fields": "Systeemvelden",
|
||||||
"General fields": "Algemene velden",
|
"General fields": "Algemene velden",
|
||||||
"Inherited fields": "Geërfde velden",
|
"Inherited fields": "Geërfde velden",
|
||||||
"Parent collection fields": "Velden van hoofdverzameling",
|
"Parent collection fields": "Velden van bovenliggende collectie",
|
||||||
"Basic": "Basis",
|
"Basic": "Basis",
|
||||||
"Single line text": "Enkele regel tekst",
|
"Single line text": "Enkele regel tekst",
|
||||||
"Long text": "Lange tekst",
|
"Long text": "Lange tekst",
|
||||||
@ -252,11 +257,11 @@
|
|||||||
"Integer": "Geheel getal",
|
"Integer": "Geheel getal",
|
||||||
"Percent": "Percentage",
|
"Percent": "Percentage",
|
||||||
"Password": "Wachtwoord",
|
"Password": "Wachtwoord",
|
||||||
"Advanced type": "Advanced",
|
"Advanced type": "Geavanceerd",
|
||||||
"Formula": "Formule",
|
"Formula": "Formule",
|
||||||
"Formula description": "Compute a value in each record based on other fields in the same record.",
|
"Formula description": "Bereken een waarde in elk record op basis van andere velden in hetzelfde record.",
|
||||||
"Choices": "Keuzes",
|
"Choices": "Keuzes",
|
||||||
"Checkbox": "Checkbox",
|
"Checkbox": "Selectievak",
|
||||||
"Single select": "Enkele selectie",
|
"Single select": "Enkele selectie",
|
||||||
"Multiple select": "Meerdere selecteren",
|
"Multiple select": "Meerdere selecteren",
|
||||||
"Radio group": "Radiogroep",
|
"Radio group": "Radiogroep",
|
||||||
@ -266,7 +271,7 @@
|
|||||||
"Datetime": "Datumtijd",
|
"Datetime": "Datumtijd",
|
||||||
"Relation": "Relatie",
|
"Relation": "Relatie",
|
||||||
"Link to": "Koppel aan",
|
"Link to": "Koppel aan",
|
||||||
"Link to description": "Used to create collection relationships quickly and compatible with most common scenarios. Suitable for non-developer use. When present as a field, it is a drop-down selection used to select records from the target collection. Once created, it will simultaneously generate the associated fields of the current collection in the target collection.",
|
"Link to description": "Gebruikt om snel collectie-relaties te maken en compatibel met de meest voorkomende scenario's. Geschikt voor niet-ontwikkelaars. Wanneer aanwezig als veld, is het een keuzelijst die wordt gebruikt om records uit de doelcollectie te selecteren. Eenmaal aangemaakt, genereert het tegelijkertijd de bijbehorende velden van de huidige collectie in de doelcollectie.",
|
||||||
"Sub-table": "Subtabel",
|
"Sub-table": "Subtabel",
|
||||||
"Sub-details": "Subdetails",
|
"Sub-details": "Subdetails",
|
||||||
"Sub-form(Popover)": "Subformulier (Popover)",
|
"Sub-form(Popover)": "Subformulier (Popover)",
|
||||||
@ -275,7 +280,7 @@
|
|||||||
"Last updated at": "Laatst bijgewerkt op",
|
"Last updated at": "Laatst bijgewerkt op",
|
||||||
"Created by": "Aangemaakt door",
|
"Created by": "Aangemaakt door",
|
||||||
"Last updated by": "Laatst bijgewerkt door",
|
"Last updated by": "Laatst bijgewerkt door",
|
||||||
"Add field": "Voeg veld toe",
|
"Add field": "Veld toevoegen",
|
||||||
"Field display name": "Weergavenaam veld",
|
"Field display name": "Weergavenaam veld",
|
||||||
"Field type": "Veldtype",
|
"Field type": "Veldtype",
|
||||||
"Field interface": "Veldinterface",
|
"Field interface": "Veldinterface",
|
||||||
@ -289,10 +294,10 @@
|
|||||||
"24 hour": "24 uur",
|
"24 hour": "24 uur",
|
||||||
"Relationship type": "Relatietype",
|
"Relationship type": "Relatietype",
|
||||||
"Inverse relationship type": "Omgekeerd relatietype",
|
"Inverse relationship type": "Omgekeerd relatietype",
|
||||||
"Source collection": "Bronverzameling",
|
"Source collection": "Broncollectie",
|
||||||
"Source key": "Bron sleutel",
|
"Source key": "Bron sleutel",
|
||||||
"Target collection": "Doelverzameling",
|
"Target collection": "Doelcollectie",
|
||||||
"Through collection": "Via verzameling",
|
"Through collection": "Via collectie",
|
||||||
"Target key": "Doel sleutel",
|
"Target key": "Doel sleutel",
|
||||||
"Foreign key": "Buitenlandse sleutel",
|
"Foreign key": "Buitenlandse sleutel",
|
||||||
"One to one": "Een-op-een",
|
"One to one": "Een-op-een",
|
||||||
@ -301,35 +306,35 @@
|
|||||||
"Many to many": "Veel-op-veel",
|
"Many to many": "Veel-op-veel",
|
||||||
"Foreign key 1": "Buitenlandse sleutel 1",
|
"Foreign key 1": "Buitenlandse sleutel 1",
|
||||||
"Foreign key 2": "Buitenlandse sleutel 2",
|
"Foreign key 2": "Buitenlandse sleutel 2",
|
||||||
"One to one description": "Used to create one-to-one relationships. For example, a user has a profile.",
|
"One to one description": "Gebruikt om een-op-een relaties te maken. For example, a user has a profile.",
|
||||||
"One to many description": "Used to create a one-to-many relationship. For example, a country will have many cities and a city can only be in one country. When present as a field, it is a sub-table that displays the records of the associated collection. When created, a Many-to-one field is automatically generated in the associated collection.",
|
"One to many description": "Gebruikt om een een-op-veel-relatie te maken. Bijvoorbeeld, een land heeft veel steden en een stad kan slechts in één land zijn. Wanneer aanwezig als veld, is het een sub-tabel die de records van de geassocieerde collectie weergeeft. Wanneer aangemaakt, wordt automatisch een Veel-op-een veld gegenereerd in de geassocieerde collectie.",
|
||||||
"Many to one description": "Used to create many-to-one relationships. For example, a city can belong to only one country and a country can have many cities. When present as a field, it is a drop-down selection used to select record from the associated collection. Once created, a One-to-many field is automatically generated in the associated collection.",
|
"Many to one description": "Gebruikt om een veel-op-een-relatie te maken. Bijvoorbeeld, een stad kan slechts tot één land behoren en een land kan veel steden hebben. Wanneer aanwezig als veld, is het een keuzelijst die wordt gebruikt om records uit de geassocieerde collectie te selecteren. Eenmaal aangemaakt, wordt automatisch een Een-op-veel veld gegenereerd in de geassocieerde collectie.",
|
||||||
"Many to many description": "Used to create many-to-many relationships. For example, a student will have many teachers and a teacher will have many students. When present as a field, it is a drop-down selection used to select records from the associated collection.",
|
"Many to many description": "Gebruikt om veel-op-veel-relaties te maken. Bijvoorbeeld, een student heeft veel leraren en een leraar heeft veel studenten. Wanneer aanwezig als veld, is het een keuzelijst die wordt gebruikt om records uit de geassocieerde collectie te selecteren.",
|
||||||
"Generated automatically if left blank": "Wordt automatisch gegenereerd als het leeg wordt gelaten",
|
"Generated automatically if left blank": "Wordt automatisch gegenereerd als het leeg wordt gelaten",
|
||||||
"Display association fields": "Toon associatievelden",
|
"Display association fields": "Associatievelden tonen",
|
||||||
"Display field title": "Toon veldtitel",
|
"Display field title": "Veldtitel tonen",
|
||||||
"Field component": "Veldcomponent",
|
"Field component": "Veldcomponent",
|
||||||
"Allow multiple": "Sta meerdere toe",
|
"Allow multiple": "Meerdere toestaan",
|
||||||
"Quick upload": "Snelle upload",
|
"Quick upload": "Snelle upload",
|
||||||
"Select file": "Selecteer bestand",
|
"Select file": "Bestand selecteren",
|
||||||
"Subtable": "Subtabel",
|
"Subtable": "Subtabel",
|
||||||
"Sub-form": "Subformulier",
|
"Sub-form": "Subformulier",
|
||||||
"Field mode": "Veldmodus",
|
"Field mode": "Veldmodus",
|
||||||
"Allow add new data": "Sta toe nieuwe data toe te voegen",
|
"Allow add new data": "Sta toe nieuwe data toe te voegen",
|
||||||
"Record picker": "Recordselector",
|
"Record picker": "Selecteer record",
|
||||||
"Toggles the subfield mode": "Wisselt de subveldmodus",
|
"Toggles the subfield mode": "Wisselt de subveldmodus",
|
||||||
"Selector mode": "Selectormodus",
|
"Selector mode": "Selectormodus",
|
||||||
"Subtable mode": "Sub-table mode",
|
"Subtable mode": "Subtabel modus",
|
||||||
"Subform mode": "Sub-form mode",
|
"Subform mode": "Subformulier modus",
|
||||||
"Edit block title": "Bewerk bloktitel",
|
"Edit block title": "Bloktitel bewerken",
|
||||||
"Block title": "Bloktitel",
|
"Block title": "Bloktitel",
|
||||||
"Pattern": "Patroon",
|
"Pattern": "Patroon",
|
||||||
"Operator": "Operator",
|
"Operator": "Operator",
|
||||||
"Editable": "Bewerkbaar",
|
"Editable": "Bewerkbaar",
|
||||||
"Readonly": "Alleen-lezen",
|
"Readonly": "Alleen-lezen",
|
||||||
"Easy-reading": "Gemakkelijk te lezen",
|
"Easy-reading": "Gemakkelijk te lezen",
|
||||||
"Add filter": "Voeg filter toe",
|
"Add filter": "Filter toevoegen",
|
||||||
"Add filter group": "Voeg filtergroep toe",
|
"Add filter group": "Filtergroep toevoegen",
|
||||||
"Comparision": "Vergelijking",
|
"Comparision": "Vergelijking",
|
||||||
"is": "is",
|
"is": "is",
|
||||||
"is not": "is niet",
|
"is not": "is niet",
|
||||||
@ -364,7 +369,7 @@
|
|||||||
"Edit form": "Bewerk formulier",
|
"Edit form": "Bewerk formulier",
|
||||||
"Create form": "Maak formulier",
|
"Create form": "Maak formulier",
|
||||||
"Form (Edit)": "Formulier (Bewerken)",
|
"Form (Edit)": "Formulier (Bewerken)",
|
||||||
"Form (Add new)": "Formulier (Nieuwe toevoegen)",
|
"Form (Add new)": "Formulier (Nieuw toevoegen)",
|
||||||
"Edit tab": "Bewerk tabblad",
|
"Edit tab": "Bewerk tabblad",
|
||||||
"Relationship blocks": "Relatieblokken",
|
"Relationship blocks": "Relatieblokken",
|
||||||
"Select record": "Selecteer record",
|
"Select record": "Selecteer record",
|
||||||
@ -433,7 +438,7 @@
|
|||||||
"Allow linking to multiple records": "Koppelen aan meerdere records toestaan",
|
"Allow linking to multiple records": "Koppelen aan meerdere records toestaan",
|
||||||
"Allow uploading multiple files": "Meerdere bestanden uploaden toestaan",
|
"Allow uploading multiple files": "Meerdere bestanden uploaden toestaan",
|
||||||
"Configure calendar": "Kalender configureren",
|
"Configure calendar": "Kalender configureren",
|
||||||
"Title field": "Titelfeld",
|
"Title field": "Titelveld",
|
||||||
"Custom title": "Aangepaste titel",
|
"Custom title": "Aangepaste titel",
|
||||||
"Daily": "Dagelijks",
|
"Daily": "Dagelijks",
|
||||||
"Weekly": "Wekelijks",
|
"Weekly": "Wekelijks",
|
||||||
@ -534,7 +539,7 @@
|
|||||||
"Position": "Positie",
|
"Position": "Positie",
|
||||||
"Insert before": "Invoegen voor",
|
"Insert before": "Invoegen voor",
|
||||||
"Insert after": "Invoegen na",
|
"Insert after": "Invoegen na",
|
||||||
"UI Editor": "UI Editor",
|
"UI Editor": "UI Bewerker",
|
||||||
"ASC": "Oplopend",
|
"ASC": "Oplopend",
|
||||||
"DESC": "Aflopend",
|
"DESC": "Aflopend",
|
||||||
"Add sort field": "Sorteerveld toevoegen",
|
"Add sort field": "Sorteerveld toevoegen",
|
||||||
@ -544,7 +549,7 @@
|
|||||||
"Dialog": "Dialoog",
|
"Dialog": "Dialoog",
|
||||||
"Delete action": "Actie verwijderen",
|
"Delete action": "Actie verwijderen",
|
||||||
"Custom column title": "Aangepaste kolomtitel",
|
"Custom column title": "Aangepaste kolomtitel",
|
||||||
"Column title": "column title",
|
"Column title": "kolomtitel",
|
||||||
"Original title: ": "Originele titel: ",
|
"Original title: ": "Originele titel: ",
|
||||||
"Delete table column": "Tabelkolom verwijderen",
|
"Delete table column": "Tabelkolom verwijderen",
|
||||||
"Skip required validation": "Vereiste validatie overslaan",
|
"Skip required validation": "Vereiste validatie overslaan",
|
||||||
@ -628,7 +633,7 @@
|
|||||||
"Field permission": "Veldrecht",
|
"Field permission": "Veldrecht",
|
||||||
"Scope name": "Bereiksnaam",
|
"Scope name": "Bereiksnaam",
|
||||||
"Unsaved changes": "Niet-opgeslagen wijzigingen",
|
"Unsaved changes": "Niet-opgeslagen wijzigingen",
|
||||||
"Are you sure you don't want to save?": "Weet je zeker dat je niet wilt opslaan?",
|
"Are you sure you don't want to save?": "Weet je zeker dat je niet wil opslaan?",
|
||||||
"Dragging": "Slepen",
|
"Dragging": "Slepen",
|
||||||
"Popup": "Popup",
|
"Popup": "Popup",
|
||||||
"Trigger workflow": "Workflow activeren",
|
"Trigger workflow": "Workflow activeren",
|
||||||
@ -729,8 +734,8 @@
|
|||||||
"Dependencies check failed, can't enable.": "Controle van afhankelijkheden mislukt, inschakelen niet mogelijk.",
|
"Dependencies check failed, can't enable.": "Controle van afhankelijkheden mislukt, inschakelen niet mogelijk.",
|
||||||
"Plugin starting...": "Plugin wordt gestart...",
|
"Plugin starting...": "Plugin wordt gestart...",
|
||||||
"Plugin stopping...": "Plugin wordt gestopt...",
|
"Plugin stopping...": "Plugin wordt gestopt...",
|
||||||
"Are you sure to delete this plugin?": "Weet je zeker dat je deze plugin wilt verwijderen?",
|
"Are you sure to delete this plugin?": "Weet je zeker dat je deze plugin wil verwijderen?",
|
||||||
"Are you sure to disable this plugin?": "Weet je zeker dat je deze plugin wilt uitschakelen?",
|
"Are you sure to disable this plugin?": "Weet je zeker dat je deze plugin wil uitschakelen?",
|
||||||
"re-download file": "bestand opnieuw downloaden",
|
"re-download file": "bestand opnieuw downloaden",
|
||||||
"Not enabled": "Niet ingeschakeld",
|
"Not enabled": "Niet ingeschakeld",
|
||||||
"Search plugin": "Plugin zoeken",
|
"Search plugin": "Plugin zoeken",
|
||||||
@ -740,7 +745,7 @@
|
|||||||
"All plugin settings": "Alle plugininstellingen",
|
"All plugin settings": "Alle plugininstellingen",
|
||||||
"Bookmark": "Bladwijzer",
|
"Bookmark": "Bladwijzer",
|
||||||
"Manage all settings": "Beheer alle instellingen",
|
"Manage all settings": "Beheer alle instellingen",
|
||||||
"Create inverse field in the target collection": "Creëer een omgekeerd veld in de doelverzameling",
|
"Create inverse field in the target collection": "Creëer een omgekeerd veld in de doelcollectie",
|
||||||
"Inverse field name": "Omgekeerde veldnaam",
|
"Inverse field name": "Omgekeerde veldnaam",
|
||||||
"Inverse field display name": "Weergavenaam omgekeerd veld",
|
"Inverse field display name": "Weergavenaam omgekeerd veld",
|
||||||
"Bulk update": "Updaten in bulk",
|
"Bulk update": "Updaten in bulk",
|
||||||
@ -758,12 +763,12 @@
|
|||||||
"Please select the records to be updated": "Selecteer de records die moeten worden bijgewerkt",
|
"Please select the records to be updated": "Selecteer de records die moeten worden bijgewerkt",
|
||||||
"Selector": "Selector",
|
"Selector": "Selector",
|
||||||
"Inner": "Intern",
|
"Inner": "Intern",
|
||||||
"Search and select collection": "Zoek en selecteer verzameling",
|
"Search and select collection": "Zoek en selecteer collectie",
|
||||||
"Please fill in the iframe URL": "Vul de iframe-URL in",
|
"Please fill in the iframe URL": "Vul de iframe-URL in",
|
||||||
"Fix block": "Blok vastzetten",
|
"Fix block": "Blok vastzetten",
|
||||||
"Plugin name": "Pluginnaam",
|
"Plugin name": "Pluginnaam",
|
||||||
"Plugin tab name": "Naam plugintabblad",
|
"Plugin tab name": "Naam plugintabblad",
|
||||||
"AutoGenId": "Auto-generated ID field",
|
"AutoGenId": "Automatisch gegenereerd ID veld",
|
||||||
"CreatedBy": "Aangemaakt door",
|
"CreatedBy": "Aangemaakt door",
|
||||||
"UpdatedBy": "Bijgewerkt door",
|
"UpdatedBy": "Bijgewerkt door",
|
||||||
"CreatedAt": "Aangemaakt op",
|
"CreatedAt": "Aangemaakt op",
|
||||||
@ -811,10 +816,10 @@
|
|||||||
"Execute": "Uitvoeren",
|
"Execute": "Uitvoeren",
|
||||||
"Please use a valid SELECT or WITH AS statement": "Gebruik een geldige SELECT of WITH AS-verklaring",
|
"Please use a valid SELECT or WITH AS statement": "Gebruik een geldige SELECT of WITH AS-verklaring",
|
||||||
"Please confirm the SQL statement first": "Bevestig eerst de SQL-verklaring",
|
"Please confirm the SQL statement first": "Bevestig eerst de SQL-verklaring",
|
||||||
"Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "Automatisch objecten verwijderen die afhankelijk zijn van de verzameling (zoals weergaven) en ook alle objecten die daarvan afhankelijk zijn",
|
"Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "Automatisch objecten verwijderen die afhankelijk zijn van de collectie (zoals weergaven) en ook alle objecten die daarvan afhankelijk zijn",
|
||||||
"Sign in with another account": "Aanmelden met een ander account",
|
"Sign in with another account": "Aanmelden met een ander account",
|
||||||
"Return to the main application": "Terug naar de hoofdapplicatie",
|
"Return to the main application": "Terug naar de hoofdapplicatie",
|
||||||
"Permission deined": "Permission denied",
|
"Permission deined": "Toegang geweigerd",
|
||||||
"loading": "laden",
|
"loading": "laden",
|
||||||
"name is required": "naam is vereist",
|
"name is required": "naam is vereist",
|
||||||
"data source": "gegevensbron",
|
"data source": "gegevensbron",
|
||||||
@ -845,11 +850,11 @@
|
|||||||
"Search": "Zoeken",
|
"Search": "Zoeken",
|
||||||
"Clear default value": "Standaardwaarde wissen",
|
"Clear default value": "Standaardwaarde wissen",
|
||||||
"Open in new window": "Openen in nieuw venster",
|
"Open in new window": "Openen in nieuw venster",
|
||||||
"Sorry, the page you visited does not exist.": "Sorry, de pagina die u bezocht bestaat niet.",
|
"Sorry, the page you visited does not exist.": "Sorry, de pagina die je bezocht bestaat niet.",
|
||||||
"is none of": "is geen van",
|
"is none of": "is geen van",
|
||||||
"is any of": "is een van",
|
"is any of": "is een van",
|
||||||
"Plugin dependency version mismatch": "Versieafhankelijkheid van plugin komt niet overeen",
|
"Plugin dependency version mismatch": "Versieafhankelijkheid van plugin komt niet overeen",
|
||||||
"The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "De huidige afhankelijkheidsversie van de plugin komt niet overeen met de versie van de applicatie en werkt mogelijk niet correct. Weet je zeker dat je de plugin wilt blijven inschakelen?",
|
"The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "De huidige afhankelijkheidsversie van de plugin komt niet overeen met de versie van de applicatie en werkt mogelijk niet correct. Weet je zeker dat je de plugin wil blijven inschakelen?",
|
||||||
"Allow multiple selection": "Meerdere selecties toestaan",
|
"Allow multiple selection": "Meerdere selecties toestaan",
|
||||||
"Parent object": "Bovenliggend object",
|
"Parent object": "Bovenliggend object",
|
||||||
"Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data": "Sla het ophalen van het totale aantal tabelrecords over tijdens paginering om het laden te versnellen. Het wordt aanbevolen om deze optie in te schakelen voor datatabellen met veel gegevens.",
|
"Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data": "Sla het ophalen van het totale aantal tabelrecords over tijdens paginering om het laden te versnellen. Het wordt aanbevolen om deze optie in te schakelen voor datatabellen met veel gegevens.",
|
||||||
@ -857,5 +862,200 @@
|
|||||||
"Notification": "Melding",
|
"Notification": "Melding",
|
||||||
"Ellipsis overflow content": "Inhoud afkorten met ellips",
|
"Ellipsis overflow content": "Inhoud afkorten met ellips",
|
||||||
"Hide column": "Kolom verbergen",
|
"Hide column": "Kolom verbergen",
|
||||||
"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 configuratiemodus wordt de hele kolom transparant. In niet-configuratiemodus wordt de hele kolom verborgen. Zelfs als de hele kolom verborgen is, blijven de geconfigureerde standaardwaarden en andere instellingen van kracht."
|
"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 configuratiemodus wordt de hele kolom transparant. In niet-configuratiemodus wordt de hele kolom verborgen. Zelfs als de hele kolom verborgen is, blijven de geconfigureerde standaardwaarden en andere instellingen van kracht.",
|
||||||
|
"Unauthenticated. Please sign in to continue.": "Niet geauthenticeerd. Meld je aan om verder te gaan.",
|
||||||
|
"User not found. Please sign in again to continue.": "Gebruiker niet gevonden. Meld je opnieuw aan om verder te gaan.",
|
||||||
|
"Your session has expired. Please sign in again.": "Je sessie is verlopen. Meld je opnieuw aan.",
|
||||||
|
"User password changed, please signin again.": "Gebruikerswachtwoord gewijzigd, meld je opnieuw aan.",
|
||||||
|
"Desktop routes": "Desktop-routes",
|
||||||
|
"Route permissions": "Route-machtigingen",
|
||||||
|
"New routes are allowed to be accessed by default": "Nieuwe routes zijn standaard toegankelijk",
|
||||||
|
"Route name": "Routenaam",
|
||||||
|
"Mobile routes": "Mobiele routes",
|
||||||
|
"Show in menu": "Weergeven in menu",
|
||||||
|
"Hide in menu": "Verbergen in menu",
|
||||||
|
"Path": "Pad",
|
||||||
|
"Type": "Type",
|
||||||
|
"Access": "Toegang",
|
||||||
|
"Routes": "Routes",
|
||||||
|
"Add child route": "Subroute toevoegen",
|
||||||
|
"Delete routes": "Routes verwijderen",
|
||||||
|
"Delete route": "Route verwijderen",
|
||||||
|
"Are you sure you want to hide these routes in menu?": "Weet je zeker dat je deze routes in het menu wil verbergen?",
|
||||||
|
"Are you sure you want to show these routes in menu?": "Weet je zeker dat je deze routes in het menu wil weergeven?",
|
||||||
|
"Are you sure you want to hide this menu?": "Weet je zeker dat je dit menu wil verbergen?",
|
||||||
|
"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.": "Na verbergen wordt dit menu niet meer weergegeven in de menubalk. Om het opnieuw te tonen, moet je naar de routebeheerpagina gaan om het in te stellen.",
|
||||||
|
"If selected, the page will display Tab pages.": "Indien geselecteerd, worden tabbladen op de pagina weergegeven.",
|
||||||
|
"If selected, the route will be displayed in the menu.": "Indien geselecteerd, wordt de route weergegeven in het menu.",
|
||||||
|
"Are you sure you want to hide this tab?": "Weet je zeker dat je dit tabblad wil verbergen?",
|
||||||
|
"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.": "Na verbergen wordt dit tabblad niet meer weergegeven in de tabbalk. Om het opnieuw te tonen, moet je naar de routebeheerpagina gaan om het in te stellen.",
|
||||||
|
"Calculation engine": "Berekeningsengine",
|
||||||
|
"Expression collection": "Expressiecollectie",
|
||||||
|
"Tree collection": "Boomcollectie",
|
||||||
|
"Parent ID": "Ouder-ID",
|
||||||
|
"Parent": "Ouder",
|
||||||
|
"Children": "Kinderen",
|
||||||
|
"Confirm": "Bevestigen",
|
||||||
|
"Block": "Blok",
|
||||||
|
"Unnamed": "Naamloos",
|
||||||
|
"SQL collection": "SQL-collectie",
|
||||||
|
"Configure field": "Veld configureren",
|
||||||
|
"Username": "Gebruikersnaam",
|
||||||
|
"Null": "Leeg",
|
||||||
|
"Boolean": "Boolean",
|
||||||
|
"String": "Tekst",
|
||||||
|
"Syntax references": "Syntax-referenties",
|
||||||
|
"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 wordt geleverd met een groot aantal ingebouwde functies en constanten en biedt een geïntegreerde oplossing om met verschillende gegevenstypen te werken.",
|
||||||
|
"Formula.js supports most Microsoft Excel formula functions.": "Formula.js ondersteunt de meeste Microsoft Excel-formulefuncties.",
|
||||||
|
"String template": "Tekstsjabloon",
|
||||||
|
"Simple string replacement, can be used to interpolate variables in a string.": "Eenvoudige tekstvervanging, kan worden gebruikt om variabelen in een tekst te interpoleren.",
|
||||||
|
"https://docs.nocobase.com/handbook/calculation-engines/formula": "https://docs.nocobase.com/handbook/calculation-engines/formula",
|
||||||
|
"https://docs.nocobase.com/handbook/calculation-engines/mathjs": "https://docs.nocobase.com/handbook/calculation-engines/mathjs",
|
||||||
|
"Display <icon></icon> when unchecked": "Toon <icon></icon> wanneer niet aangevinkt",
|
||||||
|
"Allow dissociate": "Loskoppelen toestaan",
|
||||||
|
"Edit block title & description": "Blok titel & beschrijving bewerken",
|
||||||
|
"Add Markdown": "Markdown toevoegen",
|
||||||
|
"Must be 1-50 characters in length (excluding @.<>\"'/)": "Moet 1-50 tekens lang zijn (exclusief @.<>\"'/)",
|
||||||
|
"Original title: ": "Originele titel: ",
|
||||||
|
"Original field title: ": "Originele veldtitel: ",
|
||||||
|
"Data source permissions": "Datatoegangsrechten",
|
||||||
|
"Now": "Nu",
|
||||||
|
"Access control": "Toegangscontrole",
|
||||||
|
"Remove": "Verwijderen",
|
||||||
|
"Docs": "Documentatie",
|
||||||
|
"Enable page header": "Paginahoofd inschakelen",
|
||||||
|
"Display page title": "Paginatitel weergeven",
|
||||||
|
"Edit page title": "Paginatitel bewerken",
|
||||||
|
"Enable page tabs": "Pagina-tabbladen inschakelen",
|
||||||
|
"Constant": "Constant",
|
||||||
|
"Select a variable": "Selecteer een variabele",
|
||||||
|
"Double click to choose entire object": "Dubbelklik om het hele object te kiezen",
|
||||||
|
"TRUE": "WAAR",
|
||||||
|
"FALSE": "ONWAAR",
|
||||||
|
"Prettify": "Opknappen",
|
||||||
|
"Theme": "Thema",
|
||||||
|
"Default theme": "Standaardthema",
|
||||||
|
"Compact theme": "Compact thema",
|
||||||
|
"Download": "Downloaden",
|
||||||
|
"File type is not supported for previewing, please download it to preview.": "Bestandstype wordt niet ondersteund voor voorbeeldweergave, download het om te bekijken.",
|
||||||
|
"Click or drag file to this area to upload": "Klik of sleep een bestand naar dit gebied om te uploaden.",
|
||||||
|
"Support for a single or bulk upload.": "Ondersteuning voor enkele of bulk-upload.",
|
||||||
|
"File size should not exceed {{size}}.": "Bestandsgrootte mag {{size}} niet overschrijden.",
|
||||||
|
"File size exceeds the limit": "Bestandsgrootte overschrijdt de limiet.",
|
||||||
|
"File type is not allowed": "Bestandstype is niet toegestaan.",
|
||||||
|
"Incomplete uploading files need to be resolved": "Onvolledig geüploade bestanden moeten worden opgelost.",
|
||||||
|
"Default title for each record": "Standaardtitel voor elk record.",
|
||||||
|
"If collection inherits, choose inherited collections as templates": "Als de collectie overerft, kies geërfde collecties als sjablonen.",
|
||||||
|
"Select an existing piece of data as the initialization data for the form": "Selecteer een bestaand gegeven als initiële data voor het formulier.",
|
||||||
|
"Only the selected fields will be used as the initialization data for the form": "Alleen de geselecteerde velden worden gebruikt als initiële data voor het formulier.",
|
||||||
|
"Template Data": "Sjabloongegevens",
|
||||||
|
"Data fields": "Gegevensvelden",
|
||||||
|
"Add template": "Sjabloon toevoegen",
|
||||||
|
"Enable form data template": "Formuliersjabloon inschakelen",
|
||||||
|
"Form data templates": "Formuliersjablonen",
|
||||||
|
"No configuration available.": "Geen configuratie beschikbaar.",
|
||||||
|
"Reload application": "Herlaad applicatie",
|
||||||
|
"The application is reloading, please do not close the page.": "De applicatie wordt opnieuw geladen, sluit de pagina niet.",
|
||||||
|
"Application reloading": "Applicatie wordt opnieuw geladen",
|
||||||
|
"Allows to clear cache, reboot application": "Sta toe om cache te wissen en applicatie te herstarten.",
|
||||||
|
"The will interrupt service, it may take a few seconds to restart. Are you sure to continue?": "Dit onderbreekt de service en kan enkele seconden duren. Weet je zeker dat je wilt doorgaan?",
|
||||||
|
"Clear cache": "Cache wissen",
|
||||||
|
"Are you sure you want to clear cache ?": "Weet je zeker dat je de cache wilt wissen?",
|
||||||
|
"Quick create": "Snel aanmaken",
|
||||||
|
"Dropdown": "Keuzelijst",
|
||||||
|
"Pop-up": "Pop-up",
|
||||||
|
"Direct duplicate": "Direct dupliceren",
|
||||||
|
"Copy into the form and continue to fill in": "Kopieer in het formulier en vul verder in.",
|
||||||
|
"Failed to load plugin": "Kan plug-in niet laden.",
|
||||||
|
"Date range limit": "Datumbereiklimiet",
|
||||||
|
"MinDate": "Minimale datum",
|
||||||
|
"MaxDate": "Maximale datum",
|
||||||
|
"Please select time or variable": "Selecteer een tijd of variabele.",
|
||||||
|
"Filter out a single piece or a group of records as a template": "Filter een enkel item of een groep records als sjabloon.",
|
||||||
|
"The title field is used to identify the template record": "Het titelveld wordt gebruikt om het sjabloonrecord te identificeren.",
|
||||||
|
"Template fields": "Sjabloonvelden",
|
||||||
|
"The selected fields will automatically populate the form": "De geselecteerde velden vullen automatisch het formulier in.",
|
||||||
|
"UnSelect all": "Alles deselecteren",
|
||||||
|
"Secondary confirmation": "Secundaire bevestiging",
|
||||||
|
"Perform the {{title}}": "Voer {{title}} uit.",
|
||||||
|
"Are you sure you want to perform the {{title}} action?": "Weet je zeker dat je de actie {{title}} wilt uitvoeren?",
|
||||||
|
"Permission denied": "Toestemming geweigerd.",
|
||||||
|
"Allow add new": "Sta toe om nieuw toe te voegen.",
|
||||||
|
"Data model": "Gegevensmodel",
|
||||||
|
"Security": "Beveiliging",
|
||||||
|
"Action": "Actie",
|
||||||
|
"System": "Systeem",
|
||||||
|
"Other": "Overige",
|
||||||
|
"Allow selection of existing records": "Sta selectie van bestaande records toe.",
|
||||||
|
"Data Model": "Gegevensmodel",
|
||||||
|
"Blocks": "Blokken",
|
||||||
|
"Users & permissions": "Gebruikers en machtigingen",
|
||||||
|
"System management": "Systeembeheer",
|
||||||
|
"System & security": "Systeem en beveiliging",
|
||||||
|
"Workflow": "Werkstroom",
|
||||||
|
"Third party services": "Diensten van derden",
|
||||||
|
"Data model tools": "Gegevensmodeltools",
|
||||||
|
"Data sources": "Gegevensbronnen",
|
||||||
|
"Collections": "Collecties",
|
||||||
|
"Collection fields": "Collectievelden",
|
||||||
|
"Authentication": "Authenticatie",
|
||||||
|
"Logging and monitoring": "Logging en monitoring",
|
||||||
|
"Main": "Hoofd",
|
||||||
|
"Index": "Index",
|
||||||
|
"Field values must be unique.": "Veldwaarden moeten uniek zijn.",
|
||||||
|
"Alphabet": "Alfabet",
|
||||||
|
"Accuracy": "Nauwkeurigheid",
|
||||||
|
"Millisecond": "Milliseconde",
|
||||||
|
"Second": "Seconde",
|
||||||
|
"Unix Timestamp": "Unix-tijdstempel",
|
||||||
|
"Field value do not meet the requirements": "Veldwaarde voldoet niet aan de vereisten.",
|
||||||
|
"Field value size is": "Veldwaardegrootte is",
|
||||||
|
"Unit conversion": "Eenheidsconversie",
|
||||||
|
"Separator": "Scheidingsteken",
|
||||||
|
"Prefix": "Voorvoegsel",
|
||||||
|
"Suffix": "Achtervoegsel",
|
||||||
|
"Record unique key": "Unieke recordsleutel",
|
||||||
|
"Filter target key": "Filterdoelsleutel",
|
||||||
|
"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.": "Als een collectie geen primaire sleutel heeft, moet je een unieke recordsleutel configureren om rijen binnen een blok te lokaliseren. Anders kunnen er geen gegevensblokken worden aangemaakt.",
|
||||||
|
"Filter data based on the specific field, with the requirement that the field value must be unique.": "Filter gegevens op basis van een specifiek veld, waarbij de veldwaarde uniek moet zijn.",
|
||||||
|
"Multiply by": "Vermenigvuldigen met",
|
||||||
|
"Divide by": "Delen door",
|
||||||
|
"Scientifix notation": "Wetenschappelijke notatie",
|
||||||
|
"Normal": "Normaal",
|
||||||
|
"Automatically generate default values": "Automatisch standaardwaarden genereren.",
|
||||||
|
"Refresh data on close": "Ververs gegevens bij sluiten.",
|
||||||
|
"Refresh data on action": "Ververs gegevens bij actie.",
|
||||||
|
"Unknown field type": "Onbekend veldtype.",
|
||||||
|
"The following field types are not compatible and do not support output and display": "De volgende veldtypen zijn niet compatibel en ondersteunen geen uitvoer en weergave.",
|
||||||
|
"Not fixed": "Niet vastgezet",
|
||||||
|
"Left fixed": "Links vastgezet",
|
||||||
|
"Right fixed": "Rechts vastgezet",
|
||||||
|
"Fixed": "Vastgezet",
|
||||||
|
"Set block height": "Blokhoogte instellen",
|
||||||
|
"Specify height": "Hoogte specificeren",
|
||||||
|
"Full height": "Volledige hoogte",
|
||||||
|
"Please configure the URL": "Configureer de URL.",
|
||||||
|
"URL": "URL",
|
||||||
|
"Search parameters": "Zoekparameters",
|
||||||
|
"Do not concatenate search params in the URL": "Voeg zoekparameters niet samen in de URL.",
|
||||||
|
"Edit link": "Link bewerken",
|
||||||
|
"Add parameter": "Parameter toevoegen",
|
||||||
|
"Use simple pagination mode": "Gebruik eenvoudige paginering",
|
||||||
|
"Set Template Engine": "Sjabloonengine instellen",
|
||||||
|
"Template engine": "Sjabloonengine",
|
||||||
|
"Table size": "Tabelgrootte",
|
||||||
|
"No data": "Geen data",
|
||||||
|
"Show file name": "Toon bestandsnaam",
|
||||||
|
"Filled": "Gevuld",
|
||||||
|
"Enable index column": "Indexkolom inschakelen",
|
||||||
|
"Icon only": "Enkel icoon",
|
||||||
|
"Valid range: 100-900": "Geldige waarde: 100-900",
|
||||||
|
"Valid range: 10-40": "Geldige waarde: 10-40",
|
||||||
|
"Font Size(px)": "Lettergrootte(px)",
|
||||||
|
"Font Weight": "Letterdikte",
|
||||||
|
"Font Style": "Letterstijl",
|
||||||
|
"Italic": "Cursief",
|
||||||
|
"Refresh data blocks": "Vernieuw gegevensblokken",
|
||||||
|
"Select data blocks to refresh": "Selecteer gegevensblokken om te vernieuwen",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Na succesvolle indiening worden de geselecteerde gegevensblokken automatisch vernieuwd."
|
||||||
}
|
}
|
@ -112,6 +112,7 @@
|
|||||||
"Chart type": "Tipo de gráfico",
|
"Chart type": "Tipo de gráfico",
|
||||||
"Chart config": "Configuração do gráfico",
|
"Chart config": "Configuração do gráfico",
|
||||||
"Templates": "Modelos",
|
"Templates": "Modelos",
|
||||||
|
"Template": "Modelo",
|
||||||
"Select template": "Selecione um modelo",
|
"Select template": "Selecione um modelo",
|
||||||
"Action logs": "Registros de ação",
|
"Action logs": "Registros de ação",
|
||||||
"Create template": "Criar modelo",
|
"Create template": "Criar modelo",
|
||||||
@ -778,5 +779,11 @@
|
|||||||
"If selected, the page will display Tab pages.": "Se selecionado, a página exibirá páginas de abas.",
|
"If selected, the page will display Tab pages.": "Se selecionado, a página exibirá páginas de abas.",
|
||||||
"If selected, the route will be displayed in the menu.": "Se selecionado, a rota será exibida no menu.",
|
"If selected, the route will be displayed in the menu.": "Se selecionado, a rota será exibida no menu.",
|
||||||
"Are you sure you want to hide this tab?": "Tem certeza de que deseja ocultar esta guia?",
|
"Are you sure you want to hide this tab?": "Tem certeza de que deseja ocultar esta guia?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Depois de ocultar, esta guia não aparecerá mais na barra de guias. Para mostrá-la novamente, você precisa ir à página de gerenciamento de rotas para configurá-la."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Depois de ocultar, esta guia não aparecerá mais na barra de guias. Para mostrá-la novamente, você precisa ir à página de gerenciamento de rotas para configurá-la.",
|
||||||
|
"Deprecated": "Descontinuado",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "As seguintes funcionalidades de modelo antigo foram descontinuadas e serão removidas na próxima versão.",
|
||||||
|
"Full permissions": "Todas as permissões",
|
||||||
|
"Refresh data blocks": "Atualizar blocos de dados",
|
||||||
|
"Select data blocks to refresh": "Selecionar blocos de dados para atualizar",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Após a atualização em massa bem sucedida."
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,7 @@
|
|||||||
"Chart type": "Тип диаграммы",
|
"Chart type": "Тип диаграммы",
|
||||||
"Chart config": "Конфиг. диаграммы",
|
"Chart config": "Конфиг. диаграммы",
|
||||||
"Templates": "Шаблоны",
|
"Templates": "Шаблоны",
|
||||||
|
"Template": "Шаблон",
|
||||||
"Select template": "Выбрать шаблон",
|
"Select template": "Выбрать шаблон",
|
||||||
"Action logs": "Журналы действий",
|
"Action logs": "Журналы действий",
|
||||||
"Create template": "Создать шаблон",
|
"Create template": "Создать шаблон",
|
||||||
@ -607,5 +608,11 @@
|
|||||||
"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.": "После скрытия этой вкладки она больше не будет отображаться во вкладке. Чтобы снова отобразить ее, вам нужно будет перейти на страницу управления маршрутами, чтобы установить ее.",
|
||||||
|
"Deprecated": "Устаревший",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "Следующие старые функции шаблонов устарели и будут удалены в следующей версии.",
|
||||||
|
"Full permissions": "Полные права",
|
||||||
|
"Refresh data blocks": "Обновить блоки данных",
|
||||||
|
"Select data blocks to refresh": "Выберите блоки данных для обновления",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "После успешной отправки выбранные блоки данных будут автоматически обновлены."
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,7 @@
|
|||||||
"Chart type": "Grafik türü",
|
"Chart type": "Grafik türü",
|
||||||
"Chart config": "Grafik yapılandırması",
|
"Chart config": "Grafik yapılandırması",
|
||||||
"Templates": "Şablonlar",
|
"Templates": "Şablonlar",
|
||||||
|
"Template": "Şablon",
|
||||||
"Select template": "Şablon seç",
|
"Select template": "Şablon seç",
|
||||||
"Action logs": "Eylem günlükleri",
|
"Action logs": "Eylem günlükleri",
|
||||||
"Create template": "Şablon oluştur",
|
"Create template": "Şablon oluştur",
|
||||||
@ -605,5 +606,11 @@
|
|||||||
"If selected, the page will display Tab pages.": "Seçildiğinde, sayfa Tab sayfalarını görüntüleyecektir.",
|
"If selected, the page will display Tab pages.": "Seçildiğinde, sayfa Tab sayfalarını görüntüleyecektir.",
|
||||||
"If selected, the route will be displayed in the menu.": "Seçildiğinde, yol menüde görüntülenecektir.",
|
"If selected, the route will be displayed in the menu.": "Seçildiğinde, yol menüde görüntülenecektir.",
|
||||||
"Are you sure you want to hide this tab?": "Bu sekmeyi gizlemek istediğinizden emin misiniz?",
|
"Are you sure you want to hide this tab?": "Bu sekmeyi gizlemek istediğinizden emin misiniz?",
|
||||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Gizlendikten sonra, bu sekme artık sekme çubuğunda görünmeyecek. Onu tekrar göstermek için, rotayı yönetim sayfasına gidip ayarlamanız gerekiyor."
|
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Gizlendikten sonra, bu sekme artık sekme çubuğunda görünmeyecek. Onu tekrar göstermek için, rotayı yönetim sayfasına gidip ayarlamanız gerekiyor.",
|
||||||
|
"Deprecated": "Kullanımdan kaldırıldı",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "Aşağıdaki eski şablon özellikleri kullanımdan kaldırıldı ve gelecek sürümlerde kaldırılacaktır.",
|
||||||
|
"Full permissions": "Tüm izinler",
|
||||||
|
"Refresh data blocks": "Yenile veri blokları",
|
||||||
|
"Select data blocks to refresh": "Veri bloklarını yenilemek için seçin",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Başarılı bir şekilde gönderildikten sonra, seçilen veri blokları otomatik olarak yenilenecektir."
|
||||||
}
|
}
|
||||||
|
@ -159,6 +159,7 @@
|
|||||||
"Chart type": "Тип діаграми",
|
"Chart type": "Тип діаграми",
|
||||||
"Chart config": "Налаштування діаграми",
|
"Chart config": "Налаштування діаграми",
|
||||||
"Templates": "Шаблони",
|
"Templates": "Шаблони",
|
||||||
|
"Template": "Шаблон",
|
||||||
"Select template": "Вибрати шаблон",
|
"Select template": "Вибрати шаблон",
|
||||||
"Action logs": "Журнал дій",
|
"Action logs": "Журнал дій",
|
||||||
"Create template": "Створити шаблон",
|
"Create template": "Створити шаблон",
|
||||||
@ -821,5 +822,11 @@
|
|||||||
"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.": "Після приховування цієї вкладки вона більше не з'явиться в панелі вкладок. Щоб знову показати її, вам потрібно перейти на сторінку керування маршрутами, щоб налаштувати її.",
|
||||||
|
"Deprecated": "Застаріло",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "Наступні старі функції шаблонів були застарілі і будуть видалені в наступній версії.",
|
||||||
|
"Full permissions": "Повні права",
|
||||||
|
"Refresh data blocks": "Оновити дані блоків",
|
||||||
|
"Select data blocks to refresh": "Виберіть блоки даних для оновлення",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Після успішної подачі вибрані блоки даних будуть автоматично оновлені."
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,8 @@
|
|||||||
"Year": "年",
|
"Year": "年",
|
||||||
"QuarterYear": "季度",
|
"QuarterYear": "季度",
|
||||||
"Select grouping field": "选择分组字段",
|
"Select grouping field": "选择分组字段",
|
||||||
|
"Refresh data blocks": "刷新数据区块",
|
||||||
|
"Select data blocks to refresh": "选择要刷新的数据区块",
|
||||||
"Media": "多媒体",
|
"Media": "多媒体",
|
||||||
"Markdown": "Markdown",
|
"Markdown": "Markdown",
|
||||||
"Wysiwyg": "富文本",
|
"Wysiwyg": "富文本",
|
||||||
@ -183,6 +185,7 @@
|
|||||||
"Chart type": "图表类型",
|
"Chart type": "图表类型",
|
||||||
"Chart config": "图表配置",
|
"Chart config": "图表配置",
|
||||||
"Templates": "模板",
|
"Templates": "模板",
|
||||||
|
"Template": "模板",
|
||||||
"Select template": "选择模板",
|
"Select template": "选择模板",
|
||||||
"Action logs": "操作日志",
|
"Action logs": "操作日志",
|
||||||
"Create template": "创建模板",
|
"Create template": "创建模板",
|
||||||
@ -258,6 +261,7 @@
|
|||||||
"Parent collection fields": "父表字段",
|
"Parent collection fields": "父表字段",
|
||||||
"Basic": "基本类型",
|
"Basic": "基本类型",
|
||||||
"Single line text": "单行文本",
|
"Single line text": "单行文本",
|
||||||
|
"Automatically remove heading and tailing spaces": "自动去除首尾空白字符",
|
||||||
"Long text": "多行文本",
|
"Long text": "多行文本",
|
||||||
"Phone": "手机号码",
|
"Phone": "手机号码",
|
||||||
"Email": "电子邮箱",
|
"Email": "电子邮箱",
|
||||||
@ -837,6 +841,7 @@
|
|||||||
"The will interrupt service, it may take a few seconds to restart. Are you sure to continue?": "重启将会中断当前服务,这个过程可能需要一点时间,确定要继续吗?",
|
"The will interrupt service, it may take a few seconds to restart. Are you sure to continue?": "重启将会中断当前服务,这个过程可能需要一点时间,确定要继续吗?",
|
||||||
"Restart": "重启",
|
"Restart": "重启",
|
||||||
"Clear cache": "清除缓存",
|
"Clear cache": "清除缓存",
|
||||||
|
"Are you sure you want to clear cache ?": "你确定你想清除缓存吗",
|
||||||
"Duplicate": "复制",
|
"Duplicate": "复制",
|
||||||
"Duplicating": "复制中",
|
"Duplicating": "复制中",
|
||||||
"Duplicate mode": "复制方式",
|
"Duplicate mode": "复制方式",
|
||||||
@ -1080,5 +1085,20 @@
|
|||||||
"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.": "隐藏后,该标签将不再显示在标签栏中。要想再次显示它,你需要到路由管理页面进行设置。",
|
||||||
|
"Deprecated": "已弃用",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "以下旧的模板功能已弃用,将在下个版本移除。",
|
||||||
|
"Full permissions": "全部权限",
|
||||||
|
"Enable index column": "启用序号列",
|
||||||
|
"Date scope": "日期范围",
|
||||||
|
"Icon only": "仅显示图标",
|
||||||
|
"Valid range: 100-900": "有效范围:100-900",
|
||||||
|
"Valid range: 10-40": "有效范围:10-40",
|
||||||
|
"Font Size(px)": "字体大小(像素)",
|
||||||
|
"Font Weight": "字体粗细",
|
||||||
|
"Font Style": "字体样式",
|
||||||
|
"Italic": "斜体",
|
||||||
|
"Response record":"响应结果记录",
|
||||||
|
"Colon":"冒号",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "提交成功后,会自动刷新这里选中的数据区块。"
|
||||||
}
|
}
|
||||||
|
@ -183,6 +183,7 @@
|
|||||||
"Chart type": "圖表型別",
|
"Chart type": "圖表型別",
|
||||||
"Chart config": "圖表設定",
|
"Chart config": "圖表設定",
|
||||||
"Templates": "模板",
|
"Templates": "模板",
|
||||||
|
"Template": "模板",
|
||||||
"Select template": "選擇模板",
|
"Select template": "選擇模板",
|
||||||
"Action logs": "動作日誌",
|
"Action logs": "動作日誌",
|
||||||
"Create template": "建立模板",
|
"Create template": "建立模板",
|
||||||
@ -912,6 +913,11 @@
|
|||||||
"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.": "隱藏後,這個標籤將不再出現在標籤欄中。要再次顯示它,你需要到路由管理頁面進行設置。",
|
||||||
|
"Deprecated": "已棄用",
|
||||||
|
"The following old template features have been deprecated and will be removed in next version.": "以下舊的模板功能已棄用,將在下個版本移除。",
|
||||||
|
"Full permissions": "完全權限",
|
||||||
|
"Refresh data blocks": "刷新數據區塊",
|
||||||
|
"Select data blocks to refresh": "選擇要刷新的數據區塊",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "提交成功後,選中的數據區塊將自動刷新。"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ test.describe('bulk-destroy', () => {
|
|||||||
// 3. 点击批量删除按钮,Table 显示无数据
|
// 3. 点击批量删除按钮,Table 显示无数据
|
||||||
await page.getByLabel('action-Action-Delete-destroy-').click();
|
await page.getByLabel('action-Action-Delete-destroy-').click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
await expect(page.getByLabel('block-item-CardItem-general-').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-general-').getByText('No data').last()).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Secondary confirmation', async ({ page, mockPage, mockRecords }) => {
|
test('Secondary confirmation', async ({ page, mockPage, mockRecords }) => {
|
||||||
@ -45,6 +45,7 @@ test.describe('bulk-destroy', () => {
|
|||||||
await page.getByLabel('designer-schema-settings-Action-actionSettings:bulkDelete-general').hover();
|
await page.getByLabel('designer-schema-settings-Action-actionSettings:bulkDelete-general').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Secondary confirmation' }).click();
|
await page.getByRole('menuitem', { name: 'Secondary confirmation' }).click();
|
||||||
await page.getByLabel('Enable secondary confirmation').uncheck();
|
await page.getByLabel('Enable secondary confirmation').uncheck();
|
||||||
|
await expect(page.getByRole('button', { name: 'OK' })).toHaveCount(1);
|
||||||
await page.getByRole('button', { name: 'OK' }).click();
|
await page.getByRole('button', { name: 'OK' }).click();
|
||||||
await page.mouse.move(500, 0);
|
await page.mouse.move(500, 0);
|
||||||
|
|
||||||
@ -53,6 +54,6 @@ test.describe('bulk-destroy', () => {
|
|||||||
|
|
||||||
// 3. 点击批量删除按钮,Table 显示无数据
|
// 3. 点击批量删除按钮,Table 显示无数据
|
||||||
await page.getByLabel('action-Action-Delete-destroy-').click();
|
await page.getByLabel('action-Action-Delete-destroy-').click();
|
||||||
await expect(page.getByLabel('block-item-CardItem-general-').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-general-').getByText('No data').last()).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -28,6 +28,9 @@ test.describe('Link', () => {
|
|||||||
|
|
||||||
// 2. config the Link button
|
// 2. config the Link button
|
||||||
await page.getByLabel('action-Action.Link-Link-customize:link-users-table-0').hover();
|
await page.getByLabel('action-Action.Link-Link-customize:link-users-table-0').hover();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:link-users' }),
|
||||||
|
).toHaveCount(1);
|
||||||
await page.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:link-users' }).hover();
|
await page.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:link-users' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Edit link' }).click();
|
await page.getByRole('menuitem', { name: 'Edit link' }).click();
|
||||||
await page
|
await page
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
} from './templates';
|
} from './templates';
|
||||||
|
|
||||||
test.describe('Submit: should refresh data after submit', () => {
|
test.describe('Submit: should refresh data after submit', () => {
|
||||||
test('submit in reference template block', async ({ page, mockPage, clearBlockTemplates, mockRecord }) => {
|
test.skip('submit in reference template block', async ({ page, mockPage, clearBlockTemplates, mockRecord }) => {
|
||||||
const nocoPage = await mockPage(submitInReferenceTemplateBlock).waitForInit();
|
const nocoPage = await mockPage(submitInReferenceTemplateBlock).waitForInit();
|
||||||
await mockRecord('collection', { nickname: 'abc' });
|
await mockRecord('collection', { nickname: 'abc' });
|
||||||
await nocoPage.goto();
|
await nocoPage.goto();
|
||||||
|
@ -29,6 +29,7 @@ test('basic', async ({ page, mockPage, mockRecord }) => {
|
|||||||
await expect(page.getByRole('tooltip').getByText('Disassociate')).toBeVisible();
|
await expect(page.getByRole('tooltip').getByText('Disassociate')).toBeVisible();
|
||||||
|
|
||||||
await page.getByLabel('block-item-CardItem-cc-table').hover();
|
await page.getByLabel('block-item-CardItem-cc-table').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Associate' }).waitFor({ state: 'detached' });
|
||||||
await page.getByLabel('schema-initializer-ActionBar-table:configureActions-cc').hover();
|
await page.getByLabel('schema-initializer-ActionBar-table:configureActions-cc').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associate' }).click();
|
await page.getByRole('menuitem', { name: 'Associate' }).click();
|
||||||
//点击 associate 出现弹窗
|
//点击 associate 出现弹窗
|
||||||
|
@ -18,7 +18,7 @@ test('basic', async ({ page, mockPage, mockRecord }) => {
|
|||||||
await page.getByLabel('action-Action.Link-Edit record-update-collection1-table-0').click();
|
await page.getByLabel('action-Action.Link-Edit record-update-collection1-table-0').click();
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'manyToMany' }).click();
|
await page.getByRole('menuitem', { name: 'manyToMany' }).click();
|
||||||
|
|
||||||
// 2. Table 中显示 Role UID 字段
|
// 2. Table 中显示 Role UID 字段
|
||||||
|
@ -15,8 +15,8 @@ import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
|
|||||||
import { useRecord } from '../../../record-provider';
|
import { useRecord } from '../../../record-provider';
|
||||||
import { Variable } from '../../../schema-component/antd/variable/Variable';
|
import { Variable } from '../../../schema-component/antd/variable/Variable';
|
||||||
import { useVariableOptions } from '../../../schema-settings/VariableInput/hooks/useVariableOptions';
|
import { useVariableOptions } from '../../../schema-settings/VariableInput/hooks/useVariableOptions';
|
||||||
|
import { useGlobalVariable } from '../../../application/hooks/useGlobalVariable';
|
||||||
export const getVariableComponentWithScope = (Com) => {
|
export const getVariableComponentWithScope = (Com, data = []) => {
|
||||||
return (props) => {
|
return (props) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { form } = useFormBlockContext();
|
const { form } = useFormBlockContext();
|
||||||
@ -28,13 +28,26 @@ export const getVariableComponentWithScope = (Com) => {
|
|||||||
uiSchema: fieldSchema,
|
uiSchema: fieldSchema,
|
||||||
noDisabled: true,
|
noDisabled: true,
|
||||||
});
|
});
|
||||||
return <Com {...props} scope={scope} />;
|
return <Com {...props} scope={data.concat(scope).filter(Boolean)} />;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useEvnVariable = () => {
|
||||||
|
const environmentVariables = useGlobalVariable('$env');
|
||||||
|
if (environmentVariables) {
|
||||||
|
const { children } = environmentVariables;
|
||||||
|
return {
|
||||||
|
...environmentVariables,
|
||||||
|
children: children.filter((v) => v.type === 'default'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const useURLAndHTMLSchema = () => {
|
export const useURLAndHTMLSchema = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const Com = useMemo(() => getVariableComponentWithScope(Variable.TextArea), []);
|
const environmentVariables = useEvnVariable();
|
||||||
|
const Com = useMemo(() => getVariableComponentWithScope(Variable.TextArea, [environmentVariables] || []), []);
|
||||||
|
|
||||||
const urlSchema = useMemo(() => {
|
const urlSchema = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
|
@ -14,6 +14,7 @@ import { useCollection } from '../../data-source/collection/CollectionProvider';
|
|||||||
import { useCompile } from '../../schema-component';
|
import { useCompile } from '../../schema-component';
|
||||||
import { SchemaToolbar } from '../../schema-settings/GeneralSchemaDesigner';
|
import { SchemaToolbar } from '../../schema-settings/GeneralSchemaDesigner';
|
||||||
import { useSchemaTemplate } from '../../schema-templates';
|
import { useSchemaTemplate } from '../../schema-templates';
|
||||||
|
import { useMobileLayout } from '../../route-switch/antd/admin-layout';
|
||||||
|
|
||||||
export const BlockSchemaToolbar = (props) => {
|
export const BlockSchemaToolbar = (props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -22,6 +23,7 @@ export const BlockSchemaToolbar = (props) => {
|
|||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const { association, collection } = useDataBlockProps() || {};
|
const { association, collection } = useDataBlockProps() || {};
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
|
const { isMobileLayout } = useMobileLayout();
|
||||||
|
|
||||||
if (association) {
|
if (association) {
|
||||||
const [collectionName] = association.split('.');
|
const [collectionName] = association.split('.');
|
||||||
@ -51,7 +53,7 @@ export const BlockSchemaToolbar = (props) => {
|
|||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
}, [currentCollectionTitle, currentCollectionName, associationField, associationCollection, compile, templateName]);
|
}, [currentCollectionTitle, currentCollectionName, associationField, associationCollection, compile, templateName]);
|
||||||
|
|
||||||
return <SchemaToolbar title={toolbarTitle} {...props} />;
|
return <SchemaToolbar title={toolbarTitle} {...props} draggable={!isMobileLayout} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getCollectionTitle(arg: {
|
export function getCollectionTitle(arg: {
|
||||||
|
@ -12,6 +12,7 @@ import { oneEmptyTableWithUsers } from './templatesOfBug';
|
|||||||
|
|
||||||
const deleteButton = async (page: Page, name: string) => {
|
const deleteButton = async (page: Page, name: string) => {
|
||||||
await page.getByRole('button', { name }).hover();
|
await page.getByRole('button', { name }).hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Delete' }).waitFor({ state: 'detached' });
|
||||||
await page.getByRole('button', { name }).getByLabel('designer-schema-settings-').hover();
|
await page.getByRole('button', { name }).getByLabel('designer-schema-settings-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
@ -31,6 +32,7 @@ test.describe('where multi data details block can be added', () => {
|
|||||||
// 1. 打开弹窗,通过 Associated records 添加一个详情区块
|
// 1. 打开弹窗,通过 Associated records 添加一个详情区块
|
||||||
await page.getByLabel('action-Action.Link-View').click();
|
await page.getByLabel('action-Action.Link-View').click();
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'Associated records right' }).waitFor({ state: 'detached' });
|
||||||
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records right' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Roles' }).click();
|
await page.getByRole('menuitem', { name: 'Roles' }).click();
|
||||||
@ -41,6 +43,7 @@ test.describe('where multi data details block can be added', () => {
|
|||||||
await expect(page.getByLabel('block-item-CollectionField-').getByText('admin')).toBeVisible();
|
await expect(page.getByLabel('block-item-CollectionField-').getByText('admin')).toBeVisible();
|
||||||
|
|
||||||
// 2. 打开弹窗,通过 Other records 添加一个详情区块
|
// 2. 打开弹窗,通过 Other records 添加一个详情区块
|
||||||
|
await page.getByRole('menuitem', { name: 'Details right' }).waitFor({ state: 'detached' });
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
|
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
|
||||||
@ -116,6 +119,7 @@ test.describe('configure actions', () => {
|
|||||||
await page.getByText('Delete').click();
|
await page.getByText('Delete').click();
|
||||||
|
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
|
await expect(page.getByRole('button', { name: 'Edit' })).toHaveCount(1);
|
||||||
await expect(page.getByRole('button', { name: 'Edit' })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Edit' })).toBeVisible();
|
||||||
await expect(page.getByRole('button', { name: 'Delete' })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Delete' })).toBeVisible();
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ test.describe('multi data details block schema settings', () => {
|
|||||||
'Linkage rules',
|
'Linkage rules',
|
||||||
'Set the data scope',
|
'Set the data scope',
|
||||||
'Set default sorting rules',
|
'Set default sorting rules',
|
||||||
'Save as template',
|
// 'Save as template',
|
||||||
'Delete',
|
'Delete',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -76,6 +76,7 @@ test.describe('actions schema settings', () => {
|
|||||||
await expectSettingsMenu({
|
await expectSettingsMenu({
|
||||||
page,
|
page,
|
||||||
showMenu: async () => {
|
showMenu: async () => {
|
||||||
|
await expect(page.getByRole('button', { name: 'Edit' })).toHaveCount(1);
|
||||||
await page.getByRole('button', { name: 'Edit' }).hover();
|
await page.getByRole('button', { name: 'Edit' }).hover();
|
||||||
await page.getByRole('button', { name: 'designer-schema-settings-Action' }).hover();
|
await page.getByRole('button', { name: 'designer-schema-settings-Action' }).hover();
|
||||||
},
|
},
|
||||||
|
@ -50,10 +50,10 @@ test.describe('setDataLoadingModeSettingsItem', () => {
|
|||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
|
|
||||||
// 所有区块应该显示 No data
|
// 所有区块应该显示 No data
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data').last()).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-details').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-details').getByText('No data').last()).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-list').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-list').getByText('No data').last()).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-BlockItem-users-').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-BlockItem-users-').getByText('No data').last()).toBeVisible();
|
||||||
|
|
||||||
// 3. 在筛选表单中数据一个筛选条件,点击筛选按钮,区块内应该显示数据
|
// 3. 在筛选表单中数据一个筛选条件,点击筛选按钮,区块内应该显示数据
|
||||||
await page.getByLabel('block-item-CollectionField-').getByRole('textbox').click();
|
await page.getByLabel('block-item-CollectionField-').getByRole('textbox').click();
|
||||||
@ -67,10 +67,10 @@ test.describe('setDataLoadingModeSettingsItem', () => {
|
|||||||
|
|
||||||
// 4. 点击筛选表单的 Reset 按钮,区块内应该显示 No data
|
// 4. 点击筛选表单的 Reset 按钮,区块内应该显示 No data
|
||||||
await page.getByLabel('action-Action-Reset to empty-users-').click();
|
await page.getByLabel('action-Action-Reset to empty-users-').click();
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data').last()).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-details').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-details').getByText('No data').last()).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-list').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-list').getByText('No data').last()).toBeVisible();
|
||||||
await expect(page.getByLabel('block-item-BlockItem-users-').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-BlockItem-users-').getByText('No data').last()).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('When the data block has data scope settings and dataLoadingMode is manual, data should not be displayed after the first page load', async ({
|
test('When the data block has data scope settings and dataLoadingMode is manual, data should not be displayed after the first page load', async ({
|
||||||
@ -78,7 +78,7 @@ test.describe('setDataLoadingModeSettingsItem', () => {
|
|||||||
mockPage,
|
mockPage,
|
||||||
}) => {
|
}) => {
|
||||||
await mockPage(TableBlockWithDataScope).goto();
|
await mockPage(TableBlockWithDataScope).goto();
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data').last()).toBeVisible();
|
||||||
|
|
||||||
// 此时点击 filter 按钮,应该还是没数据,因为表单没有值
|
// 此时点击 filter 按钮,应该还是没数据,因为表单没有值
|
||||||
await page.getByLabel('action-Action-Filter-submit-').click({
|
await page.getByLabel('action-Action-Filter-submit-').click({
|
||||||
@ -87,7 +87,7 @@ test.describe('setDataLoadingModeSettingsItem', () => {
|
|||||||
y: 10,
|
y: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data').last()).toBeVisible();
|
||||||
|
|
||||||
// 点击 Reset 按钮,也是一样
|
// 点击 Reset 按钮,也是一样
|
||||||
await page.getByLabel('action-Action-Reset-users-').click({
|
await page.getByLabel('action-Action-Reset-users-').click({
|
||||||
@ -96,6 +96,6 @@ test.describe('setDataLoadingModeSettingsItem', () => {
|
|||||||
y: 10,
|
y: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table').getByText('No data').last()).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,7 @@ test.describe('where single data details block can be added', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// https://nocobase.height.app/T-3848/description
|
// https://nocobase.height.app/T-3848/description
|
||||||
test('popup opened by clicking on the button for the relationship field', async ({
|
test.skip('popup opened by clicking on the button for the relationship field', async ({
|
||||||
page,
|
page,
|
||||||
mockPage,
|
mockPage,
|
||||||
mockRecord,
|
mockRecord,
|
||||||
@ -69,7 +69,7 @@ test.describe('where single data details block can be added', () => {
|
|||||||
// 3.通过 Associated records 创建一个详情区块
|
// 3.通过 Associated records 创建一个详情区块
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'manyToOne' }).hover();
|
await page.getByRole('menuitem', { name: 'manyToOne' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Blank block' }).click();
|
await page.getByRole('menuitem', { name: 'Blank block' }).click();
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
@ -82,7 +82,7 @@ test.describe('where single data details block can be added', () => {
|
|||||||
// 4.通过 Associated records 创建一个详情区块,使用模板
|
// 4.通过 Associated records 创建一个详情区块,使用模板
|
||||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'manyToOne' }).hover();
|
await page.getByRole('menuitem', { name: 'manyToOne' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Duplicate template' }).hover();
|
await page.getByRole('menuitem', { name: 'Duplicate template' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'example_Details (Fields only)' }).click();
|
await page.getByRole('menuitem', { name: 'example_Details (Fields only)' }).click();
|
||||||
|
@ -24,7 +24,7 @@ test.describe('single details block schema settings', () => {
|
|||||||
await page.getByLabel('block-item-CardItem-general-form').hover();
|
await page.getByLabel('block-item-CardItem-general-form').hover();
|
||||||
await page.getByLabel('designer-schema-settings-CardItem-FormV2.ReadPrettyDesigner-general').hover();
|
await page.getByLabel('designer-schema-settings-CardItem-FormV2.ReadPrettyDesigner-general').hover();
|
||||||
},
|
},
|
||||||
supportedOptions: ['Edit block title', 'Linkage rules', 'Save as block template', 'Delete'],
|
supportedOptions: ['Edit block title', 'Linkage rules', 'Delete'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -60,10 +60,10 @@ test.describe('configure fields', () => {
|
|||||||
await expect(page.getByRole('menuitem', { name: 'ID', exact: true }).getByRole('switch')).toBeChecked();
|
await expect(page.getByRole('menuitem', { name: 'ID', exact: true }).getByRole('switch')).toBeChecked();
|
||||||
|
|
||||||
// add association fields
|
// add association fields
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
await page.getByRole('menuitem', { name: 'Many to one right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
await page.getByRole('menuitem', { name: 'Many to one right' }).hover();
|
||||||
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).toBeChecked();
|
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).toBeChecked();
|
||||||
|
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
@ -72,13 +72,14 @@ test.describe('configure fields', () => {
|
|||||||
|
|
||||||
// delete fields
|
// delete fields
|
||||||
await page.getByLabel('schema-initializer-Grid-form:configureFields-general').hover();
|
await page.getByLabel('schema-initializer-Grid-form:configureFields-general').hover();
|
||||||
|
await expect(page.getByRole('menuitem', { name: 'ID', exact: true })).toHaveCount(1);
|
||||||
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
|
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
|
||||||
await expect(page.getByRole('menuitem', { name: 'ID', exact: true }).getByRole('switch')).not.toBeChecked();
|
await expect(page.getByRole('menuitem', { name: 'ID', exact: true }).getByRole('switch')).not.toBeChecked();
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
await page.getByRole('menuitem', { name: 'Many to one right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
await page.getByRole('menuitem', { name: 'Many to one right' }).hover();
|
||||||
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).not.toBeChecked();
|
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).not.toBeChecked();
|
||||||
|
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
@ -119,6 +120,7 @@ test.describe('configure actions', () => {
|
|||||||
// add button
|
// add button
|
||||||
await page.getByRole('menuitem', { name: 'Submit' }).click();
|
await page.getByRole('menuitem', { name: 'Submit' }).click();
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
|
await expect(page.getByRole('button', { name: 'Submit' })).toHaveCount(1);
|
||||||
await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
|
||||||
|
|
||||||
// delete button
|
// delete button
|
||||||
|
@ -279,7 +279,7 @@ test.describe('set default value', () => {
|
|||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
|
|
||||||
// 2. 设置的 ‘abcd’ 应该立即显示在 Nickname 字段的输入框中
|
// 2. 设置的 ‘abcd’ 应该立即显示在 Nickname 字段的输入框中
|
||||||
await expect(page.getByLabel('block-item-CollectionField-').getByRole('textbox')).toHaveValue('abcd');
|
await expect(page.getByLabel('block-item-CollectionField-').getByRole('textbox').last()).toHaveValue('abcd');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Current popup record', async ({ page, mockPage }) => {
|
test('Current popup record', async ({ page, mockPage }) => {
|
||||||
|
@ -51,7 +51,8 @@ test.describe('creation form block schema settings', () => {
|
|||||||
await runExpect();
|
await runExpect();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Save as block template & convert reference to duplicate', async ({ page, mockPage }) => {
|
// deprecated
|
||||||
|
test.skip('Save as block template & convert reference to duplicate', async ({ page, mockPage }) => {
|
||||||
await mockPage(oneTableBlockWithActionsAndFormBlocks).goto();
|
await mockPage(oneTableBlockWithActionsAndFormBlocks).goto();
|
||||||
await page.getByRole('button', { name: 'Add new' }).click();
|
await page.getByRole('button', { name: 'Add new' }).click();
|
||||||
|
|
||||||
@ -115,7 +116,7 @@ test.describe('creation form block schema settings', () => {
|
|||||||
await expect(page.getByLabel('block-item-CardItem-general-form')).not.toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-general-form')).not.toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('save as block Template', async ({ page, mockPage }) => {
|
test.skip('save as block Template', async ({ page, mockPage }) => {
|
||||||
await mockPage(oneEmptyForm).goto();
|
await mockPage(oneEmptyForm).goto();
|
||||||
|
|
||||||
// 先保存为模板 ------------------------------------------------------------------------
|
// 先保存为模板 ------------------------------------------------------------------------
|
||||||
@ -247,7 +248,7 @@ test.describe('creation form block schema settings', () => {
|
|||||||
|
|
||||||
// 重新选择一下数据,字段值才会被填充
|
// 重新选择一下数据,字段值才会被填充
|
||||||
// TODO: 保存后,数据应该直接被填充上
|
// TODO: 保存后,数据应该直接被填充上
|
||||||
await page.getByLabel('icon-close-select').click();
|
await page.getByLabel('icon-close-select').last().click();
|
||||||
await page.getByTestId('select-object-single').click();
|
await page.getByTestId('select-object-single').click();
|
||||||
await page.getByRole('option', { name: '2' }).click();
|
await page.getByRole('option', { name: '2' }).click();
|
||||||
|
|
||||||
@ -270,7 +271,7 @@ test.describe('creation form block schema settings', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('save block template & using block template', async ({ page, mockPage, clearBlockTemplates }) => {
|
test.skip('save block template & using block template', async ({ page, mockPage, clearBlockTemplates }) => {
|
||||||
// 确保测试结束后已保存的模板会被清空
|
// 确保测试结束后已保存的模板会被清空
|
||||||
await clearBlockTemplates();
|
await clearBlockTemplates();
|
||||||
const nocoPage = await mockPage({
|
const nocoPage = await mockPage({
|
||||||
|
@ -37,8 +37,8 @@ test.describe('linkage rules', () => {
|
|||||||
await page.getByText('Add condition', { exact: true }).click();
|
await page.getByText('Add condition', { exact: true }).click();
|
||||||
await page.getByTestId('select-filter-field').click();
|
await page.getByTestId('select-filter-field').click();
|
||||||
await page.getByRole('menuitemcheckbox', { name: 'singleLineText' }).click();
|
await page.getByRole('menuitemcheckbox', { name: 'singleLineText' }).click();
|
||||||
await page.getByLabel('Linkage rules').locator('input[type="text"]').click();
|
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByRole('textbox').click();
|
||||||
await page.getByLabel('Linkage rules').locator('input[type="text"]').fill('123');
|
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByRole('textbox').fill('123');
|
||||||
|
|
||||||
// action:禁用 longText 字段
|
// action:禁用 longText 字段
|
||||||
await page.getByText('Add property').click();
|
await page.getByText('Add property').click();
|
||||||
@ -128,9 +128,14 @@ test.describe('linkage rules', () => {
|
|||||||
|
|
||||||
// 增加一条规则:当 number 字段的值等于 123 时
|
// 增加一条规则:当 number 字段的值等于 123 时
|
||||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||||
await page.locator('.ant-collapse-header').nth(1).getByRole('img', { name: 'right' }).click();
|
await page.locator('.ant-collapse-header .ant-collapse-expand-icon').nth(1).click();
|
||||||
|
|
||||||
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add condition', { exact: true }).click();
|
await page
|
||||||
|
.getByLabel('Linkage rules')
|
||||||
|
.getByRole('tabpanel')
|
||||||
|
.getByText('Add condition', { exact: true })
|
||||||
|
.last()
|
||||||
|
.click();
|
||||||
await page.getByRole('button', { name: 'Select field' }).click();
|
await page.getByRole('button', { name: 'Select field' }).click();
|
||||||
await page.getByRole('menuitemcheckbox', { name: 'number' }).click();
|
await page.getByRole('menuitemcheckbox', { name: 'number' }).click();
|
||||||
await page.getByLabel('Linkage rules').getByRole('spinbutton').click();
|
await page.getByLabel('Linkage rules').getByRole('spinbutton').click();
|
||||||
@ -146,19 +151,19 @@ test.describe('linkage rules', () => {
|
|||||||
// action: 为 longText 字段赋上常量值
|
// action: 为 longText 字段赋上常量值
|
||||||
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add property').click();
|
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add property').click();
|
||||||
await page.getByRole('button', { name: 'Select field' }).click();
|
await page.getByRole('button', { name: 'Select field' }).click();
|
||||||
await page.getByRole('tree').getByText('longText').click();
|
await page.getByRole('tree').getByText('longText').last().click();
|
||||||
await page.getByRole('button', { name: 'action', exact: true }).click();
|
await page.getByRole('button', { name: 'action', exact: true }).click();
|
||||||
await page.getByRole('option', { name: 'Value', exact: true }).click();
|
await page.getByRole('option', { name: 'Value', exact: true }).last().click();
|
||||||
await page.getByLabel('dynamic-component-linkage-rules').getByRole('textbox').fill('456');
|
await page.getByLabel('dynamic-component-linkage-rules').getByRole('textbox').fill('456');
|
||||||
|
|
||||||
// action: 为 integer 字段附上一个表达式,使其值等于 number 字段的值
|
// action: 为 integer 字段附上一个表达式,使其值等于 number 字段的值
|
||||||
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add property').click();
|
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add property').click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Select field' }).click();
|
await page.getByRole('button', { name: 'Select field' }).click();
|
||||||
await page.getByRole('tree').getByText('integer').click();
|
await page.getByRole('tree').getByText('integer').last().click();
|
||||||
await page.getByRole('button', { name: 'action', exact: true }).click();
|
await page.getByRole('button', { name: 'action', exact: true }).click();
|
||||||
await page.getByRole('option', { name: 'Value', exact: true }).click();
|
await page.getByRole('option', { name: 'Value', exact: true }).last().click();
|
||||||
await page.getByTestId('select-linkage-value-type').nth(1).click();
|
await page.getByTestId('select-linkage-value-type').last().click();
|
||||||
await page.getByText('Expression').click();
|
await page.getByText('Expression').click();
|
||||||
|
|
||||||
await page.getByText('xSelect a variable').click();
|
await page.getByText('xSelect a variable').click();
|
||||||
@ -236,7 +241,7 @@ test.describe('linkage rules', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// https://nocobase.height.app/T-3806
|
// https://nocobase.height.app/T-3806
|
||||||
test('after save as block template', async ({ page, mockPage }) => {
|
test.skip('after save as block template', async ({ page, mockPage }) => {
|
||||||
await mockPage(T3806).goto();
|
await mockPage(T3806).goto();
|
||||||
|
|
||||||
// 1. 一开始联动规则应该正常
|
// 1. 一开始联动规则应该正常
|
||||||
|
@ -33,6 +33,7 @@ test.describe('bulk edit form', () => {
|
|||||||
await expect(page.getByLabel('block-item-BulkEditField-').getByText('*')).toBeVisible();
|
await expect(page.getByLabel('block-item-BulkEditField-').getByText('*')).toBeVisible();
|
||||||
|
|
||||||
// 3. 输入值,点击提交
|
// 3. 输入值,点击提交
|
||||||
|
await expect(page.getByLabel('block-item-BulkEditField-').getByRole('textbox')).toHaveCount(1);
|
||||||
await page.getByLabel('block-item-BulkEditField-').getByRole('textbox').fill('123');
|
await page.getByLabel('block-item-BulkEditField-').getByRole('textbox').fill('123');
|
||||||
await page.getByRole('button', { name: 'Submit' }).click();
|
await page.getByRole('button', { name: 'Submit' }).click();
|
||||||
|
|
||||||
@ -65,6 +66,7 @@ test.describe('bulk edit form', () => {
|
|||||||
await expect(page.getByLabel('block-item-BulkEditField-').getByText('*')).toBeVisible();
|
await expect(page.getByLabel('block-item-BulkEditField-').getByText('*')).toBeVisible();
|
||||||
|
|
||||||
// 4. 点击提交按钮,应该提示一个错误
|
// 4. 点击提交按钮,应该提示一个错误
|
||||||
|
await expect(page.getByRole('button', { name: 'Submit' })).toHaveCount(1);
|
||||||
await page.getByRole('button', { name: 'Submit' }).click();
|
await page.getByRole('button', { name: 'Submit' }).click();
|
||||||
await expect(page.getByLabel('block-item-BulkEditField-').getByText('The field value is required')).toBeVisible();
|
await expect(page.getByLabel('block-item-BulkEditField-').getByText('The field value is required')).toBeVisible();
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user