mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 05:29:26 +08:00
Merge branch 'develop' into feat/commercial
# Conflicts: # packages/core/cli/package.json
This commit is contained in:
commit
f1a88e2a89
4
.github/workflows/nocobase-test-backend.yml
vendored
4
.github/workflows/nocobase-test-backend.yml
vendored
@ -61,7 +61,9 @@ jobs:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
cache: 'yarn'
|
||||
- name: Install project dependencies
|
||||
run: yarn install
|
||||
run: |
|
||||
yarn install
|
||||
yarn add sqlite3 --no-save -W
|
||||
- name: Test with Sqlite
|
||||
run: yarn test --server --single-thread=false
|
||||
env:
|
||||
|
4
.github/workflows/nocobase-test-windows.yml
vendored
4
.github/workflows/nocobase-test-windows.yml
vendored
@ -63,7 +63,9 @@ jobs:
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install project dependencies
|
||||
run: yarn --prefer-offline
|
||||
run: |
|
||||
yarn --prefer-offline
|
||||
yarn add sqlite3 --no-save -W
|
||||
|
||||
- name: Test with Sqlite
|
||||
run: yarn test --server --single-thread=false
|
||||
|
82
CHANGELOG.md
82
CHANGELOG.md
@ -5,6 +5,88 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v1.6.24](https://github.com/nocobase/nocobase/compare/v1.6.23...v1.6.24) - 2025-04-24
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[client]** Adjust upload message ([#6757](https://github.com/nocobase/nocobase/pull/6757)) by @mytharcher
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- only export action in view collection is support when writableView is false ([#6763](https://github.com/nocobase/nocobase/pull/6763)) by @katherinehhh
|
||||
|
||||
- unexpected association data creation when displaying association field under sub-form/sub-table in create form ([#6727](https://github.com/nocobase/nocobase/pull/6727)) by @katherinehhh
|
||||
|
||||
- Incorrect data retrieved for many-to-many array fields from related tables in forms ([#6744](https://github.com/nocobase/nocobase/pull/6744)) by @2013xile
|
||||
|
||||
## [v1.6.23](https://github.com/nocobase/nocobase/compare/v1.6.22...v1.6.23) - 2025-04-23
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[cli]** Optimize internal logic of the `nocobase upgrade` command ([#6754](https://github.com/nocobase/nocobase/pull/6754)) by @chenos
|
||||
|
||||
- **[Template print]** Replaced datasource action control with client role-based access control. by @sheldon66
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[cli]** Auto-update package.json on upgrade ([#6747](https://github.com/nocobase/nocobase/pull/6747)) by @chenos
|
||||
|
||||
- **[client]**
|
||||
- missing filter for already associated data when adding association data ([#6750](https://github.com/nocobase/nocobase/pull/6750)) by @katherinehhh
|
||||
|
||||
- tree table 'Add Child' button linkage rule missing 'current record' ([#6752](https://github.com/nocobase/nocobase/pull/6752)) by @katherinehhh
|
||||
|
||||
- **[Action: Import records]** Fix the import and export exceptions that occur when setting field permissions. ([#6677](https://github.com/nocobase/nocobase/pull/6677)) by @aaaaaajie
|
||||
|
||||
- **[Block: Gantt]** gantt chart block overlapping months in calendar header for month view ([#6753](https://github.com/nocobase/nocobase/pull/6753)) by @katherinehhh
|
||||
|
||||
- **[Action: Export records Pro]**
|
||||
- pro export button losing filter parameters after sorting table column by @katherinehhh
|
||||
|
||||
- Fix the import and export exceptions that occur when setting field permissions. by @aaaaaajie
|
||||
|
||||
- **[File storage: S3(Pro)]** Fix response data of uploaded file by @mytharcher
|
||||
|
||||
- **[Workflow: Approval]** Fix preload association fields for records by @mytharcher
|
||||
|
||||
## [v1.6.22](https://github.com/nocobase/nocobase/compare/v1.6.21...v1.6.22) - 2025-04-22
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[create-nocobase-app]** Upgrade dependencies and remove SQLite support ([#6708](https://github.com/nocobase/nocobase/pull/6708)) by @chenos
|
||||
|
||||
- **[File manager]** Expose utils API ([#6705](https://github.com/nocobase/nocobase/pull/6705)) by @mytharcher
|
||||
|
||||
- **[Workflow]** Add date types to variable types set ([#6717](https://github.com/nocobase/nocobase/pull/6717)) by @mytharcher
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- The problem of mobile top navigation bar icons being difficult to delete ([#6734](https://github.com/nocobase/nocobase/pull/6734)) by @zhangzhonghe
|
||||
|
||||
- After connecting through a foreign key, clicking to trigger filtering results in empty filter conditions ([#6634](https://github.com/nocobase/nocobase/pull/6634)) by @zhangzhonghe
|
||||
|
||||
- picker switching issue in date field of filter button ([#6695](https://github.com/nocobase/nocobase/pull/6695)) by @katherinehhh
|
||||
|
||||
- The issue of the collapse button in the left menu being obscured by the workflow pop-up window ([#6733](https://github.com/nocobase/nocobase/pull/6733)) by @zhangzhonghe
|
||||
|
||||
- missing action option constraints when reopening linkage rules ([#6723](https://github.com/nocobase/nocobase/pull/6723)) by @katherinehhh
|
||||
|
||||
- export button shown without export permission ([#6689](https://github.com/nocobase/nocobase/pull/6689)) by @katherinehhh
|
||||
|
||||
- Required fields hidden by linkage rules should not affect form submission ([#6709](https://github.com/nocobase/nocobase/pull/6709)) by @zhangzhonghe
|
||||
|
||||
- **[server]** appVersion incorrectly generated by create-migration ([#6740](https://github.com/nocobase/nocobase/pull/6740)) by @chenos
|
||||
|
||||
- **[build]** Fix error thrown in tar command ([#6722](https://github.com/nocobase/nocobase/pull/6722)) by @mytharcher
|
||||
|
||||
- **[Workflow]** Fix error thrown when execute schedule event in subflow ([#6721](https://github.com/nocobase/nocobase/pull/6721)) by @mytharcher
|
||||
|
||||
- **[Workflow: Custom action event]** Support to execute in multiple records mode by @mytharcher
|
||||
|
||||
- **[File storage: S3(Pro)]** Add multer make logic for server-side upload by @mytharcher
|
||||
|
||||
## [v1.6.21](https://github.com/nocobase/nocobase/compare/v1.6.20...v1.6.21) - 2025-04-17
|
||||
|
||||
### 🚀 Improvements
|
||||
|
@ -5,6 +5,88 @@
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
||||
|
||||
## [v1.6.24](https://github.com/nocobase/nocobase/compare/v1.6.23...v1.6.24) - 2025-04-24
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[client]** 调整上传文件的提示信息 ([#6757](https://github.com/nocobase/nocobase/pull/6757)) by @mytharcher
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 视图表,无编辑权限时允许显示导出按钮 ([#6763](https://github.com/nocobase/nocobase/pull/6763)) by @katherinehhh
|
||||
|
||||
- 新增表单中显示关系字段子表格/子表单时关系数据也被新增 ([#6727](https://github.com/nocobase/nocobase/pull/6727)) by @katherinehhh
|
||||
|
||||
- 在表单中获取关联表中的多对多数组字段数据不正确 ([#6744](https://github.com/nocobase/nocobase/pull/6744)) by @2013xile
|
||||
|
||||
## [v1.6.23](https://github.com/nocobase/nocobase/compare/v1.6.22...v1.6.23) - 2025-04-23
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[cli]** 优化 `nocobase upgrade` 命令的内部实现逻辑 ([#6754](https://github.com/nocobase/nocobase/pull/6754)) by @chenos
|
||||
|
||||
- **[模板打印]** 用客户端角色访问控制替换了数据源操作权限控制。 by @sheldon66
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[cli]** 升级时自动更新项目的 package.json ([#6747](https://github.com/nocobase/nocobase/pull/6747)) by @chenos
|
||||
|
||||
- **[client]**
|
||||
- 添加关联表格时未过滤已关联的数据 ([#6750](https://github.com/nocobase/nocobase/pull/6750)) by @katherinehhh
|
||||
|
||||
- 树表格中添加子记录按钮的联动规则缺失「当前记录」变量 ([#6752](https://github.com/nocobase/nocobase/pull/6752)) by @katherinehhh
|
||||
|
||||
- **[操作:导入记录]** 修复设置字段权限时出现的导入导出异常。 ([#6677](https://github.com/nocobase/nocobase/pull/6677)) by @aaaaaajie
|
||||
|
||||
- **[区块:甘特图]** 甘特图区块设置月份视图时,日历头部月份重叠 ([#6753](https://github.com/nocobase/nocobase/pull/6753)) by @katherinehhh
|
||||
|
||||
- **[操作:导出记录 Pro]**
|
||||
- pro导出按钮在点击表格排序后丢失过滤参数 by @katherinehhh
|
||||
|
||||
- 修复设置字段权限时出现的导入导出异常。 by @aaaaaajie
|
||||
|
||||
- **[文件存储:S3 (Pro)]** 修复已上传文件的响应数据 by @mytharcher
|
||||
|
||||
- **[工作流:审批]** 修复预加载审批记录数据的关系字段 by @mytharcher
|
||||
|
||||
## [v1.6.22](https://github.com/nocobase/nocobase/compare/v1.6.21...v1.6.22) - 2025-04-22
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[create-nocobase-app]** 更新依赖,移除 SQLite 支持 ([#6708](https://github.com/nocobase/nocobase/pull/6708)) by @chenos
|
||||
|
||||
- **[文件管理器]** 暴露公共包 API ([#6705](https://github.com/nocobase/nocobase/pull/6705)) by @mytharcher
|
||||
|
||||
- **[工作流]** 为变量的类型集合增加日期相关类型 ([#6717](https://github.com/nocobase/nocobase/pull/6717)) by @mytharcher
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 移动端顶部的导航栏图标很难被删除的问题 ([#6734](https://github.com/nocobase/nocobase/pull/6734)) by @zhangzhonghe
|
||||
|
||||
- 通过外键连接后,点击触发筛选,筛选条件为空 ([#6634](https://github.com/nocobase/nocobase/pull/6634)) by @zhangzhonghe
|
||||
|
||||
- 筛选按钮中日期字段,切换picker 异常 ([#6695](https://github.com/nocobase/nocobase/pull/6695)) by @katherinehhh
|
||||
|
||||
- 左侧菜单的收起按钮会被绑定工作流弹窗遮挡的问题 ([#6733](https://github.com/nocobase/nocobase/pull/6733)) by @zhangzhonghe
|
||||
|
||||
- 重新打开联动规则时缺少操作选项约束 ([#6723](https://github.com/nocobase/nocobase/pull/6723)) by @katherinehhh
|
||||
|
||||
- 未设置导出权限时仍显示导出按钮 ([#6689](https://github.com/nocobase/nocobase/pull/6689)) by @katherinehhh
|
||||
|
||||
- 被联动规则隐藏的必填字段,不应该影响表单的提交 ([#6709](https://github.com/nocobase/nocobase/pull/6709)) by @zhangzhonghe
|
||||
|
||||
- **[server]** create-migration 命令生成的 appVersion 不准确 ([#6740](https://github.com/nocobase/nocobase/pull/6740)) by @chenos
|
||||
|
||||
- **[build]** 修复 tar 命令报错的问题 ([#6722](https://github.com/nocobase/nocobase/pull/6722)) by @mytharcher
|
||||
|
||||
- **[工作流]** 修复子流程执行定时任务报错的问题 ([#6721](https://github.com/nocobase/nocobase/pull/6721)) by @mytharcher
|
||||
|
||||
- **[工作流:自定义操作事件]** 支持多行记录模式的手动执行 by @mytharcher
|
||||
|
||||
- **[文件存储:S3 (Pro)]** 增加 multer 逻辑用于服务端上传 by @mytharcher
|
||||
|
||||
## [v1.6.21](https://github.com/nocobase/nocobase/compare/v1.6.20...v1.6.21) - 2025-04-17
|
||||
|
||||
### 🚀 优化
|
||||
|
@ -8,7 +8,11 @@ RUN cd /app \
|
||||
&& yarn config set network-timeout 600000 -g \
|
||||
&& npx -y create-nocobase-app@${CNA_VERSION} my-nocobase-app --skip-dev-dependencies -a -e APP_ENV=production \
|
||||
&& cd /app/my-nocobase-app \
|
||||
&& yarn install --production
|
||||
&& yarn install --production \
|
||||
&& rm -rf yarn.lock \
|
||||
&& find node_modules -type f -name "yarn.lock" -delete \
|
||||
&& find node_modules -type f -name "bower.json" -delete \
|
||||
&& find node_modules -type f -name "composer.json" -delete
|
||||
|
||||
RUN cd /app \
|
||||
&& rm -rf nocobase.tar.gz \
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"npmClientArgs": ["--ignore-engines"],
|
||||
|
@ -83,6 +83,7 @@
|
||||
"ghooks": "^2.0.4",
|
||||
"lint-staged": "^13.2.3",
|
||||
"patch-package": "^8.0.0",
|
||||
"pm2": "^6.0.5",
|
||||
"pretty-format": "^24.0.0",
|
||||
"pretty-quick": "^3.1.0",
|
||||
"react": "^18.0.0",
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/acl",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/resourcer": "1.7.0-alpha.11",
|
||||
"@nocobase/utils": "1.7.0-alpha.11",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.12",
|
||||
"@nocobase/utils": "1.7.0-alpha.12",
|
||||
"minimatch": "^5.1.1"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@nocobase/actions",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/cache": "1.7.0-alpha.11",
|
||||
"@nocobase/database": "1.7.0-alpha.11",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.11"
|
||||
"@nocobase/cache": "1.7.0-alpha.12",
|
||||
"@nocobase/database": "1.7.0-alpha.12",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.12"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "@nocobase/app",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/database": "1.7.0-alpha.11",
|
||||
"@nocobase/preset-nocobase": "1.7.0-alpha.11",
|
||||
"@nocobase/server": "1.7.0-alpha.11"
|
||||
"@nocobase/database": "1.7.0-alpha.12",
|
||||
"@nocobase/preset-nocobase": "1.7.0-alpha.12",
|
||||
"@nocobase/server": "1.7.0-alpha.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/client": "1.7.0-alpha.11"
|
||||
"@nocobase/client": "1.7.0-alpha.12"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@nocobase/auth",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/actions": "1.7.0-alpha.11",
|
||||
"@nocobase/cache": "1.7.0-alpha.11",
|
||||
"@nocobase/database": "1.7.0-alpha.11",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.11",
|
||||
"@nocobase/utils": "1.7.0-alpha.11",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"jsonwebtoken": "^8.5.1"
|
||||
"@nocobase/actions": "1.7.0-alpha.12",
|
||||
"@nocobase/cache": "1.7.0-alpha.12",
|
||||
"@nocobase/database": "1.7.0-alpha.12",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.12",
|
||||
"@nocobase/utils": "1.7.0-alpha.12",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/build",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"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.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/lock-manager": "1.7.0-alpha.11",
|
||||
"@nocobase/lock-manager": "1.7.0-alpha.12",
|
||||
"bloom-filters": "^3.0.1",
|
||||
"cache-manager": "^5.2.4",
|
||||
"cache-manager-redis-yet": "^4.1.2"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/cli",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./src/index.js",
|
||||
@ -8,12 +8,13 @@
|
||||
"nocobase": "./bin/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nocobase/app": "1.7.0-alpha.11",
|
||||
"@nocobase/app": "1.7.0-alpha.12",
|
||||
"@nocobase/license-kit": "^0.2.3",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@umijs/utils": "3.5.20",
|
||||
"chalk": "^4.1.1",
|
||||
"commander": "^9.2.0",
|
||||
"deepmerge": "^4.3.1",
|
||||
"dotenv": "^16.0.0",
|
||||
"execa": "^5.1.1",
|
||||
"fast-glob": "^3.3.1",
|
||||
@ -26,7 +27,7 @@
|
||||
"tsx": "^4.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/devtools": "1.7.0-alpha.11"
|
||||
"@nocobase/devtools": "1.7.0-alpha.12"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
const _ = require('lodash');
|
||||
const { Command } = require('commander');
|
||||
const { generatePlugins, run, postCheck, nodeCheck, promptForTs, isPortReachable } = require('../util');
|
||||
const { generatePlugins, run, postCheck, nodeCheck, promptForTs, isPortReachable, checkDBDialect } = require('../util');
|
||||
const { getPortPromise } = require('portfinder');
|
||||
const chokidar = require('chokidar');
|
||||
const { uid } = require('@formily/shared');
|
||||
@ -36,6 +36,7 @@ module.exports = (cli) => {
|
||||
.option('-i, --inspect [port]')
|
||||
.allowUnknownOption()
|
||||
.action(async (opts) => {
|
||||
checkDBDialect();
|
||||
let subprocess;
|
||||
const runDevClient = () => {
|
||||
console.log('starting client', 1 * clientPort);
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
const { Command } = require('commander');
|
||||
const { run, isPortReachable } = require('../util');
|
||||
const { run, isPortReachable, checkDBDialect } = require('../util');
|
||||
const { execSync } = require('node:child_process');
|
||||
const axios = require('axios');
|
||||
const { pTest } = require('./p-test');
|
||||
@ -165,6 +165,7 @@ const filterArgv = () => {
|
||||
*/
|
||||
module.exports = (cli) => {
|
||||
const e2e = cli.command('e2e').hook('preAction', () => {
|
||||
checkDBDialect();
|
||||
if (process.env.APP_BASE_URL) {
|
||||
process.env.APP_BASE_URL = process.env.APP_BASE_URL.replace('localhost', '127.0.0.1');
|
||||
console.log('APP_BASE_URL:', process.env.APP_BASE_URL);
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
const { Command } = require('commander');
|
||||
const { run, isDev, isProd, promptForTs, downloadPro } = require('../util');
|
||||
const { run, isDev, isProd, promptForTs, downloadPro, checkDBDialect } = require('../util');
|
||||
|
||||
/**
|
||||
*
|
||||
@ -21,6 +21,7 @@ module.exports = (cli) => {
|
||||
.option('-h, --help')
|
||||
.option('--ts-node-dev')
|
||||
.action(async (options) => {
|
||||
checkDBDialect();
|
||||
const cmd = process.argv.slice(2)?.[0];
|
||||
if (cmd === 'install') {
|
||||
await downloadPro();
|
||||
|
@ -30,6 +30,7 @@ module.exports = (cli) => {
|
||||
require('./test')(cli);
|
||||
require('./test-coverage')(cli);
|
||||
require('./umi')(cli);
|
||||
require('./update-deps')(cli);
|
||||
require('./upgrade')(cli);
|
||||
require('./postinstall')(cli);
|
||||
require('./pkg')(cli);
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
const _ = require('lodash');
|
||||
const { Command } = require('commander');
|
||||
const { run, postCheck, downloadPro, promptForTs } = require('../util');
|
||||
const { run, postCheck, downloadPro, promptForTs, checkDBDialect } = require('../util');
|
||||
const { existsSync, rmSync } = require('fs');
|
||||
const { resolve, isAbsolute } = require('path');
|
||||
const chalk = require('chalk');
|
||||
@ -48,8 +48,10 @@ module.exports = (cli) => {
|
||||
.option('-i, --instances [instances]')
|
||||
.option('--db-sync')
|
||||
.option('--quickstart')
|
||||
.option('--launch-mode [launchMode]')
|
||||
.allowUnknownOption()
|
||||
.action(async (opts) => {
|
||||
checkDBDialect();
|
||||
if (opts.quickstart) {
|
||||
await downloadPro();
|
||||
}
|
||||
@ -118,17 +120,27 @@ module.exports = (cli) => {
|
||||
]);
|
||||
process.exit();
|
||||
} else {
|
||||
run(
|
||||
'pm2-runtime',
|
||||
[
|
||||
'start',
|
||||
...instancesArgs,
|
||||
`${APP_PACKAGE_ROOT}/lib/index.js`,
|
||||
NODE_ARGS ? `--node-args="${NODE_ARGS}"` : undefined,
|
||||
'--',
|
||||
...process.argv.slice(2),
|
||||
].filter(Boolean),
|
||||
);
|
||||
const launchMode = opts.launchMode || process.env.APP_LAUNCH_MODE || 'pm2';
|
||||
if (launchMode === 'pm2') {
|
||||
run(
|
||||
'pm2-runtime',
|
||||
[
|
||||
'start',
|
||||
...instancesArgs,
|
||||
`${APP_PACKAGE_ROOT}/lib/index.js`,
|
||||
NODE_ARGS ? `--node-args="${NODE_ARGS}"` : undefined,
|
||||
'--',
|
||||
...process.argv.slice(2),
|
||||
].filter(Boolean),
|
||||
);
|
||||
} else {
|
||||
run(
|
||||
'node',
|
||||
[`${APP_PACKAGE_ROOT}/lib/index.js`, ...(NODE_ARGS || '').split(' '), ...process.argv.slice(2)].filter(
|
||||
Boolean,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
const { run } = require('../util');
|
||||
const { run, checkDBDialect } = require('../util');
|
||||
const fg = require('fast-glob');
|
||||
|
||||
const coreClientPackages = ['packages/core/client', 'packages/core/sdk'];
|
||||
@ -30,6 +30,7 @@ const getPackagesDir = (isClient) => {
|
||||
|
||||
module.exports = (cli) => {
|
||||
cli.command('test-coverage:server').action(async () => {
|
||||
checkDBDialect();
|
||||
const packageRoots = getPackagesDir(false);
|
||||
for (const dir of packageRoots) {
|
||||
try {
|
||||
@ -41,6 +42,7 @@ module.exports = (cli) => {
|
||||
});
|
||||
|
||||
cli.command('test-coverage:client').action(async () => {
|
||||
checkDBDialect();
|
||||
const packageRoots = getPackagesDir(true);
|
||||
for (const dir of packageRoots) {
|
||||
try {
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
const { Command } = require('commander');
|
||||
const { run } = require('../util');
|
||||
const { run, checkDBDialect } = require('../util');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
@ -29,6 +29,7 @@ function addTestCommand(name, cli) {
|
||||
.arguments('[paths...]')
|
||||
.allowUnknownOption()
|
||||
.action(async (paths, opts) => {
|
||||
checkDBDialect();
|
||||
if (name === 'test:server') {
|
||||
process.env.TEST_ENV = 'server-side';
|
||||
} else if (name === 'test:client') {
|
||||
|
71
packages/core/cli/src/commands/update-deps.js
Normal file
71
packages/core/cli/src/commands/update-deps.js
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
const chalk = require('chalk');
|
||||
const { Command } = require('commander');
|
||||
const { resolve } = require('path');
|
||||
const { run, promptForTs, runAppCommand, hasCorePackages, downloadPro, hasTsNode, checkDBDialect } = require('../util');
|
||||
const { existsSync, rmSync } = require('fs');
|
||||
const { readJSON, writeJSON } = require('fs-extra');
|
||||
const deepmerge = require('deepmerge');
|
||||
|
||||
const rmAppDir = () => {
|
||||
// If ts-node is not installed, do not do the following
|
||||
const appDevDir = resolve(process.cwd(), './storage/.app-dev');
|
||||
if (existsSync(appDevDir)) {
|
||||
rmSync(appDevDir, { recursive: true, force: true });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Command} cli
|
||||
*/
|
||||
module.exports = (cli) => {
|
||||
cli
|
||||
.command('update-deps')
|
||||
.option('--force')
|
||||
.allowUnknownOption()
|
||||
.action(async (options) => {
|
||||
if (hasCorePackages() || !hasTsNode()) {
|
||||
await downloadPro();
|
||||
return;
|
||||
}
|
||||
const pkg = require('../../package.json');
|
||||
let distTag = 'latest';
|
||||
if (pkg.version.includes('alpha')) {
|
||||
distTag = 'alpha';
|
||||
} else if (pkg.version.includes('beta')) {
|
||||
distTag = 'beta';
|
||||
}
|
||||
const { stdout } = await run('npm', ['info', `@nocobase/cli@${distTag}`, 'version'], {
|
||||
stdio: 'pipe',
|
||||
});
|
||||
if (!options.force && pkg.version === stdout) {
|
||||
await downloadPro();
|
||||
rmAppDir();
|
||||
return;
|
||||
}
|
||||
const descPath = resolve(process.cwd(), 'package.json');
|
||||
const descJson = await readJSON(descPath, 'utf8');
|
||||
const sourcePath = resolve(__dirname, '../../templates/create-app-package.json');
|
||||
const sourceJson = await readJSON(sourcePath, 'utf8');
|
||||
if (descJson['dependencies']?.['@nocobase/cli']) {
|
||||
descJson['dependencies']['@nocobase/cli'] = stdout;
|
||||
}
|
||||
if (descJson['devDependencies']?.['@nocobase/devtools']) {
|
||||
descJson['devDependencies']['@nocobase/devtools'] = stdout;
|
||||
}
|
||||
const json = deepmerge(descJson, sourceJson);
|
||||
await writeJSON(descPath, json, { spaces: 2, encoding: 'utf8' });
|
||||
await run('yarn', ['install']);
|
||||
await downloadPro();
|
||||
rmAppDir();
|
||||
});
|
||||
};
|
@ -10,15 +10,25 @@
|
||||
const chalk = require('chalk');
|
||||
const { Command } = require('commander');
|
||||
const { resolve } = require('path');
|
||||
const { run, promptForTs, runAppCommand, hasCorePackages, downloadPro, hasTsNode } = require('../util');
|
||||
const { run, promptForTs, runAppCommand, hasCorePackages, downloadPro, hasTsNode, checkDBDialect } = require('../util');
|
||||
const { existsSync, rmSync } = require('fs');
|
||||
const { readJSON, writeJSON } = require('fs-extra');
|
||||
const deepmerge = require('deepmerge');
|
||||
|
||||
async function updatePackage() {
|
||||
const sourcePath = resolve(__dirname, '../../templates/create-app-package.json');
|
||||
const descPath = resolve(process.cwd(), 'package.json');
|
||||
const sourceJson = await readJSON(sourcePath, 'utf8');
|
||||
const descJson = await readJSON(descPath, 'utf8');
|
||||
const json = deepmerge(descJson, sourceJson);
|
||||
await writeJSON(descPath, json, { spaces: 2, encoding: 'utf8' });
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Command} cli
|
||||
*/
|
||||
module.exports = (cli) => {
|
||||
const { APP_PACKAGE_ROOT } = process.env;
|
||||
cli
|
||||
.command('upgrade')
|
||||
.allowUnknownOption()
|
||||
@ -26,52 +36,12 @@ module.exports = (cli) => {
|
||||
.option('--next')
|
||||
.option('-S|--skip-code-update')
|
||||
.action(async (options) => {
|
||||
if (hasTsNode()) promptForTs();
|
||||
if (hasCorePackages()) {
|
||||
// await run('yarn', ['install']);
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
return;
|
||||
}
|
||||
checkDBDialect();
|
||||
if (options.skipCodeUpdate) {
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
return;
|
||||
} else {
|
||||
await run('nocobase', ['update-deps']);
|
||||
await run('nocobase', ['upgrade', '--skip-code-update']);
|
||||
}
|
||||
// await runAppCommand('upgrade');
|
||||
if (!hasTsNode()) {
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
return;
|
||||
}
|
||||
const rmAppDir = () => {
|
||||
// If ts-node is not installed, do not do the following
|
||||
const appDevDir = resolve(process.cwd(), './storage/.app-dev');
|
||||
if (existsSync(appDevDir)) {
|
||||
rmSync(appDevDir, { recursive: true, force: true });
|
||||
}
|
||||
};
|
||||
const pkg = require('../../package.json');
|
||||
let distTag = 'latest';
|
||||
if (pkg.version.includes('alpha')) {
|
||||
distTag = 'alpha';
|
||||
} else if (pkg.version.includes('beta')) {
|
||||
distTag = 'beta';
|
||||
}
|
||||
// get latest version
|
||||
const { stdout } = await run('npm', ['info', `@nocobase/cli@${distTag}`, 'version'], {
|
||||
stdio: 'pipe',
|
||||
});
|
||||
if (pkg.version === stdout) {
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
await rmAppDir();
|
||||
return;
|
||||
}
|
||||
await run('yarn', ['add', `@nocobase/cli@${distTag}`, `@nocobase/devtools@${distTag}`, '-W']);
|
||||
await run('yarn', ['install']);
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
await rmAppDir();
|
||||
});
|
||||
};
|
||||
|
@ -362,7 +362,7 @@ exports.initEnv = function initEnv() {
|
||||
API_BASE_PATH: '/api/',
|
||||
API_CLIENT_STORAGE_PREFIX: 'NOCOBASE_',
|
||||
API_CLIENT_STORAGE_TYPE: 'localStorage',
|
||||
DB_DIALECT: 'sqlite',
|
||||
// DB_DIALECT: 'sqlite',
|
||||
DB_STORAGE: 'storage/db/nocobase.sqlite',
|
||||
// DB_TIMEZONE: '+00:00',
|
||||
DB_UNDERSCORED: parseEnv('DB_UNDERSCORED'),
|
||||
@ -474,6 +474,12 @@ exports.initEnv = function initEnv() {
|
||||
}
|
||||
};
|
||||
|
||||
exports.checkDBDialect = function () {
|
||||
if (!process.env.DB_DIALECT) {
|
||||
throw new Error('DB_DIALECT is required.');
|
||||
}
|
||||
};
|
||||
|
||||
exports.generatePlugins = function () {
|
||||
try {
|
||||
require.resolve('@nocobase/devtools/umiConfig');
|
||||
|
39
packages/core/cli/templates/create-app-package.json
Normal file
39
packages/core/cli/templates/create-app-package.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"private": true,
|
||||
"workspaces": ["packages/*/*", "packages/*/*/*"],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"scripts": {
|
||||
"nocobase": "nocobase",
|
||||
"pm": "nocobase pm",
|
||||
"pm2": "nocobase pm2",
|
||||
"dev": "nocobase dev",
|
||||
"start": "nocobase start",
|
||||
"clean": "nocobase clean",
|
||||
"build": "nocobase build",
|
||||
"test": "nocobase test",
|
||||
"e2e": "nocobase e2e",
|
||||
"tar": "nocobase tar",
|
||||
"postinstall": "nocobase postinstall",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"resolutions": {
|
||||
"cytoscape": "3.28.0",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"react-router-dom": "6.28.1",
|
||||
"react-router": "6.28.1",
|
||||
"async": "^3.2.6",
|
||||
"antd": "5.12.8",
|
||||
"rollup": "4.24.0",
|
||||
"semver": "^7.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"pm2": "^6.0.5",
|
||||
"mysql2": "^3.14.0",
|
||||
"mariadb": "^2.5.6",
|
||||
"pg": "^8.14.1",
|
||||
"pg-hstore": "^2.3.4"
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/client",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.mjs",
|
||||
@ -27,9 +27,9 @@
|
||||
"@formily/reactive-react": "^2.2.27",
|
||||
"@formily/shared": "^2.2.27",
|
||||
"@formily/validator": "^2.2.27",
|
||||
"@nocobase/evaluators": "1.7.0-alpha.11",
|
||||
"@nocobase/sdk": "1.7.0-alpha.11",
|
||||
"@nocobase/utils": "1.7.0-alpha.11",
|
||||
"@nocobase/evaluators": "1.7.0-alpha.12",
|
||||
"@nocobase/sdk": "1.7.0-alpha.12",
|
||||
"@nocobase/utils": "1.7.0-alpha.12",
|
||||
"ahooks": "^3.7.2",
|
||||
"antd": "5.24.2",
|
||||
"antd-style": "3.7.1",
|
||||
|
@ -316,6 +316,8 @@ export const ACLActionProvider = (props) => {
|
||||
let actionPath = schema['x-acl-action'];
|
||||
// 只兼容这些数据表资源按钮
|
||||
const resourceActionPath = ['create', 'update', 'destroy', 'importXlsx', 'export'];
|
||||
// 视图表无编辑权限时不支持的操作
|
||||
const writableViewCollectionAction = ['create', 'update', 'destroy', 'importXlsx', 'bulkDestroy', 'bulkUpdate'];
|
||||
|
||||
if (!actionPath && resource && schema['x-action'] && resourceActionPath.includes(schema['x-action'])) {
|
||||
actionPath = `${resource}:${schema['x-action']}`;
|
||||
@ -339,16 +341,18 @@ export const ACLActionProvider = (props) => {
|
||||
if (!params) {
|
||||
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
|
||||
}
|
||||
//视图表无编辑权限时不显示
|
||||
if (resourceActionPath.includes(actionPath) || resourceActionPath.includes(actionPath?.split(':')[1])) {
|
||||
//视图表无编辑权限时不支持 writableViewCollectionAction 的按钮
|
||||
if (
|
||||
writableViewCollectionAction.includes(actionPath) ||
|
||||
writableViewCollectionAction.includes(actionPath?.split(':')[1])
|
||||
) {
|
||||
if ((collection && collection.template !== 'view') || collection?.writableView) {
|
||||
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
|
||||
}
|
||||
return null;
|
||||
return <ACLActionParamsContext.Provider value={false}>{props.children}</ACLActionParamsContext.Provider>;
|
||||
}
|
||||
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
|
||||
};
|
||||
|
||||
export const useACLFieldWhitelist = () => {
|
||||
const params = useContext(ACLActionParamsContext);
|
||||
const whitelist = useMemo(() => {
|
||||
|
@ -236,7 +236,6 @@ export const scopesSchema: ISchema = {
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
icon: 'EditOutlined',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
|
@ -167,7 +167,7 @@ export function getOperators() {
|
||||
const dateA = parseDate(a);
|
||||
const dateB = parseDate(b);
|
||||
if (!dateA || !dateB) {
|
||||
throw new Error('Invalid date format');
|
||||
return false;
|
||||
}
|
||||
return dateA < dateB;
|
||||
},
|
||||
@ -651,10 +651,11 @@ function parseYear(dateStr) {
|
||||
}
|
||||
|
||||
function parseDate(targetDateStr) {
|
||||
let dateStr = Array.isArray(targetDateStr) ? targetDateStr[1] : targetDateStr;
|
||||
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(dateStr)) {
|
||||
// ISO 8601 格式:YYYY-MM-DDTHH:mm:ss.sssZ
|
||||
return new Date(dateStr); // 直接解析为 Date 对象
|
||||
let dateStr = Array.isArray(targetDateStr) ? targetDateStr[1] ?? targetDateStr[0] : targetDateStr;
|
||||
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(dateStr)) {
|
||||
return new Date(dateStr);
|
||||
} else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(dateStr)) {
|
||||
return new Date(dateStr.replace(' ', 'T'));
|
||||
} else if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
||||
// YYYY-MM-DD 格式
|
||||
return parseFullDate(dateStr);
|
||||
@ -668,5 +669,6 @@ function parseDate(targetDateStr) {
|
||||
// YYYY 格式
|
||||
return parseYear(dateStr);
|
||||
}
|
||||
return null; // Invalid format
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -97,6 +97,30 @@ const filterValue = (value) => {
|
||||
return obj;
|
||||
};
|
||||
|
||||
function getFilteredFormValues(form) {
|
||||
const values = _.cloneDeep(form.values);
|
||||
const allFields = [];
|
||||
form.query('*').forEach((field) => {
|
||||
if (field) {
|
||||
allFields.push(field);
|
||||
}
|
||||
});
|
||||
const readonlyPaths = allFields
|
||||
.filter((field) => field?.componentProps?.readOnlySubmit)
|
||||
.map((field) => {
|
||||
const segments = field.path?.segments || [];
|
||||
if (segments.length <= 1) {
|
||||
return segments.join('.');
|
||||
}
|
||||
return segments.slice(0, -1).join('.');
|
||||
});
|
||||
for (const path of readonlyPaths) {
|
||||
_.unset(values, path);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
export function getFormValues({
|
||||
filterByTk,
|
||||
field,
|
||||
@ -124,7 +148,7 @@ export function getFormValues({
|
||||
}
|
||||
}
|
||||
|
||||
return form.values;
|
||||
return getFilteredFormValues(form);
|
||||
}
|
||||
|
||||
export function useCollectValuesToSubmit() {
|
||||
@ -203,8 +227,8 @@ export function useCollectValuesToSubmit() {
|
||||
]);
|
||||
}
|
||||
|
||||
function interpolateVariables(str: string, scope: Record<string, any>): string {
|
||||
return str.replace(/\{\{\s*([a-zA-Z0-9_$-.]+?)\s*\}\}/g, (_, key) => {
|
||||
export function interpolateVariables(str: string, scope: Record<string, any>): string {
|
||||
return str.replace(/\{\{\s*([a-zA-Z0-9_$.-]+?)\s*\}\}/g, (_, key) => {
|
||||
return scope[key] !== undefined ? String(scope[key]) : '';
|
||||
});
|
||||
}
|
||||
|
@ -822,7 +822,7 @@
|
||||
"File size exceeds the limit": "文件大小超过限制",
|
||||
"File type is not allowed": "文件类型不允许",
|
||||
"Uploading": "上传中",
|
||||
"Incomplete uploading files need to be resolved": "未完成上传的文件需要处理",
|
||||
"Some files are not uploaded correctly, please check.": "部分文件未上传成功,请检查。",
|
||||
"Default title for each record": "用作数据的默认标题",
|
||||
"If collection inherits, choose inherited collections as templates": "当前表有继承关系时,可选择继承链路上的表作为模板来源",
|
||||
"Select an existing piece of data as the initialization data for the form": "选择一条已有的数据作为表单的初始化数据",
|
||||
|
@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React, { useState, useContext } from 'react';
|
||||
import React, { useState, useContext, useEffect } from 'react';
|
||||
import { RecordPickerProvider, RecordPickerContext } from '../../../schema-component/antd/record-picker';
|
||||
import {
|
||||
SchemaComponentOptions,
|
||||
@ -41,9 +41,16 @@ const useTableSelectorProps = () => {
|
||||
export const AssociateActionProvider = (props) => {
|
||||
const [selectedRows, setSelectedRows] = useState([]);
|
||||
const collection = useCollection();
|
||||
const { resource, service, block, __parent } = useBlockRequestContext();
|
||||
const { resource, block, __parent } = useBlockRequestContext();
|
||||
const actionCtx = useActionContext();
|
||||
const { isMobile } = useOpenModeContext() || {};
|
||||
const [associationData, setAssociationData] = useState([]);
|
||||
useEffect(() => {
|
||||
resource?.list?.().then((res) => {
|
||||
setAssociationData(res.data?.data || []);
|
||||
});
|
||||
}, [resource]);
|
||||
|
||||
const pickerProps = {
|
||||
size: 'small',
|
||||
onChange: props?.onChange,
|
||||
@ -73,8 +80,8 @@ export const AssociateActionProvider = (props) => {
|
||||
};
|
||||
const getFilter = () => {
|
||||
const targetKey = collection?.filterTargetKey || 'id';
|
||||
if (service.data?.data) {
|
||||
const list = service.data?.data.map((option) => option[targetKey]).filter(Boolean);
|
||||
if (associationData) {
|
||||
const list = associationData.map((option) => option[targetKey]).filter(Boolean);
|
||||
const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {};
|
||||
return filter;
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ export const Action: ComposedAction = withDynamicSchemaProps(
|
||||
className={className}
|
||||
type={props.type}
|
||||
Designer={Designer}
|
||||
onClick={onClick}
|
||||
onClick={handleClick}
|
||||
confirm={confirm}
|
||||
confirmTitle={confirmTitle}
|
||||
popover={popover}
|
||||
@ -354,11 +354,7 @@ const InternalAction: React.FC<InternalActionProps> = observer(function Com(prop
|
||||
}
|
||||
|
||||
if (addChild) {
|
||||
return wrapSSR(
|
||||
<RecordProvider record={null} parent={parentRecordData}>
|
||||
<TreeRecordProvider parent={recordData}>{result}</TreeRecordProvider>
|
||||
</RecordProvider>,
|
||||
) as React.ReactElement;
|
||||
return wrapSSR(<TreeRecordProvider parent={recordData}>{result}</TreeRecordProvider>) as React.ReactElement;
|
||||
}
|
||||
|
||||
return wrapSSR(result) as React.ReactElement;
|
||||
|
@ -29,10 +29,9 @@ export const useAfterSuccessOptions = () => {
|
||||
}, [fieldsOptions, userFieldOptions]);
|
||||
const { settings: popupRecordSettings, shouldDisplayPopupRecord } = usePopupVariable();
|
||||
const { currentRoleSettings } = useCurrentRoleVariable();
|
||||
const record = useCollectionRecordData();
|
||||
return useMemo(() => {
|
||||
return [
|
||||
(record || form) && {
|
||||
form && {
|
||||
value: '$record',
|
||||
label: t('Response record', { ns: 'client' }),
|
||||
children: [...fields],
|
||||
|
@ -34,7 +34,7 @@ import useServiceOptions, { useAssociationFieldContext } from './hooks';
|
||||
|
||||
const removeIfKeyEmpty = (obj, filterTargetKey) => {
|
||||
if (!obj || typeof obj !== 'object' || !filterTargetKey || Array.isArray(obj)) return obj;
|
||||
return !obj[filterTargetKey] ? null : obj;
|
||||
return !obj[filterTargetKey] ? undefined : obj;
|
||||
};
|
||||
|
||||
export const AssociationFieldAddNewer = (props) => {
|
||||
@ -106,8 +106,13 @@ const InternalAssociationSelect = observer(
|
||||
useEffect(() => {
|
||||
const initValue = isVariable(field.value) ? undefined : field.value;
|
||||
const value = Array.isArray(initValue) ? initValue.filter(Boolean) : initValue;
|
||||
setInnerValue(value);
|
||||
}, [field.value]);
|
||||
const result = removeIfKeyEmpty(value, filterTargetKey);
|
||||
setInnerValue(result);
|
||||
if (!isEqual(field.value, result)) {
|
||||
field.value = result;
|
||||
}
|
||||
}, [field.value, filterTargetKey]);
|
||||
|
||||
useEffect(() => {
|
||||
const id = uid();
|
||||
form.addEffects(id, () => {
|
||||
|
@ -101,6 +101,10 @@ const useLazyLoadDisplayAssociationFieldsOfForm = () => {
|
||||
field.value = null;
|
||||
} else {
|
||||
field.value = result;
|
||||
field.componentProps = {
|
||||
...field.componentProps,
|
||||
readOnlySubmit: true,
|
||||
}; // 让它不参与提交
|
||||
}
|
||||
});
|
||||
})
|
||||
|
@ -36,16 +36,15 @@ const findOption = (str, options) => {
|
||||
|
||||
const [firstKey, ...subKeys] = match[1].split('.'); // 拆分层级
|
||||
const keys = [`$${firstKey}`, ...subKeys]; // 第一层保留 `$`,后续不带 `$`
|
||||
|
||||
let currentOptions = options;
|
||||
let option = null;
|
||||
for (const key of keys) {
|
||||
option = currentOptions.find((opt) => opt.value === key);
|
||||
option = currentOptions.find((opt) => opt.value === key || opt.name === key);
|
||||
if (!option) return null;
|
||||
|
||||
// 进入下一层 children 查找
|
||||
if (Array.isArray(option.children) || option.isLeaf === false) {
|
||||
currentOptions = option.children;
|
||||
currentOptions = option.children || option.field.children;
|
||||
} else {
|
||||
return option; // 没有 children 直接返回
|
||||
}
|
||||
|
@ -393,10 +393,11 @@ export function Uploader({ rules, ...props }: UploadProps) {
|
||||
|
||||
useEffect(() => {
|
||||
if (pendingList.length) {
|
||||
const errorFiles = pendingList.filter((item) => item.status === 'error');
|
||||
field.setFeedback({
|
||||
type: 'error',
|
||||
code: 'ValidateError',
|
||||
messages: [t('Incomplete uploading files need to be resolved')],
|
||||
messages: [errorFiles.length ? t('Some files are not uploaded correctly, please check.') : ' '],
|
||||
});
|
||||
} else {
|
||||
field.setFeedback({});
|
||||
|
@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { dayjs, getPickerFormat, Handlebars } from '@nocobase/utils/client';
|
||||
import { dayjs, getPickerFormat, Handlebars, getFormatFromDateStr } from '@nocobase/utils/client';
|
||||
import _, { every, findIndex, some } from 'lodash';
|
||||
import { replaceVariableValue } from '../../../block-provider/hooks';
|
||||
import { VariableOption, VariablesContextType } from '../../../variables/types';
|
||||
|
@ -352,11 +352,6 @@ const InternalSchemaToolbar: FC<SchemaToolbarProps> = React.memo((props) => {
|
||||
}
|
||||
}
|
||||
|
||||
// const style = window.getComputedStyle(parentElement);
|
||||
// if (style.position === 'static') {
|
||||
// parentElement.style.position = 'relative';
|
||||
// }
|
||||
|
||||
el.addEventListener('mouseenter', show);
|
||||
el.addEventListener('mouseleave', hide);
|
||||
return () => {
|
||||
|
@ -131,7 +131,7 @@ export const ValueDynamicComponent = (props: ValueDynamicComponentProps) => {
|
||||
<span style={{ marginLeft: '.25em' }} className={'ant-formily-item-extra'}>
|
||||
{t('Syntax references')}:
|
||||
</span>
|
||||
<a href="https://formulajs.info/functions/" target="_blank" rel="noreferrer">
|
||||
<a href="https://docs.nocobase.com/handbook/calculation-engines/formula" target="_blank" rel="noreferrer">
|
||||
Formula.js
|
||||
</a>
|
||||
</div>
|
||||
|
@ -48,6 +48,7 @@ import {
|
||||
SchemaSettingsItemType,
|
||||
SchemaToolbarVisibleContext,
|
||||
VariablesContext,
|
||||
getZIndex,
|
||||
useCollection,
|
||||
useCollectionManager,
|
||||
useZIndexContext,
|
||||
@ -697,7 +698,7 @@ export const SchemaSettingsActionModalItem: FC<SchemaSettingsActionModalItemProp
|
||||
const upLevelActiveFields = useFormActiveFields();
|
||||
const parentZIndex = useZIndexContext();
|
||||
|
||||
const zIndex = parentZIndex + 10;
|
||||
const zIndex = getZIndex('modal', parentZIndex + 10, 0);
|
||||
|
||||
const form = useMemo(
|
||||
() =>
|
||||
|
@ -31,3 +31,4 @@ export { default as useParseDataScopeFilter } from './hooks/useParseDataScopeFil
|
||||
export * from './isPatternDisabled';
|
||||
export { SchemaSettingsPlugin } from './SchemaSettingsPlugin';
|
||||
export * from './VariableInput';
|
||||
export { replaceVariables } from './LinkageRules/bindLinkageRulesToFiled';
|
||||
|
@ -8,11 +8,11 @@
|
||||
*/
|
||||
|
||||
const TYPE_TO_ACTION = {
|
||||
hasMany: 'list?pageSize=9999',
|
||||
hasMany: 'list?paginate=false',
|
||||
belongsTo: 'get',
|
||||
hasOne: 'get',
|
||||
belongsToMany: 'list?pageSize=9999',
|
||||
belongsToArray: 'get',
|
||||
belongsToMany: 'list?paginate=false',
|
||||
belongsToArray: 'list?paginate=false',
|
||||
};
|
||||
export const getAction = (type: string) => {
|
||||
if (process.env.NODE_ENV !== 'production' && !(type in TYPE_TO_ACTION)) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-nocobase-app",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "src/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
@ -21,7 +21,7 @@ cli
|
||||
.option('--quickstart', 'quickstart app creation')
|
||||
.option('--skip-dev-dependencies')
|
||||
.option('-a, --all-db-dialect', 'install all database dialect dependencies')
|
||||
.option('-d, --db-dialect <dbDialect>', 'database dialect, current support sqlite/mysql/postgres', 'sqlite')
|
||||
.option('-d, --db-dialect [dbDialect]', 'database dialect, current support postgres, mysql, mariadb, kingbase')
|
||||
.option('-e, --env <env>', 'environment variables write into .env file', concat, [])
|
||||
.description('create a new application')
|
||||
.action(async (name, options) => {
|
||||
|
@ -37,21 +37,6 @@ class AppGenerator extends Generator {
|
||||
return items;
|
||||
}
|
||||
|
||||
checkDbEnv() {
|
||||
const dialect = this.args.dbDialect;
|
||||
const env = this.env;
|
||||
if (dialect === 'sqlite') {
|
||||
return;
|
||||
}
|
||||
if (!env.DB_DATABASE || !env.DB_USER || !env.DB_PASSWORD) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
`Please set DB_HOST, DB_PORT, DB_DATABASE, DB_USER, DB_PASSWORD in .env file to complete database settings`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
checkProjectPath() {
|
||||
if (existsSync(this.cwd)) {
|
||||
console.log(chalk.red('Project directory already exists'));
|
||||
@ -59,44 +44,14 @@ class AppGenerator extends Generator {
|
||||
}
|
||||
}
|
||||
|
||||
checkDialect() {
|
||||
const dialect = this.args.dbDialect;
|
||||
const supportDialects = ['mysql', 'mariadb', 'sqlite', 'postgres'];
|
||||
if (!supportDialects.includes(dialect)) {
|
||||
console.log(
|
||||
`dialect ${chalk.red(dialect)} is not supported, currently supported dialects are ${chalk.green(
|
||||
supportDialects.join(','),
|
||||
)}.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
getContext() {
|
||||
const env = this.env;
|
||||
const envs = [];
|
||||
const dependencies = [];
|
||||
const { dbDialect, allDbDialect } = this.args;
|
||||
|
||||
if (allDbDialect) {
|
||||
dependencies.push(`"mysql2": "^3.11.0"`);
|
||||
dependencies.push(`"mariadb": "^2.5.6"`);
|
||||
dependencies.push(`"pg": "^8.7.3"`);
|
||||
dependencies.push(`"pg-hstore": "^2.3.4"`);
|
||||
dependencies.push(`"sqlite3": "^5.0.8"`);
|
||||
}
|
||||
const { dbDialect } = this.args;
|
||||
|
||||
switch (dbDialect) {
|
||||
case 'sqlite':
|
||||
if (!allDbDialect) {
|
||||
dependencies.push(`"sqlite3": "^5.0.8"`);
|
||||
}
|
||||
envs.push(`DB_STORAGE=${env.DB_STORAGE || 'storage/db/nocobase.sqlite'}`);
|
||||
break;
|
||||
case 'mysql':
|
||||
if (!allDbDialect) {
|
||||
dependencies.push(`"mysql2": "^3.11.0"`);
|
||||
}
|
||||
envs.push(`DB_HOST=${env.DB_HOST || 'localhost'}`);
|
||||
envs.push(`DB_PORT=${env.DB_PORT || 3306}`);
|
||||
envs.push(`DB_DATABASE=${env.DB_DATABASE || ''}`);
|
||||
@ -104,9 +59,6 @@ class AppGenerator extends Generator {
|
||||
envs.push(`DB_PASSWORD=${env.DB_PASSWORD || ''}`);
|
||||
break;
|
||||
case 'mariadb':
|
||||
if (!allDbDialect) {
|
||||
dependencies.push(`"mariadb": "^2.5.6"`);
|
||||
}
|
||||
envs.push(`DB_HOST=${env.DB_HOST || 'localhost'}`);
|
||||
envs.push(`DB_PORT=${env.DB_PORT || 3306}`);
|
||||
envs.push(`DB_DATABASE=${env.DB_DATABASE || ''}`);
|
||||
@ -115,10 +67,6 @@ class AppGenerator extends Generator {
|
||||
break;
|
||||
case 'kingbase':
|
||||
case 'postgres':
|
||||
if (!allDbDialect) {
|
||||
dependencies.push(`"pg": "^8.7.3"`);
|
||||
dependencies.push(`"pg-hstore": "^2.3.4"`);
|
||||
}
|
||||
envs.push(`DB_HOST=${env.DB_HOST || 'localhost'}`);
|
||||
envs.push(`DB_PORT=${env.DB_PORT || 5432}`);
|
||||
envs.push(`DB_DATABASE=${env.DB_DATABASE || ''}`);
|
||||
@ -177,28 +125,33 @@ class AppGenerator extends Generator {
|
||||
|
||||
async writing() {
|
||||
this.checkProjectPath();
|
||||
this.checkDialect();
|
||||
|
||||
const { name } = this.context;
|
||||
|
||||
console.log(`Creating a new NocoBase application at ${chalk.green(name)}`);
|
||||
console.log('Creating files');
|
||||
|
||||
const context = this.getContext();
|
||||
|
||||
this.copyDirectory({
|
||||
context: this.getContext(),
|
||||
context,
|
||||
path: join(__dirname, '../templates/app'),
|
||||
target: this.cwd,
|
||||
});
|
||||
|
||||
this.checkDbEnv();
|
||||
const json = {
|
||||
name: context.name,
|
||||
...(await fs.readJSON(join(this.cwd, 'package.json'), 'utf8')),
|
||||
};
|
||||
|
||||
const skipDevDependencies = this.args.skipDevDependencies;
|
||||
if (skipDevDependencies) {
|
||||
const json = await fs.readJSON(join(this.cwd, 'package.json'), 'utf8');
|
||||
delete json['devDependencies'];
|
||||
await fs.writeJSON(join(this.cwd, 'package.json'), json, { encoding: 'utf8', spaces: 2 });
|
||||
json['dependencies']['@nocobase/cli'] = context.version;
|
||||
|
||||
if (!this.args.skipDevDependencies) {
|
||||
json['devDependencies'] = json['devDependencies'] || {};
|
||||
json['devDependencies']['@nocobase/devtools'] = context.version;
|
||||
}
|
||||
|
||||
await fs.writeJSON(join(this.cwd, 'package.json'), json, { encoding: 'utf8', spaces: 2 });
|
||||
console.log('');
|
||||
console.log(chalk.green(`$ cd ${name}`));
|
||||
console.log(chalk.green(`$ yarn install`));
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"name": "{{{name}}}",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*/*",
|
||||
@ -29,14 +28,15 @@
|
||||
"react-router-dom": "6.28.1",
|
||||
"react-router": "6.28.1",
|
||||
"antd": "5.24.2",
|
||||
"async": "3.2.6",
|
||||
"rollup": "4.24.0"
|
||||
"async": "^3.2.6",
|
||||
"rollup": "4.24.0",
|
||||
"semver": "^7.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nocobase/cli": "{{{version}}}",
|
||||
{{{dependencies}}}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/devtools": "{{{version}}}"
|
||||
"pm2": "^6.0.5",
|
||||
"mysql2": "^3.14.0",
|
||||
"mariadb": "^2.5.6",
|
||||
"pg": "^8.14.1",
|
||||
"pg-hstore": "^2.3.4"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@nocobase/data-source-manager",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/actions": "1.7.0-alpha.11",
|
||||
"@nocobase/cache": "1.7.0-alpha.11",
|
||||
"@nocobase/database": "1.7.0-alpha.11",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.11",
|
||||
"@nocobase/utils": "1.7.0-alpha.11",
|
||||
"@nocobase/actions": "1.7.0-alpha.12",
|
||||
"@nocobase/cache": "1.7.0-alpha.12",
|
||||
"@nocobase/database": "1.7.0-alpha.12",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.12",
|
||||
"@nocobase/utils": "1.7.0-alpha.12",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"jsonwebtoken": "^8.5.1"
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/database",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@nocobase/logger": "1.7.0-alpha.11",
|
||||
"@nocobase/utils": "1.7.0-alpha.11",
|
||||
"@nocobase/logger": "1.7.0-alpha.12",
|
||||
"@nocobase/utils": "1.7.0-alpha.12",
|
||||
"async-mutex": "^0.3.2",
|
||||
"chalk": "^4.1.1",
|
||||
"cron-parser": "4.4.0",
|
||||
|
@ -26,7 +26,7 @@ export class ViewCollection extends Collection {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ['create', 'update', 'destroy'];
|
||||
return ['create', 'update', 'destroy', 'importXlsx', 'destroyMany', 'updateMany'];
|
||||
}
|
||||
|
||||
protected sequelizeModelOptions(): any {
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/devtools",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./src/index.js",
|
||||
"dependencies": {
|
||||
"@nocobase/build": "1.7.0-alpha.11",
|
||||
"@nocobase/client": "1.7.0-alpha.11",
|
||||
"@nocobase/test": "1.7.0-alpha.11",
|
||||
"@nocobase/build": "1.7.0-alpha.12",
|
||||
"@nocobase/client": "1.7.0-alpha.12",
|
||||
"@nocobase/test": "1.7.0-alpha.12",
|
||||
"@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.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@formulajs/formulajs": "4.4.9",
|
||||
"@nocobase/utils": "1.7.0-alpha.11",
|
||||
"@nocobase/utils": "1.7.0-alpha.12",
|
||||
"mathjs": "^10.6.0"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@nocobase/lock-manager",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "lib/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"devDependencies": {
|
||||
"@nocobase/utils": "1.7.0-alpha.11",
|
||||
"@nocobase/utils": "1.7.0-alpha.12",
|
||||
"async-mutex": "^0.5.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/logger",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "nocobase logging library",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
|
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@nocobase/resourcer",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@nocobase/utils": "1.7.0-alpha.11",
|
||||
"@nocobase/utils": "1.7.0-alpha.12",
|
||||
"deepmerge": "^4.2.2",
|
||||
"koa-compose": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"path-to-regexp": "6.2.2",
|
||||
"path-to-regexp": "^6.3.0",
|
||||
"qs": "^6.9.4"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -367,7 +367,7 @@ export class ResourceManager {
|
||||
: params.resourceName;
|
||||
ctx.action.params.filterByTk = params.resourceIndex;
|
||||
const query = parseQuery(ctx.request.querystring);
|
||||
if (pathToRegexp('/resourcer/{:associatedName.}?:resourceName{\\::actionName}').test(ctx.request.path)) {
|
||||
if (pathToRegexp('/resourcer/:rest(.*)').test(ctx.request.path)) {
|
||||
ctx.action.mergeParams({
|
||||
...query,
|
||||
...params,
|
||||
|
@ -67,17 +67,25 @@ export function parseRequest(request: ParseRequest, options: ParseOptions = {}):
|
||||
...(options.accessors || {}),
|
||||
};
|
||||
const keys = [];
|
||||
const regexp = pathToRegexp('/resourcer/{:associatedName.}?:resourceName{\\::actionName}', keys);
|
||||
|
||||
const regexp = pathToRegexp('/resourcer/:rest(.*)', keys);
|
||||
const reqPath = decodeURI(request.path);
|
||||
const matches = regexp.exec(reqPath);
|
||||
if (matches) {
|
||||
const params = {};
|
||||
keys.forEach((obj, index) => {
|
||||
if (matches[index + 1] === undefined) {
|
||||
return;
|
||||
const [resource, action] = matches[1].split(':');
|
||||
const [res1, res2] = resource.split('.');
|
||||
if (res1) {
|
||||
if (res2) {
|
||||
params['associatedName'] = res1;
|
||||
params['resourceName'] = res2;
|
||||
} else {
|
||||
params['resourceName'] = res1;
|
||||
}
|
||||
params[obj.name] = matches[index + 1];
|
||||
});
|
||||
}
|
||||
if (action) {
|
||||
params['actionName'] = action;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
const defaults = {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/sdk",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/server",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
@ -8,25 +8,25 @@
|
||||
"@formily/json-schema": "2.x",
|
||||
"@hapi/topo": "^6.0.0",
|
||||
"@koa/cors": "^5.0.0",
|
||||
"@koa/multer": "^3.0.2",
|
||||
"@koa/router": "^9.4.0",
|
||||
"@nocobase/acl": "1.7.0-alpha.11",
|
||||
"@nocobase/actions": "1.7.0-alpha.11",
|
||||
"@nocobase/auth": "1.7.0-alpha.11",
|
||||
"@nocobase/cache": "1.7.0-alpha.11",
|
||||
"@nocobase/data-source-manager": "1.7.0-alpha.11",
|
||||
"@nocobase/database": "1.7.0-alpha.11",
|
||||
"@nocobase/evaluators": "1.7.0-alpha.11",
|
||||
"@nocobase/lock-manager": "1.7.0-alpha.11",
|
||||
"@nocobase/logger": "1.7.0-alpha.11",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.11",
|
||||
"@nocobase/sdk": "1.7.0-alpha.11",
|
||||
"@nocobase/telemetry": "1.7.0-alpha.11",
|
||||
"@nocobase/utils": "1.7.0-alpha.11",
|
||||
"@koa/multer": "^3.1.0",
|
||||
"@koa/router": "^13.1.0",
|
||||
"@nocobase/acl": "1.7.0-alpha.12",
|
||||
"@nocobase/actions": "1.7.0-alpha.12",
|
||||
"@nocobase/auth": "1.7.0-alpha.12",
|
||||
"@nocobase/cache": "1.7.0-alpha.12",
|
||||
"@nocobase/data-source-manager": "1.7.0-alpha.12",
|
||||
"@nocobase/database": "1.7.0-alpha.12",
|
||||
"@nocobase/evaluators": "1.7.0-alpha.12",
|
||||
"@nocobase/lock-manager": "1.7.0-alpha.12",
|
||||
"@nocobase/logger": "1.7.0-alpha.12",
|
||||
"@nocobase/resourcer": "1.7.0-alpha.12",
|
||||
"@nocobase/sdk": "1.7.0-alpha.12",
|
||||
"@nocobase/telemetry": "1.7.0-alpha.12",
|
||||
"@nocobase/utils": "1.7.0-alpha.12",
|
||||
"@types/decompress": "4.2.7",
|
||||
"@types/ini": "^1.3.31",
|
||||
"@types/koa-send": "^4.1.3",
|
||||
"@types/multer": "^1.4.5",
|
||||
"@types/multer": "^1.4.12",
|
||||
"async-mutex": "^0.5.0",
|
||||
"axios": "^1.7.0",
|
||||
"chalk": "^4.1.1",
|
||||
@ -45,7 +45,7 @@
|
||||
"koa-send": "^5.0.1",
|
||||
"koa-static": "^5.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"multer": "^1.4.2",
|
||||
"multer": "^1.4.5-lts.2",
|
||||
"nanoid": "^3.3.11",
|
||||
"semver": "^7.7.1",
|
||||
"serve-handler": "^6.1.6",
|
||||
|
@ -29,14 +29,13 @@ export default (app: Application) => {
|
||||
'migrations',
|
||||
`${dayjs().format('YYYYMMDDHHmmss')}-${name}.ts`,
|
||||
);
|
||||
const version = app.getVersion();
|
||||
// 匹配主版本号、次版本号、小版本号和后缀的正则表达式
|
||||
const version = app.getPackageVersion();
|
||||
const regex = /(\d+)\.(\d+)\.(\d+)(-[\w.]+)?/;
|
||||
const nextVersion = version.replace(regex, (match, major, minor, patch, suffix) => {
|
||||
// 将小版本号转换为整数并加1
|
||||
const newPatch = parseInt(patch) + 1;
|
||||
// 返回新的版本号
|
||||
return `${major}.${minor}.${newPatch}${suffix || ''}`;
|
||||
if (version.includes('beta') || version.includes('alpha')) {
|
||||
return `${major}.${minor}.${patch}`;
|
||||
}
|
||||
return `${major}.${1 + 1 * minor}.0`;
|
||||
});
|
||||
const from = pkg === '@nocobase/server' ? `../migration` : '@nocobase/server';
|
||||
const data = `import { Migration } from '${from}';
|
||||
|
@ -15,7 +15,7 @@ const deps: Record<string, string> = {
|
||||
'@formily': '2.x',
|
||||
|
||||
'@formily/antd-v5': '1.x',
|
||||
jsonwebtoken: '8.x',
|
||||
jsonwebtoken: '9.x',
|
||||
'cache-manager': '5.x',
|
||||
sequelize: '6.x',
|
||||
umzug: '3.x',
|
||||
@ -26,7 +26,7 @@ const deps: Record<string, string> = {
|
||||
'winston-daily-rotate-file': '4.x',
|
||||
koa: '2.x',
|
||||
'@koa/cors': '5.x',
|
||||
'@koa/router': '9.x',
|
||||
'@koa/router': '13.x',
|
||||
multer: '1.x',
|
||||
'@koa/multer': '3.x',
|
||||
'koa-bodyparser': '4.x',
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/telemetry",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"description": "nocobase telemetry library",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
@ -11,7 +11,7 @@
|
||||
"directory": "packages/telemetry"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nocobase/utils": "1.7.0-alpha.11",
|
||||
"@nocobase/utils": "1.7.0-alpha.12",
|
||||
"@opentelemetry/api": "^1.7.0",
|
||||
"@opentelemetry/instrumentation": "^0.46.0",
|
||||
"@opentelemetry/resources": "^1.19.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/test",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"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.7.0-alpha.11",
|
||||
"@nocobase/server": "1.7.0-alpha.12",
|
||||
"@playwright/test": "^1.45.3",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
@ -69,7 +69,6 @@
|
||||
"mysql2": "^3.11.0",
|
||||
"pg": "^8.7.3",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sqlite3": "^5.0.8",
|
||||
"supertest": "^6.1.6",
|
||||
"vite": "^5.0.0",
|
||||
"vitest": "^1.5.0",
|
||||
|
@ -7,13 +7,13 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import http from 'http';
|
||||
import url from 'url';
|
||||
import pg from 'pg';
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
import mysql from 'mysql2/promise';
|
||||
import http from 'http';
|
||||
import mariadb from 'mariadb';
|
||||
import mysql from 'mysql2/promise';
|
||||
import path from 'path';
|
||||
import pg from 'pg';
|
||||
import url from 'url';
|
||||
|
||||
dotenv.config({ path: path.resolve(process.cwd(), '.env.test') });
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/utils",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
@ -13,7 +13,7 @@
|
||||
"flat-to-nested": "^1.1.1",
|
||||
"graphlib": "^2.1.8",
|
||||
"handlebars": "^4.7.8",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"multer": "^1.4.5-lts.2",
|
||||
"object-path": "^0.11.8"
|
||||
},
|
||||
"gitHead": "d0b4efe4be55f8c79a98a331d99d9f8cf99021a1"
|
||||
|
@ -236,3 +236,13 @@ export const getDateTimeFormat = (picker, format, showTime, timeFormat) => {
|
||||
}
|
||||
return format;
|
||||
};
|
||||
|
||||
export function getFormatFromDateStr(dateStr: string): string | null {
|
||||
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(dateStr)) return 'YYYY-MM-DD HH:mm:ss';
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return 'YYYY-MM-DD';
|
||||
if (/^\d{4}-\d{2}$/.test(dateStr)) return 'YYYY-MM';
|
||||
if (/^\d{4}$/.test(dateStr)) return 'YYYY';
|
||||
if (/^\d{4}Q[1-4]$/.test(dateStr)) return 'YYYY[Q]Q';
|
||||
if (/^\d{4}-\d{2}-\d{2}T/.test(dateStr)) return 'YYYY-MM-DDTHH:mm:ss.SSSZ';
|
||||
return null;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "权限控制",
|
||||
"description": "Based on roles, resources, and actions, access control can precisely manage interface configuration permissions, data operation permissions, menu access permissions, and plugin permissions.",
|
||||
"description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/acl",
|
||||
@ -13,8 +13,8 @@
|
||||
"Users & permissions"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
@ -234,7 +234,6 @@ export const scopesSchema: ISchema = {
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
icon: 'EditOutlined',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-action-bulk-edit",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/action-bulk-edit",
|
||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-edit",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-action-bulk-update",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/action-bulk-update",
|
||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-update",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-action-custom-request",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/action-custom-request",
|
||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request",
|
||||
|
@ -18,6 +18,10 @@ import {
|
||||
useNavigateNoUpdate,
|
||||
useBlockRequestContext,
|
||||
useContextVariable,
|
||||
useLocalVariables,
|
||||
useVariables,
|
||||
replaceVariables,
|
||||
interpolateVariables,
|
||||
} from '@nocobase/client';
|
||||
import { isURL } from '@nocobase/utils/client';
|
||||
import { App } from 'antd';
|
||||
@ -38,12 +42,21 @@ export const useCustomizeRequestActionProps = () => {
|
||||
const { modal, message } = App.useApp();
|
||||
const dataSourceKey = useDataSourceKey();
|
||||
const { ctx } = useContextVariable();
|
||||
const localVariables = useLocalVariables();
|
||||
const variables = useVariables();
|
||||
|
||||
return {
|
||||
async onClick(e?, callBack?) {
|
||||
const selectedRecord = field.data?.selectedRowData ? field.data?.selectedRowData : ctx;
|
||||
const selectedRecord = field?.data?.selectedRowData ? field?.data?.selectedRowData : ctx;
|
||||
const { skipValidator, onSuccess } = actionSchema?.['x-action-settings'] ?? {};
|
||||
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
|
||||
const {
|
||||
manualClose,
|
||||
redirecting,
|
||||
redirectTo,
|
||||
successMessage: rawSuccessMessage,
|
||||
actionAfterSuccess,
|
||||
} = onSuccess || {};
|
||||
let successMessage = rawSuccessMessage;
|
||||
const xAction = actionSchema?.['x-action'];
|
||||
if (skipValidator !== true && xAction === 'customize:form:request') {
|
||||
await form.submit();
|
||||
@ -71,6 +84,19 @@ export const useCustomizeRequestActionProps = () => {
|
||||
},
|
||||
responseType: fieldSchema['x-response-type'] === 'stream' ? 'blob' : 'json',
|
||||
});
|
||||
try {
|
||||
const { exp, scope: expScope } = await replaceVariables(successMessage, {
|
||||
variables,
|
||||
localVariables: [
|
||||
...localVariables,
|
||||
{ name: '$nResponse', ctx: new Proxy({ ...res?.data, ...res?.data?.data }, {}) },
|
||||
],
|
||||
});
|
||||
successMessage = interpolateVariables(exp, expScope);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
if (res.headers['content-disposition']) {
|
||||
const contentDisposition = res.headers['content-disposition'];
|
||||
const utf8Match = contentDisposition.match(/filename\*=utf-8''([^;]+)/i);
|
||||
|
@ -1,92 +0,0 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import {
|
||||
AfterSuccess,
|
||||
ButtonEditor,
|
||||
RefreshDataBlockRequest,
|
||||
RemoveButton,
|
||||
SchemaSettings,
|
||||
SchemaSettingsLinkageRules,
|
||||
SecondConFirm,
|
||||
useCollection,
|
||||
useCollectionRecord,
|
||||
useSchemaToolbar,
|
||||
SchemaSettingAccessControl,
|
||||
useDataBlockProps,
|
||||
useCollectionManager_deprecated,
|
||||
} from '@nocobase/client';
|
||||
import { CustomRequestSettingsItem } from './components/CustomRequestActionDesigner';
|
||||
|
||||
export const customizeCustomRequestActionSettings = new SchemaSettings({
|
||||
name: 'actionSettings:customRequest',
|
||||
items: [
|
||||
{
|
||||
name: 'editButton',
|
||||
Component: ButtonEditor,
|
||||
useComponentProps() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return {
|
||||
isLink: fieldSchema['x-action'] === 'customize:table:request',
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'secondConFirm',
|
||||
Component: SecondConFirm,
|
||||
},
|
||||
{
|
||||
name: 'afterSuccessfulSubmission',
|
||||
Component: AfterSuccess,
|
||||
},
|
||||
{
|
||||
name: 'request settings',
|
||||
Component: CustomRequestSettingsItem,
|
||||
},
|
||||
{
|
||||
...SchemaSettingAccessControl,
|
||||
useVisible() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'refreshDataBlockRequest',
|
||||
Component: RefreshDataBlockRequest,
|
||||
useComponentProps() {
|
||||
return {
|
||||
isPopupAction: false,
|
||||
};
|
||||
},
|
||||
useVisible() {
|
||||
const collection = useCollection();
|
||||
return !!collection;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
sort: 100,
|
||||
Component: RemoveButton as any,
|
||||
useComponentProps() {
|
||||
const { removeButtonProps } = useSchemaToolbar();
|
||||
return removeButtonProps;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
@ -0,0 +1,242 @@
|
||||
/**
|
||||
* 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 {
|
||||
ButtonEditor,
|
||||
RefreshDataBlockRequest,
|
||||
RemoveButton,
|
||||
SchemaSettings,
|
||||
SchemaSettingsLinkageRules,
|
||||
SecondConFirm,
|
||||
useCollection,
|
||||
useSchemaToolbar,
|
||||
SchemaSettingAccessControl,
|
||||
useDesignable,
|
||||
useGlobalVariable,
|
||||
usePlugin,
|
||||
SchemaSettingsModalItem,
|
||||
useAfterSuccessOptions,
|
||||
BlocksSelector,
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { ISchema, useFieldSchema } from '@formily/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CustomRequestSettingsItem } from './components/CustomRequestActionDesigner';
|
||||
|
||||
const useVariableOptions = () => {
|
||||
const scopes = useAfterSuccessOptions();
|
||||
const { t } = useTranslation();
|
||||
return [
|
||||
{
|
||||
value: '$nResponse',
|
||||
label: t('Response', { ns: 'client' }),
|
||||
children: null,
|
||||
},
|
||||
...scopes.filter((v: any) => ['currentUser', 'currentTime', '$nRole'].includes(v.value)),
|
||||
].filter(Boolean);
|
||||
};
|
||||
|
||||
const useLinkVariableOptions = () => {
|
||||
const scopes = useAfterSuccessOptions();
|
||||
const environmentVariables = useGlobalVariable('$env');
|
||||
return [...scopes.filter((v: any) => v.value !== '$record'), environmentVariables].filter(Boolean);
|
||||
};
|
||||
const useLinkVariableProps = () => {
|
||||
const scope = useLinkVariableOptions();
|
||||
return {
|
||||
scope,
|
||||
useTypedConstant: true,
|
||||
};
|
||||
};
|
||||
export function AfterSuccess() {
|
||||
const { dn } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { onSuccess } = fieldSchema?.['x-action-settings'] || {};
|
||||
const templatePlugin: any = usePlugin('@nocobase/plugin-block-template');
|
||||
const isInBlockTemplateConfigPage = templatePlugin?.isInBlockTemplateConfigPage?.();
|
||||
return (
|
||||
<SchemaSettingsModalItem
|
||||
dialogRootClassName="dialog-after-successful-submission"
|
||||
width={700}
|
||||
title={t('After successful submission')}
|
||||
initialValues={
|
||||
onSuccess
|
||||
? {
|
||||
actionAfterSuccess: onSuccess?.redirecting ? 'redirect' : 'previous',
|
||||
...onSuccess,
|
||||
}
|
||||
: {
|
||||
manualClose: false,
|
||||
redirecting: false,
|
||||
successMessage: '{{t("Saved successfully")}}',
|
||||
actionAfterSuccess: 'previous',
|
||||
}
|
||||
}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('After successful submission'),
|
||||
properties: {
|
||||
successMessage: {
|
||||
title: t('Popup message'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.RawTextArea',
|
||||
'x-component-props': {
|
||||
scope: useVariableOptions,
|
||||
style: { minWidth: '220px' },
|
||||
},
|
||||
},
|
||||
manualClose: {
|
||||
title: t('Message popup close method'),
|
||||
enum: [
|
||||
{ label: t('Automatic close'), value: false },
|
||||
{ label: t('Manually close'), value: true },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
'x-component-props': {},
|
||||
},
|
||||
redirecting: {
|
||||
title: t('Then'),
|
||||
'x-hidden': true,
|
||||
enum: [
|
||||
{ label: t('Stay on current page'), value: false },
|
||||
{ label: t('Redirect to'), value: true },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
'x-component-props': {},
|
||||
'x-reactions': {
|
||||
target: 'redirectTo',
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{!!$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
actionAfterSuccess: {
|
||||
title: t('Action after successful submission'),
|
||||
enum: [
|
||||
{ label: t('Stay on the current popup or page'), value: 'stay' },
|
||||
{ label: t('Return to the previous popup or page'), value: 'previous' },
|
||||
{ label: t('Redirect to'), value: 'redirect' },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
'x-component-props': {},
|
||||
'x-reactions': {
|
||||
target: 'redirectTo',
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: "{{$self.value==='redirect'}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
redirectTo: {
|
||||
title: t('Link'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.TextArea',
|
||||
'x-use-component-props': useLinkVariableProps,
|
||||
},
|
||||
blocksToRefresh: {
|
||||
type: 'array',
|
||||
title: t('Refresh data blocks'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-use-decorator-props': () => {
|
||||
return {
|
||||
tooltip: t('After successful submission, the selected data blocks will be automatically refreshed.'),
|
||||
};
|
||||
},
|
||||
'x-component': BlocksSelector,
|
||||
'x-hidden': isInBlockTemplateConfigPage, // 模板配置页面暂不支持该配置
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={(onSuccess) => {
|
||||
fieldSchema['x-action-settings']['onSuccess'] = onSuccess;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
'x-action-settings': fieldSchema['x-action-settings'],
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const customizeCustomRequestActionSettings = new SchemaSettings({
|
||||
name: 'actionSettings:customRequest',
|
||||
items: [
|
||||
{
|
||||
name: 'editButton',
|
||||
Component: ButtonEditor,
|
||||
useComponentProps() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return {
|
||||
isLink: fieldSchema['x-action'] === 'customize:table:request',
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'secondConFirm',
|
||||
Component: SecondConFirm,
|
||||
},
|
||||
{
|
||||
name: 'afterSuccessfulSubmission',
|
||||
Component: AfterSuccess,
|
||||
},
|
||||
{
|
||||
name: 'request settings',
|
||||
Component: CustomRequestSettingsItem,
|
||||
},
|
||||
{
|
||||
...SchemaSettingAccessControl,
|
||||
useVisible() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'refreshDataBlockRequest',
|
||||
Component: RefreshDataBlockRequest,
|
||||
useComponentProps() {
|
||||
return {
|
||||
isPopupAction: false,
|
||||
};
|
||||
},
|
||||
useVisible() {
|
||||
const collection = useCollection();
|
||||
return !!collection;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
sort: 100,
|
||||
Component: RemoveButton as any,
|
||||
useComponentProps() {
|
||||
const { removeButtonProps } = useSchemaToolbar();
|
||||
return removeButtonProps;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-action-duplicate",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/action-duplicate",
|
||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-duplicate",
|
||||
|
@ -55,6 +55,7 @@ test.describe('direct duplicate & copy into the form and continue to fill in', (
|
||||
await page.getByRole('menuitem', { name: 'oneToMany' }).click();
|
||||
await page.getByRole('menuitem', { name: 'manyToOne', exact: true }).click();
|
||||
await page.getByRole('menuitem', { name: 'manyToMany' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByLabel('schema-initializer-ActionBar-createForm:configureActions-general').click();
|
||||
await page.getByRole('menuitem', { name: 'Submit' }).click();
|
||||
await page.getByLabel('drawer-Action.Container-general-Duplicate-mask').click();
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "操作:导出记录",
|
||||
"description": "Export filtered records to excel, you can configure which fields to export.",
|
||||
"description.zh-CN": "导出筛选后的记录到 Excel 中,可以配置导出哪些字段。",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/action-export",
|
||||
|
@ -1,3 +1,12 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import {
|
||||
FindOptions,
|
||||
ICollection,
|
||||
@ -10,6 +19,7 @@ import EventEmitter from 'events';
|
||||
import { deepGet } from '../utils/deep-get';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import _ from 'lodash';
|
||||
|
||||
export type ExportOptions = {
|
||||
collectionManager: ICollectionManager;
|
||||
@ -49,11 +59,11 @@ abstract class BaseExporter<T extends ExportOptions = ExportOptions> extends Eve
|
||||
|
||||
const { collection, chunkSize, repository } = this.options;
|
||||
|
||||
const total = await (repository || collection.repository).count(this.getFindOptions());
|
||||
const total = await (repository || collection.repository).count(this.getFindOptions(ctx));
|
||||
let current = 0;
|
||||
|
||||
await (repository || collection.repository).chunk({
|
||||
...this.getFindOptions(),
|
||||
...this.getFindOptions(ctx),
|
||||
chunkSize: chunkSize || 200,
|
||||
callback: async (rows, options) => {
|
||||
for (const row of rows) {
|
||||
@ -71,31 +81,34 @@ abstract class BaseExporter<T extends ExportOptions = ExportOptions> extends Eve
|
||||
return this.finalize();
|
||||
}
|
||||
|
||||
protected getAppendOptionsFromFields() {
|
||||
return this.options.fields
|
||||
protected getAppendOptionsFromFields(ctx?) {
|
||||
const fields = this.options.fields.map((x) => x[0]);
|
||||
const hasPermissionFields = _.isEmpty(ctx?.permission?.can?.params)
|
||||
? fields
|
||||
: _.intersection(ctx?.permission?.can?.params?.appends || [], fields);
|
||||
return hasPermissionFields
|
||||
.map((field) => {
|
||||
const fieldInstance = this.options.collection.getField(field[0]);
|
||||
const fieldInstance = this.options.collection.getField(field);
|
||||
if (!fieldInstance) {
|
||||
throw new Error(`Field "${field[0]}" not found: , please check the fields configuration.`);
|
||||
throw new Error(`Field "${field}" not found: , please check the fields configuration.`);
|
||||
}
|
||||
|
||||
if (fieldInstance.isRelationField()) {
|
||||
return field.join('.');
|
||||
return field;
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
protected getFindOptions() {
|
||||
protected getFindOptions(ctx?) {
|
||||
const { findOptions = {} } = this.options;
|
||||
|
||||
if (this.limit) {
|
||||
findOptions.limit = this.limit;
|
||||
}
|
||||
|
||||
const appendOptions = this.getAppendOptionsFromFields();
|
||||
const appendOptions = this.getAppendOptionsFromFields(ctx);
|
||||
|
||||
if (appendOptions.length) {
|
||||
return {
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "操作:导入记录",
|
||||
"description": "Import records using excel templates. You can configure which fields to import and templates will be generated automatically.",
|
||||
"description.zh-CN": "使用 Excel 模板导入数据,可以配置导入哪些字段,自动生成模板。",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/action-import",
|
||||
@ -15,7 +15,7 @@
|
||||
"@formily/core": "2.x",
|
||||
"@formily/react": "2.x",
|
||||
"@formily/shared": "2.x",
|
||||
"@koa/multer": "^3.0.2",
|
||||
"@koa/multer": "^3.1.0",
|
||||
"@types/node-xlsx": "^0.15.1",
|
||||
"antd": "5.x",
|
||||
"async-mutex": "^0.5.0",
|
||||
|
@ -34,7 +34,7 @@ export class PluginActionImportClient extends Plugin {
|
||||
skipScopeCheck: true,
|
||||
},
|
||||
},
|
||||
useVisible: () => useActionAvailable('import'),
|
||||
useVisible: () => useActionAvailable('importXlsx'),
|
||||
};
|
||||
|
||||
const tableActionInitializers = this.app.schemaInitializerManager.get('table:configureActions');
|
||||
|
@ -2156,6 +2156,76 @@ describe('xlsx importer', () => {
|
||||
expect(await Post.repository.count()).toBe(1);
|
||||
});
|
||||
|
||||
it('should filter no permission columns', async () => {
|
||||
const User = app.db.collection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'email',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await app.db.sync();
|
||||
|
||||
const templateCreator = new TemplateCreator({
|
||||
collection: User,
|
||||
explain: 'test',
|
||||
columns: [
|
||||
{
|
||||
dataIndex: ['name'],
|
||||
defaultTitle: '姓名',
|
||||
},
|
||||
{
|
||||
dataIndex: ['email'],
|
||||
defaultTitle: '邮箱',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const template = (await templateCreator.run({ returnXLSXWorkbook: true })) as XLSX.WorkBook;
|
||||
|
||||
const worksheet = template.Sheets[template.SheetNames[0]];
|
||||
|
||||
XLSX.utils.sheet_add_aoa(worksheet, [['User1', 'test@test.com']], {
|
||||
origin: 'A3',
|
||||
});
|
||||
|
||||
const importer = new XlsxImporter({
|
||||
collectionManager: app.mainDataSource.collectionManager,
|
||||
collection: User,
|
||||
explain: 'test',
|
||||
columns: [
|
||||
{
|
||||
dataIndex: ['name'],
|
||||
defaultTitle: '姓名',
|
||||
},
|
||||
{
|
||||
dataIndex: ['email'],
|
||||
defaultTitle: '邮箱',
|
||||
},
|
||||
],
|
||||
workbook: template,
|
||||
});
|
||||
await importer.run({
|
||||
context: {
|
||||
permission: {
|
||||
can: { params: { fields: ['name'] } },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(await User.repository.count()).toBe(1);
|
||||
const user = await User.repository.findOne();
|
||||
expect(user.get('name')).toBe('User1');
|
||||
expect(user.get('email')).not.exist;
|
||||
});
|
||||
|
||||
it('should import time field successfully', async () => {
|
||||
const TimeCollection = app.db.collection({
|
||||
name: 'time_tests',
|
||||
|
@ -14,6 +14,8 @@ import { Collection as DBCollection, Database } from '@nocobase/database';
|
||||
import { Transaction } from 'sequelize';
|
||||
import EventEmitter from 'events';
|
||||
import { ImportValidationError, ImportError } from '../errors';
|
||||
import { Context } from '@nocobase/actions';
|
||||
import _ from 'lodash';
|
||||
|
||||
export type ImportColumn = {
|
||||
dataIndex: Array<string>;
|
||||
@ -54,8 +56,9 @@ export class XlsxImporter extends EventEmitter {
|
||||
this.repository = options.repository ? options.repository : options.collection.repository;
|
||||
}
|
||||
|
||||
async validate() {
|
||||
if (this.options.columns.length == 0) {
|
||||
async validate(ctx?: Context) {
|
||||
const columns = this.getColumnsByPermission(ctx);
|
||||
if (columns.length == 0) {
|
||||
throw new ImportValidationError('Columns configuration is empty');
|
||||
}
|
||||
|
||||
@ -66,7 +69,7 @@ export class XlsxImporter extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
const data = await this.getData();
|
||||
const data = await this.getData(ctx);
|
||||
return data;
|
||||
}
|
||||
|
||||
@ -80,7 +83,7 @@ export class XlsxImporter extends EventEmitter {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.validate();
|
||||
await this.validate(options.context);
|
||||
const imported = await this.performImport(options);
|
||||
|
||||
// @ts-ignore
|
||||
@ -111,7 +114,7 @@ export class XlsxImporter extends EventEmitter {
|
||||
}
|
||||
|
||||
let hasImportedAutoIncrementPrimary = false;
|
||||
for (const importedDataIndex of this.options.columns) {
|
||||
for (const importedDataIndex of this.getColumnsByPermission(options?.context)) {
|
||||
if (importedDataIndex.dataIndex[0] === autoIncrementAttribute) {
|
||||
hasImportedAutoIncrementPrimary = true;
|
||||
break;
|
||||
@ -150,9 +153,18 @@ export class XlsxImporter extends EventEmitter {
|
||||
this.emit('seqReset', { maxVal, seqName: autoIncrInfo.seqName });
|
||||
}
|
||||
|
||||
private getColumnsByPermission(ctx: Context): ImportColumn[] {
|
||||
const columns = this.options.columns;
|
||||
return columns.filter((x) =>
|
||||
_.isEmpty(ctx?.permission?.can?.params)
|
||||
? true
|
||||
: _.includes(ctx?.permission?.can?.params?.fields || [], x.dataIndex[0]),
|
||||
);
|
||||
}
|
||||
|
||||
async performImport(options?: RunOptions): Promise<any> {
|
||||
const transaction = options?.transaction;
|
||||
const data = await this.getData();
|
||||
const data = await this.getData(options?.context);
|
||||
const chunks = lodash.chunk(data.slice(1), this.options.chunkSize || 200);
|
||||
|
||||
let handingRowIndex = 1;
|
||||
@ -271,25 +283,26 @@ export class XlsxImporter extends EventEmitter {
|
||||
return str;
|
||||
}
|
||||
|
||||
private getExpectedHeaders(): string[] {
|
||||
return this.options.columns.map((col) => col.title || col.defaultTitle);
|
||||
private getExpectedHeaders(ctx?: Context): string[] {
|
||||
const columns = this.getColumnsByPermission(ctx);
|
||||
return columns.map((col) => col.title || col.defaultTitle);
|
||||
}
|
||||
|
||||
async getData() {
|
||||
async getData(ctx?: Context) {
|
||||
const workbook = this.options.workbook;
|
||||
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
|
||||
const data = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null }) as string[][];
|
||||
let data = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null }) as string[][];
|
||||
|
||||
// Find and validate header row
|
||||
const expectedHeaders = this.getExpectedHeaders();
|
||||
const { headerRowIndex, headers } = this.findAndValidateHeaders(data);
|
||||
const expectedHeaders = this.getExpectedHeaders(ctx);
|
||||
const { headerRowIndex, headers } = this.findAndValidateHeaders({ data, expectedHeaders });
|
||||
if (headerRowIndex === -1) {
|
||||
throw new ImportValidationError('Headers not found. Expected headers: {{headers}}', {
|
||||
headers: expectedHeaders.join(', '),
|
||||
});
|
||||
}
|
||||
|
||||
data = this.alignWithHeaders({ data, expectedHeaders, headers });
|
||||
// Extract data rows
|
||||
const rows = data.slice(headerRowIndex + 1);
|
||||
|
||||
@ -301,8 +314,18 @@ export class XlsxImporter extends EventEmitter {
|
||||
return [headers, ...rows];
|
||||
}
|
||||
|
||||
private findAndValidateHeaders(data: string[][]): { headerRowIndex: number; headers: string[] } {
|
||||
const expectedHeaders = this.getExpectedHeaders();
|
||||
private alignWithHeaders(params: { headers: string[]; expectedHeaders: string[]; data: string[][] }): string[][] {
|
||||
const { expectedHeaders, headers, data } = params;
|
||||
const keepCols = headers.map((x, i) => (expectedHeaders.includes(x) ? i : -1)).filter((i) => i > -1);
|
||||
|
||||
return data.map((row) => keepCols.map((i) => row[i]));
|
||||
}
|
||||
|
||||
private findAndValidateHeaders(options: { expectedHeaders: string[]; data: string[][] }): {
|
||||
headerRowIndex: number;
|
||||
headers: string[];
|
||||
} {
|
||||
const { data, expectedHeaders } = options;
|
||||
|
||||
// Find header row and validate
|
||||
for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
|
||||
@ -310,31 +333,11 @@ export class XlsxImporter extends EventEmitter {
|
||||
const actualHeaders = row.filter((cell) => cell !== null && cell !== '');
|
||||
|
||||
const allHeadersFound = expectedHeaders.every((header) => actualHeaders.includes(header));
|
||||
const noExtraHeaders = actualHeaders.length === expectedHeaders.length;
|
||||
|
||||
if (allHeadersFound && noExtraHeaders) {
|
||||
const mismatchIndex = expectedHeaders.findIndex((title, index) => actualHeaders[index] !== title);
|
||||
|
||||
if (mismatchIndex === -1) {
|
||||
// All headers match
|
||||
return { headerRowIndex: rowIndex, headers: actualHeaders };
|
||||
} else {
|
||||
// Found potential header row but with mismatch
|
||||
throw new ImportValidationError(
|
||||
'Header mismatch at column {{column}}: expected "{{expected}}", but got "{{actual}}"',
|
||||
{
|
||||
column: mismatchIndex + 1,
|
||||
expected: expectedHeaders[mismatchIndex],
|
||||
actual: actualHeaders[mismatchIndex] || 'empty',
|
||||
},
|
||||
);
|
||||
}
|
||||
if (allHeadersFound) {
|
||||
const orderedHeaders = expectedHeaders.filter((h) => actualHeaders.includes(h));
|
||||
return { headerRowIndex: rowIndex, headers: orderedHeaders };
|
||||
}
|
||||
}
|
||||
|
||||
// No row with matching headers found
|
||||
throw new ImportValidationError('Headers not found. Expected headers: {{headers}}', {
|
||||
headers: expectedHeaders.join(', '),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-action-print",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/action-print",
|
||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-print",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "AI 集成",
|
||||
"description": "Support integration with AI services, providing AI-related workflow nodes to enhance business processing capabilities.",
|
||||
"description.zh-CN": "支持接入 AI 服务,提供 AI 相关的工作流节点,增强业务处理能力。",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "dist/server/index.js",
|
||||
"peerDependencies": {
|
||||
"@nocobase/client": "1.x",
|
||||
|
@ -187,7 +187,6 @@ export const llmsSchema = {
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
icon: 'EditOutlined',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-api-doc",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"displayName": "API documentation",
|
||||
"displayName.zh-CN": "API 文档",
|
||||
"description": "An OpenAPI documentation generator for NocoBase HTTP API.",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "认证:API 密钥",
|
||||
"description": "Allows users to use API key to access application's HTTP API",
|
||||
"description.zh-CN": "允许用户使用 API 密钥访问应用的 HTTP API",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/api-keys",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "异步任务管理器",
|
||||
"description": "Manage and monitor asynchronous tasks such as data import/export. Support task progress tracking and notification.",
|
||||
"description.zh-CN": "管理和监控数据导入导出等异步任务。支持任务进度跟踪和通知。",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "dist/server/index.js",
|
||||
"peerDependencies": {
|
||||
"@nocobase/client": "1.x",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-audit-logs",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"displayName": "Audit logs (deprecated)",
|
||||
"displayName.zh-CN": "审计日志(废弃)",
|
||||
"description": "This plugin is deprecated. There will be a new audit log plugin in the future.",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "认证:短信",
|
||||
"description": "SMS authentication.",
|
||||
"description.zh-CN": "通过短信验证码认证身份。",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/auth-sms",
|
||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth-sms",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-auth",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/auth",
|
||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "应用的备份与还原(废弃)",
|
||||
"description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.",
|
||||
"description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/backup-restore",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "区块:iframe",
|
||||
"description": "Create an iframe block on the page to embed and display external web pages or content.",
|
||||
"description.zh-CN": "在页面上创建和管理iframe,用于嵌入和展示外部网页或内容。",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/block-iframe",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"displayName.zh-CN": "区块:模板",
|
||||
"description": "Create and manage block templates for reuse on pages.",
|
||||
"description.zh-CN": "创建和管理区块模板,用于在页面中重复使用。",
|
||||
"version": "1.7.0-alpha.11",
|
||||
"version": "1.7.0-alpha.12",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "dist/server/index.js",
|
||||
"homepage": "https://docs.nocobase.com/handbook/block-template",
|
||||
|
@ -68,7 +68,7 @@ export const BlockTemplateMenusProvider = ({ children }) => {
|
||||
method: 'get',
|
||||
params: {
|
||||
filter: {
|
||||
configured: true,
|
||||
configured: { $isTruly: true },
|
||||
type: isMobile ? 'Mobile' : { $ne: 'Mobile' },
|
||||
},
|
||||
paginate: false,
|
||||
|
@ -0,0 +1,182 @@
|
||||
/**
|
||||
* 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 { SchemaSettingsItem, useAPIClient, useDesignable, useFormBlockProps } from '@nocobase/client';
|
||||
import { useFieldSchema, useForm, useField } from '@formily/react';
|
||||
import { App } from 'antd';
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Schema } from '@formily/json-schema';
|
||||
import { useT } from '../locale';
|
||||
import { uid } from '@nocobase/utils/client';
|
||||
|
||||
const findInsertPosition = (parentSchema, uid) => {
|
||||
const postion = {
|
||||
insertPosition: 'beforeBegin',
|
||||
insertTarget: null,
|
||||
};
|
||||
const properties = Object.values(parentSchema.properties || {}).sort((a, b) => {
|
||||
return (a as any)['x-index'] - (b as any)['x-index'];
|
||||
});
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
const property = properties[i];
|
||||
if ((property as any)['x-uid'] === uid) {
|
||||
postion.insertPosition = 'beforeBegin';
|
||||
if (i === properties.length - 1) {
|
||||
postion.insertPosition = 'beforeEnd';
|
||||
postion.insertTarget = parentSchema['x-uid'];
|
||||
} else {
|
||||
postion.insertPosition = 'beforeBegin';
|
||||
postion.insertTarget = (properties[i + 1] as any)['x-uid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return postion;
|
||||
};
|
||||
|
||||
const findParentRootTemplateSchema = (fieldSchema) => {
|
||||
if (!fieldSchema) {
|
||||
return null;
|
||||
}
|
||||
if (fieldSchema['x-template-root-uid']) {
|
||||
return fieldSchema;
|
||||
} else {
|
||||
return findParentRootTemplateSchema(fieldSchema.parent);
|
||||
}
|
||||
};
|
||||
|
||||
// Create a copy of the schema with all template associations removed
|
||||
const convertToNormalBlockSchema = (schema) => {
|
||||
const newSchema = _.cloneDeep(schema);
|
||||
|
||||
// Remove template associations from the schema
|
||||
const removeTemplateAssociations = (s) => {
|
||||
// Remove template-specific properties
|
||||
delete s['x-template-uid'];
|
||||
delete s['x-template-root-uid'];
|
||||
delete s['x-template-version'];
|
||||
delete s['x-block-template-key'];
|
||||
delete s['x-template-root-ref'];
|
||||
delete s['x-template-title'];
|
||||
delete s['x-virtual'];
|
||||
|
||||
if (s['x-toolbar-props']?.toolbarClassName?.includes('nb-in-template')) {
|
||||
s['x-toolbar-props'].toolbarClassName = s['x-toolbar-props'].toolbarClassName.replace('nb-in-template', '');
|
||||
}
|
||||
|
||||
if (s['x-uid']) {
|
||||
s['x-uid'] = uid();
|
||||
}
|
||||
// Process nested properties
|
||||
if (s.properties) {
|
||||
for (const key in s.properties) {
|
||||
if (!s.properties[key]['x-template-root-uid']) {
|
||||
removeTemplateAssociations(s.properties[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
removeTemplateAssociations(newSchema);
|
||||
return newSchema;
|
||||
};
|
||||
|
||||
export const ConvertToNormalBlockSetting = () => {
|
||||
const { refresh } = useDesignable();
|
||||
const t = useT();
|
||||
const api = useAPIClient();
|
||||
const form = useForm();
|
||||
const field = useField();
|
||||
const { form: blockForm } = useFormBlockProps();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { modal, message } = App.useApp();
|
||||
const blockTemplatesResource = api.resource('blockTemplates');
|
||||
|
||||
const confirm = {
|
||||
okText: t('Yes'),
|
||||
cancelText: t('No'),
|
||||
};
|
||||
|
||||
return (
|
||||
<SchemaSettingsItem
|
||||
title={t('Convert to normal block')}
|
||||
onClick={() => {
|
||||
modal.confirm({
|
||||
title: t('Convert to normal block'),
|
||||
content: t('Are you sure you want to convert this template block to a normal block?'),
|
||||
...confirm,
|
||||
async onOk() {
|
||||
const newSchema = convertToNormalBlockSchema(fieldSchema.toJSON());
|
||||
const position = findInsertPosition(fieldSchema.parent, fieldSchema['x-uid']);
|
||||
// TODO: Remove old schema, and links
|
||||
|
||||
// Remove old schema
|
||||
await api.request({
|
||||
url: `/uiSchemas:remove/${fieldSchema['x-uid']}`,
|
||||
});
|
||||
|
||||
// Insert new schema
|
||||
const schema = new Schema(newSchema);
|
||||
await api.request({
|
||||
url: `/uiSchemas:insertAdjacent/${position.insertTarget}?position=${position.insertPosition}`,
|
||||
method: 'post',
|
||||
data: {
|
||||
schema,
|
||||
},
|
||||
});
|
||||
|
||||
// Update the UI to show the new schema
|
||||
fieldSchema.toJSON = () => {
|
||||
const ret = schema.toJSON();
|
||||
return ret;
|
||||
};
|
||||
|
||||
refresh({ refreshParentSchema: true });
|
||||
|
||||
// Update component properties
|
||||
field['componentProps'] = {
|
||||
...field['componentProps'],
|
||||
key: uid(),
|
||||
};
|
||||
|
||||
if (field.parent?.['componentProps']) {
|
||||
field.parent['componentProps'] = {
|
||||
...field.parent['componentProps'],
|
||||
key: uid(),
|
||||
};
|
||||
}
|
||||
|
||||
// Update decorator properties
|
||||
field['decoratorProps'] = {
|
||||
...field['decoratorProps'],
|
||||
key: uid(),
|
||||
};
|
||||
|
||||
if (field.parent?.['decoratorProps']) {
|
||||
field.parent['decoratorProps'] = {
|
||||
...field.parent['decoratorProps'],
|
||||
key: uid(),
|
||||
};
|
||||
}
|
||||
|
||||
// Reset forms
|
||||
form.reset();
|
||||
blockForm?.reset();
|
||||
form.clearFormGraph('*', false);
|
||||
blockForm?.clearFormGraph('*', false);
|
||||
|
||||
message.success(t('Converted successfully'), 0.2);
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('Convert to normal block')}
|
||||
</SchemaSettingsItem>
|
||||
);
|
||||
};
|
@ -74,7 +74,7 @@ export const SaveAsTemplateSetting = () => {
|
||||
key: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
title: t('Key'),
|
||||
title: t('Name'),
|
||||
'x-component': 'Input',
|
||||
'x-validator': 'uid',
|
||||
required: true,
|
||||
@ -298,6 +298,10 @@ function getTemplateSchemaFromPage(schema: ISchema) {
|
||||
}
|
||||
_.set(t, `properties.['${key}']`, {});
|
||||
traverseSchema(s.properties[key], t.properties[key]);
|
||||
// array's key will be set to number when render, so we need to set the name to the key
|
||||
if (s.type === 'array' && t['properties']?.[key]?.name) {
|
||||
_.set(t, `properties.['${key}'].name`, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -22,8 +22,9 @@ export const useIsPageBlock = () => {
|
||||
const isPage = location.pathname.startsWith('/admin/') || location.pathname.startsWith('/page/');
|
||||
const notInPopup = !location.pathname.includes('/popups/');
|
||||
const notInSetting = !location.pathname.startsWith('/admin/settings/');
|
||||
const notInWorkflow = !location.pathname.startsWith('/admin/workflow/workflows/');
|
||||
const notInBlockTemplate = !location.pathname.startsWith('/block-templates/');
|
||||
return isPage && notInPopup && notInSetting && notInBlockTemplate;
|
||||
return isPage && notInPopup && notInSetting && notInWorkflow && notInBlockTemplate;
|
||||
}, [location.pathname, fieldSchema]);
|
||||
|
||||
return isPageBlock;
|
||||
|
@ -28,6 +28,8 @@ import {
|
||||
import { BlockTemplateMenusProvider } from './components/BlockTemplateMenusProvider';
|
||||
import { disabledDeleteSettingItem } from './settings/disabledDeleteSetting';
|
||||
import { saveAsTemplateSetting } from './settings/saveAsTemplateSetting';
|
||||
import { convertToNormalBlockSettingItem } from './settings/convertToNormalBlockSetting';
|
||||
|
||||
export class PluginBlockTemplateClient extends Plugin {
|
||||
templateInfos = new Map();
|
||||
templateschemacache = {};
|
||||
@ -158,9 +160,10 @@ export class PluginBlockTemplateClient extends Plugin {
|
||||
deleteItemIndex !== -1 &&
|
||||
!schemaSetting.items.find((item) => item.name === 'template-revertSettingItem')
|
||||
) {
|
||||
schemaSetting.items.splice(deleteItemIndex, 0, revertSettingItem);
|
||||
schemaSetting.items.splice(deleteItemIndex, 0, revertSettingItem, convertToNormalBlockSettingItem);
|
||||
} else {
|
||||
schemaSetting.add('template-revertSettingItem', revertSettingItem);
|
||||
schemaSetting.add('template-convertToNormalBlockSettingItem', convertToNormalBlockSettingItem);
|
||||
}
|
||||
schemaSetting.add('template-disabledDeleteItem', disabledDeleteSettingItem);
|
||||
}
|
||||
|
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