mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
Merge branch '2.0' into fields-flow-modal
This commit is contained in:
commit
3590b0219f
2
.github/workflows/build-docker.yml
vendored
2
.github/workflows/build-docker.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./docker/nocobase
|
||||
file: ./docker/nocobase/Dockerfile-cn
|
||||
file: ./docker/nocobase/Dockerfile-full
|
||||
build-args: |
|
||||
CNA_VERSION=${{ inputs.tag_name }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -216,7 +216,7 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./docker/nocobase
|
||||
file: ./docker/nocobase/Dockerfile-cn
|
||||
file: ./docker/nocobase/Dockerfile-full
|
||||
build-args: |
|
||||
CNA_VERSION=${{ steps.get-info.outputs.defaultTag }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
116
CHANGELOG.md
116
CHANGELOG.md
@ -5,6 +5,122 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v1.7.15](https://github.com/nocobase/nocobase/compare/v1.7.14...v1.7.15) - 2025-06-18
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- Use independent variable scope for each field ([#7012](https://github.com/nocobase/nocobase/pull/7012)) by @mytharcher
|
||||
|
||||
- Assign field values: Unable to clear data for relation fields ([#7086](https://github.com/nocobase/nocobase/pull/7086)) by @zhangzhonghe
|
||||
|
||||
- Table column text alignment function is not working ([#7094](https://github.com/nocobase/nocobase/pull/7094)) by @zhangzhonghe
|
||||
|
||||
- **[Workflow]** Fix incorrectly executed checking on big integer number ([#7091](https://github.com/nocobase/nocobase/pull/7091)) by @mytharcher
|
||||
|
||||
- **[File manager]** Fix attachments field can not be updated in approval process ([#7093](https://github.com/nocobase/nocobase/pull/7093)) by @mytharcher
|
||||
|
||||
- **[Workflow: Approval]** Use comparison instead of implicit logic to avoid type issues by @mytharcher
|
||||
|
||||
## [v1.7.14](https://github.com/nocobase/nocobase/compare/v1.7.13...v1.7.14) - 2025-06-17
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[client]** Auto-hide grid card block action bar when empty ([#7069](https://github.com/nocobase/nocobase/pull/7069)) by @zhangzhonghe
|
||||
|
||||
- **[Verification]** Remove verifier options from the response of the `verifiers:listByUser` API ([#7090](https://github.com/nocobase/nocobase/pull/7090)) by @2013xile
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[database]** support association updates in updateOrCreate and firstOrCreate ([#7088](https://github.com/nocobase/nocobase/pull/7088)) by @chenos
|
||||
|
||||
- **[client]**
|
||||
- URL query parameter variables not working in public form field default value ([#7084](https://github.com/nocobase/nocobase/pull/7084)) by @katherinehhh
|
||||
|
||||
- style condition on subtable column fields not applied correctly ([#7083](https://github.com/nocobase/nocobase/pull/7083)) by @katherinehhh
|
||||
|
||||
- Filtering through relationship collection fields in filter forms is invalid ([#7070](https://github.com/nocobase/nocobase/pull/7070)) by @zhangzhonghe
|
||||
|
||||
- **[Collection field: Many to many (array)]** Updating a many to many (array) field throws an error when the `updatedBy` field is present ([#7089](https://github.com/nocobase/nocobase/pull/7089)) by @2013xile
|
||||
|
||||
- **[Public forms]** Public forms: Fix unauthorized access issue on form submission ([#7085](https://github.com/nocobase/nocobase/pull/7085)) by @zhangzhonghe
|
||||
|
||||
## [v1.7.13](https://github.com/nocobase/nocobase/compare/v1.7.12...v1.7.13) - 2025-06-17
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[client]** Logo container width adapts to content type (fixed 168px for images, auto width for text) ([#7075](https://github.com/nocobase/nocobase/pull/7075)) by @Cyx649312038
|
||||
|
||||
- **[Workflow: Approval]** Add extra field option for re-assignees list by @mytharcher
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- required validation message in subtable persists when switching page ([#7080](https://github.com/nocobase/nocobase/pull/7080)) by @katherinehhh
|
||||
|
||||
- decimal point lost after switching amount component from mask to inputNumer ([#7077](https://github.com/nocobase/nocobase/pull/7077)) by @katherinehhh
|
||||
|
||||
- incorrect Markdown (Vditor) rendering in subtable ([#7074](https://github.com/nocobase/nocobase/pull/7074)) by @katherinehhh
|
||||
|
||||
- **[Collection field: Sequence]** Fix string based bigint sequence calculation ([#7079](https://github.com/nocobase/nocobase/pull/7079)) by @mytharcher
|
||||
|
||||
- **[Backup manager]** unknow command error when restoring MySQL backups on windows platform by @gchust
|
||||
|
||||
## [v1.7.12](https://github.com/nocobase/nocobase/compare/v1.7.11...v1.7.12) - 2025-06-16
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[client]** add "empty" and "not empty" options to checkbox field linkage rules ([#7073](https://github.com/nocobase/nocobase/pull/7073)) by @katherinehhh
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]** After creating the reverse relation field, the option "Create reverse relation field in the target data table" in the association field settings was not checked. ([#6914](https://github.com/nocobase/nocobase/pull/6914)) by @aaaaaajie
|
||||
|
||||
- **[Data source manager]** Scope changes now take effect immediately for all related roles. ([#7065](https://github.com/nocobase/nocobase/pull/7065)) by @aaaaaajie
|
||||
|
||||
- **[Access control]** Fixed an issue where the app blocked entry when no default role existed ([#7059](https://github.com/nocobase/nocobase/pull/7059)) by @aaaaaajie
|
||||
|
||||
- **[Workflow: Custom action event]** Fix variable of redirect url not parsed by @mytharcher
|
||||
|
||||
## [v1.7.11](https://github.com/nocobase/nocobase/compare/v1.7.10...v1.7.11) - 2025-06-15
|
||||
|
||||
### 🎉 New Features
|
||||
|
||||
- **[Text copy]** Support one-click copying of text field content ([#6954](https://github.com/nocobase/nocobase/pull/6954)) by @zhangzhonghe
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- association field selector does not clear selected data after submission ([#7067](https://github.com/nocobase/nocobase/pull/7067)) by @katherinehhh
|
||||
|
||||
- Fix upload size hint ([#7057](https://github.com/nocobase/nocobase/pull/7057)) by @mytharcher
|
||||
|
||||
- **[server]** Cannot read properties of undefined (reading 'setMaaintainingMessage') ([#7064](https://github.com/nocobase/nocobase/pull/7064)) by @chenos
|
||||
|
||||
- **[Workflow: Loop node]** Fix loop branch runs when condition not satisfied ([#7063](https://github.com/nocobase/nocobase/pull/7063)) by @mytharcher
|
||||
|
||||
- **[Workflow: Approval]**
|
||||
- Fix todo stats not updated when execution canceled by @mytharcher
|
||||
|
||||
- Fix trigger variable when filter by type by @mytharcher
|
||||
|
||||
## [v1.7.10](https://github.com/nocobase/nocobase/compare/v1.7.9...v1.7.10) - 2025-06-12
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- Fix the issue where linkage rules cause infinite loop ([#7050](https://github.com/nocobase/nocobase/pull/7050)) by @zhangzhonghe
|
||||
|
||||
- Fix: use optional chaining to safely reject requests in APIClient when handler may be undefined ([#7054](https://github.com/nocobase/nocobase/pull/7054)) by @sheldon66
|
||||
|
||||
- auto-closing issue when configuring fields in the secondary popup form ([#7052](https://github.com/nocobase/nocobase/pull/7052)) by @katherinehhh
|
||||
|
||||
- **[Data visualization]** incorrect display of between date field in chart filter ([#7051](https://github.com/nocobase/nocobase/pull/7051)) by @katherinehhh
|
||||
|
||||
- **[API documentation]** non-NocoBase official plugins fail to display API documentation ([#7045](https://github.com/nocobase/nocobase/pull/7045)) by @chenzhizdt
|
||||
|
||||
- **[Action: Import records]** Fixed xlsx import to restrict textarea fields from accepting non-string formatted data ([#7049](https://github.com/nocobase/nocobase/pull/7049)) by @aaaaaajie
|
||||
|
||||
## [v1.7.9](https://github.com/nocobase/nocobase/compare/v1.7.8...v1.7.9) - 2025-06-11
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
@ -5,6 +5,122 @@
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
||||
|
||||
## [v1.7.15](https://github.com/nocobase/nocobase/compare/v1.7.14...v1.7.15) - 2025-06-18
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 对每个字段使用独立的变量范围 ([#7012](https://github.com/nocobase/nocobase/pull/7012)) by @mytharcher
|
||||
|
||||
- 字段赋值:关系字段无法被清空数据 ([#7086](https://github.com/nocobase/nocobase/pull/7086)) by @zhangzhonghe
|
||||
|
||||
- 表格列的文本对齐功能无效 ([#7094](https://github.com/nocobase/nocobase/pull/7094)) by @zhangzhonghe
|
||||
|
||||
- **[工作流]** 修复已执行数在大整型数时检查错误的问题 ([#7091](https://github.com/nocobase/nocobase/pull/7091)) by @mytharcher
|
||||
|
||||
- **[文件管理器]** 修复审批处理中附件字段无法被更新的问题 ([#7093](https://github.com/nocobase/nocobase/pull/7093)) by @mytharcher
|
||||
|
||||
- **[工作流:审批]** 使用比较代替隐式逻辑以避免类型问题 by @mytharcher
|
||||
|
||||
## [v1.7.14](https://github.com/nocobase/nocobase/compare/v1.7.13...v1.7.14) - 2025-06-17
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[client]** 网格卡片区块操作栏为空时自动隐藏 ([#7069](https://github.com/nocobase/nocobase/pull/7069)) by @zhangzhonghe
|
||||
|
||||
- **[验证]** 移除 `verifiers:listByUser` 接口中响应的认证器配置信息 ([#7090](https://github.com/nocobase/nocobase/pull/7090)) by @2013xile
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[database]** 修复 updateOrCreate 和 firstOrCreate 不支持关系更新的问题 ([#7088](https://github.com/nocobase/nocobase/pull/7088)) by @chenos
|
||||
|
||||
- **[client]**
|
||||
- 修复公开表单字段默认值中 URL 查询参数变量无效的问题 ([#7084](https://github.com/nocobase/nocobase/pull/7084)) by @katherinehhh
|
||||
|
||||
- 修复 子表格列字段 style 条件判断无效的问题 ([#7083](https://github.com/nocobase/nocobase/pull/7083)) by @katherinehhh
|
||||
|
||||
- 筛选表单中,通过关系表字段筛选无效 ([#7070](https://github.com/nocobase/nocobase/pull/7070)) by @zhangzhonghe
|
||||
|
||||
- **[数据表字段:多对多 (数组)]** 存在 `updatedBy` 字段的时,更新多对多(数组)字段报错 ([#7089](https://github.com/nocobase/nocobase/pull/7089)) by @2013xile
|
||||
|
||||
- **[公开表单]** 公开表单:修复提交表单时报无权限的问题 ([#7085](https://github.com/nocobase/nocobase/pull/7085)) by @zhangzhonghe
|
||||
|
||||
## [v1.7.13](https://github.com/nocobase/nocobase/compare/v1.7.12...v1.7.13) - 2025-06-17
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[client]** Logo 容器宽度根据内容类型自适应(图片固定 168px,文本自动宽度) ([#7075](https://github.com/nocobase/nocobase/pull/7075)) by @Cyx649312038
|
||||
|
||||
- **[工作流:审批]** 为转签、加签的人员选择列表增加额外字段显示的配置项 by @mytharcher
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 修复子表格字段切换页面后必填提示不消失的问题 ([#7080](https://github.com/nocobase/nocobase/pull/7080)) by @katherinehhh
|
||||
|
||||
- 修复金额字段组件从掩码改为数字后小数点丢失的问题 ([#7077](https://github.com/nocobase/nocobase/pull/7077)) by @katherinehhh
|
||||
|
||||
- 修复子表格中 Markdown(Vditor)字段组件渲染不正确的问题 ([#7074](https://github.com/nocobase/nocobase/pull/7074)) by @katherinehhh
|
||||
|
||||
- **[数据表字段:自动编码]** 修复基于字符串的大整数序列计算 ([#7079](https://github.com/nocobase/nocobase/pull/7079)) by @mytharcher
|
||||
|
||||
- **[备份管理器]** windows 平台下,还原 MySQL 应用时提示无法识别的命令错误 by @gchust
|
||||
|
||||
## [v1.7.12](https://github.com/nocobase/nocobase/compare/v1.7.11...v1.7.12) - 2025-06-16
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[client]** checkbox 字段联动条件判断支持 "为空”和“不为空” ([#7073](https://github.com/nocobase/nocobase/pull/7073)) by @katherinehhh
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]** 创建反向关系字段后,编辑关系字段设置项“在目标数据表里创建反向关系字段”未勾选 ([#6914](https://github.com/nocobase/nocobase/pull/6914)) by @aaaaaajie
|
||||
|
||||
- **[数据源管理]** 修改权限的数据范围后,相关角色同步生效 ([#7065](https://github.com/nocobase/nocobase/pull/7065)) by @aaaaaajie
|
||||
|
||||
- **[权限控制]** 修复了在没有默认角色时无法进入应用的问题 ([#7059](https://github.com/nocobase/nocobase/pull/7059)) by @aaaaaajie
|
||||
|
||||
- **[工作流:自定义操作事件]** 修复操作成功后配置中的重定向链接变量未解析的问题 by @mytharcher
|
||||
|
||||
## [v1.7.11](https://github.com/nocobase/nocobase/compare/v1.7.10...v1.7.11) - 2025-06-15
|
||||
|
||||
### 🎉 新特性
|
||||
|
||||
- **[文本复制]** 支持一键复制文本字段内容 ([#6954](https://github.com/nocobase/nocobase/pull/6954)) by @zhangzhonghe
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 关系字段数据选择器提交后未清空选中数据 ([#7067](https://github.com/nocobase/nocobase/pull/7067)) by @katherinehhh
|
||||
|
||||
- 修复上传组件的大小提示文字 ([#7057](https://github.com/nocobase/nocobase/pull/7057)) by @mytharcher
|
||||
|
||||
- **[server]** Cannot read properties of undefined (reading 'setMaaintainingMessage') ([#7064](https://github.com/nocobase/nocobase/pull/7064)) by @chenos
|
||||
|
||||
- **[工作流:循环节点]** 修复循环分支在条件未满足时仍然执行的问题 ([#7063](https://github.com/nocobase/nocobase/pull/7063)) by @mytharcher
|
||||
|
||||
- **[工作流:审批]**
|
||||
- 修复待办统计在执行计划取消后未更新的问题 by @mytharcher
|
||||
|
||||
- 修复触发器变量中按类型过滤的缺陷 by @mytharcher
|
||||
|
||||
## [v1.7.10](https://github.com/nocobase/nocobase/compare/v1.7.9...v1.7.10) - 2025-06-12
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 修复联动规则卡死的问题 ([#7050](https://github.com/nocobase/nocobase/pull/7050)) by @zhangzhonghe
|
||||
|
||||
- 修复:在 APIClient 中添加可选链以避免 handler 未定义时报错 ([#7054](https://github.com/nocobase/nocobase/pull/7054)) by @sheldon66
|
||||
|
||||
- 修复二级弹窗配置表单字段时自动关闭弹窗的问题 ([#7052](https://github.com/nocobase/nocobase/pull/7052)) by @katherinehhh
|
||||
|
||||
- **[数据可视化]** 修复图表区块中筛选表单的日期字段设置为“介于”时组件未正确显示的问题 ([#7051](https://github.com/nocobase/nocobase/pull/7051)) by @katherinehhh
|
||||
|
||||
- **[API 文档]** 非 NocoBase 官方插件无法展示API文档 ([#7045](https://github.com/nocobase/nocobase/pull/7045)) by @chenzhizdt
|
||||
|
||||
- **[操作:导入记录]** 导入 xlsx 禁止多行文本字段插入非字符串格式数据 ([#7049](https://github.com/nocobase/nocobase/pull/7049)) by @aaaaaajie
|
||||
|
||||
## [v1.7.9](https://github.com/nocobase/nocobase/compare/v1.7.8...v1.7.9) - 2025-06-11
|
||||
|
||||
### 🐛 修复
|
||||
|
@ -1,19 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
nginx
|
||||
echo 'nginx started';
|
||||
|
||||
cd /app/nocobase && yarn nocobase db:auth --retry=30
|
||||
cd /app/nocobase && yarn nocobase install -s
|
||||
cd /app/nocobase && yarn nocobase upgrade -S
|
||||
cd /app/nocobase && yarn start
|
||||
|
||||
# Run command with node if the first argument contains a "-" or is not a system command. The last
|
||||
# part inside the "{}" is a workaround for the following bug in ash/dash:
|
||||
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264
|
||||
if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ] || { [ -f "${1}" ] && ! [ -x "${1}" ]; }; then
|
||||
set -- node "$@"
|
||||
fi
|
||||
|
||||
exec "$@"
|
@ -1,43 +0,0 @@
|
||||
log_format apm '"$time_local" client=$remote_addr '
|
||||
'method=$request_method request="$request" '
|
||||
'request_length=$request_length '
|
||||
'status=$status bytes_sent=$bytes_sent '
|
||||
'body_bytes_sent=$body_bytes_sent '
|
||||
'referer=$http_referer '
|
||||
'user_agent="$http_user_agent" '
|
||||
'upstream_addr=$upstream_addr '
|
||||
'upstream_status=$upstream_status '
|
||||
'request_time=$request_time '
|
||||
'upstream_response_time=$upstream_response_time '
|
||||
'upstream_connect_time=$upstream_connect_time '
|
||||
'upstream_header_time=$upstream_header_time';
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /app/nocobase/packages/app/client/dist;
|
||||
index index.html;
|
||||
client_max_body_size 0;
|
||||
|
||||
access_log /var/log/nginx/nocobase.log apm;
|
||||
|
||||
location /storage/uploads/ {
|
||||
alias /app/nocobase/storage/uploads/;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /app/nocobase/packages/app/client/dist;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location ^~ /api/ {
|
||||
proxy_pass http://127.0.0.1:13000/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"npmClientArgs": ["--ignore-engines"],
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/acl",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/resourcer": "1.8.0-alpha.5",
|
||||
"@nocobase/utils": "1.8.0-alpha.5",
|
||||
"@nocobase/resourcer": "1.8.0-alpha.8",
|
||||
"@nocobase/utils": "1.8.0-alpha.8",
|
||||
"minimatch": "^5.1.1"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@nocobase/actions",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/cache": "1.8.0-alpha.5",
|
||||
"@nocobase/database": "1.8.0-alpha.5",
|
||||
"@nocobase/resourcer": "1.8.0-alpha.5"
|
||||
"@nocobase/cache": "1.8.0-alpha.8",
|
||||
"@nocobase/database": "1.8.0-alpha.8",
|
||||
"@nocobase/resourcer": "1.8.0-alpha.8"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -9,4 +9,7 @@
|
||||
|
||||
import { proxyToRepository } from './proxy-to-repository';
|
||||
|
||||
export const firstOrCreate = proxyToRepository(['values', 'filterKeys'], 'firstOrCreate');
|
||||
export const firstOrCreate = proxyToRepository(
|
||||
['values', 'filterKeys', 'whitelist', 'blacklist', 'updateAssociationValues', 'targetCollection'],
|
||||
'firstOrCreate',
|
||||
);
|
||||
|
@ -9,4 +9,7 @@
|
||||
|
||||
import { proxyToRepository } from './proxy-to-repository';
|
||||
|
||||
export const updateOrCreate = proxyToRepository(['values', 'filterKeys'], 'updateOrCreate');
|
||||
export const updateOrCreate = proxyToRepository(
|
||||
['values', 'filterKeys', 'whitelist', 'blacklist', 'updateAssociationValues', 'targetCollection'],
|
||||
'updateOrCreate',
|
||||
);
|
||||
|
@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "@nocobase/app",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/database": "1.8.0-alpha.5",
|
||||
"@nocobase/preset-nocobase": "1.8.0-alpha.5",
|
||||
"@nocobase/server": "1.8.0-alpha.5"
|
||||
"@nocobase/database": "1.8.0-alpha.8",
|
||||
"@nocobase/preset-nocobase": "1.8.0-alpha.8",
|
||||
"@nocobase/server": "1.8.0-alpha.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/client": "1.8.0-alpha.5"
|
||||
"@nocobase/client": "1.8.0-alpha.8"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@nocobase/auth",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/actions": "1.8.0-alpha.5",
|
||||
"@nocobase/cache": "1.8.0-alpha.5",
|
||||
"@nocobase/database": "1.8.0-alpha.5",
|
||||
"@nocobase/resourcer": "1.8.0-alpha.5",
|
||||
"@nocobase/utils": "1.8.0-alpha.5",
|
||||
"@nocobase/actions": "1.8.0-alpha.8",
|
||||
"@nocobase/cache": "1.8.0-alpha.8",
|
||||
"@nocobase/database": "1.8.0-alpha.8",
|
||||
"@nocobase/resourcer": "1.8.0-alpha.8",
|
||||
"@nocobase/utils": "1.8.0-alpha.8",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/build",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "Library build tool based on rollup.",
|
||||
"main": "lib/index.js",
|
||||
"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",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/lock-manager": "1.8.0-alpha.5",
|
||||
"@nocobase/lock-manager": "1.8.0-alpha.8",
|
||||
"bloom-filters": "^3.0.1",
|
||||
"cache-manager": "^5.2.4",
|
||||
"cache-manager-redis-yet": "^4.1.2"
|
||||
|
15
packages/core/cache/src/__tests__/cache.test.ts
vendored
15
packages/core/cache/src/__tests__/cache.test.ts
vendored
@ -117,4 +117,19 @@ describe('cache', () => {
|
||||
expect(val2).toBe(obj);
|
||||
expect(await cache.get('key')).toMatchObject(obj);
|
||||
});
|
||||
|
||||
it('redis cache wrap null throw error', async () => {
|
||||
if (!process.env.CACHE_REDIS_URL) {
|
||||
return;
|
||||
}
|
||||
const cacheManager = new CacheManager({
|
||||
stores: {
|
||||
redis: {
|
||||
url: process.env.CACHE_REDIS_URL,
|
||||
},
|
||||
},
|
||||
});
|
||||
const c = await cacheManager.createCache({ name: 'test', store: 'redis' });
|
||||
expect(async () => c.wrap('test', async () => null)).rejects.toThrowError('"null" is not a cacheable value');
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/cli",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./src/index.js",
|
||||
@ -8,7 +8,7 @@
|
||||
"nocobase": "./bin/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nocobase/app": "1.8.0-alpha.5",
|
||||
"@nocobase/app": "1.8.0-alpha.8",
|
||||
"@nocobase/license-kit": "^0.2.3",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@umijs/utils": "3.5.20",
|
||||
@ -27,7 +27,7 @@
|
||||
"tsx": "^4.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/devtools": "1.8.0-alpha.5"
|
||||
"@nocobase/devtools": "1.8.0-alpha.8"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -115,7 +115,7 @@ exports.postCheck = async (opts) => {
|
||||
const port = opts.port || process.env.APP_PORT;
|
||||
const result = await exports.isPortReachable(port);
|
||||
if (result) {
|
||||
console.error(chalk.red(`post already in use ${port}`));
|
||||
console.error(chalk.red(`Port ${port} already in use`));
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as icons from '@ant-design/icons';
|
||||
import { Plugin } from '@nocobase/client';
|
||||
import { defineFlow, FlowModel, FlowModelRenderer } from '@nocobase/flow-engine';
|
||||
import { defineAction, defineFlow, FlowModel, FlowModelRenderer } from '@nocobase/flow-engine';
|
||||
import { Button } from 'antd';
|
||||
import React from 'react';
|
||||
import { createApp } from './createApp';
|
||||
@ -40,40 +40,42 @@ const myEventFlow = defineFlow({
|
||||
|
||||
MyModel.registerFlow(myEventFlow);
|
||||
|
||||
const myConfirm = defineAction({
|
||||
name: 'confirm',
|
||||
uiSchema: {
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Confirm title',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
title: 'Confirm content',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
},
|
||||
},
|
||||
defaultParams: {
|
||||
title: 'Confirm Deletion',
|
||||
content: 'Are you sure you want to delete this record?',
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
const confirmed = await ctx.globals.modal.confirm({
|
||||
title: params.title,
|
||||
content: params.content,
|
||||
});
|
||||
if (!confirmed) {
|
||||
ctx.globals.message.info('Action cancelled.');
|
||||
return ctx.exit();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
class PluginDemo extends Plugin {
|
||||
async load() {
|
||||
this.flowEngine.registerModels({ MyModel });
|
||||
this.flowEngine.registerAction({
|
||||
name: 'confirm',
|
||||
uiSchema: {
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Confirm title',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
title: 'Confirm content',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
},
|
||||
},
|
||||
defaultParams: {
|
||||
title: 'Confirm Deletion',
|
||||
content: 'Are you sure you want to delete this record?',
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
const confirmed = await ctx.globals.modal.confirm({
|
||||
title: params.title,
|
||||
content: params.content,
|
||||
});
|
||||
if (!confirmed) {
|
||||
ctx.globals.message.info('Action cancelled.');
|
||||
return ctx.exit();
|
||||
}
|
||||
},
|
||||
});
|
||||
this.flowEngine.registerAction(myConfirm);
|
||||
const model = this.flowEngine.createModel({
|
||||
use: 'MyModel',
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import React from 'react';
|
||||
// 实现一个本地存储的模型仓库,负责模型的持久化
|
||||
class FlowModelRepository implements IFlowModelRepository<FlowModel> {
|
||||
// 从本地存储加载模型数据
|
||||
async load(uid: string) {
|
||||
async findOne({ uid }) {
|
||||
const data = localStorage.getItem(`flow-model:${uid}`);
|
||||
if (!data) return null;
|
||||
return JSON.parse(data);
|
||||
|
@ -1,6 +1,6 @@
|
||||
# FlowAction
|
||||
|
||||
`FlowAction` 是 NocoBase 流引擎中用于定义和注册流步骤可复用操作(Action)的核心对象。每个操作(Action)封装一段可执行的业务逻辑,可以在多个流步骤中复用,支持参数配置、UI 配置和类型推断。
|
||||
`FlowAction` 是 NocoBase 流引擎中用于定义和注册流步骤可复用操作(Action)的核心对象。每个操作封装一段可执行的业务逻辑,可在多个流步骤中复用,并支持参数配置、UI 配置和类型推断。
|
||||
|
||||
---
|
||||
|
||||
@ -10,48 +10,50 @@
|
||||
interface ActionDefinition {
|
||||
name: string; // 操作唯一标识,必须唯一
|
||||
title?: string; // 操作显示名称(可选)
|
||||
uiSchema?: Record<string, ISchema>; // (可选)用于参数配置界面渲染
|
||||
uiSchema?: Record<string, ISchema>; // (可选)参数配置界面渲染
|
||||
defaultParams?: Record<string, any>; // (可选)默认参数
|
||||
paramsRequired?: boolean; // (可选)是否需要参数配置,为true时添加模型前会打开配置对话框
|
||||
hideInSettings?: boolean; // (可选)是否在设置菜单中隐藏该步骤
|
||||
paramsRequired?: boolean; // (可选)是否需要参数配置
|
||||
hideInSettings?: boolean; // (可选)是否在设置菜单中隐藏
|
||||
handler: (ctx: FlowContext, params: any) => Promise<any> | any; // 操作执行逻辑
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 定义操作的方式
|
||||
## 使用说明
|
||||
|
||||
### 1. 使用 defineAction 工具函数
|
||||
### 1. 定义 Action
|
||||
|
||||
推荐方式,结构清晰、类型推断友好:
|
||||
#### 方式一:使用 defineAction 工具函数(推荐)
|
||||
|
||||
结构清晰,类型推断友好:
|
||||
|
||||
```ts
|
||||
const myAction = defineAction({
|
||||
name: 'actionName',
|
||||
name: 'myAction',
|
||||
title: '操作显示名称',
|
||||
uiSchema: {},
|
||||
defaultParams: {},
|
||||
paramsRequired: true, // 添加模型前强制打开配置对话框
|
||||
hideInSettings: false, // 在设置菜单中显示
|
||||
paramsRequired: true,
|
||||
hideInSettings: false,
|
||||
async handler(ctx, params) {
|
||||
// 操作逻辑
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 实现 ActionDefinition 接口
|
||||
#### 方式二:实现 ActionDefinition 接口
|
||||
|
||||
适合需要扩展属性或方法的场景:
|
||||
复杂场景时可以通过定义 Action 类来处理更复杂的操作
|
||||
|
||||
```ts
|
||||
class MyAction implements ActionDefinition {
|
||||
name = 'actionName';
|
||||
name = 'myAction';
|
||||
title = '操作显示名称';
|
||||
uiSchema = {};
|
||||
defaultParams = {};
|
||||
paramsRequired = true; // 添加模型前强制打开配置对话框
|
||||
hideInSettings = false; // 在设置菜单中显示
|
||||
paramsRequired = true;
|
||||
hideInSettings = false;
|
||||
async handler(ctx, params) {
|
||||
// 操作逻辑
|
||||
}
|
||||
@ -60,59 +62,115 @@ class MyAction implements ActionDefinition {
|
||||
|
||||
---
|
||||
|
||||
## 注册操作
|
||||
|
||||
注册后可在流步骤中通过 `use` 字段复用:
|
||||
### 2. 注册到 FlowEngine 里
|
||||
|
||||
```ts
|
||||
flowEngine.registerAction({
|
||||
name: 'actionName',
|
||||
title: '操作显示名称',
|
||||
uiSchema: {},
|
||||
defaultParams: {},
|
||||
paramsRequired: true, // 添加模型前强制打开配置对话框
|
||||
hideInSettings: false, // 在设置菜单中显示
|
||||
handler(ctx, params) {
|
||||
// 操作逻辑
|
||||
},
|
||||
});
|
||||
|
||||
flowEngine.registerAction(myAction); // 注册 defineAction 返回的对象
|
||||
flowEngine.registerAction(new MyAction()); // 注册类实例
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 在流中复用操作
|
||||
### 3. 在流中使用
|
||||
|
||||
在流步骤定义中通过 `use` 字段引用已注册的操作:
|
||||
|
||||
```ts
|
||||
steps: {
|
||||
step1: {
|
||||
use: 'actionName', // 复用已注册的操作
|
||||
use: 'myAction', // 复用已注册的操作
|
||||
defaultParams: {},
|
||||
paramsRequired: true, // 可以在步骤级别覆盖操作的paramsRequired设置
|
||||
hideInSettings: false, // 可以在步骤级别覆盖操作的hideInSettings设置
|
||||
paramsRequired: true, // 可覆盖操作的 paramsRequired
|
||||
hideInSettings: false, // 可覆盖操作的 hideInSettings
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 配置选项说明
|
||||
## 参数配置详解
|
||||
|
||||
### name
|
||||
|
||||
- **类型**: `string`
|
||||
- **说明**: 操作唯一标识,必须全局唯一。建议使用有业务含义的英文名,便于维护和复用。
|
||||
|
||||
### title
|
||||
|
||||
- **类型**: `string`
|
||||
- **说明**: 操作的显示名称,通常用于界面展示。支持多语言配置。
|
||||
|
||||
### defaultParams
|
||||
|
||||
- **类型**: `Record<string, any>` 或 `(ctx) => Record<string, any>`
|
||||
- **说明**: 操作参数的默认值。支持静态对象或函数(可根据 context 动态生成)。
|
||||
- **作用**: 作为 handler 的 params 默认值。
|
||||
|
||||
**静态用法:**
|
||||
```ts
|
||||
{
|
||||
defaultParams: { key1: 'val1' },
|
||||
async handler(ctx, params) {
|
||||
console.log(params.key1); // val1
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**动态用法:**
|
||||
```ts
|
||||
{
|
||||
defaultParams(ctx) {
|
||||
return { key1: 'val1' }
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
console.log(params.key1); // val1
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### handler
|
||||
|
||||
- **类型**: `(ctx: FlowContext, params: any) => Promise<any> | any`
|
||||
- **说明**: 操作的核心执行逻辑。支持异步和同步函数。`ctx` 提供当前流上下文,`params` 为参数对象。
|
||||
|
||||
### uiSchema
|
||||
|
||||
- **类型**: `Omit<FormilySchema, 'default'>`
|
||||
- **说明**: 用于参数的可视化配置表单。推荐与 defaultParams 配合使用,提升用户体验。
|
||||
- **注意**: uiSchema 不支持 default 参数,避免与 defaultParams 重复。
|
||||
|
||||
### paramsRequired
|
||||
|
||||
- **类型**: `boolean`
|
||||
- **默认值**: `false`
|
||||
- **说明**: 当设置为 `true` 时,在添加该步骤模型前会强制打开参数配置对话框,确保用户配置必要的参数。适用于需要用户必须配置参数才能正常工作的操作。
|
||||
- **说明**: 为 `true` 时,添加步骤前会强制打开参数配置对话框,确保用户配置必要参数。适用于参数必填的场景。
|
||||
|
||||
### hideInSettings
|
||||
|
||||
- **类型**: `boolean`
|
||||
- **默认值**: `false`
|
||||
- **说明**: 当设置为 `true` 时,该步骤将在设置菜单中隐藏,用户无法通过 Settings 界面直接添加该步骤。适用于初始化配置场景。
|
||||
- **说明**: 为 `true` 时,该步骤在设置菜单中隐藏,用户无法通过 Settings 界面直接添加。适用于初始化配置或内部步骤。
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
- 推荐优先使用 `defineAction` 工具函数定义操作,结构更清晰,类型推断更友好。
|
||||
- `name` 字段建议采用有业务含义的英文名,避免重名。
|
||||
- `uiSchema` 和 `defaultParams` 配合使用,提升参数配置体验。
|
||||
- 对于需要用户强制配置参数的操作,设置 `paramsRequired: true`。
|
||||
- 内部或自动化步骤可设置 `hideInSettings: true`,避免用户误操作。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题与注意事项
|
||||
|
||||
- **Q: defaultParams 和 uiSchema 有什么区别?**
|
||||
> defaultParams 用于设置参数默认值,uiSchema 用于渲染参数配置表单。两者配合使用,互不冲突。
|
||||
- **Q: uiSchema 为什么不支持 default?**
|
||||
> 1. 为避免与 defaultParams 重复,uiSchema 仅用于表单结构描述,不处理默认值;
|
||||
> 2. 使用 defaultParams 处理可以有更好的 ts 类型提示;
|
||||
> 3. uiSchema 的结构可能较为复杂,解析 uiSchema 来提取 default 值非常繁琐且容易出错,因此不建议在 uiSchema 中处理 default;
|
||||
|
||||
---
|
||||
|
||||
@ -121,3 +179,4 @@ steps: {
|
||||
- **FlowAction** 让流步骤逻辑高度复用,便于维护和扩展。
|
||||
- 支持多种定义方式,适应不同复杂度的业务场景。
|
||||
- 可通过 `uiSchema` 和 `defaultParams` 配置参数界面和默认值,提升易用性。
|
||||
- 合理使用 `paramsRequired` 和 `hideInSettings`,提升操作安全性和灵活性。
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
## 主要方法
|
||||
|
||||
- **load(uid: string): Promise<FlowModel \| null>**
|
||||
- **findOne(query: Query): Promise<FlowModel \| null>**
|
||||
根据唯一标识符 uid 从远程加载模型数据。
|
||||
|
||||
- **save(model: FlowModel): Promise<any>**
|
||||
@ -19,7 +19,8 @@
|
||||
class FlowModelRepository implements IFlowModelRepository<FlowModel> {
|
||||
constructor(private app: Application) {}
|
||||
|
||||
async load(uid: string) {
|
||||
async findOne(query) {
|
||||
const { uid, parentId } = query;
|
||||
// 实现:根据 uid 获取模型
|
||||
return null;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import React from 'react';
|
||||
|
||||
class FlowModelRepository implements IFlowModelRepository<FlowModel> {
|
||||
constructor(private app: Application) {}
|
||||
async load(uid: string) {
|
||||
async findOne({ uid, parentId }) {
|
||||
// implement fetching a model by id
|
||||
return null;
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ import { Button, Tabs } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
class FlowModelRepository implements IFlowModelRepository<FlowModel<{parent: never, subModels: { tabs: TabFlowModel[] } }>> {
|
||||
class FlowModelRepository
|
||||
implements IFlowModelRepository<FlowModel<{ parent: never; subModels: { tabs: TabFlowModel[] } }>>
|
||||
{
|
||||
get models() {
|
||||
const models = new Map();
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
@ -22,8 +24,12 @@ class FlowModelRepository implements IFlowModelRepository<FlowModel<{parent: nev
|
||||
return models;
|
||||
}
|
||||
|
||||
async findOne(query) {
|
||||
return this.load(query.uid);
|
||||
}
|
||||
|
||||
// 从本地存储加载模型数据
|
||||
async load(uid: string) {
|
||||
async load({ uid }) {
|
||||
const data = localStorage.getItem(`flow-model:${uid}`);
|
||||
if (!data) return null;
|
||||
const json: FlowModel = JSON.parse(data);
|
||||
@ -57,7 +63,10 @@ class FlowModelRepository implements IFlowModelRepository<FlowModel<{parent: nev
|
||||
localStorage.setItem(`flow-model:${subModel.uid}`, JSON.stringify(subModel.serialize()));
|
||||
});
|
||||
} else if (model.subModels[subModelKey] instanceof FlowModel) {
|
||||
localStorage.setItem(`flow-model:${model.subModels[subModelKey].uid}`, JSON.stringify(model.subModels[subModelKey].serialize()));
|
||||
localStorage.setItem(
|
||||
`flow-model:${model.subModels[subModelKey].uid}`,
|
||||
JSON.stringify(model.subModels[subModelKey].serialize()),
|
||||
);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
@ -72,8 +81,7 @@ class FlowModelRepository implements IFlowModelRepository<FlowModel<{parent: nev
|
||||
|
||||
class TabFlowModel extends FlowModel {}
|
||||
|
||||
class HelloFlowModel extends FlowModel<{parent: never, subModels: { tabs: TabFlowModel[] } }> {
|
||||
|
||||
class HelloFlowModel extends FlowModel<{ parent: never; subModels: { tabs: TabFlowModel[] } }> {
|
||||
addTab(tab: any) {
|
||||
// 使用新的 addSubModel API 添加子模型
|
||||
const model = this.addSubModel('tabs', tab);
|
||||
@ -88,7 +96,7 @@ class HelloFlowModel extends FlowModel<{parent: never, subModels: { tabs: TabFlo
|
||||
items={this.subModels.tabs?.map((tab) => ({
|
||||
key: tab.getProps().key,
|
||||
label: tab.getProps().label,
|
||||
children: tab.render()
|
||||
children: tab.render(),
|
||||
}))}
|
||||
tabBarExtraContent={
|
||||
<Button
|
||||
@ -98,7 +106,7 @@ class HelloFlowModel extends FlowModel<{parent: never, subModels: { tabs: TabFlo
|
||||
use: 'TabFlowModel',
|
||||
uid: tabId,
|
||||
props: { key: tabId, label: `Tab - ${tabId}` },
|
||||
})
|
||||
});
|
||||
}}
|
||||
>
|
||||
Add Tab
|
||||
@ -134,7 +142,7 @@ class PluginHelloModel extends Plugin {
|
||||
props: { key: 'tab-2', label: 'Tab 2' },
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
});
|
||||
this.router.add('root', { path: '/', element: <FlowModelRenderer model={model} /> });
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/client",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.mjs",
|
||||
@ -26,9 +26,9 @@
|
||||
"@formily/reactive-react": "^2.2.27",
|
||||
"@formily/shared": "^2.2.27",
|
||||
"@formily/validator": "^2.2.27",
|
||||
"@nocobase/evaluators": "1.8.0-alpha.5",
|
||||
"@nocobase/sdk": "1.8.0-alpha.5",
|
||||
"@nocobase/utils": "1.8.0-alpha.5",
|
||||
"@nocobase/evaluators": "1.8.0-alpha.8",
|
||||
"@nocobase/sdk": "1.8.0-alpha.8",
|
||||
"@nocobase/utils": "1.8.0-alpha.8",
|
||||
"ahooks": "^3.7.2",
|
||||
"antd": "5.24.2",
|
||||
"antd-style": "3.7.1",
|
||||
|
@ -72,7 +72,7 @@ export class APIClient extends APIClientSDK {
|
||||
api.notification = this.notification;
|
||||
const handlers = [];
|
||||
for (const handler of this.axios.interceptors.response['handlers']) {
|
||||
if (handler.rejected['_name'] === 'handleNotificationError') {
|
||||
if (handler?.rejected?.['_name'] === 'handleNotificationError') {
|
||||
handlers.push({
|
||||
...handler,
|
||||
rejected: api.handleNotificationError.bind(api),
|
||||
|
@ -19,9 +19,10 @@ import { useDetailsProps } from '../modules/blocks/data-blocks/details-single/ho
|
||||
import { FormItemSchemaToolbar } from '../modules/blocks/data-blocks/form/FormItemSchemaToolbar';
|
||||
import { useCreateFormBlockDecoratorProps } from '../modules/blocks/data-blocks/form/hooks/useCreateFormBlockDecoratorProps';
|
||||
import { useCreateFormBlockProps } from '../modules/blocks/data-blocks/form/hooks/useCreateFormBlockProps';
|
||||
import { useDataFormItemProps } from '../modules/blocks/data-blocks/form/hooks/useDataFormItemProps';
|
||||
import { useEditFormBlockDecoratorProps } from '../modules/blocks/data-blocks/form/hooks/useEditFormBlockDecoratorProps';
|
||||
import { useEditFormBlockProps } from '../modules/blocks/data-blocks/form/hooks/useEditFormBlockProps';
|
||||
import { useDataFormItemProps } from '../modules/blocks/data-blocks/form/hooks/useDataFormItemProps';
|
||||
import { useGridCardActionBarProps } from '../modules/blocks/data-blocks/grid-card/hooks/useGridCardActionBarProps';
|
||||
import {
|
||||
useGridCardBlockDecoratorProps,
|
||||
useGridCardBlockItemProps,
|
||||
@ -97,6 +98,7 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
|
||||
useGridCardBlockProps,
|
||||
useFormItemProps,
|
||||
useDataFormItemProps,
|
||||
useGridCardActionBarProps,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
@ -161,6 +163,7 @@ export class BlockSchemaComponentPlugin extends Plugin {
|
||||
useGridCardBlockItemProps,
|
||||
useFormItemProps,
|
||||
useDataFormItemProps,
|
||||
useGridCardActionBarProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ interface Props {
|
||||
expandFlag?: boolean;
|
||||
dragSortBy?: string;
|
||||
association?: string;
|
||||
enableIndexÏColumn?: boolean;
|
||||
enableIndexColumn?: boolean;
|
||||
}
|
||||
|
||||
const InternalTableBlockProvider = (props: Props) => {
|
||||
@ -77,7 +77,7 @@ const InternalTableBlockProvider = (props: Props) => {
|
||||
fieldNames,
|
||||
collection,
|
||||
association,
|
||||
enableIndexÏColumn,
|
||||
enableIndexColumn,
|
||||
} = props;
|
||||
const field: any = useField();
|
||||
const { resource, service } = useBlockRequestContext();
|
||||
@ -136,7 +136,7 @@ const InternalTableBlockProvider = (props: Props) => {
|
||||
setExpandFlag: setExpandFlagValue,
|
||||
heightProps,
|
||||
association,
|
||||
enableIndexÏColumn,
|
||||
enableIndexColumn,
|
||||
}),
|
||||
[
|
||||
allIncludesChildren,
|
||||
@ -153,7 +153,7 @@ const InternalTableBlockProvider = (props: Props) => {
|
||||
setExpandFlagValue,
|
||||
showIndex,
|
||||
association,
|
||||
enableIndexÏColumn,
|
||||
enableIndexColumn,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -197,9 +197,7 @@ export function useCollectValuesToSubmit() {
|
||||
|
||||
if (isVariable(value)) {
|
||||
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
|
||||
if (parsedValue !== null && parsedValue !== undefined) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
} else if (value !== '') {
|
||||
assignedValues[key] = value;
|
||||
}
|
||||
@ -385,9 +383,7 @@ export const useAssociationCreateActionProps = () => {
|
||||
|
||||
if (isVariable(value)) {
|
||||
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
} else if (value !== '') {
|
||||
assignedValues[key] = value;
|
||||
}
|
||||
@ -658,9 +654,7 @@ export const useCustomizeUpdateActionProps = () => {
|
||||
|
||||
if (isVariable(value)) {
|
||||
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
} else if (value !== '') {
|
||||
assignedValues[key] = value;
|
||||
}
|
||||
@ -771,9 +765,7 @@ export const useCustomizeBulkUpdateActionProps = () => {
|
||||
|
||||
if (isVariable(value)) {
|
||||
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
} else if (value !== '') {
|
||||
assignedValues[key] = value;
|
||||
}
|
||||
@ -999,9 +991,7 @@ export const useUpdateActionProps = () => {
|
||||
|
||||
if (isVariable(value)) {
|
||||
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
} else if (value !== '') {
|
||||
assignedValues[key] = value;
|
||||
}
|
||||
|
@ -193,6 +193,8 @@ export const EditFieldAction = (props) => {
|
||||
defaultValues.reverseField = interfaceConf?.default?.reverseField;
|
||||
set(defaultValues.reverseField, 'name', `f_${uid()}`);
|
||||
set(defaultValues.reverseField, 'uiSchema.title', record.__parent?.title);
|
||||
} else {
|
||||
defaultValues.autoCreateReverseField = true;
|
||||
}
|
||||
const schema = getSchema(interfaceConf, defaultValues, record, compile, getContainer);
|
||||
setSchema(schema);
|
||||
|
@ -24,7 +24,7 @@ export class CheckboxFieldInterface extends CollectionFieldInterface {
|
||||
'x-component': 'Checkbox',
|
||||
},
|
||||
};
|
||||
availableTypes = ['boolean', 'integer', 'bigInt'];
|
||||
availableTypes = ['boolean', 'integer', 'bigInt', 'bit'];
|
||||
hasDefaultValue = true;
|
||||
properties = {
|
||||
...defaultProps,
|
||||
|
@ -242,6 +242,8 @@ export const boolean = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{ label: "{{ t('is empty') }}", value: '$empty', noValue: true },
|
||||
{ label: "{{ t('is not empty') }}", value: '$notEmpty', noValue: true },
|
||||
];
|
||||
|
||||
export const tableoid = [
|
||||
|
@ -159,7 +159,7 @@ export class InheritanceCollectionMixin extends Collection {
|
||||
const targetField = filterFields.find((k) => {
|
||||
return k.name === v.name;
|
||||
});
|
||||
return targetField.collectionName !== this.name;
|
||||
return targetField?.collectionName !== this.name;
|
||||
});
|
||||
return this.parentCollectionFields[parentCollectionName];
|
||||
}
|
||||
|
@ -58,9 +58,13 @@ export const fieldComponentSettingsItem: SchemaSettingsItemType = {
|
||||
value: fieldSchema['x-component-props']?.['component'] || options[0]?.value,
|
||||
onChange(component) {
|
||||
const componentOptions = options.find((item) => item.value === component);
|
||||
const baseProps = componentOptions?.useProps?.() || {};
|
||||
const componentProps = {
|
||||
component,
|
||||
...(componentOptions?.useProps?.() || {}),
|
||||
...baseProps,
|
||||
...(component === collectionField['uiSchema']['x-component']
|
||||
? collectionField['uiSchema']['x-component-props']
|
||||
: {}),
|
||||
};
|
||||
_.set(fieldSchema, 'x-component-props', componentProps);
|
||||
field.componentProps = componentProps;
|
||||
|
@ -118,11 +118,17 @@ export const transformToFilter = (
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (value?.type) {
|
||||
|
||||
const collectionField = getCollectionJoinField(`${collectionName}.${path}`);
|
||||
|
||||
if (
|
||||
['datetime', 'datetimeNoTz', 'date', 'unixTimestamp', 'createdAt', 'updatedAt'].includes(
|
||||
collectionField?.interface,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const collectionField = getCollectionJoinField(`${collectionName}.${path}`);
|
||||
if (collectionField?.target) {
|
||||
if (Array.isArray(value)) {
|
||||
return true;
|
||||
|
@ -26,6 +26,26 @@ export class MockFlowModelRepository implements IFlowModelRepository<FlowModel>
|
||||
return models;
|
||||
}
|
||||
|
||||
async findOne(query) {
|
||||
const { uid, parentId } = query;
|
||||
if (uid) {
|
||||
return this.load(uid);
|
||||
} else if (parentId) {
|
||||
return this.loadByParentId(parentId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async loadByParentId(parentId: string) {
|
||||
for (const model of this.models.values()) {
|
||||
if (model.parentId == parentId) {
|
||||
console.log('Loading model by parentId:', parentId, model);
|
||||
return this.load(model.uid);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 从本地存储加载模型数据
|
||||
async load(uid: string) {
|
||||
const data = localStorage.getItem(`flow-model:${uid}`);
|
||||
@ -43,7 +63,9 @@ export class MockFlowModelRepository implements IFlowModelRepository<FlowModel>
|
||||
json.subModels[model.subKey].push(subModel);
|
||||
} else if (model.subType === 'object') {
|
||||
const subModel = await this.load(model.uid);
|
||||
json.subModels[model.subKey] = subModel;
|
||||
if (subModel) {
|
||||
json.subModels[model.subKey] = subModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
import { FlowModelRenderer, useFlowEngine, useFlowModel } from '@nocobase/flow-engine';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { Spin } from 'antd';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
function InternalFlowPage({ uid, sharedContext }) {
|
||||
@ -20,15 +20,16 @@ function InternalFlowPage({ uid, sharedContext }) {
|
||||
|
||||
export const FlowPage = () => {
|
||||
const params = useParams();
|
||||
return <FlowPageComponent uid={params.name} sharedContext={{}} />;
|
||||
return <FlowPageComponent uid={params.name} />;
|
||||
};
|
||||
|
||||
export const FlowPageComponent = ({ uid, sharedContext }) => {
|
||||
export const FlowPageComponent = (props) => {
|
||||
const { uid, parentId, sharedContext } = props;
|
||||
const flowEngine = useFlowEngine();
|
||||
const { loading } = useRequest(
|
||||
() => {
|
||||
return flowEngine.loadOrCreateModel({
|
||||
uid: uid,
|
||||
const { loading, data } = useRequest(
|
||||
async () => {
|
||||
const options = {
|
||||
uid,
|
||||
use: 'PageFlowModel',
|
||||
subModels: {
|
||||
tabs: [
|
||||
@ -42,14 +43,22 @@ export const FlowPageComponent = ({ uid, sharedContext }) => {
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
};
|
||||
if (!uid && parentId) {
|
||||
options['async'] = true;
|
||||
options['parentId'] = parentId;
|
||||
options['subKey'] = 'page';
|
||||
options['subType'] = 'object';
|
||||
}
|
||||
const data = await flowEngine.loadOrCreateModel(options);
|
||||
return data;
|
||||
},
|
||||
{
|
||||
refreshDeps: [uid],
|
||||
refreshDeps: [uid || parentId],
|
||||
},
|
||||
);
|
||||
if (loading) {
|
||||
if (loading || !data?.uid) {
|
||||
return <Spin />;
|
||||
}
|
||||
return <InternalFlowPage uid={uid} sharedContext={sharedContext} />;
|
||||
return <InternalFlowPage uid={data.uid} sharedContext={sharedContext} />;
|
||||
};
|
||||
|
46
packages/core/client/src/flow/models/AddNewActionModel.tsx
Normal file
46
packages/core/client/src/flow/models/AddNewActionModel.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 type { ButtonType } from 'antd/es/button';
|
||||
import React from 'react';
|
||||
import { FlowPageComponent } from '../FlowPage';
|
||||
import { ActionModel } from './ActionModel';
|
||||
|
||||
export class AddNewActionModel extends ActionModel {
|
||||
title = 'Add new';
|
||||
}
|
||||
|
||||
AddNewActionModel.registerFlow({
|
||||
key: 'event1',
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
steps: {
|
||||
step1: {
|
||||
handler(ctx, params) {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let currentDrawer: any;
|
||||
|
||||
function DrawerContent() {
|
||||
return (
|
||||
<div>
|
||||
<FlowPageComponent parentId={ctx.model.uid} sharedContext={{ ...ctx.extra, currentDrawer }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
currentDrawer = ctx.globals.drawer.open({
|
||||
title: '命令式 Drawer',
|
||||
width: 800,
|
||||
content: <DrawerContent />,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
@ -65,7 +65,7 @@ export class FormModel extends BlockFlowModel {
|
||||
/>
|
||||
<FormButtonGroup>
|
||||
{this.mapSubModels('actions', (action) => (
|
||||
<FlowModelRenderer model={action} showFlowSettings />
|
||||
<FlowModelRenderer model={action} showFlowSettings extraContext={{ currentModel: this }} />
|
||||
))}
|
||||
<AddActionButton model={this} subModelBaseClass="ActionModel" />
|
||||
</FormButtonGroup>
|
||||
@ -107,20 +107,22 @@ FormModel.registerFlow({
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
ctx.model.form = ctx.extra.form || createForm();
|
||||
if (ctx.model.collection) {
|
||||
return;
|
||||
if (!ctx.model.collection) {
|
||||
ctx.model.collection = ctx.globals.dataSourceManager.getCollection(
|
||||
params.dataSourceKey,
|
||||
params.collectionName,
|
||||
);
|
||||
const resource = new SingleRecordResource();
|
||||
resource.setDataSourceKey(params.dataSourceKey);
|
||||
resource.setResourceName(params.collectionName);
|
||||
resource.setAPIClient(ctx.globals.api);
|
||||
ctx.model.resource = resource;
|
||||
}
|
||||
ctx.model.collection = ctx.globals.dataSourceManager.getCollection(params.dataSourceKey, params.collectionName);
|
||||
const resource = new SingleRecordResource();
|
||||
resource.setDataSourceKey(params.dataSourceKey);
|
||||
resource.setResourceName(params.collectionName);
|
||||
resource.setAPIClient(ctx.globals.api);
|
||||
ctx.model.resource = resource;
|
||||
console.log('FormModel flow context', ctx.extra);
|
||||
if (ctx.extra.currentRecord) {
|
||||
resource.setFilterByTk(ctx.extra.currentRecord.id);
|
||||
await resource.refresh();
|
||||
ctx.model.form.setInitialValues(resource.getData());
|
||||
console.log('FormModel flow context', ctx.shared, ctx.model.getSharedContext());
|
||||
if (ctx.shared.currentRecord) {
|
||||
ctx.model.resource.setFilterByTk(ctx.shared.currentRecord.id);
|
||||
await ctx.model.resource.refresh();
|
||||
ctx.model.form.setInitialValues(ctx.model.resource.getData());
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -25,11 +25,16 @@ SubmitActionModel.registerFlow({
|
||||
steps: {
|
||||
step1: {
|
||||
async handler(ctx, params) {
|
||||
await ctx.model.parent.form.submit();
|
||||
const values = ctx.model.parent.form.values;
|
||||
await ctx.model.parent.resource.save(values);
|
||||
if (ctx.model.parent.dialog) {
|
||||
ctx.model.parent.dialog.close();
|
||||
if (ctx.extra.currentModel) {
|
||||
await ctx.extra.currentModel.form.submit();
|
||||
const values = ctx.extra.currentModel.form.values;
|
||||
await ctx.extra.currentModel.resource.save(values);
|
||||
}
|
||||
if (ctx.shared.currentDrawer) {
|
||||
ctx.shared.currentDrawer.destroy();
|
||||
}
|
||||
if (ctx.shared.currentResource) {
|
||||
ctx.shared.currentResource.refresh();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -115,11 +115,11 @@ TableColumnModel.define({
|
||||
sort: 0,
|
||||
});
|
||||
|
||||
const Columns = observer<any>(({ record, model }) => {
|
||||
const Columns = observer<any>(({ record, model, index }) => {
|
||||
return (
|
||||
<Space>
|
||||
{model.mapSubModels('actions', (action: ActionModel) => {
|
||||
const fork = action.createFork({}, `${record.id}`);
|
||||
const fork = action.createFork({}, `${index}`);
|
||||
return (
|
||||
<FlowModelRenderer
|
||||
showFlowSettings
|
||||
@ -183,7 +183,7 @@ export class TableActionsColumnModel extends TableColumnModel {
|
||||
}
|
||||
|
||||
render() {
|
||||
return (value, record, index) => <Columns record={record} model={this} />;
|
||||
return (value, record, index) => <Columns record={record} model={this} index={index} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
import { SettingOutlined } from '@ant-design/icons';
|
||||
import { css } from '@emotion/css';
|
||||
import {
|
||||
AddActionButton,
|
||||
AddActionModel,
|
||||
AddFieldButton,
|
||||
Collection,
|
||||
@ -108,10 +109,20 @@ export class TableModel extends BlockFlowModel<S> {
|
||||
extraContext={{ currentModel: this, currentResource: this.resource }}
|
||||
/>
|
||||
))}
|
||||
<AddActionModel
|
||||
<AddActionButton model={this} subModelBaseClass="ActionModel">
|
||||
<Button icon={<SettingOutlined />}>Configure actions</Button>
|
||||
</AddActionButton>
|
||||
{/* <AddActionModel
|
||||
model={this}
|
||||
subModelKey={'actions'}
|
||||
items={() => [
|
||||
{
|
||||
key: 'addnew',
|
||||
label: 'Add new',
|
||||
createModelOptions: {
|
||||
use: 'AddNewActionModel',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
@ -121,10 +132,8 @@ export class TableModel extends BlockFlowModel<S> {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Button type="primary" icon={<SettingOutlined />}>
|
||||
Configure actions
|
||||
</Button>
|
||||
</AddActionModel>
|
||||
<Button icon={<SettingOutlined />}>Configure actions</Button>
|
||||
</AddActionModel> */}
|
||||
</Space>
|
||||
<Table
|
||||
className={css`
|
||||
|
@ -7,7 +7,6 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { uid } from '@formily/shared';
|
||||
import type { ButtonType } from 'antd/es/button';
|
||||
import React from 'react';
|
||||
import { FlowPageComponent } from '../FlowPage';
|
||||
@ -26,14 +25,21 @@ ViewActionModel.registerFlow({
|
||||
steps: {
|
||||
step1: {
|
||||
handler(ctx, params) {
|
||||
ctx.globals.drawer.open({
|
||||
// eslint-disable-next-line prefer-const
|
||||
let currentDrawer: any;
|
||||
|
||||
function DrawerContent() {
|
||||
return (
|
||||
<div>
|
||||
<FlowPageComponent parentId={ctx.model.uid} sharedContext={{ ...ctx.extra, currentDrawer }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
currentDrawer = ctx.globals.drawer.open({
|
||||
title: '命令式 Drawer',
|
||||
width: 800,
|
||||
content: (
|
||||
<div>
|
||||
<FlowPageComponent uid={`${ctx.model.uid}-drawer`} sharedContext={ctx.extra} />
|
||||
</div>
|
||||
),
|
||||
content: <DrawerContent />,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -8,9 +8,10 @@
|
||||
*/
|
||||
|
||||
export * from './ActionModel';
|
||||
export * from './BulkDeleteActionModel';
|
||||
export * from './AddNewActionModel';
|
||||
export * from './BlockFlowModel';
|
||||
export * from './BlockGridFlowModel';
|
||||
export * from './BulkDeleteActionModel';
|
||||
export * from './CalendarBlockFlowModel';
|
||||
export * from './DeleteActionModel';
|
||||
export * from './FormFieldModel';
|
||||
|
@ -12,7 +12,8 @@ export const useEvaluatedExpression = (expression: string) => {
|
||||
|
||||
useEffect(() => {
|
||||
const run = async () => {
|
||||
if (!expression) {
|
||||
if (expression == null || expression === '') {
|
||||
setParsedValue(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -27,12 +27,8 @@ describe('createGridCardBlockSchema', () => {
|
||||
"actionBar": {
|
||||
"type": "void",
|
||||
"x-component": "ActionBar",
|
||||
"x-component-props": {
|
||||
"style": {
|
||||
"marginBottom": "var(--nb-spacing)",
|
||||
},
|
||||
},
|
||||
"x-initializer": "gridCard:configureActions",
|
||||
"x-use-component-props": "useGridCardActionBarProps",
|
||||
},
|
||||
"list": {
|
||||
"properties": {
|
||||
@ -103,12 +99,8 @@ describe('createGridCardBlockSchema', () => {
|
||||
"actionBar": {
|
||||
"type": "void",
|
||||
"x-component": "ActionBar",
|
||||
"x-component-props": {
|
||||
"style": {
|
||||
"marginBottom": "var(--nb-spacing)",
|
||||
},
|
||||
},
|
||||
"x-initializer": "gridCard:configureActions",
|
||||
"x-use-component-props": "useGridCardActionBarProps",
|
||||
},
|
||||
"list": {
|
||||
"properties": {
|
||||
|
@ -49,11 +49,7 @@ export const createGridCardBlockUISchema = (options: {
|
||||
type: 'void',
|
||||
'x-initializer': 'gridCard:configureActions',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
marginBottom: 'var(--nb-spacing)',
|
||||
},
|
||||
},
|
||||
'x-use-component-props': 'useGridCardActionBarProps',
|
||||
},
|
||||
list: {
|
||||
type: 'array',
|
||||
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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 { useFieldSchema } from '@formily/react';
|
||||
import _ from 'lodash';
|
||||
import { useDesignable } from '../../../../../schema-component';
|
||||
|
||||
export const useGridCardActionBarProps = () => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { designable } = useDesignable();
|
||||
|
||||
return {
|
||||
style: {
|
||||
marginBottom: 'var(--nb-spacing)',
|
||||
},
|
||||
|
||||
// In non-configuration mode, when there are no buttons, ActionBar doesn't need to be displayed
|
||||
hidden: !designable && _.isEmpty(fieldSchema.properties),
|
||||
};
|
||||
};
|
@ -52,6 +52,7 @@ test.describe('where table block can be added', () => {
|
||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||
await page.getByRole('menuitem', { name: 'Associated records right' }).waitFor({ state: 'detached' });
|
||||
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
||||
await page.waitForTimeout(300);
|
||||
await page.getByRole('menuitem', { name: 'Associated records right' }).hover();
|
||||
await page.getByRole('menuitem', { name: 'parentAssociationField' }).click();
|
||||
await page.getByLabel('schema-initializer-TableV2-table:configureColumns-parentTargetCollection').hover();
|
||||
|
@ -751,6 +751,7 @@ test.describe('actions schema settings', () => {
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:popup-general' })
|
||||
.hover();
|
||||
await page.waitForTimeout(300);
|
||||
};
|
||||
|
||||
test('supported options', async ({ page, mockPage, mockRecord }) => {
|
||||
|
@ -39,11 +39,11 @@ const enabledIndexColumn: SchemaSettingsItemType = {
|
||||
const { dn } = useDesignable();
|
||||
return {
|
||||
title: t('Enable index column'),
|
||||
checked: field.decoratorProps.enableIndexÏColumn !== false,
|
||||
onChange: async (enableIndexÏColumn) => {
|
||||
checked: field.decoratorProps.enableIndexColumn !== false,
|
||||
onChange: async (enableIndexColumn) => {
|
||||
field.decoratorProps = field.decoratorProps || {};
|
||||
field.decoratorProps.enableIndexÏColumn = enableIndexÏColumn;
|
||||
fieldSchema['x-decorator-props'].enableIndexÏColumn = enableIndexÏColumn;
|
||||
field.decoratorProps.enableIndexColumn = enableIndexColumn;
|
||||
fieldSchema['x-decorator-props'].enableIndexColumn = enableIndexColumn;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
|
@ -41,11 +41,11 @@ const enabledIndexColumn: SchemaSettingsItemType = {
|
||||
const { dn } = useDesignable();
|
||||
return {
|
||||
title: t('Enable index column'),
|
||||
checked: field.componentProps.enableIndexÏColumn !== false,
|
||||
onChange: async (enableIndexÏColumn) => {
|
||||
checked: field.componentProps.enableIndexColumn !== false,
|
||||
onChange: async (enableIndexColumn) => {
|
||||
field.componentProps = field.componentProps || {};
|
||||
field.componentProps.enableIndexÏColumn = enableIndexÏColumn;
|
||||
fieldSchema['x-component-props'].enableIndexÏColumn = enableIndexÏColumn;
|
||||
field.componentProps.enableIndexColumn = enableIndexColumn;
|
||||
fieldSchema['x-component-props'].enableIndexColumn = enableIndexColumn;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
|
@ -180,7 +180,6 @@ const layoutContentClass = css`
|
||||
`;
|
||||
|
||||
const className1 = css`
|
||||
width: 168px;
|
||||
height: var(--nb-header-height);
|
||||
margin-right: 4px;
|
||||
display: inline-flex;
|
||||
@ -189,6 +188,15 @@ const className1 = css`
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
`;
|
||||
const className1WithFixedWidth = css`
|
||||
${className1}
|
||||
width: 168px;
|
||||
`;
|
||||
const className1WithAutoWidth = css`
|
||||
${className1}
|
||||
width: auto;
|
||||
min-width: 168px;
|
||||
`;
|
||||
const className2 = css`
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
@ -266,7 +274,8 @@ const NocoBaseLogo = () => {
|
||||
const { token } = useToken();
|
||||
const fontSizeStyle = useMemo(() => ({ fontSize: token.fontSizeHeading3 }), [token.fontSizeHeading3]);
|
||||
|
||||
const logo = result?.data?.data?.logo?.url ? (
|
||||
const hasLogo = result?.data?.data?.logo?.url;
|
||||
const logo = hasLogo ? (
|
||||
<img className={className2} src={result?.data?.data?.logo?.url} />
|
||||
) : (
|
||||
<span style={fontSizeStyle} className={className3}>
|
||||
@ -274,7 +283,9 @@ const NocoBaseLogo = () => {
|
||||
</span>
|
||||
);
|
||||
|
||||
return <div className={className1}>{result?.loading ? null : logo}</div>;
|
||||
return (
|
||||
<div className={hasLogo ? className1WithFixedWidth : className1WithAutoWidth}>{result?.loading ? null : logo}</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -318,6 +329,7 @@ const GroupItem: FC<{ item: any }> = (props) => {
|
||||
{...item._route.options.badge}
|
||||
count={badgeCount}
|
||||
style={{ marginLeft: 4, color: item._route.options?.badge?.textColor }}
|
||||
dot={false}
|
||||
></Badge>
|
||||
)}
|
||||
</SortableItem>
|
||||
@ -334,7 +346,7 @@ const WithTooltip: FC<{ title: string; hidden: boolean; badgeProps: any }> = (pr
|
||||
{(context) =>
|
||||
context.collapsed && !props.hidden && !inHeader ? (
|
||||
<Tooltip title={props.title} placement="right">
|
||||
<Badge {...props.badgeProps} style={{ transform: 'none' }}>
|
||||
<Badge {...props.badgeProps} style={{ transform: 'none' }} dot={false}>
|
||||
{props.children}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
@ -427,6 +439,7 @@ const MenuItem: FC<{ item: any; options: { isMobile: boolean; collapsed: boolean
|
||||
{...item._route.options?.badge}
|
||||
count={badgeCount}
|
||||
style={{ marginLeft: 4, color: item._route.options?.badge?.textColor }}
|
||||
dot={false}
|
||||
></Badge>
|
||||
)}
|
||||
</SortableItem>
|
||||
@ -454,7 +467,11 @@ const MenuItem: FC<{ item: any; options: { isMobile: boolean; collapsed: boolean
|
||||
</WithTooltip>
|
||||
<MenuSchemaToolbar />
|
||||
{badgeCount != null && (
|
||||
<Badge {...badgeProps} style={{ marginLeft: 4, color: item._route.options?.badge?.textColor }}></Badge>
|
||||
<Badge
|
||||
{...badgeProps}
|
||||
style={{ marginLeft: 4, color: item._route.options?.badge?.textColor }}
|
||||
dot={false}
|
||||
></Badge>
|
||||
)}
|
||||
</SortableItem>
|
||||
</NocoBaseRouteContext.Provider>
|
||||
|
@ -147,6 +147,10 @@ const InternalActionBar: FC = (props: any) => {
|
||||
|
||||
export const ActionBar = withDynamicSchemaProps(
|
||||
(props: any) => {
|
||||
if (props.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <InternalActionBar {...props} />;
|
||||
},
|
||||
{ displayName: 'ActionBar' },
|
||||
|
@ -8,9 +8,9 @@
|
||||
*/
|
||||
|
||||
import { Field } from '@formily/core';
|
||||
import { observer, useField, useFieldSchema } from '@formily/react';
|
||||
import { observer, useField, useFieldSchema, SchemaOptionsContext } from '@formily/react';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState, useRef } from 'react';
|
||||
import { useAPIClient, useRequest } from '../../../api-client';
|
||||
import { useCollectionManager } from '../../../data-source/collection';
|
||||
import { markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
|
||||
@ -18,6 +18,7 @@ import { getDataSourceHeaders } from '../../../data-source/utils';
|
||||
import { useKeepAlive } from '../../../route-switch/antd/admin-layout/KeepAlive';
|
||||
import { useSchemaComponentContext } from '../../hooks';
|
||||
import { AssociationFieldContext } from './context';
|
||||
import { FormItem, useSchemaOptionsContext } from '../../../schema-component';
|
||||
|
||||
export const AssociationFieldProvider = observer(
|
||||
(props) => {
|
||||
@ -25,6 +26,8 @@ export const AssociationFieldProvider = observer(
|
||||
const cm = useCollectionManager();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const api = useAPIClient();
|
||||
const option = useSchemaOptionsContext();
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 这里有点奇怪,在 Table 切换显示的组件时,这个组件并不会触发重新渲染,所以增加这个 Hooks 让其重新渲染
|
||||
useSchemaComponentContext();
|
||||
@ -151,13 +154,34 @@ export const AssociationFieldProvider = observer(
|
||||
if (loading || rLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const components = {
|
||||
...option.components,
|
||||
FormItem: (props) => {
|
||||
return (
|
||||
<FormItem
|
||||
{...props}
|
||||
getPopupContainer={(triggerNode) => {
|
||||
return rootRef.current || document.body;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
return collectionField ? (
|
||||
<AssociationFieldContext.Provider
|
||||
value={{ options: collectionField, field, fieldSchema, allowMultiple, allowDissociate, currentMode }}
|
||||
>
|
||||
{props.children}
|
||||
</AssociationFieldContext.Provider>
|
||||
<div ref={rootRef}>
|
||||
<AssociationFieldContext.Provider
|
||||
value={{ options: collectionField, field, fieldSchema, allowMultiple, allowDissociate, currentMode }}
|
||||
>
|
||||
<SchemaOptionsContext.Provider
|
||||
value={{
|
||||
components,
|
||||
scope: option.scope,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</SchemaOptionsContext.Provider>
|
||||
</AssociationFieldContext.Provider>
|
||||
</div>
|
||||
) : null;
|
||||
},
|
||||
{ displayName: 'AssociationFieldProvider' },
|
||||
|
@ -11,7 +11,7 @@ import { observer, useField, useFieldSchema } from '@formily/react';
|
||||
import { transformMultiColumnToSingleColumn } from '@nocobase/utils/client';
|
||||
import { Select, Space } from 'antd';
|
||||
import { differenceBy, unionBy } from 'lodash';
|
||||
import React, { useContext, useMemo, useState } from 'react';
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
FormProvider,
|
||||
PopupSettingsProvider,
|
||||
@ -135,6 +135,11 @@ export const InternalPicker = observer(
|
||||
const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {};
|
||||
return filter;
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
setSelectedRows([]);
|
||||
}
|
||||
}, [value]);
|
||||
const usePickActionProps = () => {
|
||||
const { setVisible } = useActionContext();
|
||||
const { multiple, selectedRows, onChange, options, collectionField } = useContext(RecordPickerContext);
|
||||
|
@ -107,7 +107,7 @@ export const SubTable: any = observer(
|
||||
const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label');
|
||||
const recordV2 = useCollectionRecord();
|
||||
const collection = useCollection();
|
||||
const { allowSelectExistingRecord, allowAddnew, allowDisassociation, enableIndexÏColumn } = field.componentProps;
|
||||
const { allowSelectExistingRecord, allowAddnew, allowDisassociation, enableIndexColumn } = field.componentProps;
|
||||
|
||||
useSubTableSpecialCase({ rootField: field, rootSchema: schema });
|
||||
|
||||
@ -263,7 +263,7 @@ export const SubTable: any = observer(
|
||||
locale={{
|
||||
emptyText: <span> {field.editable ? t('Please add or select record') : t('No data')}</span>,
|
||||
}}
|
||||
enableIndexÏColumn={enableIndexÏColumn !== false}
|
||||
enableIndexColumn={enableIndexColumn !== false}
|
||||
footer={() => (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
{field.editable && (
|
||||
|
@ -515,6 +515,7 @@ const InternalBodyCellComponent = (props) => {
|
||||
const displayNone = { display: 'none' };
|
||||
const BodyCellComponent = ({ columnHidden, ...props }) => {
|
||||
const { designable } = useDesignable();
|
||||
const collection = useCollection();
|
||||
|
||||
if (columnHidden) {
|
||||
return (
|
||||
@ -524,7 +525,11 @@ const BodyCellComponent = ({ columnHidden, ...props }) => {
|
||||
);
|
||||
}
|
||||
|
||||
return <InternalBodyCellComponent {...props} />;
|
||||
return (
|
||||
<SubFormProvider value={{ value: props?.record, collection, fieldSchema: props.schema }}>
|
||||
<InternalBodyCellComponent {...props} />{' '}
|
||||
</SubFormProvider>
|
||||
);
|
||||
};
|
||||
|
||||
interface TableProps {
|
||||
@ -673,7 +678,7 @@ export const Table: any = withDynamicSchemaProps(
|
||||
onExpand,
|
||||
loading,
|
||||
onClickRow,
|
||||
enableIndexÏColumn,
|
||||
enableIndexColumn,
|
||||
...others
|
||||
} = { ...others1, ...others2 } as any;
|
||||
const field = useArrayField(others);
|
||||
@ -826,7 +831,7 @@ export const Table: any = withDynamicSchemaProps(
|
||||
|
||||
const restProps = useMemo(
|
||||
() => ({
|
||||
rowSelection: enableIndexÏColumn
|
||||
rowSelection: enableIndexColumn
|
||||
? memoizedRowSelection
|
||||
? {
|
||||
type: 'checkbox',
|
||||
@ -900,7 +905,7 @@ export const Table: any = withDynamicSchemaProps(
|
||||
isRowSelect,
|
||||
memoizedRowSelection,
|
||||
paginationProps,
|
||||
enableIndexÏColumn,
|
||||
enableIndexColumn,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -56,16 +56,23 @@ ReadPretty.Input = (props: InputReadPrettyProps) => {
|
||||
return props.value && typeof props.value === 'object' ? JSON.stringify(props.value) : compile(props.value);
|
||||
}, [props.value]);
|
||||
|
||||
const flexStyle = props.ellipsis ? { display: 'flex', alignItems: 'center' } : {};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cls(prefixCls, props.className)}
|
||||
style={{ overflowWrap: 'break-word', whiteSpace: 'normal', ...props.style }}
|
||||
style={{
|
||||
...flexStyle,
|
||||
overflowWrap: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
...props.style,
|
||||
}}
|
||||
>
|
||||
{props.addonBefore}
|
||||
{props.prefix}
|
||||
{compile(props.addonBefore)}
|
||||
{compile(props.prefix)}
|
||||
{props.ellipsis ? <EllipsisWithTooltip ellipsis={props.ellipsis}>{content}</EllipsisWithTooltip> : content}
|
||||
{props.suffix}
|
||||
{props.addonAfter}
|
||||
{compile(props.suffix)}
|
||||
{compile(props.addonAfter)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -244,7 +244,7 @@ const TabBadge: FC<{ tabRoute: NocoBaseDesktopRoute; style?: React.CSSProperties
|
||||
if (badgeCount == null) return null;
|
||||
|
||||
return (
|
||||
<Badge {...props.tabRoute.options?.badge} count={badgeCount} style={props.style}>
|
||||
<Badge {...props.tabRoute.options?.badge} count={badgeCount} style={props.style} dot={false}>
|
||||
{props.children}
|
||||
</Badge>
|
||||
);
|
||||
|
@ -857,7 +857,7 @@ export const Table: any = withDynamicSchemaProps(
|
||||
const collection = useCollection();
|
||||
const isTableSelector = schema?.parent?.['x-decorator'] === 'TableSelectorProvider';
|
||||
const ctx = isTableSelector ? useTableSelectorContext() : useTableBlockContext();
|
||||
const { expandFlag, allIncludesChildren, enableIndexÏColumn } = ctx;
|
||||
const { expandFlag, allIncludesChildren, enableIndexColumn } = ctx;
|
||||
const onRowDragEnd = useMemoizedFn(others.onRowDragEnd || (() => {}));
|
||||
const paginationProps = usePaginationProps(pagination1, pagination2, props);
|
||||
const columns = useTableColumns(others, paginationProps);
|
||||
@ -1023,7 +1023,7 @@ export const Table: any = withDynamicSchemaProps(
|
||||
const restProps = useMemo(
|
||||
() => ({
|
||||
rowSelection:
|
||||
enableIndexÏColumn !== false
|
||||
enableIndexColumn !== false
|
||||
? memoizedRowSelection
|
||||
? {
|
||||
type: 'checkbox',
|
||||
@ -1105,7 +1105,7 @@ export const Table: any = withDynamicSchemaProps(
|
||||
memoizedRowSelection,
|
||||
paginationProps,
|
||||
tableBlockContextBasicValue,
|
||||
enableIndexÏColumn,
|
||||
enableIndexColumn,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -182,10 +182,10 @@ export const TableBlockDesigner = () => {
|
||||
<SchemaSettingsSwitchItem
|
||||
title={t('Enable index column')}
|
||||
checked={field.decoratorProps?.enableSelectColumn !== false}
|
||||
onChange={async (enableIndexÏColumn) => {
|
||||
onChange={async (enableIndexColumn) => {
|
||||
field.decoratorProps = field.decoratorProps || {};
|
||||
field.decoratorProps.enableIndexÏColumn = enableIndexÏColumn;
|
||||
fieldSchema['x-decorator-props'].enableIndexÏColumn = enableIndexÏColumn;
|
||||
field.decoratorProps.enableIndexColumn = enableIndexColumn;
|
||||
fieldSchema['x-decorator-props'].enableIndexColumn = enableIndexColumn;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
|
@ -240,7 +240,7 @@ function useSizeHint(size: number) {
|
||||
const s = size ?? FILE_SIZE_LIMIT_DEFAULT;
|
||||
const { t, i18n } = useTranslation();
|
||||
const sizeString = filesize(s, { base: 2, standard: 'jedec', locale: i18n.language });
|
||||
return s !== 0 ? t('File size should not exceed {{size}}.', { size: 10000000 }) : '';
|
||||
return s !== 0 ? t('File size should not exceed {{size}}.', { size: sizeString }) : '';
|
||||
}
|
||||
|
||||
function DefaultThumbnailPreviewer({ file }) {
|
||||
|
@ -144,7 +144,7 @@ export const DateFilterDynamicComponent = (props) => {
|
||||
<Select
|
||||
key="unit"
|
||||
value={value?.unit}
|
||||
style={{ maxWidth: 140, minWidth: 130 }}
|
||||
style={{ maxWidth: 140 }}
|
||||
onChange={(val) => {
|
||||
const obj = {
|
||||
...value,
|
||||
@ -158,6 +158,7 @@ export const DateFilterDynamicComponent = (props) => {
|
||||
{ value: 'month', label: t('Calendar Month') },
|
||||
{ value: 'year', label: t('Calendar Year') },
|
||||
]}
|
||||
popupMatchSelectWidth
|
||||
/>,
|
||||
]}
|
||||
{(value?.type === 'exact' || !value?.type) && <SmartDatePicker {...props} />}
|
||||
|
@ -10,8 +10,9 @@
|
||||
import { Field } from '@formily/core';
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
import { merge } from '@formily/shared';
|
||||
import _, { cloneDeep } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import _, { cloneDeepWith } from 'lodash';
|
||||
import React, { isValidElement, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useBlockContext } from '../../../block-provider';
|
||||
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
|
||||
import {
|
||||
useCollectionField_deprecated,
|
||||
@ -20,14 +21,13 @@ import {
|
||||
useCollection_deprecated,
|
||||
} from '../../../collection-manager';
|
||||
import { CollectionFieldProvider } from '../../../data-source';
|
||||
import { FlagProvider } from '../../../flag-provider';
|
||||
import { useRecord } from '../../../record-provider';
|
||||
import { useCompile, useComponent } from '../../../schema-component';
|
||||
import { VariableInput, getShouldChange } from '../../../schema-settings/VariableInput/VariableInput';
|
||||
import { Option } from '../../../schema-settings/VariableInput/type';
|
||||
import { formatVariableScop } from '../../../schema-settings/VariableInput/utils/formatVariableScop';
|
||||
import { useLocalVariables, useVariables } from '../../../variables';
|
||||
import { useBlockContext } from '../../../block-provider';
|
||||
import { FlagProvider } from '../../../flag-provider';
|
||||
interface AssignedFieldProps {
|
||||
value: any;
|
||||
onChange: (value: any) => void;
|
||||
@ -126,7 +126,14 @@ export const AssignedFieldInner = (props: AssignedFieldProps) => {
|
||||
currentForm.children = formatVariableScop(currentFormFields);
|
||||
}
|
||||
|
||||
return cloneDeep(scope);
|
||||
return cloneDeepWith(scope, (value) => {
|
||||
// 不对 `ReactElement` 进行深拷贝,因为会报错
|
||||
if (isValidElement(value)) {
|
||||
return value;
|
||||
}
|
||||
// 对于其他类型的对象,继续正常的深拷贝
|
||||
return undefined;
|
||||
});
|
||||
},
|
||||
[currentFormFields, name],
|
||||
);
|
||||
|
@ -110,6 +110,7 @@ const quickEditField = [
|
||||
'circle',
|
||||
'point',
|
||||
'lineString',
|
||||
'vditor',
|
||||
];
|
||||
|
||||
export function useTableColumnInitializerFields() {
|
||||
|
@ -83,14 +83,17 @@ export function bindLinkageRulesToFiled(
|
||||
// 3. value 表达式中的变量值;
|
||||
() => {
|
||||
// 获取条件中的字段值
|
||||
getFieldValuesInCondition({ linkageRules, formValues });
|
||||
const fieldValuesInCondition = getFieldValuesInCondition({ linkageRules, formValues });
|
||||
// 获取条件中的变量值
|
||||
getVariableValuesInCondition({ linkageRules, localVariables });
|
||||
const variableValuesInCondition = getVariableValuesInCondition({ linkageRules, localVariables });
|
||||
|
||||
// 获取 value 表达式中的变量值
|
||||
getVariableValuesInExpression({ action, localVariables });
|
||||
const variableValuesInExpression = getVariableValuesInExpression({ action, localVariables });
|
||||
|
||||
return uid();
|
||||
const result = [fieldValuesInCondition, variableValuesInCondition, variableValuesInExpression]
|
||||
.map((item) => JSON.stringify(item))
|
||||
.join(',');
|
||||
return result;
|
||||
},
|
||||
getSubscriber({ action, field, rule, variables, localVariables, variableNameOfLeftCondition }, jsonLogic),
|
||||
{ fireImmediately: true, equals: _.isEqual },
|
||||
|
@ -34,7 +34,8 @@ export function useSatisfiedActionValues({
|
||||
const variables = useVariables();
|
||||
const localVariables = useLocalVariables({ currentForm: { values: formValues } as any });
|
||||
const localSchema = schema ?? fieldSchema;
|
||||
const styleRules = rules ?? localSchema[LinkageRuleDataKeyMap[category]];
|
||||
const styleRules =
|
||||
rules ?? (localSchema[LinkageRuleDataKeyMap[category]] || localSchema?.parent[LinkageRuleDataKeyMap[category]]);
|
||||
const app = useApp();
|
||||
|
||||
const compute = useCallback(() => {
|
||||
@ -65,7 +66,7 @@ export function useSatisfiedActionValues({
|
||||
form.removeEffects(id);
|
||||
};
|
||||
}
|
||||
}, [form, compute]);
|
||||
}, [form, compute, formValues]);
|
||||
|
||||
return { valueMap };
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import { useParentPopupVariableContext } from '../../schema-settings/VariableInp
|
||||
import { useCurrentParentRecordContext } from '../../schema-settings/VariableInput/hooks/useParentRecordVariable';
|
||||
import { usePopupVariableContext } from '../../schema-settings/VariableInput/hooks/usePopupVariable';
|
||||
import { useCurrentRecordContext } from '../../schema-settings/VariableInput/hooks/useRecordVariable';
|
||||
import { useURLSearchParamsVariable } from '../../schema-settings/VariableInput/hooks/useURLSearchParamsVariable';
|
||||
import { VariableOption } from '../types';
|
||||
import useContextVariable from './useContextVariable';
|
||||
import { useApp } from '../../application/hooks/useApp';
|
||||
@ -53,6 +54,7 @@ const useLocalVariables = (props?: Props) => {
|
||||
dataSource: parentPopupDataSource,
|
||||
defaultValue: defaultValueOfParentPopupRecord,
|
||||
} = useParentPopupVariableContext();
|
||||
const { urlSearchParamsCtx, shouldDisplay: shouldDisplayURLSearchParams } = useURLSearchParamsVariable();
|
||||
const { datetimeCtx } = useDatetimeVariableContext();
|
||||
const { currentFormCtx } = useCurrentFormContext({ form: props?.currentForm });
|
||||
const { name: currentCollectionName } = useCollection_deprecated();
|
||||
@ -156,6 +158,10 @@ const useLocalVariables = (props?: Props) => {
|
||||
ctx: parentObjectCtx,
|
||||
collectionName: collectionNameOfParentObject,
|
||||
},
|
||||
shouldDisplayURLSearchParams && {
|
||||
name: '$nURLSearchParams',
|
||||
ctx: urlSearchParamsCtx,
|
||||
},
|
||||
...customVariables,
|
||||
] as VariableOption[]
|
||||
).filter(Boolean);
|
||||
@ -181,6 +187,7 @@ const useLocalVariables = (props?: Props) => {
|
||||
parentObjectCtx,
|
||||
collectionNameOfParentObject,
|
||||
contextVariable,
|
||||
urlSearchParamsCtx,
|
||||
...customVariables.map((item) => item.ctx),
|
||||
]); // 尽量保持返回的值不变,这样可以减少接口的请求次数,因为关系字段会缓存到变量的 ctx 中
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-nocobase-app",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"main": "src/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@nocobase/data-source-manager",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/actions": "1.8.0-alpha.5",
|
||||
"@nocobase/cache": "1.8.0-alpha.5",
|
||||
"@nocobase/database": "1.8.0-alpha.5",
|
||||
"@nocobase/resourcer": "1.8.0-alpha.5",
|
||||
"@nocobase/utils": "1.8.0-alpha.5",
|
||||
"@nocobase/actions": "1.8.0-alpha.8",
|
||||
"@nocobase/cache": "1.8.0-alpha.8",
|
||||
"@nocobase/database": "1.8.0-alpha.8",
|
||||
"@nocobase/resourcer": "1.8.0-alpha.8",
|
||||
"@nocobase/utils": "1.8.0-alpha.8",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
},
|
||||
|
@ -45,11 +45,11 @@ const actions: Actions = {
|
||||
method: 'destroy',
|
||||
},
|
||||
firstOrCreate: {
|
||||
params: ['values', 'filterKeys'],
|
||||
params: ['values', 'filterKeys', 'whitelist', 'blacklist', 'updateAssociationValues', 'targetCollection'],
|
||||
method: 'firstOrCreate',
|
||||
},
|
||||
updateOrCreate: {
|
||||
params: ['values', 'filterKeys'],
|
||||
params: ['values', 'filterKeys', 'whitelist', 'blacklist', 'updateAssociationValues', 'targetCollection'],
|
||||
method: 'updateOrCreate',
|
||||
},
|
||||
remove: {
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/database",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@nocobase/logger": "1.8.0-alpha.5",
|
||||
"@nocobase/utils": "1.8.0-alpha.5",
|
||||
"@nocobase/logger": "1.8.0-alpha.8",
|
||||
"@nocobase/utils": "1.8.0-alpha.8",
|
||||
"async-mutex": "^0.3.2",
|
||||
"chalk": "^4.1.1",
|
||||
"cron-parser": "4.4.0",
|
||||
|
@ -70,4 +70,32 @@ describe('eq operator', () => {
|
||||
|
||||
expect(results).toEqual(0);
|
||||
});
|
||||
|
||||
it('should eq string field with number value', async () => {
|
||||
await db.getRepository('tests').create({
|
||||
values: [{ name: '123' }, { name: '234' }, { name: '345' }],
|
||||
});
|
||||
|
||||
const results = await db.getRepository('tests').count({
|
||||
filter: {
|
||||
'name.$eq': 123,
|
||||
},
|
||||
});
|
||||
|
||||
expect(results).toEqual(1);
|
||||
});
|
||||
|
||||
it('should eq string field with number value (array)', async () => {
|
||||
await db.getRepository('tests').create({
|
||||
values: [{ name: '123' }, { name: '234' }, { name: '345' }],
|
||||
});
|
||||
|
||||
const results = await db.getRepository('tests').count({
|
||||
filter: {
|
||||
'name.$eq': [123],
|
||||
},
|
||||
});
|
||||
|
||||
expect(results).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
@ -30,6 +30,7 @@ export class BelongsToArrayAssociation {
|
||||
targetKey: string;
|
||||
identifierField: string;
|
||||
as: string;
|
||||
options: any;
|
||||
|
||||
constructor(options: {
|
||||
db: Database;
|
||||
@ -40,6 +41,7 @@ export class BelongsToArrayAssociation {
|
||||
targetKey: string;
|
||||
}) {
|
||||
const { db, source, as, foreignKey, target, targetKey } = options;
|
||||
this.options = options;
|
||||
this.associationType = 'BelongsToArray';
|
||||
this.db = db;
|
||||
this.source = source;
|
||||
|
@ -14,6 +14,11 @@ export default {
|
||||
if (ctx?.fieldPath) {
|
||||
const field = ctx.db.getFieldByPath(ctx.fieldPath);
|
||||
if (field?.type === 'string' && typeof val !== 'string') {
|
||||
if (Array.isArray(val)) {
|
||||
return {
|
||||
[Op.in]: val.map((v) => String(v)),
|
||||
};
|
||||
}
|
||||
return {
|
||||
[Op.eq]: String(val),
|
||||
};
|
||||
|
@ -26,7 +26,10 @@ import {
|
||||
WhereOperators,
|
||||
} from 'sequelize';
|
||||
|
||||
import _ from 'lodash';
|
||||
import { BelongsToArrayRepository } from './belongs-to-array/belongs-to-array-repository';
|
||||
import { Collection } from './collection';
|
||||
import { SmartCursorBuilder } from './cursor-builder';
|
||||
import { Database } from './database';
|
||||
import mustHaveFilter from './decorators/must-have-filter-decorator';
|
||||
import injectTargetCollection from './decorators/target-collection-decorator';
|
||||
@ -38,7 +41,6 @@ import FilterParser from './filter-parser';
|
||||
import { Model } from './model';
|
||||
import operators from './operators';
|
||||
import { OptionsParser } from './options-parser';
|
||||
import { BelongsToArrayRepository } from './belongs-to-array/belongs-to-array-repository';
|
||||
import { BelongsToManyRepository } from './relation-repository/belongs-to-many-repository';
|
||||
import { BelongsToRepository } from './relation-repository/belongs-to-repository';
|
||||
import { HasManyRepository } from './relation-repository/hasmany-repository';
|
||||
@ -47,8 +49,6 @@ import { RelationRepository } from './relation-repository/relation-repository';
|
||||
import { updateAssociations, updateModelByValues } from './update-associations';
|
||||
import { UpdateGuard } from './update-guard';
|
||||
import { valuesToFilter } from './utils/filter-utils';
|
||||
import _ from 'lodash';
|
||||
import { SmartCursorBuilder } from './cursor-builder';
|
||||
|
||||
const debug = require('debug')('noco-database');
|
||||
|
||||
@ -242,6 +242,7 @@ export interface FirstOrCreateOptions extends Transactionable {
|
||||
values?: Values;
|
||||
hooks?: boolean;
|
||||
context?: any;
|
||||
updateAssociationValues?: AssociationKeysToBeUpdate;
|
||||
}
|
||||
|
||||
export class Repository<TModelAttributes extends {} = any, TCreationAttributes extends {} = TModelAttributes> {
|
||||
@ -521,7 +522,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
||||
* Get the first record matching the attributes or create it.
|
||||
*/
|
||||
async firstOrCreate(options: FirstOrCreateOptions) {
|
||||
const { filterKeys, values, transaction, hooks, context } = options;
|
||||
const { filterKeys, values, transaction, context, ...rest } = options;
|
||||
const filter = Repository.valuesToFilter(values, filterKeys);
|
||||
|
||||
const instance = await this.findOne({ filter, transaction, context });
|
||||
@ -530,11 +531,12 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
||||
return instance;
|
||||
}
|
||||
|
||||
return this.create({ values, transaction, hooks, context });
|
||||
return this.create({ values, transaction, context, ...rest });
|
||||
}
|
||||
|
||||
async updateOrCreate(options: FirstOrCreateOptions) {
|
||||
const { filterKeys, values, transaction, hooks, context } = options;
|
||||
const { filterKeys, values, transaction, context, ...rest } = options;
|
||||
|
||||
const filter = Repository.valuesToFilter(values, filterKeys);
|
||||
|
||||
const instance = await this.findOne({ filter, transaction, context });
|
||||
@ -544,12 +546,12 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
||||
filterByTk: instance.get(this.collection.filterTargetKey || this.collection.model.primaryKeyAttribute),
|
||||
values,
|
||||
transaction,
|
||||
hooks,
|
||||
context,
|
||||
...rest,
|
||||
});
|
||||
}
|
||||
|
||||
return this.create({ values, transaction, hooks, context });
|
||||
return this.create({ values, transaction, context, ...rest });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/devtools",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./src/index.js",
|
||||
"dependencies": {
|
||||
"@nocobase/build": "1.8.0-alpha.5",
|
||||
"@nocobase/client": "1.8.0-alpha.5",
|
||||
"@nocobase/test": "1.8.0-alpha.5",
|
||||
"@nocobase/build": "1.8.0-alpha.8",
|
||||
"@nocobase/client": "1.8.0-alpha.8",
|
||||
"@nocobase/test": "1.8.0-alpha.8",
|
||||
"@types/koa": "^2.15.0",
|
||||
"@types/koa-bodyparser": "^4.3.4",
|
||||
"@types/lodash": "^4.14.177",
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/evaluators",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@formulajs/formulajs": "4.4.9",
|
||||
"@nocobase/utils": "1.8.0-alpha.5",
|
||||
"@nocobase/utils": "1.8.0-alpha.8",
|
||||
"mathjs": "^10.6.0"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -92,9 +92,6 @@ const FlowModelRendererWithAutoFlows: React.FC<{
|
||||
}) => {
|
||||
const defaultExtraContext = useFlowExtraContext();
|
||||
useApplyAutoFlows(model, extraContext || defaultExtraContext, !independentAutoFlowExecution);
|
||||
useEffect(() => {
|
||||
model.setSharedContext(sharedContext);
|
||||
}, [sharedContext]);
|
||||
|
||||
return (
|
||||
<FlowModelRendererCore
|
||||
@ -117,10 +114,6 @@ const FlowModelRendererWithoutAutoFlows: React.FC<{
|
||||
hideRemoveInSettings: boolean;
|
||||
sharedContext?: Record<string, any>;
|
||||
}> = observer(({ model, showFlowSettings, flowSettingsVariant, hideRemoveInSettings, sharedContext }) => {
|
||||
useEffect(() => {
|
||||
model.setSharedContext(sharedContext);
|
||||
}, [sharedContext]);
|
||||
|
||||
return (
|
||||
<FlowModelRendererCore
|
||||
model={model}
|
||||
@ -217,6 +210,10 @@ export const FlowModelRenderer: React.FC<FlowModelRendererProps> = observer(
|
||||
return null;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
model.setSharedContext(sharedContext);
|
||||
}, [model, sharedContext]);
|
||||
|
||||
// 根据 skipApplyAutoFlows 选择不同的内部组件
|
||||
if (skipApplyAutoFlows) {
|
||||
return (
|
||||
|
@ -13,6 +13,26 @@ import React from 'react';
|
||||
import { ActionStepDefinition } from '../../../../types';
|
||||
import { resolveDefaultParams } from '../../../../utils';
|
||||
|
||||
/**
|
||||
* 检查步骤是否已经有了所需的配置值
|
||||
* @param uiSchema 步骤的 UI Schema
|
||||
* @param currentParams 当前步骤的参数
|
||||
* @returns 是否已经有了所需的配置值
|
||||
*/
|
||||
function hasRequiredParams(uiSchema: Record<string, any>, currentParams: Record<string, any>): boolean {
|
||||
// 检查 uiSchema 中所有 required 为 true 的字段
|
||||
for (const [fieldKey, fieldSchema] of Object.entries(uiSchema)) {
|
||||
if (fieldSchema.required === true) {
|
||||
// 如果字段是必需的,但当前参数中没有值或值为空
|
||||
const value = currentParams[fieldKey];
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const SchemaField = createSchemaField();
|
||||
|
||||
/**
|
||||
@ -87,16 +107,25 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
}
|
||||
});
|
||||
|
||||
// 如果有可配置的UI Schema,添加到列表中
|
||||
// 如果有可配置的UI Schema,检查是否已经有了所需的配置值
|
||||
if (Object.keys(mergedUiSchema).length > 0) {
|
||||
requiredSteps.push({
|
||||
flowKey,
|
||||
stepKey,
|
||||
step,
|
||||
uiSchema: mergedUiSchema,
|
||||
title: step.title || stepKey,
|
||||
flowTitle: flow.title || flowKey,
|
||||
});
|
||||
// 获取当前步骤的参数
|
||||
const currentStepParams = model.getStepParams(flowKey, stepKey) || {};
|
||||
|
||||
// 检查是否已经有了所需的配置值
|
||||
const hasAllRequiredParams = hasRequiredParams(mergedUiSchema, currentStepParams);
|
||||
|
||||
// 只有当缺少必需参数时才添加到列表中
|
||||
if (!hasAllRequiredParams) {
|
||||
requiredSteps.push({
|
||||
flowKey,
|
||||
stepKey,
|
||||
step,
|
||||
uiSchema: mergedUiSchema,
|
||||
title: step.title || stepKey,
|
||||
flowTitle: flow.title || flowKey,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,7 +172,7 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
// 构建分步表单的 Schema
|
||||
const stepPanes: Record<string, any> = {};
|
||||
|
||||
requiredSteps.forEach(({ flowKey, stepKey, uiSchema, title, flowTitle }, index) => {
|
||||
requiredSteps.forEach(({ flowKey, stepKey, uiSchema, title, flowTitle }) => {
|
||||
const stepId = `${flowKey}_${stepKey}`;
|
||||
|
||||
stepPanes[stepId] = {
|
||||
@ -267,7 +296,7 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
formStep.next();
|
||||
}
|
||||
})
|
||||
.catch((errors) => {
|
||||
.catch((errors: any) => {
|
||||
console.log('表单验证失败:', errors);
|
||||
// 可以在这里添加更详细的错误处理
|
||||
});
|
||||
@ -316,7 +345,7 @@ const openRequiredParamsStepFormDialog = async ({
|
||||
formStep.next();
|
||||
}
|
||||
})
|
||||
.catch((errors) => {
|
||||
.catch((errors: any) => {
|
||||
console.log('表单验证失败:', errors);
|
||||
// 可以在这里添加更详细的错误处理
|
||||
});
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { AddSubModelButton } from './AddSubModelButton';
|
||||
import { AddSubModelButton, SubModelItemsType } from './AddSubModelButton';
|
||||
import { FlowModel } from '../../models/flowModel';
|
||||
import { ModelConstructor } from '../../types';
|
||||
import { Button } from 'antd';
|
||||
@ -32,6 +32,14 @@ interface AddActionButtonProps {
|
||||
* 按钮文本
|
||||
*/
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* 过滤Model菜单的函数
|
||||
*/
|
||||
filter?: (blockClass: ModelConstructor, className: string) => boolean;
|
||||
/**
|
||||
* 自定义 items(如果提供,将覆盖默认的action菜单)
|
||||
*/
|
||||
items?: SubModelItemsType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,12 +59,17 @@ export const AddActionButton: React.FC<AddActionButtonProps> = ({
|
||||
subModelKey = 'actions',
|
||||
children = <Button>Configure actions</Button>,
|
||||
subModelType = 'array',
|
||||
items,
|
||||
filter,
|
||||
onModelAdded,
|
||||
}) => {
|
||||
const items = useMemo(() => {
|
||||
const blockClasses = model.flowEngine.filterModelClassByParent(subModelBaseClass);
|
||||
const allActionsItems = useMemo(() => {
|
||||
const actionClasses = model.flowEngine.filterModelClassByParent(subModelBaseClass);
|
||||
const registeredBlocks = [];
|
||||
for (const [className, ModelClass] of blockClasses) {
|
||||
for (const [className, ModelClass] of actionClasses) {
|
||||
if (filter && !filter(ModelClass, className)) {
|
||||
continue;
|
||||
}
|
||||
const item = {
|
||||
key: className,
|
||||
label: ModelClass.meta?.title || className,
|
||||
@ -76,7 +89,7 @@ export const AddActionButton: React.FC<AddActionButtonProps> = ({
|
||||
model={model}
|
||||
subModelKey={subModelKey}
|
||||
subModelType={subModelType}
|
||||
items={items}
|
||||
items={items ?? allActionsItems}
|
||||
onModelAdded={onModelAdded}
|
||||
>
|
||||
{children}
|
||||
|
@ -8,10 +8,11 @@
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { AddSubModelButton, SubModelItemsType } from './AddSubModelButton';
|
||||
import { AddSubModelButton, SubModelItemsType, mergeSubModelItems } from './AddSubModelButton';
|
||||
import { FlowModel } from '../../models/flowModel';
|
||||
import { ModelConstructor } from '../../types';
|
||||
import { Button } from 'antd';
|
||||
import { createBlockItems } from './blockItems';
|
||||
|
||||
interface AddBlockButtonProps {
|
||||
/**
|
||||
@ -33,19 +34,42 @@ interface AddBlockButtonProps {
|
||||
*/
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* 自定义 items
|
||||
* 自定义 items(如果提供,将覆盖默认的区块菜单)
|
||||
*/
|
||||
items?: SubModelItemsType;
|
||||
/**
|
||||
* 过滤Model菜单的函数
|
||||
*/
|
||||
filter?: (blockClass: ModelConstructor, className: string) => boolean;
|
||||
/**
|
||||
* 追加额外的菜单项到默认菜单中
|
||||
*/
|
||||
appendItems?: SubModelItemsType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 专门用于添加块模型的按钮组件
|
||||
*
|
||||
* 提供类似 page:addBlock 的区块类型 -> 数据源 -> 数据表的层级结构
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* // 基本用法
|
||||
* <AddBlockButton
|
||||
* model={parentModel}
|
||||
* subModelBaseClass={'FlowModel'}
|
||||
* subModelBaseClass={'BlockFlowModel'}
|
||||
* />
|
||||
*
|
||||
* // 追加自定义菜单项
|
||||
* <AddBlockButton
|
||||
* model={parentModel}
|
||||
* appendItems={[
|
||||
* {
|
||||
* key: 'customBlock',
|
||||
* label: 'Custom Block',
|
||||
* createModelOptions: { use: 'CustomBlock' }
|
||||
* }
|
||||
* ]}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
@ -56,31 +80,32 @@ export const AddBlockButton: React.FC<AddBlockButtonProps> = ({
|
||||
children = <Button>Add block</Button>,
|
||||
subModelType = 'array',
|
||||
items,
|
||||
filter: filterBlocks,
|
||||
appendItems,
|
||||
onModelAdded,
|
||||
}) => {
|
||||
const defaultItems = useMemo(() => {
|
||||
const blockClasses = model.flowEngine.filterModelClassByParent(subModelBaseClass);
|
||||
const registeredBlocks = [];
|
||||
for (const [className, ModelClass] of blockClasses) {
|
||||
registeredBlocks.push({
|
||||
key: className,
|
||||
label: ModelClass.meta?.title || className,
|
||||
icon: ModelClass.meta?.icon,
|
||||
createModelOptions: {
|
||||
...ModelClass.meta?.defaultOptions,
|
||||
use: className,
|
||||
},
|
||||
});
|
||||
// 确定最终使用的 items
|
||||
const finalItems = useMemo(() => {
|
||||
if (items) {
|
||||
// 如果明确提供了 items,直接使用
|
||||
return items;
|
||||
}
|
||||
return registeredBlocks;
|
||||
}, [model, subModelBaseClass]);
|
||||
|
||||
// 创建区块菜单项,并合并追加的 items
|
||||
const blockItems = createBlockItems(model, {
|
||||
subModelBaseClass,
|
||||
filterBlocks,
|
||||
});
|
||||
|
||||
return mergeSubModelItems([blockItems, appendItems]);
|
||||
}, [items, model, subModelBaseClass, filterBlocks, appendItems]);
|
||||
|
||||
return (
|
||||
<AddSubModelButton
|
||||
model={model}
|
||||
subModelKey={subModelKey}
|
||||
subModelType={subModelType}
|
||||
items={items || defaultItems}
|
||||
items={finalItems}
|
||||
onModelAdded={onModelAdded}
|
||||
>
|
||||
{children}
|
||||
|
@ -42,6 +42,10 @@ export interface AddFieldButtonProps {
|
||||
* 显示的UI组件
|
||||
*/
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* 自定义 items(如果提供,将覆盖默认的字段菜单)
|
||||
*/
|
||||
items?: SubModelItemsType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,6 +67,7 @@ export const AddFieldButton: React.FC<AddFieldButtonProps> = ({
|
||||
subModelType = 'array',
|
||||
collection,
|
||||
buildCreateModelOptions,
|
||||
items,
|
||||
appendItems,
|
||||
onModelAdded,
|
||||
}) => {
|
||||
@ -117,7 +122,7 @@ export const AddFieldButton: React.FC<AddFieldButtonProps> = ({
|
||||
};
|
||||
}, [model, subModelBaseClass, fields, buildCreateModelOptions]);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const fieldItems = useMemo(() => {
|
||||
return mergeSubModelItems([buildFieldItems, appendItems], { addDividers: true });
|
||||
}, [buildFieldItems, appendItems]);
|
||||
|
||||
@ -126,7 +131,7 @@ export const AddFieldButton: React.FC<AddFieldButtonProps> = ({
|
||||
model={model}
|
||||
subModelKey={subModelKey}
|
||||
subModelType={subModelType}
|
||||
items={items}
|
||||
items={items ?? fieldItems}
|
||||
onModelAdded={onModelAdded}
|
||||
>
|
||||
{children}
|
||||
|
189
packages/core/flow-engine/src/components/subModel/blockItems.ts
Normal file
189
packages/core/flow-engine/src/components/subModel/blockItems.ts
Normal file
@ -0,0 +1,189 @@
|
||||
/**
|
||||
* 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 { FlowModel } from '../../models/flowModel';
|
||||
import { ModelConstructor } from '../../types';
|
||||
import { SubModelItem, SubModelItemsType } from './AddSubModelButton';
|
||||
import { DataSource, DataSourceManager, Collection } from '../../data-source';
|
||||
|
||||
export interface BlockItemsOptions {
|
||||
/**
|
||||
* 子模型基类,用于确定支持的区块类型
|
||||
*/
|
||||
subModelBaseClass?: string | ModelConstructor;
|
||||
/**
|
||||
* 过滤区块类型的函数
|
||||
*/
|
||||
filterBlocks?: (blockClass: ModelConstructor, className: string) => boolean;
|
||||
/**
|
||||
* 自定义区块选项
|
||||
*/
|
||||
customBlocks?: SubModelItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据源和数据表信息
|
||||
* 从 flowEngine 的全局上下文获取数据源管理器信息
|
||||
*/
|
||||
async function getDataSourcesWithCollections(model: FlowModel) {
|
||||
try {
|
||||
// 从 flowEngine 的全局上下文获取数据源管理器
|
||||
const globalContext = model.flowEngine.getContext();
|
||||
const dataSourceManager: DataSourceManager = globalContext?.dataSourceManager;
|
||||
|
||||
if (!dataSourceManager) {
|
||||
// 如果没有数据源管理器,返回空数组
|
||||
return [];
|
||||
}
|
||||
|
||||
// 获取所有数据源
|
||||
const allDataSources: DataSource[] = dataSourceManager.getDataSources();
|
||||
|
||||
// 转换为我们需要的格式
|
||||
return allDataSources.map((dataSource: DataSource) => {
|
||||
const key = dataSource.name;
|
||||
const displayName = dataSource.options.displayName || dataSource.name;
|
||||
|
||||
// 从 collectionManager 获取 collections
|
||||
const collections: Collection[] = dataSource.getCollections();
|
||||
|
||||
return {
|
||||
key,
|
||||
displayName,
|
||||
collections: collections.map((collection: Collection) => ({
|
||||
name: collection.name,
|
||||
title: collection.title,
|
||||
dataSource: key,
|
||||
})),
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Failed to get data sources:', error);
|
||||
// 返回空数组,不提供假数据
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建区块菜单项的工具函数
|
||||
*
|
||||
* 生成包含数据区块和其他区块的完整菜单结构:
|
||||
* - 数据区块:区块类型 → 数据源 → 数据表的层级结构
|
||||
* - 其他区块:平铺的区块列表
|
||||
*
|
||||
* @param model FlowModel 实例
|
||||
* @param options 配置选项
|
||||
* @returns SubModelItemsType 格式的菜单项
|
||||
*/
|
||||
export function createBlockItems(model: FlowModel, options: BlockItemsOptions = {}): SubModelItemsType {
|
||||
const { subModelBaseClass = 'BlockFlowModel', filterBlocks, customBlocks = [] } = options;
|
||||
|
||||
// 获取所有注册的区块类
|
||||
const blockClasses = model.flowEngine.filterModelClassByParent(subModelBaseClass);
|
||||
|
||||
// 分类区块:数据区块 vs 其他区块
|
||||
const dataBlocks: Array<{ className: string; ModelClass: ModelConstructor }> = [];
|
||||
const otherBlocks: Array<{ className: string; ModelClass: ModelConstructor }> = [];
|
||||
|
||||
for (const [className, ModelClass] of blockClasses) {
|
||||
// 应用过滤器
|
||||
if (filterBlocks && !filterBlocks(ModelClass, className)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 判断是否为数据区块
|
||||
const meta = (ModelClass as any).meta;
|
||||
const isDataBlock =
|
||||
meta?.category === 'data' ||
|
||||
meta?.requiresDataSource === true ||
|
||||
className.toLowerCase().includes('table') ||
|
||||
className.toLowerCase().includes('form') ||
|
||||
className.toLowerCase().includes('details') ||
|
||||
className.toLowerCase().includes('list') ||
|
||||
className.toLowerCase().includes('grid');
|
||||
|
||||
if (isDataBlock) {
|
||||
dataBlocks.push({ className, ModelClass });
|
||||
} else {
|
||||
otherBlocks.push({ className, ModelClass });
|
||||
}
|
||||
}
|
||||
|
||||
const result: SubModelItem[] = [];
|
||||
|
||||
// 数据区块分组
|
||||
if (dataBlocks.length > 0) {
|
||||
result.push({
|
||||
key: 'dataBlocks',
|
||||
label: 'Data blocks',
|
||||
type: 'group',
|
||||
children: async () => {
|
||||
const dataSources = await getDataSourcesWithCollections(model);
|
||||
|
||||
// 按区块类型组织菜单:区块 → 数据源 → 数据表
|
||||
return dataBlocks.map(({ className, ModelClass }) => {
|
||||
const meta = (ModelClass as any).meta;
|
||||
return {
|
||||
key: className,
|
||||
label: meta?.title || className,
|
||||
icon: meta?.icon,
|
||||
children: dataSources.map((dataSource) => ({
|
||||
key: `${className}.${dataSource.key}`,
|
||||
label: dataSource.displayName,
|
||||
children: dataSource.collections.map((collection) => ({
|
||||
key: `${className}.${dataSource.key}.${collection.name}`,
|
||||
label: collection.title || collection.name,
|
||||
createModelOptions: {
|
||||
...meta?.defaultOptions,
|
||||
use: className,
|
||||
stepParams: {
|
||||
default: {
|
||||
step1: {
|
||||
dataSourceKey: dataSource.key,
|
||||
collectionName: collection.name,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
})),
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 其他区块分组
|
||||
if (otherBlocks.length > 0 || customBlocks.length > 0) {
|
||||
const otherBlockItems = [
|
||||
...otherBlocks.map(({ className, ModelClass }) => {
|
||||
const meta = (ModelClass as any).meta;
|
||||
return {
|
||||
key: className,
|
||||
label: meta?.title || className,
|
||||
icon: meta?.icon,
|
||||
createModelOptions: {
|
||||
...meta?.defaultOptions,
|
||||
use: className,
|
||||
},
|
||||
};
|
||||
}),
|
||||
...customBlocks,
|
||||
];
|
||||
|
||||
result.push({
|
||||
key: 'otherBlocks',
|
||||
label: 'Other blocks',
|
||||
type: 'group',
|
||||
children: otherBlockItems,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
@ -12,4 +12,5 @@ export * from './AddBlockButton';
|
||||
export * from './AddFieldButton';
|
||||
export * from './AddSubModel';
|
||||
export * from './AddSubModelButton';
|
||||
export * from './blockItems';
|
||||
//
|
||||
|
@ -227,13 +227,13 @@ export class FlowEngine {
|
||||
|
||||
async loadModel<T extends FlowModel = FlowModel>(uid: string): Promise<T | null> {
|
||||
if (!this.ensureModelRepository()) return;
|
||||
const data = await this.modelRepository.load(uid);
|
||||
const data = await this.modelRepository.findOne({ uid });
|
||||
return data?.uid ? this.createModel<T>(data as any) : null;
|
||||
}
|
||||
|
||||
async loadOrCreateModel<T extends FlowModel = FlowModel>(options): Promise<T | null> {
|
||||
if (!this.ensureModelRepository()) return;
|
||||
const data = await this.modelRepository.load(options.uid);
|
||||
const data = await this.modelRepository.findOne(options);
|
||||
if (data?.uid) {
|
||||
return this.createModel<T>(data as any);
|
||||
} else {
|
||||
|
@ -14,7 +14,6 @@ import { uid } from 'uid/secure';
|
||||
import { openRequiredParamsStepFormDialog as openRequiredParamsStepFormDialogFn } from '../components/settings/wrappers/contextual/StepRequiredSettingsDialog';
|
||||
import { openStepSettingsDialog as openStepSettingsDialogFn } from '../components/settings/wrappers/contextual/StepSettingsDialog';
|
||||
import { FlowEngine } from '../flowEngine';
|
||||
import { FlowExitException, resolveDefaultParams } from '../utils';
|
||||
import type {
|
||||
ActionStepDefinition,
|
||||
ArrayElementType,
|
||||
@ -30,7 +29,7 @@ import type {
|
||||
StepParams,
|
||||
} from '../types';
|
||||
import { ExtendedFlowDefinition, FlowExtraContext, IModelComponentProps, ReadonlyModelProps } from '../types';
|
||||
import { generateUid, mergeFlowDefinitions } from '../utils';
|
||||
import { FlowExitException, generateUid, mergeFlowDefinitions, resolveDefaultParams } from '../utils';
|
||||
import { ForkFlowModel } from './forkFlowModel';
|
||||
|
||||
// 使用WeakMap存储每个类的meta
|
||||
@ -47,6 +46,8 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
public flowEngine: FlowEngine;
|
||||
public parent: Structure['parent'];
|
||||
public subModels: Structure['subModels'];
|
||||
private _options: FlowModelOptions<Structure>;
|
||||
|
||||
/**
|
||||
* 所有 fork 实例的引用集合。
|
||||
* 使用 Set 便于在销毁时主动遍历并调用 dispose,避免悬挂引用。
|
||||
@ -60,29 +61,23 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
// model 树的共享运行上下文
|
||||
private _sharedContext: Record<string, any> = {};
|
||||
|
||||
public setSharedContext(ctx: Record<string, any>) {
|
||||
this._sharedContext = ctx;
|
||||
}
|
||||
|
||||
public getSharedContext() {
|
||||
return {
|
||||
...this.parent?.getSharedContext(),
|
||||
...this._sharedContext, // 当前实例的 context 优先级最高
|
||||
};
|
||||
}
|
||||
|
||||
constructor(protected options: FlowModelOptions<Structure>) {
|
||||
constructor(options: FlowModelOptions<Structure>) {
|
||||
if (options?.flowEngine?.getModel(options.uid)) {
|
||||
// 此时 new FlowModel 并不创建新实例,而是返回已存在的实例,避免重复创建同一个model实例
|
||||
return options.flowEngine.getModel(options.uid);
|
||||
}
|
||||
|
||||
this.uid = options.uid || uid();
|
||||
if (!options.uid) {
|
||||
options.uid = uid();
|
||||
}
|
||||
|
||||
this.uid = options.uid;
|
||||
this.props = options.props || {};
|
||||
this.stepParams = options.stepParams || {};
|
||||
this.subModels = {};
|
||||
this.flowEngine = options.flowEngine;
|
||||
this.sortIndex = options.sortIndex || 0;
|
||||
this._options = options;
|
||||
|
||||
define(this, {
|
||||
props: observable,
|
||||
@ -555,7 +550,7 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
throw new Error('Parent must be an instance of FlowModel.');
|
||||
}
|
||||
this.parent = parent;
|
||||
this.options.parentId = parent.uid;
|
||||
this._options.parentId = parent.uid;
|
||||
}
|
||||
|
||||
addSubModel(subKey: string, options: CreateModelOptions | FlowModel) {
|
||||
@ -731,11 +726,22 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
});
|
||||
}
|
||||
|
||||
public setSharedContext(ctx: Record<string, any>) {
|
||||
this._sharedContext = ctx;
|
||||
}
|
||||
|
||||
public getSharedContext() {
|
||||
return {
|
||||
...this.parent?.getSharedContext(),
|
||||
...this._sharedContext, // 当前实例的 context 优先级最高
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: 不完整,需要考虑 sub-model 的情况
|
||||
serialize(): Record<string, any> {
|
||||
const data = {
|
||||
uid: this.uid,
|
||||
..._.omit(this.options, ['flowEngine']),
|
||||
..._.omit(this._options, ['flowEngine']),
|
||||
props: this.props,
|
||||
stepParams: this.stepParams,
|
||||
sortIndex: this.sortIndex,
|
||||
|
@ -248,7 +248,7 @@ export interface CreateModelOptions {
|
||||
[key: string]: any; // 允许额外的自定义选项
|
||||
}
|
||||
export interface IFlowModelRepository<T extends FlowModel = FlowModel> {
|
||||
load(uid: string): Promise<Record<string, any> | null>;
|
||||
findOne(query: Record<string, any>): Promise<Record<string, any> | null>;
|
||||
save(model: T): Promise<Record<string, any>>;
|
||||
destroy(uid: string): Promise<boolean>;
|
||||
}
|
||||
|
@ -8,8 +8,8 @@
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { DeepPartial, ModelConstructor, FlowDefinition, ParamsContext, FlowContext } from './types';
|
||||
import type { FlowModel } from './models';
|
||||
import { ActionDefinition, DeepPartial, FlowContext, FlowDefinition, ModelConstructor, ParamsContext } from './types';
|
||||
|
||||
export function generateUid(): string {
|
||||
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
@ -118,3 +118,7 @@ export class FlowExitException extends Error {
|
||||
this.modelUid = modelUid;
|
||||
}
|
||||
}
|
||||
|
||||
export function defineAction(options: ActionDefinition) {
|
||||
return options;
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@nocobase/lock-manager",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"main": "lib/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"devDependencies": {
|
||||
"@nocobase/utils": "1.8.0-alpha.5",
|
||||
"@nocobase/utils": "1.8.0-alpha.8",
|
||||
"async-mutex": "^0.5.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/logger",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "nocobase logging library",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@nocobase/resourcer",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@nocobase/utils": "1.8.0-alpha.5",
|
||||
"@nocobase/utils": "1.8.0-alpha.8",
|
||||
"deepmerge": "^4.2.2",
|
||||
"koa-compose": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/sdk",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/server",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"main": "lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
@ -10,19 +10,19 @@
|
||||
"@koa/cors": "^5.0.0",
|
||||
"@koa/multer": "^3.1.0",
|
||||
"@koa/router": "^13.1.0",
|
||||
"@nocobase/acl": "1.8.0-alpha.5",
|
||||
"@nocobase/actions": "1.8.0-alpha.5",
|
||||
"@nocobase/auth": "1.8.0-alpha.5",
|
||||
"@nocobase/cache": "1.8.0-alpha.5",
|
||||
"@nocobase/data-source-manager": "1.8.0-alpha.5",
|
||||
"@nocobase/database": "1.8.0-alpha.5",
|
||||
"@nocobase/evaluators": "1.8.0-alpha.5",
|
||||
"@nocobase/lock-manager": "1.8.0-alpha.5",
|
||||
"@nocobase/logger": "1.8.0-alpha.5",
|
||||
"@nocobase/resourcer": "1.8.0-alpha.5",
|
||||
"@nocobase/sdk": "1.8.0-alpha.5",
|
||||
"@nocobase/telemetry": "1.8.0-alpha.5",
|
||||
"@nocobase/utils": "1.8.0-alpha.5",
|
||||
"@nocobase/acl": "1.8.0-alpha.8",
|
||||
"@nocobase/actions": "1.8.0-alpha.8",
|
||||
"@nocobase/auth": "1.8.0-alpha.8",
|
||||
"@nocobase/cache": "1.8.0-alpha.8",
|
||||
"@nocobase/data-source-manager": "1.8.0-alpha.8",
|
||||
"@nocobase/database": "1.8.0-alpha.8",
|
||||
"@nocobase/evaluators": "1.8.0-alpha.8",
|
||||
"@nocobase/lock-manager": "1.8.0-alpha.8",
|
||||
"@nocobase/logger": "1.8.0-alpha.8",
|
||||
"@nocobase/resourcer": "1.8.0-alpha.8",
|
||||
"@nocobase/sdk": "1.8.0-alpha.8",
|
||||
"@nocobase/telemetry": "1.8.0-alpha.8",
|
||||
"@nocobase/utils": "1.8.0-alpha.8",
|
||||
"@types/decompress": "4.2.7",
|
||||
"@types/ini": "^1.3.31",
|
||||
"@types/koa-send": "^4.1.3",
|
||||
|
@ -22,10 +22,14 @@ export default (app: Application) => {
|
||||
|
||||
const Collection = app.db.getCollection('collections');
|
||||
if (Collection) {
|
||||
// @ts-ignore
|
||||
await Collection.repository.setApp(app);
|
||||
// @ts-ignore
|
||||
await Collection.repository.load();
|
||||
}
|
||||
|
||||
app.log.info('syncing database...');
|
||||
|
||||
const force = false;
|
||||
await app.db.sync({
|
||||
force,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/telemetry",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"description": "nocobase telemetry library",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
@ -11,7 +11,7 @@
|
||||
"directory": "packages/telemetry"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nocobase/utils": "1.8.0-alpha.5",
|
||||
"@nocobase/utils": "1.8.0-alpha.8",
|
||||
"@opentelemetry/api": "^1.7.0",
|
||||
"@opentelemetry/instrumentation": "^0.46.0",
|
||||
"@opentelemetry/resources": "^1.19.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/test",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"main": "lib/index.js",
|
||||
"module": "./src/index.ts",
|
||||
"types": "./lib/index.d.ts",
|
||||
@ -51,7 +51,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "8.1.0",
|
||||
"@nocobase/server": "1.8.0-alpha.5",
|
||||
"@nocobase/server": "1.8.0-alpha.8",
|
||||
"@playwright/test": "^1.45.3",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/utils",
|
||||
"version": "1.8.0-alpha.5",
|
||||
"version": "1.8.0-alpha.8",
|
||||
"main": "lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
|
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