Merge branch 'develop' into feat/commercial

# Conflicts:
#	packages/core/cli/package.json
This commit is contained in:
chenos 2025-04-24 20:56:34 +08:00
commit f1a88e2a89
178 changed files with 1412 additions and 827 deletions

View File

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

View File

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

View File

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

View File

@ -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
### 🚀 优化

View File

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

View File

@ -1,5 +1,5 @@
{
"version": "1.7.0-alpha.11",
"version": "1.7.0-alpha.12",
"npmClient": "yarn",
"useWorkspaces": true,
"npmClientArgs": ["--ignore-engines"],

View File

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

View File

@ -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": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();

View File

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

View File

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

View File

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

View File

@ -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') {

View 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();
});
};

View File

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

View File

@ -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');

View 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"
}
}

View File

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

View File

@ -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(() => {

View File

@ -236,7 +236,6 @@ export const scopesSchema: ISchema = {
'x-component': 'Action.Link',
'x-component-props': {
openMode: 'drawer',
icon: 'EditOutlined',
},
properties: {
drawer: {

View File

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

View File

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

View File

@ -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": "选择一条已有的数据作为表单的初始化数据",

View File

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

View File

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

View File

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

View File

@ -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, () => {

View File

@ -101,6 +101,10 @@ const useLazyLoadDisplayAssociationFieldsOfForm = () => {
field.value = null;
} else {
field.value = result;
field.componentProps = {
...field.componentProps,
readOnlySubmit: true,
}; // 让它不参与提交
}
});
})

View File

@ -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 直接返回
}

View File

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

View File

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

View File

@ -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 () => {

View File

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

View File

@ -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(
() =>

View File

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

View File

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

View File

@ -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": {

View File

@ -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) => {

View File

@ -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`));

View File

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

View File

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

View File

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

View File

@ -26,7 +26,7 @@ export class ViewCollection extends Collection {
return [];
}
return ['create', 'update', 'destroy'];
return ['create', 'update', 'destroy', 'importXlsx', 'destroyMany', 'updateMany'];
}
protected sequelizeModelOptions(): any {

View File

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

View File

@ -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": {

View File

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

View File

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

View File

@ -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": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -234,7 +234,6 @@ export const scopesSchema: ISchema = {
'x-component': 'Action.Link',
'x-component-props': {
openMode: 'drawer',
icon: 'EditOutlined',
},
properties: {
drawer: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();

View File

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

View File

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

View File

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

View File

@ -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');

View File

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

View File

@ -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(', '),
});
}
}

View File

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

View File

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

View File

@ -187,7 +187,6 @@ export const llmsSchema = {
'x-component': 'Action.Link',
'x-component-props': {
openMode: 'drawer',
icon: 'EditOutlined',
},
properties: {
drawer: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -68,7 +68,7 @@ export const BlockTemplateMenusProvider = ({ children }) => {
method: 'get',
params: {
filter: {
configured: true,
configured: { $isTruly: true },
type: isMobile ? 'Mobile' : { $ne: 'Mobile' },
},
paginate: false,

View File

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

View File

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

View File

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

View File

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