Merge remote-tracking branch 'origin/main' into merge_from_upstream_1

This commit is contained in:
sealday 2024-03-17 04:28:06 +08:00
commit d07932e9eb
149 changed files with 1788 additions and 462 deletions

View File

@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
## [v0.20.0-alpha.12](https://github.com/nocobase/nocobase/compare/v0.20.0-alpha.11...v0.20.0-alpha.12) - 2024-03-16
### Merged
- fix: compatibility of @ant-design/plots 2.x [`#3734`](https://github.com/nocobase/nocobase/pull/3734)
### Commits
- chore(versions): 😊 publish v0.20.0-alpha.12 [`c1be864`](https://github.com/nocobase/nocobase/commit/c1be86412440c617771d079ded95f64aca3fa155)
- fix: yarn dev error [`c191f14`](https://github.com/nocobase/nocobase/commit/c191f149f96def2672fc50d4d042ca976c863ebc)
- chore: update changelog [`8278262`](https://github.com/nocobase/nocobase/commit/8278262728387fbd3963a0286ed09b2219eb7739)
## [v0.20.0-alpha.11](https://github.com/nocobase/nocobase/compare/v0.20.0-alpha.10...v0.20.0-alpha.11) - 2024-03-16
### Merged
- feat: supports subdirectory deployment [`#3731`](https://github.com/nocobase/nocobase/pull/3731)
- fix(variables): fix varaibles for table selector [`#3725`](https://github.com/nocobase/nocobase/pull/3725)
- fixing timezone header when it is negative value [`#3732`](https://github.com/nocobase/nocobase/pull/3732)
- fix(data-source): foreignkey [`#3707`](https://github.com/nocobase/nocobase/pull/3707)
- feat: add data source filter [`#3724`](https://github.com/nocobase/nocobase/pull/3724)
- fix(Table): fix disappearing content after selecting a row [`#3726`](https://github.com/nocobase/nocobase/pull/3726)
- refactor: view collection set name as default title when title is missing [`#3719`](https://github.com/nocobase/nocobase/pull/3719)
- refactor: add blocks in a unified way [`#3668`](https://github.com/nocobase/nocobase/pull/3668)
- feat: support to set data loading mode [`#3712`](https://github.com/nocobase/nocobase/pull/3712)
- fix: block template [`#3714`](https://github.com/nocobase/nocobase/pull/3714)
- refactor(SchemaInitializers): unify naming style [`#3604`](https://github.com/nocobase/nocobase/pull/3604)
- fix: remove env in colletion delete button [`#3682`](https://github.com/nocobase/nocobase/pull/3682)
- fix(data-vi): update antv version [`#3710`](https://github.com/nocobase/nocobase/pull/3710)
### Commits
- chore(versions): 😊 publish v0.20.0-alpha.11 [`15ef818`](https://github.com/nocobase/nocobase/commit/15ef81854e4f900ed53e5229d66d1c016d71b8e7)
- chore: update registry in yarn.lock [`e877e3b`](https://github.com/nocobase/nocobase/commit/e877e3b5d9c1543c74a55b41eb57f69f15ff9847)
- chore: update changelog [`42448ec`](https://github.com/nocobase/nocobase/commit/42448ecddb1afe455cced9c4a8b6ca78586a060f)
## [v0.20.0-alpha.10](https://github.com/nocobase/nocobase/compare/v0.20.0-alpha.9...v0.20.0-alpha.10) - 2024-03-13 ## [v0.20.0-alpha.10](https://github.com/nocobase/nocobase/compare/v0.20.0-alpha.9...v0.20.0-alpha.10) - 2024-03-13
### Merged ### Merged

View File

@ -30,7 +30,6 @@ RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \
&& apt-get update && apt-get install -y nginx && apt-get update && apt-get install -y nginx
RUN rm -rf /etc/nginx/sites-enabled/default RUN rm -rf /etc/nginx/sites-enabled/default
COPY ./nocobase.conf /etc/nginx/sites-enabled/nocobase.conf
COPY --from=builder /app/nocobase.tar.gz /app/nocobase.tar.gz COPY --from=builder /app/nocobase.tar.gz /app/nocobase.tar.gz
WORKDIR /app/nocobase WORKDIR /app/nocobase

View File

@ -1,9 +1,6 @@
#!/bin/sh #!/bin/sh
set -e set -e
nginx
echo 'nginx started';
if [ ! -d "/app/nocobase" ]; then if [ ! -d "/app/nocobase" ]; then
mkdir nocobase mkdir nocobase
fi fi
@ -14,10 +11,16 @@ if [ ! -f "/app/nocobase/package.json" ]; then
touch /app/nocobase/node_modules/@nocobase/app/dist/client/index.html touch /app/nocobase/node_modules/@nocobase/app/dist/client/index.html
fi fi
cd /app/nocobase && yarn nocobase create-nginx-conf
rm -rf /etc/nginx/sites-enabled/nocobase.conf
ln -s /app/nocobase/storage/nocobase.conf /etc/nginx/sites-enabled/nocobase.conf
nginx
echo 'nginx started';
if [ -z "$PM2_INSTANCE_NUM" ]; then if [ -z "$PM2_INSTANCE_NUM" ]; then
PM2_INSTANCE_NUM=1 PM2_INSTANCE_NUM=1
fi fi
cd /app/nocobase && yarn start --quickstart -i $PM2_INSTANCE_NUM cd /app/nocobase && yarn start --quickstart -i $PM2_INSTANCE_NUM
# Run command with node if the first argument contains a "-" or is not a system command. The last # Run command with node if the first argument contains a "-" or is not a system command. The last

View File

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

View File

@ -1,13 +1,13 @@
{ {
"name": "@nocobase/acl", "name": "@nocobase/acl",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/resourcer": "0.20.0-alpha.10", "@nocobase/resourcer": "0.20.0-alpha.12",
"@nocobase/utils": "0.20.0-alpha.10", "@nocobase/utils": "0.20.0-alpha.12",
"minimatch": "^5.1.1" "minimatch": "^5.1.1"
}, },
"repository": { "repository": {

View File

@ -1,14 +1,14 @@
{ {
"name": "@nocobase/actions", "name": "@nocobase/actions",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/cache": "0.20.0-alpha.10", "@nocobase/cache": "0.20.0-alpha.12",
"@nocobase/database": "0.20.0-alpha.10", "@nocobase/database": "0.20.0-alpha.12",
"@nocobase/resourcer": "0.20.0-alpha.10" "@nocobase/resourcer": "0.20.0-alpha.12"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -16,19 +16,35 @@ const outputPluginPath = path.join(__dirname, 'src', '.plugins');
const indexGenerator = new IndexGenerator(outputPluginPath, pluginDirs); const indexGenerator = new IndexGenerator(outputPluginPath, pluginDirs);
indexGenerator.generate(); indexGenerator.generate();
const isDevCmd = !!process.env.IS_DEV_CMD;
const appPublicPath = isDevCmd ? '/' : '{{env.APP_PUBLIC_PATH}}';
export default defineConfig({ export default defineConfig({
title: 'Loading...', title: 'Loading...',
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : false, devtool: process.env.NODE_ENV === 'development' ? 'source-map' : false,
favicons: ['/favicon/favicon.ico'], favicons: [`${appPublicPath}favicon/favicon.ico`],
metas: [{ name: 'viewport', content: 'initial-scale=0.1' }], metas: [{ name: 'viewport', content: 'initial-scale=0.1' }],
links: [ links: [
{ rel: 'apple-touch-icon', size: '180x180', ref: '/favicon/apple-touch-icon.png' }, { rel: 'apple-touch-icon', size: '180x180', ref: `${appPublicPath}favicon/apple-touch-icon.png` },
{ rel: 'icon', type: 'image/png', size: '32x32', ref: '/favicon/favicon-32x32.png' }, { rel: 'icon', type: 'image/png', size: '32x32', ref: `${appPublicPath}favicon/favicon-32x32.png` },
{ rel: 'icon', type: 'image/png', size: '16x16', ref: '/favicon/favicon-16x16.png' }, { rel: 'icon', type: 'image/png', size: '16x16', ref: `${appPublicPath}favicon/favicon-16x16.png` },
{ rel: 'manifest', href: '/favicon/site.webmanifest' }, { rel: 'manifest', href: `${appPublicPath}favicon/site.webmanifest` },
{ rel: 'stylesheet', href: '/global.css' }, { rel: 'stylesheet', href: `${appPublicPath}global.css` },
],
headScripts: [
{
src: `${appPublicPath}browser-checker.js`,
},
{
content: isDevCmd ? '' : `
window['__webpack_public_path__'] = '{{env.APP_PUBLIC_PATH}}';
window['__nocobase_public_path__'] = '{{env.APP_PUBLIC_PATH}}';
window['__nocobase_api_base_url__'] = '{{env.API_BASE_URL}}';
window['__nocobase_ws_url__'] = '{{env.WS_URL}}';
window['__nocobase_ws_path__'] = '{{env.WS_PATH}}';
`,
},
], ],
headScripts: ['/browser-checker.js'],
outputPath: path.resolve(__dirname, '../dist/client'), outputPath: path.resolve(__dirname, '../dist/client'),
hash: true, hash: true,
alias: { alias: {
@ -41,6 +57,7 @@ export default defineConfig({
proxy: { proxy: {
...umiConfig.proxy, ...umiConfig.proxy,
}, },
publicPath: 'auto',
fastRefresh: false, // 热更新会导致 Context 丢失,不开启 fastRefresh: false, // 热更新会导致 Context 丢失,不开启
mfsu: false, mfsu: false,
esbuildMinifyIIFE: true, esbuildMinifyIIFE: true,

View File

@ -4,10 +4,18 @@ import devDynamicImport from '../.plugins/index';
export const app = new Application({ export const app = new Application({
apiClient: { apiClient: {
baseURL: process.env.API_BASE_URL, // @ts-ignore
baseURL: window['__nocobase_api_base_url__'] || process.env.API_BASE_URL || '/api/',
}, },
// @ts-ignore
publicPath: window['__nocobase_public_path__'] || process.env.APP_PUBLIC_PATH || '/',
plugins: [NocoBaseClientPresetPlugin], plugins: [NocoBaseClientPresetPlugin],
ws: true, ws: {
// @ts-ignore
url: window['__nocobase_ws_url__'] || process.env.WEBSOCKET_URL || '',
// @ts-ignore
basename: window['__nocobase_ws_path__'] || process.env.WS_PATH || '/ws',
},
loadRemotePlugins: true, loadRemotePlugins: true,
devDynamicImport, devDynamicImport,
}); });

View File

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

View File

@ -1,16 +1,16 @@
{ {
"name": "@nocobase/auth", "name": "@nocobase/auth",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/actions": "0.20.0-alpha.10", "@nocobase/actions": "0.20.0-alpha.12",
"@nocobase/cache": "0.20.0-alpha.10", "@nocobase/cache": "0.20.0-alpha.12",
"@nocobase/database": "0.20.0-alpha.10", "@nocobase/database": "0.20.0-alpha.12",
"@nocobase/resourcer": "0.20.0-alpha.10", "@nocobase/resourcer": "0.20.0-alpha.12",
"@nocobase/utils": "0.20.0-alpha.10", "@nocobase/utils": "0.20.0-alpha.12",
"@types/jsonwebtoken": "^8.5.8", "@types/jsonwebtoken": "^8.5.8",
"jsonwebtoken": "^8.5.1" "jsonwebtoken": "^8.5.1"
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/build", "name": "@nocobase/build",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "Library build tool based on rollup.", "description": "Library build tool based on rollup.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/cache", "name": "@nocobase/cache",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",

View File

@ -0,0 +1,89 @@
log_format apm '"$time_local" client=$remote_addr '
'method=$request_method request="$request" '
'request_length=$request_length '
'status=$status bytes_sent=$bytes_sent '
'body_bytes_sent=$body_bytes_sent '
'referer=$http_referer '
'user_agent="$http_user_agent" '
'upstream_addr=$upstream_addr '
'upstream_status=$upstream_status '
'request_time=$request_time '
'upstream_response_time=$upstream_response_time '
'upstream_connect_time=$upstream_connect_time '
'upstream_header_time=$upstream_header_time';
server {
listen 80;
server_name _;
root {{cwd}}/node_modules/@nocobase/app/dist/client;
index index.html;
client_max_body_size 1000M;
access_log /var/log/nginx/nocobase.log apm;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# 不缓存 HTML 文件
# location ~ \.html$ {
# if_modified_since off;
# expires off;
# etag off;
# }
# # 缓存 JavaScript 和 CSS 文件
# location ~* \.(js|css)$ {
# expires 365d;
# add_header Cache-Control "public";
# }
location {{publicPath}}storage/uploads/ {
alias {{cwd}}/storage/uploads/;
add_header Cache-Control "public";
access_log off;
autoindex off;
}
location {{publicPath}} {
alias {{cwd}}/node_modules/@nocobase/app/dist/client/;
try_files $uri $uri/ /index.html;
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache';
if_modified_since off;
expires off;
etag off;
}
location ^~ {{publicPath}}api/ {
proxy_pass http://127.0.0.1:{{apiPort}}{{publicPath}}api/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
}
location ^~ {{publicPath}}static/plugins/ {
proxy_pass http://127.0.0.1:{{apiPort}}{{publicPath}}static/plugins/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
}
location {{publicPath}}ws {
proxy_pass http://127.0.0.1:{{apiPort}}{{publicPath}}ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/cli", "name": "@nocobase/cli",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./src/index.js", "main": "./src/index.js",
@ -8,12 +8,12 @@
"nocobase": "./bin/index.js" "nocobase": "./bin/index.js"
}, },
"dependencies": { "dependencies": {
"@nocobase/app": "0.20.0-alpha.10", "@nocobase/app": "0.20.0-alpha.12",
"@types/fs-extra": "^11.0.1", "@types/fs-extra": "^11.0.1",
"@umijs/utils": "3.5.20", "@umijs/utils": "3.5.20",
"chalk": "^4.1.1", "chalk": "^4.1.1",
"commander": "^9.2.0", "commander": "^9.2.0",
"dotenv": "^10.0.0", "dotenv": "^16.0.0",
"execa": "^5.1.1", "execa": "^5.1.1",
"fast-glob": "^3.3.1", "fast-glob": "^3.3.1",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",
@ -25,7 +25,7 @@
"tsx": "^4.6.2" "tsx": "^4.6.2"
}, },
"devDependencies": { "devDependencies": {
"@nocobase/devtools": "0.20.0-alpha.10" "@nocobase/devtools": "0.20.0-alpha.12"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,6 +1,6 @@
const { resolve } = require('path'); const { resolve } = require('path');
const { Command } = require('commander'); const { Command } = require('commander');
const { run, nodeCheck, isPackageValid } = require('../util'); const { run, nodeCheck, isPackageValid, buildIndexHtml } = require('../util');
/** /**
* *
@ -31,5 +31,6 @@ module.exports = (cli) => {
!options.dts ? '--no-dts' : '', !options.dts ? '--no-dts' : '',
options.sourcemap ? '--sourcemap' : '', options.sourcemap ? '--sourcemap' : '',
]); ]);
buildIndexHtml(true);
}); });
}; };

View File

@ -0,0 +1,21 @@
const { resolve } = require('path');
const { Command } = require('commander');
const { readFileSync, writeFileSync } = require('fs');
/**
*
* @param {Command} cli
*/
module.exports = (cli) => {
cli.command('create-nginx-conf').action(async (name, options) => {
const file = resolve(__dirname, '../../nocobase.conf.tpl');
const data = readFileSync(file, 'utf-8');
const replaced = data
.replace(/\{\{cwd\}\}/g, '/app/nocobase')
.replace(/\{\{publicPath\}\}/g, process.env.APP_PUBLIC_PATH)
.replace(/\{\{apiPort\}\}/g, process.env.APP_PORT);
const targetFile = resolve(process.cwd(), 'storage', 'nocobase.conf');
writeFileSync(targetFile, replaced);
});
};

View File

@ -8,6 +8,7 @@ const { isPackageValid, generateAppDir } = require('../util');
module.exports = (cli) => { module.exports = (cli) => {
generateAppDir(); generateAppDir();
require('./global')(cli); require('./global')(cli);
require('./create-nginx-conf')(cli);
require('./build')(cli); require('./build')(cli);
require('./tar')(cli); require('./tar')(cli);
require('./dev')(cli); require('./dev')(cli);

View File

@ -180,6 +180,7 @@ exports.generateAppDir = function generateAppDir() {
} else { } else {
process.env.APP_PACKAGE_ROOT = appPkgPath; process.env.APP_PACKAGE_ROOT = appPkgPath;
} }
buildIndexHtml();
}; };
exports.genTsConfigPaths = function genTsConfigPaths() { exports.genTsConfigPaths = function genTsConfigPaths() {
@ -257,6 +258,30 @@ function parseEnv(name) {
} }
} }
function buildIndexHtml(force = false) {
const file = `${process.env.APP_PACKAGE_ROOT}/dist/client/index.html`;
if (!fs.existsSync(file)) {
return;
}
const tpl = `${process.env.APP_PACKAGE_ROOT}/dist/client/index.html.tpl`;
if (force && fs.existsSync(tpl)) {
fs.rmSync(tpl);
}
if (!fs.existsSync(tpl)) {
fs.copyFileSync(file, tpl);
}
const data = fs.readFileSync(tpl, 'utf-8');
const replacedData = data
.replace(/\{\{env.APP_PUBLIC_PATH\}\}/g, process.env.APP_PUBLIC_PATH)
.replace(/\{\{env.API_BASE_URL\}\}/g, process.env.API_BASE_URL || process.env.API_BASE_PATH)
.replace(/\{\{env.WS_URL\}\}/g, process.env.WEBSOCKET_URL || '')
.replace(/\{\{env.WS_PATH\}\}/g, process.env.WS_PATH)
.replace('src="/umi.', `src="${process.env.APP_PUBLIC_PATH}umi.`);
fs.writeFileSync(file, replacedData, 'utf-8');
}
exports.buildIndexHtml = buildIndexHtml;
exports.initEnv = function initEnv() { exports.initEnv = function initEnv() {
const env = { const env = {
APP_ENV: 'development', APP_ENV: 'development',
@ -280,7 +305,10 @@ exports.initEnv = function initEnv() {
PLAYWRIGHT_AUTH_FILE: resolve(process.cwd(), 'storage/playwright/.auth/admin.json'), PLAYWRIGHT_AUTH_FILE: resolve(process.cwd(), 'storage/playwright/.auth/admin.json'),
CACHE_DEFAULT_STORE: 'memory', CACHE_DEFAULT_STORE: 'memory',
CACHE_MEMORY_MAX: 2000, CACHE_MEMORY_MAX: 2000,
PLUGIN_STATICS_PATH: '/static/plugins/',
LOGGER_BASE_PATH: 'storage/logs', LOGGER_BASE_PATH: 'storage/logs',
APP_SERVER_BASE_URL: '',
APP_PUBLIC_PATH: '/',
}; };
if ( if (
@ -319,4 +347,16 @@ exports.initEnv = function initEnv() {
process.env[key] = env[key]; process.env[key] = env[key];
} }
} }
if (process.env.APP_PUBLIC_PATH) {
const publicPath = process.env.APP_PUBLIC_PATH.replace(/\/$/g, '');
const keys = ['API_BASE_PATH', 'WS_PATH', 'PLUGIN_STATICS_PATH'];
for (const key of keys) {
process.env[key] = publicPath + process.env[key];
}
}
if (process.env.APP_SERVER_BASE_URL && !process.env.API_BASE_URL) {
process.env.API_BASE_URL = process.env.APP_SERVER_BASE_URL + process.env.API_BASE_PATH;
}
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/client", "name": "@nocobase/client",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "lib/index.js", "main": "lib/index.js",
"module": "es/index.mjs", "module": "es/index.mjs",
@ -26,9 +26,9 @@
"@formily/reactive-react": "^2.2.27", "@formily/reactive-react": "^2.2.27",
"@formily/shared": "^2.2.27", "@formily/shared": "^2.2.27",
"@formily/validator": "^2.2.27", "@formily/validator": "^2.2.27",
"@nocobase/evaluators": "0.20.0-alpha.10", "@nocobase/evaluators": "0.20.0-alpha.12",
"@nocobase/sdk": "0.20.0-alpha.10", "@nocobase/sdk": "0.20.0-alpha.12",
"@nocobase/utils": "0.20.0-alpha.10", "@nocobase/utils": "0.20.0-alpha.12",
"ahooks": "^3.7.2", "ahooks": "^3.7.2",
"antd": "^5.12.8", "antd": "^5.12.8",
"antd-style": "3.4.5", "antd-style": "3.4.5",

View File

@ -1,4 +1,4 @@
import { APIClient as APIClientSDK } from '@nocobase/sdk'; import { APIClient as APIClientSDK, getSubAppName } from '@nocobase/sdk';
import { Result } from 'ahooks/es/useRequest/src/types'; import { Result } from 'ahooks/es/useRequest/src/types';
import { notification } from 'antd'; import { notification } from 'antd';
import React from 'react'; import React from 'react';
@ -39,9 +39,9 @@ export class APIClient extends APIClientSDK {
interceptors() { interceptors() {
this.axios.interceptors.request.use((config) => { this.axios.interceptors.request.use((config) => {
config.headers['X-With-ACL-Meta'] = true; config.headers['X-With-ACL-Meta'] = true;
const match = location.pathname.match(/^\/apps\/([^/]*)\//); const appName = this.app ? getSubAppName(this.app.getPublicPath()) : null;
if (match) { if (appName) {
config.headers['X-App'] = match[1]; config.headers['X-App'] = appName;
} }
return config; return config;
}); });

View File

@ -9,14 +9,14 @@ import { createRoot } from 'react-dom/client';
import { I18nextProvider } from 'react-i18next'; import { I18nextProvider } from 'react-i18next';
import { Link, NavLink, Navigate } from 'react-router-dom'; import { Link, NavLink, Navigate } from 'react-router-dom';
import { APIClient, APIClientProvider } from '../api-client';
import { CSSVariableProvider } from '../css-variable'; import { CSSVariableProvider } from '../css-variable';
import { AntdAppProvider, GlobalThemeProvider } from '../global-theme'; import { AntdAppProvider, GlobalThemeProvider } from '../global-theme';
import { i18n } from '../i18n';
import { PluginManager, PluginType } from './PluginManager'; import { PluginManager, PluginType } from './PluginManager';
import { PluginSettingOptions, PluginSettingsManager } from './PluginSettingsManager'; import { PluginSettingOptions, PluginSettingsManager } from './PluginSettingsManager';
import { ComponentTypeAndString, RouterManager, RouterOptions } from './RouterManager'; import { ComponentTypeAndString, RouterManager, RouterOptions } from './RouterManager';
import { WebSocketClient, WebSocketClientOptions } from './WebSocketClient'; import { WebSocketClient, WebSocketClientOptions } from './WebSocketClient';
import { APIClient, APIClientProvider } from '../api-client';
import { i18n } from '../i18n';
import { AppComponent, BlankComponent, defaultAppComponents } from './components'; import { AppComponent, BlankComponent, defaultAppComponents } from './components';
import { SchemaInitializer, SchemaInitializerManager } from './schema-initializer'; import { SchemaInitializer, SchemaInitializerManager } from './schema-initializer';
import * as schemaInitializerComponents from './schema-initializer/components'; import * as schemaInitializerComponents from './schema-initializer/components';
@ -25,10 +25,10 @@ import { compose, normalizeContainer } from './utils';
import { defineGlobalDeps } from './utils/globalDeps'; import { defineGlobalDeps } from './utils/globalDeps';
import { getRequireJs } from './utils/requirejs'; import { getRequireJs } from './utils/requirejs';
import { type DataSourceManagerOptions, DataSourceManager } from '../data-source/data-source/DataSourceManager';
import { DataSourceApplicationProvider } from '../data-source/components/DataSourceApplicationProvider';
import { CollectionField } from '../data-source/collection-field/CollectionField'; import { CollectionField } from '../data-source/collection-field/CollectionField';
import { DataSourceApplicationProvider } from '../data-source/components/DataSourceApplicationProvider';
import { DataBlockProvider } from '../data-source/data-block/DataBlockProvider'; import { DataBlockProvider } from '../data-source/data-block/DataBlockProvider';
import { DataSourceManager, type DataSourceManagerOptions } from '../data-source/data-source/DataSourceManager';
import { AppSchemaComponentProvider } from './AppSchemaComponentProvider'; import { AppSchemaComponentProvider } from './AppSchemaComponentProvider';
import type { Plugin } from './Plugin'; import type { Plugin } from './Plugin';
@ -44,6 +44,7 @@ export type DevDynamicImport = (packageName: string) => Promise<{ default: typeo
export type ComponentAndProps<T = any> = [ComponentType, T]; export type ComponentAndProps<T = any> = [ComponentType, T];
export interface ApplicationOptions { export interface ApplicationOptions {
name?: string; name?: string;
publicPath?: string;
apiClient?: APIClientOptions | APIClient; apiClient?: APIClientOptions | APIClient;
ws?: WebSocketClientOptions | boolean; ws?: WebSocketClientOptions | boolean;
i18n?: i18next; i18n?: i18next;
@ -116,9 +117,10 @@ export class Application {
this.addReactRouterComponents(); this.addReactRouterComponents();
this.addProviders(options.providers || []); this.addProviders(options.providers || []);
this.ws = new WebSocketClient(options.ws); this.ws = new WebSocketClient(options.ws);
this.ws.app = this;
this.pluginSettingsManager = new PluginSettingsManager(options.pluginSettings, this); this.pluginSettingsManager = new PluginSettingsManager(options.pluginSettings, this);
this.addRoutes(); this.addRoutes();
this.name = this.options.name || getSubAppName() || 'main'; this.name = this.options.name || getSubAppName(options.publicPath) || 'main';
} }
private initRequireJs() { private initRequireJs() {
@ -157,6 +159,18 @@ export class Application {
}); });
} }
getOptions() {
return this.options;
}
getPublicPath() {
return this.options.publicPath || '/';
}
getRouteUrl(pathname: string) {
return this.options.publicPath.replace(/\/$/g, '') + pathname;
}
getCollectionManager(dataSource?: string) { getCollectionManager(dataSource?: string) {
return this.dataSourceManager.getDataSource(dataSource)?.collectionManager; return this.dataSourceManager.getDataSource(dataSource)?.collectionManager;
} }

View File

@ -1,4 +1,4 @@
import { set, get } from 'lodash'; import { get, set } from 'lodash';
import React, { ComponentType } from 'react'; import React, { ComponentType } from 'react';
import { import {
BrowserRouter, BrowserRouter,
@ -10,8 +10,8 @@ import {
RouteObject, RouteObject,
useRoutes, useRoutes,
} from 'react-router-dom'; } from 'react-router-dom';
import { BlankComponent, RouterContextCleaner } from './components';
import { Application } from './Application'; import { Application } from './Application';
import { BlankComponent, RouterContextCleaner } from './components';
export interface BrowserRouterOptions extends Omit<BrowserRouterProps, 'children'> { export interface BrowserRouterOptions extends Omit<BrowserRouterProps, 'children'> {
type?: 'browser'; type?: 'browser';
@ -98,6 +98,10 @@ export class RouterManager {
this.options.type = type; this.options.type = type;
} }
getBasename() {
return this.options.basename;
}
setBasename(basename: string) { setBasename(basename: string) {
this.options.basename = basename; this.options.basename = basename;
} }

View File

@ -1,34 +1,13 @@
import { define, observable } from '@formily/reactive'; import { define, observable } from '@formily/reactive';
import { getSubAppName } from '@nocobase/sdk'; import { getSubAppName } from '@nocobase/sdk';
import { Application } from './Application';
export const getWebSocketURL = () => {
if (!process.env.API_BASE_URL) {
return;
}
const subApp = getSubAppName();
const queryString = subApp ? `?__appName=${subApp}` : '';
const wsPath = process.env.WS_PATH || '/ws';
if (process.env.WEBSOCKET_URL) {
const url = new URL(process.env.WEBSOCKET_URL);
if (url.hostname === 'localhost') {
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
return `${protocol}://${location.hostname}:${url.port}${wsPath}${queryString}`;
}
return `${process.env.WEBSOCKET_URL}${queryString}`;
}
try {
const url = new URL(process.env.API_BASE_URL);
return `${url.protocol === 'https:' ? 'wss' : 'ws'}://${url.host}${wsPath}${queryString}`;
} catch (error) {
return `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}${wsPath}${queryString}`;
}
};
export type WebSocketClientOptions = { export type WebSocketClientOptions = {
reconnectInterval?: number; reconnectInterval?: number;
reconnectAttempts?: number; reconnectAttempts?: number;
pingInterval?: number; pingInterval?: number;
url?: string; url?: string;
basename?: string;
protocols?: string | string[]; protocols?: string | string[];
onServerDown?: any; onServerDown?: any;
}; };
@ -38,6 +17,7 @@ export class WebSocketClient {
protected _reconnectTimes = 0; protected _reconnectTimes = 0;
protected events = []; protected events = [];
protected options: WebSocketClientOptions; protected options: WebSocketClientOptions;
app: Application;
enabled: boolean; enabled: boolean;
connected = false; connected = false;
serverDown = false; serverDown = false;
@ -57,6 +37,34 @@ export class WebSocketClient {
}); });
} }
getURL() {
if (!this.app) {
return;
}
const options = this.app.getOptions();
const apiBaseURL = options?.apiClient?.['baseURL'];
if (!apiBaseURL) {
return;
}
const subApp = getSubAppName(this.app.getPublicPath());
const queryString = subApp ? `?__appName=${subApp}` : '';
const wsPath = this.options.basename || '/ws';
if (this.options.url) {
const url = new URL(this.options.url);
if (url.hostname === 'localhost') {
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
return `${protocol}://${location.hostname}:${url.port}${wsPath}${queryString}`;
}
return `${this.options.url}${queryString}`;
}
try {
const url = new URL(apiBaseURL);
return `${url.protocol === 'https:' ? 'wss' : 'ws'}://${url.host}${wsPath}${queryString}`;
} catch (error) {
return `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}${wsPath}${queryString}`;
}
}
get reconnectAttempts() { get reconnectAttempts() {
return this.options?.reconnectAttempts || 30; return this.options?.reconnectAttempts || 30;
} }
@ -90,7 +98,7 @@ export class WebSocketClient {
return; return;
} }
this._reconnectTimes++; this._reconnectTimes++;
const ws = new WebSocket(this.options.url || getWebSocketURL(), this.options.protocols); const ws = new WebSocket(this.getURL(), this.options.protocols);
let pingIntervalTimer: any; let pingIntervalTimer: any;
ws.onopen = () => { ws.onopen = () => {
console.log('[nocobase-ws]: connected.'); console.log('[nocobase-ws]: connected.');

View File

@ -3,7 +3,7 @@ import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react
import { Spin } from 'antd'; import { Spin } from 'antd';
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react'; import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
import { useCollection_deprecated } from '../collection-manager'; import { useCollection_deprecated } from '../collection-manager';
import { useCollectionParentRecordData, useCollectionRecord } from '../data-source'; import { CollectionRecord, useCollectionParentRecordData, useCollectionRecord } from '../data-source';
import { RecordProvider, useRecord } from '../record-provider'; import { RecordProvider, useRecord } from '../record-provider';
import { useActionContext, useDesignable } from '../schema-component'; import { useActionContext, useDesignable } from '../schema-component';
import { Templates as DataTemplateSelect } from '../schema-component/antd/form-v2/Templates'; import { Templates as DataTemplateSelect } from '../schema-component/antd/form-v2/Templates';
@ -11,7 +11,20 @@ import { BlockProvider, useBlockRequestContext } from './BlockProvider';
import { TemplateBlockProvider } from './TemplateBlockProvider'; import { TemplateBlockProvider } from './TemplateBlockProvider';
import { FormActiveFieldsProvider } from './hooks/useFormActiveFields'; import { FormActiveFieldsProvider } from './hooks/useFormActiveFields';
export const FormBlockContext = createContext<any>({}); export const FormBlockContext = createContext<{
form?: any;
type?: 'update' | 'create';
action?: string;
field?: any;
service?: any;
resource?: any;
updateAssociationValues?: any;
formBlockRef?: any;
collectionName?: string;
params?: any;
formRecord?: CollectionRecord;
[key: string]: any;
}>({});
FormBlockContext.displayName = 'FormBlockContext'; FormBlockContext.displayName = 'FormBlockContext';
const InternalFormBlockProvider = (props) => { const InternalFormBlockProvider = (props) => {
@ -28,7 +41,7 @@ const InternalFormBlockProvider = (props) => {
const { resource, service, updateAssociationValues } = useBlockRequestContext(); const { resource, service, updateAssociationValues } = useBlockRequestContext();
const formBlockRef = useRef(); const formBlockRef = useRef();
const record = useCollectionRecord(); const record = useCollectionRecord();
const formBlockValue = useMemo(() => { const formBlockValue: any = useMemo(() => {
return { return {
...ctx, ...ctx,
params, params,
@ -42,8 +55,9 @@ const InternalFormBlockProvider = (props) => {
updateAssociationValues, updateAssociationValues,
formBlockRef, formBlockRef,
collectionName: collection, collectionName: collection,
formRecord: record,
}; };
}, [action, field, form, params, resource, service, updateAssociationValues]); }, [action, collection, ctx, field, form, params, record, resource, service, updateAssociationValues]);
if (service.loading && Object.keys(form?.initialValues)?.length === 0 && action) { if (service.loading && Object.keys(form?.initialValues)?.length === 0 && action) {
return <Spin />; return <Spin />;

View File

@ -1,5 +1,6 @@
import { expectSettingsMenu, test } from '@nocobase/test/e2e'; import { expectSettingsMenu, test, expect } from '@nocobase/test/e2e';
import { createTable } from './utils'; import { createTable } from './utils';
import { tableSelectorDataScopeVariable } from './templatesOfBug';
test.describe('table data selector schema settings', () => { test.describe('table data selector schema settings', () => {
test('supported options', async ({ page, mockPage }) => { test('supported options', async ({ page, mockPage }) => {
@ -11,6 +12,126 @@ test.describe('table data selector schema settings', () => {
supportedOptions: ['Set the data scope', 'Set default sorting rules', 'Records per page', 'Delete'], supportedOptions: ['Set the data scope', 'Set default sorting rules', 'Records per page', 'Delete'],
}); });
}); });
test('should have a current form variable', async ({ page, mockPage, mockRecord }) => {
const nocoPage = await mockPage(tableSelectorDataScopeVariable).waitForInit();
const record = await mockRecord('table-selector-data-scope-variable');
await nocoPage.goto();
await page.getByLabel('action-Action.Link-View-view-table-selector-data-scope-variable-table-0').click();
await page.getByTestId('select-data-picker').click();
await page
.getByTestId('drawer-AssociationField.Selector-table-selector-data-scope-variable-Select record')
.getByLabel('block-item-CardItem-table-')
.hover();
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:tableSelector-table-selector-').hover();
await page.getByRole('menuitem', { name: 'Set the data scope' }).click();
// 使用 `当前表单` 设置数据范围
await page.getByText('Add condition', { exact: true }).click();
await page.getByTestId('select-filter-field').click();
await page.getByRole('menuitemcheckbox', { name: 'singleLineText' }).click();
await page.getByTestId('select-filter-operator').click();
await page.getByRole('option', { name: 'is', exact: true }).click();
await page.getByLabel('variable-button').click();
await page.getByRole('menuitemcheckbox', { name: 'Current form right' }).click();
await page.getByRole('menuitemcheckbox', { name: 'singleLineText' }).click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
// 保存数据范围之后,表格选择器中应该只显示一条数据,且该数据与页面表格的第一行数据相同
await expect(
page
.getByTestId('drawer-AssociationField.Selector-table-selector-data-scope-variable-Select record')
.getByLabel('block-item-CardItem-table-')
.getByRole('row'),
).toHaveCount(2); // 这里之所以是 2是因为表头也是一个 row
await expect(
page
.getByTestId('drawer-AssociationField.Selector-table-selector-data-scope-variable-Select record')
.getByLabel('block-item-CardItem-table-')
.getByText(record['singleLineText']),
).toBeVisible();
});
test('should have a current object variable', async ({ page, mockPage, mockRecord }) => {
const nocoPage = await mockPage(tableSelectorDataScopeVariable).waitForInit();
const record = await mockRecord('table-selector-data-scope-variable');
await nocoPage.goto();
await page.getByLabel('action-Action.Link-View-view-table-selector-data-scope-variable-table-0').click();
// 1. 将一个关系字段的 Select 组件改为子表单
await page
.getByLabel(
'block-item-CollectionField-table-selector-data-scope-variable-form-table-selector-data-scope-variable.manyToMany-manyToMany',
{ exact: true },
)
.hover();
await page
.getByLabel(
'designer-schema-settings-CollectionField-fieldSettings:FormItem-table-selector-data-scope-variable-table-selector-data-scope-variable.manyToMany',
{ exact: true },
)
.hover();
await page.getByText('Field componentRecord picker').click();
await page.getByRole('option', { name: 'Sub-form', exact: true }).click();
// 使菜单消失
await page.getByRole('menuitem', { name: 'Delete' }).hover();
await page.mouse.move(-300, 0);
// 2. 在子表单中创建一个关系字段,并切换成数据选择器组件
await page.getByLabel('schema-initializer-Grid-form:').first().hover();
await page.getByRole('menuitem', { name: 'manyToMany' }).click();
await page.getByRole('menuitem', { name: 'singleLineText' }).click();
await page.getByLabel('block-item-CollectionField-').nth(1).hover();
await page
.getByLabel('designer-schema-settings-CollectionField-fieldSettings:FormItem-table-selector-')
.nth(1)
.hover();
await page.getByRole('menuitem', { name: 'Field component Select' }).click();
await page.getByRole('option', { name: 'Record picker' }).click();
// 使菜单消失
await page.getByRole('menuitem', { name: 'Delete' }).hover();
await page.mouse.move(-300, 0);
await page.getByLabel('block-item-CollectionField-').nth(1).click();
// 3. 创建 Table 区块
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'form Table' }).click();
// 4. 设置数据范围
await page.getByLabel('block-item-CardItem-table-selector-data-scope-variable-table-selector').hover();
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:tableSelector-table-selector-').hover();
await page.getByRole('menuitem', { name: 'Set the data scope' }).click();
// 使用 `当前对象` 设置数据范围
await page.getByText('Add condition', { exact: true }).click();
await page.getByTestId('select-filter-field').click();
await page.getByRole('menuitemcheckbox', { name: 'singleLineText' }).click();
await page.getByTestId('select-filter-operator').click();
await page.getByRole('option', { name: 'is', exact: true }).click();
await page.getByLabel('variable-button').click();
await page.getByRole('menuitemcheckbox', { name: 'Current object right' }).click();
await page.getByRole('menuitemcheckbox', { name: 'singleLineText' }).click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
// Table 中显示 singleLineText 字段
await page.getByLabel('schema-initializer-TableV2.').hover();
await page.getByRole('menuitem', { name: 'singleLineText' }).click();
await page.mouse.move(300, 0);
// 保存数据范围之后,表格选择器中应该只显示一条数据,且该数据与页面表格的第一行数据相同
await expect(
page.getByLabel('block-item-CardItem-table-selector-data-scope-variable-table-selector').getByRole('row'),
).toHaveCount(2); // 这里之所以是 2是因为表头也是一个 row
await expect(
page
.getByLabel('block-item-CardItem-table-selector-data-scope-variable-table-selector')
.getByRole('row')
.getByText(record['manyToMany'][0]['singleLineText']),
).toBeVisible();
});
}); });
async function showSettingsMenu(page) { async function showSettingsMenu(page) {

View File

@ -0,0 +1,851 @@
import { PageConfig } from '@nocobase/test/e2e';
const commonCollections = [
{
name: 'table-selector-data-scope-variable',
fields: [
{
name: 'manyToMany',
interface: 'm2m',
target: 'table-selector-data-scope-variable',
},
{
name: 'singleLineText',
interface: 'input',
},
],
},
];
export const tableSelectorDataScopeVariable: PageConfig = {
collections: commonCollections,
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
properties: {
ij2wf9hi0si: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'BlockInitializers',
properties: {
jdz50aeuduh: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
properties: {
ij66b8u5o5m: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
properties: {
'5j0kimft2lz': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'table-selector-data-scope-variable:list',
'x-decorator-props': {
collection: 'table-selector-data-scope-variable',
dataSource: 'main',
resource: 'table-selector-data-scope-variable',
action: 'list',
params: {
pageSize: 20,
},
rowKey: 'id',
showIndex: true,
dragSort: false,
disableTemplate: false,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-filter-targets': [],
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'TableActionInitializers',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
'x-uid': 'vdvzzbfp53e',
'x-async': false,
'x-index': 1,
},
ttcsp9slh2z: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'TableColumnInitializers',
'x-component': 'TableV2',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useProps: '{{ useTableBlockProps }}',
},
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-designer': 'TableV2.ActionColumnDesigner',
'x-initializer': 'TableActionColumnInitializers',
properties: {
f1gruzhfm8r: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {
esrrc38rvqe: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("View") }}',
'x-action': 'view',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:view',
'x-component': 'Action.Link',
'x-component-props': {
openMode: 'drawer',
},
'x-decorator': 'ACLActionProvider',
'x-designer-props': {
linkageAction: true,
},
properties: {
drawer: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("View record") }}',
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
},
properties: {
tabs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'TabPaneInitializers',
properties: {
tab1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'RecordBlockInitializers',
properties: {
zhykdvqpa7k: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
properties: {
luhz7fq6529: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
properties: {
gecihd85864: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-acl-action-props': {
skipScopeCheck: false,
},
'x-acl-action':
'table-selector-data-scope-variable:update',
'x-decorator': 'FormBlockProvider',
'x-decorator-props': {
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
action: 'get',
dataSource: 'main',
resource: 'table-selector-data-scope-variable',
collection: 'table-selector-data-scope-variable',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:editForm',
'x-component': 'CardItem',
'x-component-props': {},
properties: {
'3h121b8k9ey': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'FormV2',
'x-component-props': {
useProps: '{{ useFormBlockProps }}',
},
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'FormItemInitializers',
properties: {
t4tin6z9nq1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
properties: {
xb7fdug52ex: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
properties: {
manyToMany: {
'x-uid': 'p0km7m8dnj4',
_isJSONSchemaObject: true,
version: '2.0',
type: 'string',
'x-toolbar':
'FormItemSchemaToolbar',
'x-settings':
'fieldSettings:FormItem',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field':
'table-selector-data-scope-variable.manyToMany',
'x-component-props': {
fieldNames: {
label: 'id',
value: 'id',
},
mode: 'Picker',
},
properties: {
guunasm2a3h: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component':
'AssociationField.Selector',
title:
'{{ t("Select record") }}',
'x-component-props': {
className:
'nb-record-picker-selector',
},
'x-index': 1,
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer':
'TableSelectorInitializers',
properties: {
'0uui41pxdye': {
_isJSONSchemaObject:
true,
version: '2.0',
type: 'void',
'x-component':
'Grid.Row',
properties: {
'476w75depv3': {
_isJSONSchemaObject:
true,
version: '2.0',
type: 'void',
'x-component':
'Grid.Col',
properties: {
alibfkv2g0v: {
'x-uid':
'y8f4lswt52q',
_isJSONSchemaObject:
true,
version: '2.0',
type: 'void',
'x-acl-action':
'table-selector-data-scope-variable:list',
'x-decorator':
'TableSelectorProvider',
'x-decorator-props':
{
collection:
'table-selector-data-scope-variable',
resource:
'table-selector-data-scope-variable',
dataSource:
'main',
action:
'list',
params: {
pageSize: 20,
filter:
{},
},
rowKey:
'id',
},
'x-toolbar':
'BlockSchemaToolbar',
'x-settings':
'blockSettings:tableSelector',
'x-component':
'CardItem',
properties: {
rblwp47r7fz: {
_isJSONSchemaObject:
true,
version:
'2.0',
type: 'void',
'x-initializer':
'TableActionInitializers',
'x-component':
'ActionBar',
'x-component-props':
{
style: {
marginBottom:
'var(--nb-spacing)',
},
},
'x-uid':
'ubog2cks3ay',
'x-async':
false,
'x-index': 1,
},
value: {
_isJSONSchemaObject:
true,
version:
'2.0',
type: 'array',
'x-initializer':
'TableColumnInitializers',
'x-component':
'TableV2.Selector',
'x-component-props':
{
rowSelection:
{
type: 'checkbox',
},
useProps:
'{{ useTableSelectorProps }}',
},
properties:
{
jryz1oqazsj:
{
_isJSONSchemaObject:
true,
version:
'2.0',
type: 'void',
title:
'{{ t("Actions") }}',
'x-decorator':
'TableV2.Column.ActionBar',
'x-component':
'TableV2.Column',
'x-designer':
'TableV2.ActionColumnDesigner',
'x-initializer':
'TableActionColumnInitializers',
'x-action-column':
'actions',
properties:
{
pkzvqfi6lr5:
{
_isJSONSchemaObject:
true,
version:
'2.0',
type: 'void',
'x-decorator':
'DndContext',
'x-component':
'Space',
'x-component-props':
{
split:
'|',
},
properties:
{
xffx2jx6u7q:
{
_isJSONSchemaObject:
true,
version:
'2.0',
type: 'void',
title:
'{{ t("View") }}',
'x-action':
'view',
'x-toolbar':
'ActionSchemaToolbar',
'x-settings':
'actionSettings:view',
'x-component':
'Action.Link',
'x-component-props':
{
openMode:
'drawer',
},
'x-decorator':
'ACLActionProvider',
'x-designer-props':
{
linkageAction:
true,
},
properties:
{
drawer:
{
_isJSONSchemaObject:
true,
version:
'2.0',
type: 'void',
title:
'{{ t("View record") }}',
'x-component':
'Action.Container',
'x-component-props':
{
className:
'nb-action-popup',
},
properties:
{
tabs: {
_isJSONSchemaObject:
true,
version:
'2.0',
type: 'void',
'x-component':
'Tabs',
'x-component-props':
{},
'x-initializer':
'TabPaneInitializers',
properties:
{
tab1: {
_isJSONSchemaObject:
true,
version:
'2.0',
type: 'void',
title:
'{{t("Details")}}',
'x-component':
'Tabs.TabPane',
'x-designer':
'Tabs.Designer',
'x-component-props':
{},
properties:
{
grid: {
_isJSONSchemaObject:
true,
version:
'2.0',
type: 'void',
'x-component':
'Grid',
'x-initializer':
'RecordBlockInitializers',
'x-uid':
'wkqrpvcqiih',
'x-async':
false,
'x-index': 1,
},
},
'x-uid':
'2lzui9z88st',
'x-async':
false,
'x-index': 1,
},
},
'x-uid':
'ee4zj3qx2ip',
'x-async':
false,
'x-index': 1,
},
},
'x-uid':
'l5ew3g2ka6s',
'x-async':
false,
'x-index': 1,
},
},
'x-uid':
'0m2i5xxfw1k',
'x-async':
false,
'x-index': 1,
},
},
'x-uid':
'zm42p4adruu',
'x-async':
false,
'x-index': 1,
},
},
'x-uid':
'4ybzswir3zr',
'x-async':
false,
'x-index': 2,
},
d51klxxphw4:
{
_isJSONSchemaObject:
true,
version:
'2.0',
type: 'void',
'x-decorator':
'TableV2.Column.Decorator',
'x-toolbar':
'TableColumnSchemaToolbar',
'x-settings':
'fieldSettings:TableColumn',
'x-component':
'TableV2.Column',
properties:
{
singleLineText:
{
_isJSONSchemaObject:
true,
version:
'2.0',
'x-collection-field':
'table-selector-data-scope-variable.singleLineText',
'x-component':
'CollectionField',
'x-component-props':
{
ellipsis:
true,
},
'x-read-pretty':
true,
'x-decorator':
null,
'x-decorator-props':
{
labelStyle:
{
display:
'none',
},
},
'x-uid':
'n069twt8nsc',
'x-async':
false,
'x-index': 1,
},
},
'x-uid':
'51p2r7zlo9k',
'x-async':
false,
'x-index': 3,
},
},
'x-uid':
'gkbturli19t',
'x-async':
false,
'x-index': 2,
},
},
'x-async':
false,
'x-index': 1,
},
},
'x-uid':
't45on50ncwe',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '9hzvnye1ntr',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'k2bdu4w8zz2',
'x-async': false,
'x-index': 1,
},
footer: {
_isJSONSchemaObject: true,
version: '2.0',
'x-component':
'Action.Container.Footer',
'x-component-props': {},
properties: {
actions: {
_isJSONSchemaObject:
true,
version: '2.0',
type: 'void',
'x-component':
'ActionBar',
'x-component-props': {},
properties: {
submit: {
_isJSONSchemaObject:
true,
version: '2.0',
title:
'{{ t("Submit") }}',
'x-action':
'submit',
'x-component':
'Action',
'x-toolbar':
'ActionSchemaToolbar',
'x-settings':
'actionSettings:updateSubmit',
'x-component-props':
{
type: 'primary',
htmlType:
'submit',
useProps:
'{{ usePickActionProps }}',
},
'x-uid':
'n6qpnb48sbd',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '92iud7xsva7',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '3c8prk8jgoh',
'x-async': false,
'x-index': 2,
},
},
'x-uid': '41hvryuv2jy',
'x-async': false,
},
},
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'qbnfti92f1t',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '4dx0hfhj9mj',
'x-async': false,
'x-index': 1,
},
'23d5tg85dmy': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
properties: {
wwivbgmqgly: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
properties: {
singleLineText: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'string',
'x-toolbar':
'FormItemSchemaToolbar',
'x-settings':
'fieldSettings:FormItem',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field':
'table-selector-data-scope-variable.singleLineText',
'x-component-props': {},
'x-uid': 'sj6aet9kqxc',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '9cj7a3ivw6b',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '5w0lqwa932g',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'gmbrw8r2h2s',
'x-async': false,
'x-index': 1,
},
gi40rgo7rbw: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'UpdateFormActionInitializers',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
style: {
marginTop: 24,
},
},
'x-uid': 'go3p2x3eo9j',
'x-async': false,
'x-index': 2,
},
},
'x-uid': '5wir035vnz7',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'r82q143nugf',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'rqwpj88fpss',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '4yfu43yq0ff',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '1xvy1k1xpig',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'ip9pwu9kuf0',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'h9iowhtt6mu',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'so8ptulb3b0',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'lcy4ohio47t',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'oh16739fgf8',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'ut6738nt9in',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '4e89got7e7b',
'x-async': false,
'x-index': 2,
},
},
'x-uid': '5yt30cpknsy',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '0s3jvo8y7y3',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'irunx7mpta6',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '8es0fh04717',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'hwlep783jqy',
'x-async': true,
'x-index': 1,
},
};

View File

@ -10,6 +10,7 @@ import { useAPIClient } from '../api-client';
import { Application } from '../application'; import { Application } from '../application';
import { Plugin } from '../application/Plugin'; import { Plugin } from '../application/Plugin';
import { BlockSchemaComponentPlugin } from '../block-provider'; import { BlockSchemaComponentPlugin } from '../block-provider';
import { CollectionPlugin } from '../collection-manager';
import { RemoteDocumentTitlePlugin } from '../document-title'; import { RemoteDocumentTitlePlugin } from '../document-title';
import { PinnedListPlugin } from '../plugin-manager'; import { PinnedListPlugin } from '../plugin-manager';
import { PMPlugin } from '../pm'; import { PMPlugin } from '../pm';
@ -22,7 +23,6 @@ import { BlockTemplateDetails, BlockTemplatePage } from '../schema-templates';
import { SystemSettingsPlugin } from '../system-settings'; import { SystemSettingsPlugin } from '../system-settings';
import { CurrentUserProvider, CurrentUserSettingsMenuProvider } from '../user'; import { CurrentUserProvider, CurrentUserSettingsMenuProvider } from '../user';
import { LocalePlugin } from './plugins/LocalePlugin'; import { LocalePlugin } from './plugins/LocalePlugin';
import { CollectionPlugin } from '../collection-manager';
const AppSpin = () => { const AppSpin = () => {
return ( return (
@ -36,7 +36,7 @@ const useErrorProps = (app: Application, error: any) => {
return {}; return {};
} }
const err = error?.response?.data?.errors?.[0] || error; const err = error?.response?.data?.errors?.[0] || error;
const subApp = getSubAppName(); const subApp = getSubAppName(app.getPublicPath());
switch (err.code) { switch (err.code) {
case 'USER_HAS_NO_ROLES_ERR': case 'USER_HAS_NO_ROLES_ERR':
return { return {

View File

@ -25,6 +25,7 @@ import {
import { AssociationFieldContext } from './context'; import { AssociationFieldContext } from './context';
import { SubFormProvider, useAssociationFieldContext } from './hooks'; import { SubFormProvider, useAssociationFieldContext } from './hooks';
import { isNewRecord, markRecordAsNew } from '../../../data-source/collection-record/isNewRecord'; import { isNewRecord, markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
import { useCollection } from '../../../data-source';
export const Nester = (props) => { export const Nester = (props) => {
const { options } = useContext(AssociationFieldContext); const { options } = useContext(AssociationFieldContext);
@ -48,6 +49,7 @@ export const Nester = (props) => {
const ToOneNester = (props) => { const ToOneNester = (props) => {
const { field } = useAssociationFieldContext<ArrayField>(); const { field } = useAssociationFieldContext<ArrayField>();
const recordV2 = useCollectionRecord(); const recordV2 = useCollectionRecord();
const collection = useCollection();
const isAllowToSetDefaultValue = useCallback( const isAllowToSetDefaultValue = useCallback(
({ form, fieldSchema, collectionField, getInterface, formBlockType }: IsAllowToSetDefaultValueParams) => { ({ form, fieldSchema, collectionField, getInterface, formBlockType }: IsAllowToSetDefaultValueParams) => {
@ -84,7 +86,7 @@ const ToOneNester = (props) => {
return ( return (
<FormActiveFieldsProvider name="nester"> <FormActiveFieldsProvider name="nester">
<SubFormProvider value={field.value}> <SubFormProvider value={{ value: field.value, collection }}>
<RecordProvider isNew={recordV2?.isNew} record={field.value} parent={recordV2?.data}> <RecordProvider isNew={recordV2?.isNew} record={field.value} parent={recordV2?.data}>
<DefaultValueProvider isAllowToSetDefaultValue={isAllowToSetDefaultValue}> <DefaultValueProvider isAllowToSetDefaultValue={isAllowToSetDefaultValue}>
<Card bordered={true}>{props.children}</Card> <Card bordered={true}>{props.children}</Card>
@ -101,6 +103,7 @@ const ToManyNester = observer(
const { options, field, allowMultiple, allowDissociate } = useAssociationFieldContext<ArrayField>(); const { options, field, allowMultiple, allowDissociate } = useAssociationFieldContext<ArrayField>();
const { t } = useTranslation(); const { t } = useTranslation();
const recordData = useCollectionRecordData(); const recordData = useCollectionRecordData();
const collection = useCollection();
if (!Array.isArray(field.value)) { if (!Array.isArray(field.value)) {
field.value = []; field.value = [];
@ -193,7 +196,7 @@ const ToManyNester = observer(
)} )}
</div> </div>
<FormActiveFieldsProvider name="nester"> <FormActiveFieldsProvider name="nester">
<SubFormProvider value={value}> <SubFormProvider value={{ value, collection }}>
<RecordProvider isNew={isNewRecord(value)} record={value} parent={recordData}> <RecordProvider isNew={isNewRecord(value)} record={value} parent={recordData}>
<RecordIndexProvider index={index}> <RecordIndexProvider index={index}>
<DefaultValueProvider isAllowToSetDefaultValue={isAllowToSetDefaultValue}> <DefaultValueProvider isAllowToSetDefaultValue={isAllowToSetDefaultValue}>

View File

@ -16,6 +16,7 @@ import { getVariableName } from '../../../variables/utils/getVariableName';
import { isVariable } from '../../../variables/utils/isVariable'; import { isVariable } from '../../../variables/utils/isVariable';
import { useDesignable } from '../../hooks'; import { useDesignable } from '../../hooks';
import { AssociationFieldContext } from './context'; import { AssociationFieldContext } from './context';
import { Collection } from '../../../data-source';
export const useInsertSchema = (component) => { export const useInsertSchema = (component) => {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
@ -178,7 +179,10 @@ export const useFieldNames = (props) => {
return { label: 'label', value: 'value', ...fieldNames }; return { label: 'label', value: 'value', ...fieldNames };
}; };
const SubFormContext = createContext<Record<string, any>>(null); const SubFormContext = createContext<{
value: any;
collection: Collection;
}>(null);
SubFormContext.displayName = 'SubFormContext'; SubFormContext.displayName = 'SubFormContext';
export const SubFormProvider = SubFormContext.Provider; export const SubFormProvider = SubFormContext.Provider;
@ -192,8 +196,9 @@ export const SubFormProvider = SubFormContext.Provider;
* @returns * @returns
*/ */
export const useSubFormValue = () => { export const useSubFormValue = () => {
const context = useContext(SubFormContext); const { value, collection } = useContext(SubFormContext) || {};
return { return {
formValue: context, formValue: value,
collection,
}; };
}; };

View File

@ -18,6 +18,7 @@ import { DndContext, useDesignable, useTableSize } from '../..';
import { import {
RecordIndexProvider, RecordIndexProvider,
RecordProvider, RecordProvider,
useCollection,
useCollection_deprecated, useCollection_deprecated,
useCollectionParentRecordData, useCollectionParentRecordData,
useSchemaInitializerRender, useSchemaInitializerRender,
@ -44,6 +45,8 @@ const useTableColumns = (props: { showDel?: boolean; isSubTable?: boolean }) =>
const { designable } = useDesignable(); const { designable } = useDesignable();
const { exists, render } = useSchemaInitializerRender(schema['x-initializer'], schema['x-initializer-props']); const { exists, render } = useSchemaInitializerRender(schema['x-initializer'], schema['x-initializer-props']);
const parentRecordData = useCollectionParentRecordData(); const parentRecordData = useCollectionParentRecordData();
const collection = useCollection();
const dataSource = field?.value?.slice?.()?.filter?.(Boolean) || []; const dataSource = field?.value?.slice?.()?.filter?.(Boolean) || [];
const columns = schema const columns = schema
.reduceProperties((buf, s) => { .reduceProperties((buf, s) => {
@ -69,7 +72,7 @@ const useTableColumns = (props: { showDel?: boolean; isSubTable?: boolean }) =>
render: (v, record) => { render: (v, record) => {
const index = field.value?.indexOf(record); const index = field.value?.indexOf(record);
return ( return (
<SubFormProvider value={record}> <SubFormProvider value={{ value: record, collection }}>
<RecordIndexProvider index={record.__index || index}> <RecordIndexProvider index={record.__index || index}>
<RecordProvider isNew={isNewRecord(record)} record={record} parent={parentRecordData}> <RecordProvider isNew={isNewRecord(record)} record={record} parent={parentRecordData}>
<ColumnFieldProvider schema={s} basePath={field.address.concat(record.__index || index)}> <ColumnFieldProvider schema={s} basePath={field.address.concat(record.__index || index)}>

View File

@ -734,7 +734,7 @@ const recursiveParent = (schema: Schema) => {
export const useCurrentSchema = (action: string, key: string, find = findSchema, rm = removeSchema) => { export const useCurrentSchema = (action: string, key: string, find = findSchema, rm = removeSchema) => {
const { removeActiveFieldName } = useFormActiveFields() || {}; const { removeActiveFieldName } = useFormActiveFields() || {};
const { form }: { form: Form } = useFormBlockContext(); const { form }: { form?: Form } = useFormBlockContext();
let fieldSchema = useFieldSchema(); let fieldSchema = useFieldSchema();
if (!fieldSchema?.['x-initializer'] && fieldSchema?.['x-decorator'] === 'FormItem') { if (!fieldSchema?.['x-initializer'] && fieldSchema?.['x-decorator'] === 'FormItem') {
const recursiveInitializerSchema = recursiveParent(fieldSchema); const recursiveInitializerSchema = recursiveParent(fieldSchema);

View File

@ -15,7 +15,6 @@ export const ChildDynamicComponent = observer(
collectionField, collectionField,
}); });
const { currentObjectSettings } = useCurrentObjectVariable({ const { currentObjectSettings } = useCurrentObjectVariable({
currentCollection: collectionField?.collectionName,
schema: collectionField?.uiSchema, schema: collectionField?.uiSchema,
collectionField, collectionField,
}); });

View File

@ -167,7 +167,7 @@ export const FormLinkageRules = observer(
); );
return ( return (
<FormBlockContext.Provider value={{ form, type: formBlockType }}> <FormBlockContext.Provider value={{ form, type: formBlockType, collectionName }}>
<RecordProvider record={record} parent={parentRecordData}> <RecordProvider record={record} parent={parentRecordData}>
<FilterContext.Provider value={value}> <FilterContext.Provider value={value}>
<SchemaComponent components={components} schema={schema} /> <SchemaComponent components={components} schema={schema} />

View File

@ -70,6 +70,7 @@ import {
} from '..'; } from '..';
import { import {
BlockRequestContext_deprecated, BlockRequestContext_deprecated,
FormBlockContext,
useFormBlockContext, useFormBlockContext,
useFormBlockType, useFormBlockType,
useTableBlockContext, useTableBlockContext,
@ -968,9 +969,10 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
const dataSourceKey = useDataSourceKey(); const dataSourceKey = useDataSourceKey();
const record = useCollectionRecord(); const record = useCollectionRecord();
const { association } = useDataBlockProps() || {}; const { association } = useDataBlockProps() || {};
const formCtx = useFormBlockContext();
// 解决变量`当前对象`值在弹窗中丢失的问题 // 解决变量`当前对象`值在弹窗中丢失的问题
const { formValue: subFormValue } = useSubFormValue(); const { formValue: subFormValue, collection: subFormCollection } = useSubFormValue();
if (hidden) { if (hidden) {
return null; return null;
@ -987,44 +989,49 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
() => { () => {
return ( return (
<CollectionRecordProvider record={record}> <CollectionRecordProvider record={record}>
<SubFormProvider value={subFormValue}> <FormBlockContext.Provider value={formCtx}>
<FormActiveFieldsProvider name="form" getActiveFieldsName={upLevelActiveFields?.getActiveFieldsName}> <SubFormProvider value={{ value: subFormValue, collection: subFormCollection }}>
<Router location={location} navigator={null}> <FormActiveFieldsProvider
<BlockRequestContext_deprecated.Provider value={ctx}> name="form"
<DataSourceApplicationProvider dataSourceManager={dm} dataSource={dataSourceKey}> getActiveFieldsName={upLevelActiveFields?.getActiveFieldsName}
<AssociationOrCollectionProvider >
allowNull <Router location={location} navigator={null}>
collection={collection.name} <BlockRequestContext_deprecated.Provider value={ctx}>
association={association} <DataSourceApplicationProvider dataSourceManager={dm} dataSource={dataSourceKey}>
> <AssociationOrCollectionProvider
<SchemaComponentOptions scope={options.scope} components={options.components}> allowNull
<FormLayout collection={collection.name}
layout={'vertical'} association={association}
className={css` >
// screen > 576px <SchemaComponentOptions scope={options.scope} components={options.components}>
@media (min-width: 576px) { <FormLayout
min-width: 520px; layout={'vertical'}
} className={css`
// screen > 576px
@media (min-width: 576px) {
min-width: 520px;
}
// screen <= 576px // screen <= 576px
@media (max-width: 576px) { @media (max-width: 576px) {
min-width: 320px; min-width: 320px;
} }
`} `}
> >
<APIClientProvider apiClient={apiClient}> <APIClientProvider apiClient={apiClient}>
<ConfigProvider locale={locale}> <ConfigProvider locale={locale}>
<SchemaComponent components={components} scope={scope} schema={schema} /> <SchemaComponent components={components} scope={scope} schema={schema} />
</ConfigProvider> </ConfigProvider>
</APIClientProvider> </APIClientProvider>
</FormLayout> </FormLayout>
</SchemaComponentOptions> </SchemaComponentOptions>
</AssociationOrCollectionProvider> </AssociationOrCollectionProvider>
</DataSourceApplicationProvider> </DataSourceApplicationProvider>
</BlockRequestContext_deprecated.Provider> </BlockRequestContext_deprecated.Provider>
</Router> </Router>
</FormActiveFieldsProvider> </FormActiveFieldsProvider>
</SubFormProvider> </SubFormProvider>
</FormBlockContext.Provider>
</CollectionRecordProvider> </CollectionRecordProvider>
); );
}, },

View File

@ -41,10 +41,6 @@ type Props = {
style?: React.CSSProperties; style?: React.CSSProperties;
collectionField: CollectionFieldOptions_deprecated; collectionField: CollectionFieldOptions_deprecated;
contextCollectionName?: string; contextCollectionName?: string;
/**指定当前表单数据表 */
currentFormCollectionName?: string;
/**指定当前对象数据表 */
currentIterationCollectionName?: string;
/** /**
* `onChange` `onChange` * `onChange` `onChange`
* @param value `onChange` * @param value `onChange`
@ -86,8 +82,6 @@ export const VariableInput = (props: Props) => {
record, record,
returnScope = _.identity, returnScope = _.identity,
targetFieldSchema, targetFieldSchema,
currentFormCollectionName,
currentIterationCollectionName,
} = props; } = props;
const { name: blockCollectionName } = useBlockCollection(); const { name: blockCollectionName } = useBlockCollection();
const scope = useVariableScope(); const scope = useVariableScope();
@ -100,8 +94,6 @@ export const VariableInput = (props: Props) => {
operator, operator,
uiSchema, uiSchema,
targetFieldSchema, targetFieldSchema,
currentFormCollectionName,
currentIterationCollectionName,
}); });
const contextVariable = useContextAssociationFields({ schema, maxDepth: 2, contextCollectionName, collectionField }); const contextVariable = useContextAssociationFields({ schema, maxDepth: 2, contextCollectionName, collectionField });
const { compatOldVariables } = useCompatOldVariables({ const { compatOldVariables } = useCompatOldVariables({
@ -245,7 +237,6 @@ export function useCompatOldVariables(props: {
}); });
const { currentRecordSettings } = useCurrentRecordVariable({ const { currentRecordSettings } = useCurrentRecordVariable({
schema: uiSchema, schema: uiSchema,
collectionName: blockCollectionName,
collectionField, collectionField,
noDisabled, noDisabled,
targetFieldSchema, targetFieldSchema,

View File

@ -57,7 +57,6 @@ export const useFormVariable = ({ collectionName, collectionField, schema, noDis
* @returns * @returns
*/ */
export const useCurrentFormVariable = ({ export const useCurrentFormVariable = ({
collectionName,
collectionField, collectionField,
schema, schema,
noDisabled, noDisabled,
@ -66,7 +65,7 @@ export const useCurrentFormVariable = ({
}: Props = {}) => { }: Props = {}) => {
// const { getActiveFieldsName } = useFormActiveFields() || {}; // const { getActiveFieldsName } = useFormActiveFields() || {};
const { t } = useTranslation(); const { t } = useTranslation();
const { form } = useFormBlockContext(); const { form, collectionName } = useFormBlockContext();
const currentFormSettings = useBaseVariable({ const currentFormSettings = useBaseVariable({
collectionField, collectionField,
uiSchema: schema, uiSchema: schema,
@ -97,6 +96,6 @@ export const useCurrentFormVariable = ({
/** 变量值 */ /** 变量值 */
currentFormCtx: formInstance?.values, currentFormCtx: formInstance?.values,
/** 用来判断是否可以显示`当前表单`变量 */ /** 用来判断是否可以显示`当前表单`变量 */
shouldDisplayCurrentForm: !!formInstance && !formInstance.readPretty, shouldDisplayCurrentForm: formInstance && !formInstance.readPretty,
}; };
}; };

View File

@ -5,6 +5,7 @@ import { CollectionFieldOptions } from '../../../data-source/collection/Collecti
import { useFlag } from '../../../flag-provider'; import { useFlag } from '../../../flag-provider';
import { useSubFormValue } from '../../../schema-component/antd/association-field/hooks'; import { useSubFormValue } from '../../../schema-component/antd/association-field/hooks';
import { useBaseVariable } from './useBaseVariable'; import { useBaseVariable } from './useBaseVariable';
import { useCollection } from '../../../data-source';
/** /**
* @deprecated * @deprecated
@ -61,13 +62,11 @@ export const useIterationVariable = ({
* @returns * @returns
*/ */
export const useCurrentObjectVariable = ({ export const useCurrentObjectVariable = ({
currentCollection,
collectionField, collectionField,
schema, schema,
noDisabled, noDisabled,
targetFieldSchema, targetFieldSchema,
}: { }: {
currentCollection?: string;
collectionField?: CollectionFieldOptions; collectionField?: CollectionFieldOptions;
schema?: any; schema?: any;
noDisabled?: boolean; noDisabled?: boolean;
@ -75,7 +74,8 @@ export const useCurrentObjectVariable = ({
targetFieldSchema?: Schema; targetFieldSchema?: Schema;
} = {}) => { } = {}) => {
// const { getActiveFieldsName } = useFormActiveFields() || {}; // const { getActiveFieldsName } = useFormActiveFields() || {};
const { formValue: currentObjectCtx } = useSubFormValue(); const collection = useCollection();
const { formValue: currentObjectCtx, collection: collectionOfCurrentObject } = useSubFormValue();
const { isInSubForm, isInSubTable } = useFlag() || {}; const { isInSubForm, isInSubTable } = useFlag() || {};
const { t } = useTranslation(); const { t } = useTranslation();
const currentObjectSettings = useBaseVariable({ const currentObjectSettings = useBaseVariable({
@ -85,7 +85,7 @@ export const useCurrentObjectVariable = ({
maxDepth: 4, maxDepth: 4,
name: '$iteration', name: '$iteration',
title: t('Current object'), title: t('Current object'),
collectionName: currentCollection, collectionName: collectionOfCurrentObject?.name || collection?.name,
noDisabled, noDisabled,
returnFields: (fields, option) => { returnFields: (fields, option) => {
// fix https://nocobase.height.app/T-2277 // fix https://nocobase.height.app/T-2277

View File

@ -2,10 +2,8 @@ import { Schema } from '@formily/json-schema';
import _ from 'lodash'; import _ from 'lodash';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { CollectionFieldOptions_deprecated } from '../../../collection-manager'; import { CollectionFieldOptions_deprecated } from '../../../collection-manager';
import { useCollection } from '../../../data-source/collection/CollectionProvider';
import { useCollectionRecord } from '../../../data-source/collection-record/CollectionRecordProvider';
import { useFlag } from '../../../flag-provider/hooks/useFlag';
import { useBaseVariable } from './useBaseVariable'; import { useBaseVariable } from './useBaseVariable';
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
interface Props { interface Props {
collectionField?: CollectionFieldOptions_deprecated; collectionField?: CollectionFieldOptions_deprecated;
@ -47,16 +45,13 @@ export const useRecordVariable = (props: Props) => {
*/ */
export const useCurrentRecordVariable = (props: Props = {}) => { export const useCurrentRecordVariable = (props: Props = {}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { isInSubForm, isInSubTable } = useFlag() || {}; const { formRecord, collectionName } = useFormBlockContext();
const record = useCollectionRecord();
const recordData = isInSubForm || isInSubTable ? record?.parentRecord?.data : record?.data;
const { name: collectionName } = useCollection() || {};
const currentRecordSettings = useBaseVariable({ const currentRecordSettings = useBaseVariable({
collectionField: props.collectionField, collectionField: props.collectionField,
uiSchema: props.schema, uiSchema: props.schema,
name: '$nRecord', name: '$nRecord',
title: t('Current record'), title: t('Current record'),
collectionName: props.collectionName || collectionName, collectionName: collectionName,
noDisabled: props.noDisabled, noDisabled: props.noDisabled,
targetFieldSchema: props.targetFieldSchema, targetFieldSchema: props.targetFieldSchema,
}); });
@ -65,9 +60,9 @@ export const useCurrentRecordVariable = (props: Props = {}) => {
/** 变量配置 */ /** 变量配置 */
currentRecordSettings, currentRecordSettings,
/** 变量值 */ /** 变量值 */
currentRecordCtx: recordData, currentRecordCtx: formRecord?.data,
/** 用于判断是否需要显示配置项 */ /** 用于判断是否需要显示配置项 */
shouldDisplayCurrentRecord: !_.isEmpty(recordData), shouldDisplayCurrentRecord: !formRecord?.isNew && !_.isEmpty(formRecord?.data),
/** 当前记录对应的 collection name */ /** 当前记录对应的 collection name */
collectionName, collectionName,
}; };

View File

@ -35,10 +35,6 @@ interface Props {
noDisabled?: boolean; noDisabled?: boolean;
/** 消费变量值的字段 */ /** 消费变量值的字段 */
targetFieldSchema?: Schema; targetFieldSchema?: Schema;
/**指定当前表单数据表 */
currentFormCollectionName?: string;
/**指定当前对象数据表 */
currentIterationCollectionName?: string;
} }
export const useVariableOptions = ({ export const useVariableOptions = ({
@ -49,11 +45,8 @@ export const useVariableOptions = ({
noDisabled, noDisabled,
targetFieldSchema, targetFieldSchema,
record, record,
currentFormCollectionName,
currentIterationCollectionName,
}: Props) => { }: Props) => {
const { name: blockCollectionName = record?.__collectionName } = useBlockCollection(); const { name: blockCollectionName = record?.__collectionName } = useBlockCollection();
const { name } = useCollection_deprecated();
const blockParentCollectionName = record?.__parent?.__collectionName; const blockParentCollectionName = record?.__parent?.__collectionName;
const { currentUserSettings } = useCurrentUserVariable({ const { currentUserSettings } = useCurrentUserVariable({
maxDepth: 3, maxDepth: 3,
@ -71,14 +64,12 @@ export const useVariableOptions = ({
const { datetimeSettings } = useDatetimeVariable({ operator, schema: uiSchema, noDisabled }); const { datetimeSettings } = useDatetimeVariable({ operator, schema: uiSchema, noDisabled });
const { currentFormSettings, shouldDisplayCurrentForm } = useCurrentFormVariable({ const { currentFormSettings, shouldDisplayCurrentForm } = useCurrentFormVariable({
schema: uiSchema, schema: uiSchema,
collectionName: currentFormCollectionName || blockCollectionName,
collectionField, collectionField,
noDisabled, noDisabled,
targetFieldSchema, targetFieldSchema,
form, form,
}); });
const { currentObjectSettings, shouldDisplayCurrentObject } = useCurrentObjectVariable({ const { currentObjectSettings, shouldDisplayCurrentObject } = useCurrentObjectVariable({
currentCollection: currentIterationCollectionName || name,
collectionField, collectionField,
schema: uiSchema, schema: uiSchema,
noDisabled, noDisabled,
@ -86,7 +77,6 @@ export const useVariableOptions = ({
}); });
const { currentRecordSettings, shouldDisplayCurrentRecord } = useCurrentRecordVariable({ const { currentRecordSettings, shouldDisplayCurrentRecord } = useCurrentRecordVariable({
schema: uiSchema, schema: uiSchema,
collectionName: blockCollectionName,
collectionField, collectionField,
noDisabled, noDisabled,
targetFieldSchema, targetFieldSchema,

View File

@ -1,6 +1,6 @@
{ {
"name": "create-nocobase-app", "name": "create-nocobase-app",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "src/index.js", "main": "src/index.js",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {

View File

@ -1,16 +1,16 @@
{ {
"name": "@nocobase/data-source-manager", "name": "@nocobase/data-source-manager",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/actions": "0.20.0-alpha.10", "@nocobase/actions": "0.20.0-alpha.12",
"@nocobase/cache": "0.20.0-alpha.10", "@nocobase/cache": "0.20.0-alpha.12",
"@nocobase/database": "0.20.0-alpha.10", "@nocobase/database": "0.20.0-alpha.12",
"@nocobase/resourcer": "0.20.0-alpha.10", "@nocobase/resourcer": "0.20.0-alpha.12",
"@nocobase/utils": "0.20.0-alpha.10", "@nocobase/utils": "0.20.0-alpha.12",
"@types/jsonwebtoken": "^8.5.8", "@types/jsonwebtoken": "^8.5.8",
"jsonwebtoken": "^8.5.1" "jsonwebtoken": "^8.5.1"
}, },

View File

@ -1,13 +1,13 @@
{ {
"name": "@nocobase/database", "name": "@nocobase/database",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "", "description": "",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@nocobase/logger": "0.20.0-alpha.10", "@nocobase/logger": "0.20.0-alpha.12",
"@nocobase/utils": "0.20.0-alpha.10", "@nocobase/utils": "0.20.0-alpha.12",
"async-mutex": "^0.3.2", "async-mutex": "^0.3.2",
"chalk": "^4.1.1", "chalk": "^4.1.1",
"cron-parser": "4.4.0", "cron-parser": "4.4.0",

View File

@ -1,13 +1,13 @@
{ {
"name": "@nocobase/devtools", "name": "@nocobase/devtools",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./src/index.js", "main": "./src/index.js",
"dependencies": { "dependencies": {
"@nocobase/build": "0.20.0-alpha.10", "@nocobase/build": "0.20.0-alpha.12",
"@nocobase/client": "0.20.0-alpha.10", "@nocobase/client": "0.20.0-alpha.12",
"@nocobase/test": "0.20.0-alpha.10", "@nocobase/test": "0.20.0-alpha.12",
"@types/koa": "^2.13.4", "@types/koa": "^2.13.4",
"@types/koa-bodyparser": "^4.3.4", "@types/koa-bodyparser": "^4.3.4",
"@types/lodash": "^4.14.177", "@types/lodash": "^4.14.177",

View File

@ -8,11 +8,11 @@ const path = require('path');
console.log('VERSION: ', packageJson.version); console.log('VERSION: ', packageJson.version);
function getUmiConfig() { function getUmiConfig() {
const { APP_PORT, API_BASE_URL } = process.env; const { APP_PORT, API_BASE_URL, APP_PUBLIC_PATH } = process.env;
const API_BASE_PATH = process.env.API_BASE_PATH || '/api/'; const API_BASE_PATH = process.env.API_BASE_PATH || '/api/';
const PROXY_TARGET_URL = process.env.PROXY_TARGET_URL || `http://127.0.0.1:${APP_PORT}`; const PROXY_TARGET_URL = process.env.PROXY_TARGET_URL || `http://127.0.0.1:${APP_PORT}`;
const LOCAL_STORAGE_BASE_URL = '/storage/uploads/'; const LOCAL_STORAGE_BASE_URL = 'storage/uploads/';
const STATIC_PATH = '/static/'; const STATIC_PATH = 'static/';
function getLocalStorageProxy() { function getLocalStorageProxy() {
if (LOCAL_STORAGE_BASE_URL.startsWith('http')) { if (LOCAL_STORAGE_BASE_URL.startsWith('http')) {
@ -20,11 +20,11 @@ function getUmiConfig() {
} }
return { return {
[LOCAL_STORAGE_BASE_URL]: { [APP_PUBLIC_PATH + LOCAL_STORAGE_BASE_URL]: {
target: PROXY_TARGET_URL, target: PROXY_TARGET_URL,
changeOrigin: true, changeOrigin: true,
}, },
[STATIC_PATH]: { [APP_PUBLIC_PATH + STATIC_PATH]: {
target: PROXY_TARGET_URL, target: PROXY_TARGET_URL,
changeOrigin: true, changeOrigin: true,
}, },
@ -37,6 +37,7 @@ function getUmiConfig() {
return memo; return memo;
}, {}), }, {}),
define: { define: {
'process.env.APP_PUBLIC_PATH': process.env.APP_PUBLIC_PATH,
'process.env.WS_PATH': process.env.WS_PATH, 'process.env.WS_PATH': process.env.WS_PATH,
'process.env.API_BASE_URL': API_BASE_URL || API_BASE_PATH, 'process.env.API_BASE_URL': API_BASE_URL || API_BASE_PATH,
'process.env.APP_ENV': process.env.APP_ENV, 'process.env.APP_ENV': process.env.APP_ENV,
@ -221,9 +222,10 @@ export default function devDynamicImport(packageName: string): Promise<any> {
const pluginPackageJson = require(pluginPackageJsonPath); const pluginPackageJson = require(pluginPackageJsonPath);
const pluginPathArr = pluginPackageJsonPath.replaceAll(path.sep, '/').split('/'); const pluginPathArr = pluginPackageJsonPath.replaceAll(path.sep, '/').split('/');
const hasNamespace = pluginPathArr[pluginPathArr.length - 3].startsWith('@'); const hasNamespace = pluginPathArr[pluginPathArr.length - 3].startsWith('@');
const pluginFileName = (hasNamespace const pluginFileName = (
? `${pluginPathArr[pluginPathArr.length - 3].replace('@', '')}_${pluginPathArr[pluginPathArr.length - 2]}` hasNamespace
: pluginPathArr[pluginPathArr.length - 2] ? `${pluginPathArr[pluginPathArr.length - 3].replace('@', '')}_${pluginPathArr[pluginPathArr.length - 2]}`
: pluginPathArr[pluginPathArr.length - 2]
).replaceAll('-', '_'); ).replaceAll('-', '_');
let exportStatement = ''; let exportStatement = '';

View File

@ -1,13 +1,13 @@
{ {
"name": "@nocobase/evaluators", "name": "@nocobase/evaluators",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "", "description": "",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@formulajs/formulajs": "4.2.0", "@formulajs/formulajs": "4.2.0",
"@nocobase/utils": "0.20.0-alpha.10", "@nocobase/utils": "0.20.0-alpha.12",
"mathjs": "^10.6.0" "mathjs": "^10.6.0"
}, },
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/logger", "name": "@nocobase/logger",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "nocobase logging library", "description": "nocobase logging library",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",

View File

@ -1,12 +1,12 @@
{ {
"name": "@nocobase/resourcer", "name": "@nocobase/resourcer",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "", "description": "",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@nocobase/utils": "0.20.0-alpha.10", "@nocobase/utils": "0.20.0-alpha.12",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"koa-compose": "^4.1.0", "koa-compose": "^4.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/sdk", "name": "@nocobase/sdk",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",

View File

@ -48,7 +48,7 @@ export class Auth {
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
return; return;
} }
const appName = getSubAppName(); const appName = getSubAppName(this.api['app'] ? this.api['app'].getPublicPath() : '/');
if (!appName) { if (!appName) {
return; return;
} }

View File

@ -1,9 +1,11 @@
const getSubAppName = () => { const getSubAppName = (publicPath = '/') => {
const match = window.location.pathname.match(/^\/apps\/([^/]*)\/?/); const prefix = `${publicPath}apps/`;
if (!match) { if (!window.location.pathname.startsWith(prefix)) {
return ''; return;
} }
return match[1]; const pathname = window.location.pathname.substring(prefix.length);
const args = pathname.split('/', 1);
return args[0] || '';
}; };
export default getSubAppName; export default getSubAppName;

View File

@ -1,3 +1,3 @@
export * from './APIClient'; export * from './APIClient';
export { default as getSubAppName } from './getSubAppName';
export { default as getSubAppName } from './getSubAppName';

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/server", "name": "@nocobase/server",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "lib/index.js", "main": "lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -10,18 +10,18 @@
"@koa/cors": "^3.1.0", "@koa/cors": "^3.1.0",
"@koa/multer": "^3.0.2", "@koa/multer": "^3.0.2",
"@koa/router": "^9.4.0", "@koa/router": "^9.4.0",
"@nocobase/acl": "0.20.0-alpha.10", "@nocobase/acl": "0.20.0-alpha.12",
"@nocobase/actions": "0.20.0-alpha.10", "@nocobase/actions": "0.20.0-alpha.12",
"@nocobase/auth": "0.20.0-alpha.10", "@nocobase/auth": "0.20.0-alpha.12",
"@nocobase/cache": "0.20.0-alpha.10", "@nocobase/cache": "0.20.0-alpha.12",
"@nocobase/data-source-manager": "0.20.0-alpha.10", "@nocobase/data-source-manager": "0.20.0-alpha.12",
"@nocobase/database": "0.20.0-alpha.10", "@nocobase/database": "0.20.0-alpha.12",
"@nocobase/evaluators": "0.20.0-alpha.10", "@nocobase/evaluators": "0.20.0-alpha.12",
"@nocobase/logger": "0.20.0-alpha.10", "@nocobase/logger": "0.20.0-alpha.12",
"@nocobase/resourcer": "0.20.0-alpha.10", "@nocobase/resourcer": "0.20.0-alpha.12",
"@nocobase/sdk": "0.20.0-alpha.10", "@nocobase/sdk": "0.20.0-alpha.12",
"@nocobase/telemetry": "0.20.0-alpha.10", "@nocobase/telemetry": "0.20.0-alpha.12",
"@nocobase/utils": "0.20.0-alpha.10", "@nocobase/utils": "0.20.0-alpha.12",
"@types/decompress": "4.2.4", "@types/decompress": "4.2.4",
"@types/ini": "^1.3.31", "@types/ini": "^1.3.31",
"@types/koa-send": "^4.1.3", "@types/koa-send": "^4.1.3",

View File

@ -15,7 +15,7 @@ import handler from 'serve-handler';
import { parse } from 'url'; import { parse } from 'url';
import { AppSupervisor } from '../app-supervisor'; import { AppSupervisor } from '../app-supervisor';
import { ApplicationOptions } from '../application'; import { ApplicationOptions } from '../application';
import { PLUGIN_STATICS_PATH, getPackageDirByExposeUrl, getPackageNameByExposeUrl } from '../plugin-manager'; import { getPackageDirByExposeUrl, getPackageNameByExposeUrl } from '../plugin-manager';
import { applyErrorWithArgs, getErrorWithCode } from './errors'; import { applyErrorWithArgs, getErrorWithCode } from './errors';
import { IPCSocketClient } from './ipc-socket-client'; import { IPCSocketClient } from './ipc-socket-client';
import { IPCSocketServer } from './ipc-socket-server'; import { IPCSocketServer } from './ipc-socket-server';
@ -168,14 +168,16 @@ export class Gateway extends EventEmitter {
async requestHandler(req: IncomingMessage, res: ServerResponse) { async requestHandler(req: IncomingMessage, res: ServerResponse) {
const { pathname } = parse(req.url); const { pathname } = parse(req.url);
const { PLUGIN_STATICS_PATH, APP_PUBLIC_PATH } = process.env;
if (pathname === '/__umi/api/bundle-status') { if (pathname.endsWith('/__umi/api/bundle-status')) {
res.statusCode = 200; res.statusCode = 200;
res.end('ok'); res.end('ok');
return; return;
} }
if (pathname.startsWith('/storage/uploads/')) { if (pathname.startsWith(APP_PUBLIC_PATH + 'storage/uploads/')) {
req.url = req.url.substring(APP_PUBLIC_PATH.length - 1);
await compress(req, res); await compress(req, res);
return handler(req, res, { return handler(req, res, {
public: resolve(process.cwd()), public: resolve(process.cwd()),
@ -203,7 +205,8 @@ export class Gateway extends EventEmitter {
}); });
} }
if (!pathname.startsWith('/api')) { if (!pathname.startsWith(process.env.API_BASE_PATH)) {
req.url = req.url.substring(APP_PUBLIC_PATH.length - 1);
await compress(req, res); await compress(req, res);
return handler(req, res, { return handler(req, res, {
public: `${process.env.APP_PACKAGE_ROOT}/dist/client`, public: `${process.env.APP_PACKAGE_ROOT}/dist/client`,

View File

@ -36,7 +36,7 @@ export function getPackageFilePathWithExistCheck(packageName: string, filePath:
} }
export function getExposeUrl(packageName: string, filePath: string) { export function getExposeUrl(packageName: string, filePath: string) {
return `${PLUGIN_STATICS_PATH}${packageName}/${filePath}`; return `${process.env.PLUGIN_STATICS_PATH}${packageName}/${filePath}`;
} }
export function getExposeReadmeUrl(packageName: string, lang: string) { export function getExposeReadmeUrl(packageName: string, lang: string) {
@ -63,7 +63,7 @@ export function getExposeChangelogUrl(packageName: string) {
* getPluginNameByClientStaticUrl('/static/plugins/@nocobase/foo/README.md') => '@nocobase/foo' * getPluginNameByClientStaticUrl('/static/plugins/@nocobase/foo/README.md') => '@nocobase/foo'
*/ */
export function getPackageNameByExposeUrl(pathname: string) { export function getPackageNameByExposeUrl(pathname: string) {
pathname = pathname.replace(PLUGIN_STATICS_PATH, ''); pathname = pathname.replace(process.env.PLUGIN_STATICS_PATH, '');
const pathArr = pathname.split('/'); const pathArr = pathname.split('/');
if (pathname.startsWith('@')) { if (pathname.startsWith('@')) {
return pathArr.slice(0, 2).join('/'); return pathArr.slice(0, 2).join('/');

View File

@ -5,7 +5,6 @@ import Application from '../../application';
import { getExposeUrl } from '../clientStaticUtils'; import { getExposeUrl } from '../clientStaticUtils';
import PluginManager from '../plugin-manager'; import PluginManager from '../plugin-manager';
//@ts-ignore //@ts-ignore
import { version } from '../../../package.json';
export default { export default {
name: 'pm', name: 'pm',
@ -131,7 +130,10 @@ export default {
try { try {
return { return {
...item.toJSON(), ...item.toJSON(),
url: `${getExposeUrl(item.packageName, PLUGIN_CLIENT_ENTRY_FILE)}?version=${item.version}`, url: `${process.env.APP_SERVER_BASE_URL}${getExposeUrl(
item.packageName,
PLUGIN_CLIENT_ENTRY_FILE,
)}?version=${item.version}`,
}; };
} catch { } catch {
return false; return false;

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/telemetry", "name": "@nocobase/telemetry",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "nocobase telemetry library", "description": "nocobase telemetry library",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
@ -11,7 +11,7 @@
"directory": "packages/telemetry" "directory": "packages/telemetry"
}, },
"dependencies": { "dependencies": {
"@nocobase/utils": "0.20.0-alpha.10", "@nocobase/utils": "0.20.0-alpha.12",
"@opentelemetry/api": "^1.7.0", "@opentelemetry/api": "^1.7.0",
"@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/instrumentation": "^0.46.0",
"@opentelemetry/resources": "^1.19.0", "@opentelemetry/resources": "^1.19.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/test", "name": "@nocobase/test",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "lib/index.js", "main": "lib/index.js",
"module": "./src/index.ts", "module": "./src/index.ts",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
@ -40,7 +40,7 @@
}, },
"dependencies": { "dependencies": {
"@faker-js/faker": "8.1.0", "@faker-js/faker": "8.1.0",
"@nocobase/server": "0.20.0-alpha.10", "@nocobase/server": "0.20.0-alpha.12",
"@playwright/test": "^1.42.1", "@playwright/test": "^1.42.1",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "^14.4.3",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/utils", "name": "@nocobase/utils",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "lib/index.js", "main": "lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "权限控制", "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": "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": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。", "description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/acl", "homepage": "https://docs.nocobase.com/handbook/acl",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-action-bulk-edit", "name": "@nocobase/plugin-action-bulk-edit",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-bulk-edit", "homepage": "https://docs.nocobase.com/handbook/action-bulk-edit",
"homepage.zh-CN": "https://docs-cn.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", "name": "@nocobase/plugin-action-bulk-update",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-bulk-update", "homepage": "https://docs.nocobase.com/handbook/action-bulk-update",
"homepage.zh-CN": "https://docs-cn.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-duplicate", "name": "@nocobase/plugin-action-duplicate",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-duplicate", "homepage": "https://docs.nocobase.com/handbook/action-duplicate",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-duplicate", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-duplicate",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-action-print", "name": "@nocobase/plugin-action-print",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-print", "homepage": "https://docs.nocobase.com/handbook/action-print",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-print", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-print",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-api-doc", "name": "@nocobase/plugin-api-doc",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"displayName": "API documentation", "displayName": "API documentation",
"displayName.zh-CN": "API 文档", "displayName.zh-CN": "API 文档",
"description": "An OpenAPI documentation generator for NocoBase HTTP API.", "description": "An OpenAPI documentation generator for NocoBase HTTP API.",

View File

@ -1,4 +1,5 @@
import { css, useAPIClient, useRequest } from '@nocobase/client'; import { css, useAPIClient, useApp, useRequest } from '@nocobase/client';
import { getSubAppName } from '@nocobase/sdk';
import { Select, Space, Spin, Typography } from 'antd'; import { Select, Space, Spin, Typography } from 'antd';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import SwaggerUIBundle from 'swagger-ui-dist/swagger-ui-bundle'; import SwaggerUIBundle from 'swagger-ui-dist/swagger-ui-bundle';
@ -13,11 +14,12 @@ const Documentation = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const swaggerUIRef = useRef(); const swaggerUIRef = useRef();
const { data: urls } = useRequest<{ data: { name: string; url: string }[] }>({ url: 'swagger:getUrls' }); const { data: urls } = useRequest<{ data: { name: string; url: string }[] }>({ url: 'swagger:getUrls' });
const app = useApp();
const requestInterceptor = (req) => { const requestInterceptor = (req) => {
if (!req.headers['Authorization']) { if (!req.headers['Authorization']) {
const match = location.pathname.match(/^\/apps\/([^/]*)\//); const appName = getSubAppName(app.getPublicPath());
if (match?.[1]) { if (appName) {
req.headers['X-App'] = match?.[1]; req.headers['X-App'] = appName;
} }
req.headers['Authorization'] = `Bearer ${apiClient.auth.getToken()}`; req.headers['Authorization'] = `Bearer ${apiClient.auth.getToken()}`;
} }

View File

@ -1,10 +1,10 @@
import APIDocPlugin from '../server'; import APIDocPlugin from '../server';
import baseSwagger from './base-swagger'; import baseSwagger from './base-swagger';
import collectionToSwaggerObject from './collections';
import { SchemaTypeMapping } from './constants'; import { SchemaTypeMapping } from './constants';
import { createDefaultActionSwagger, getInterfaceCollection } from './helpers'; import { createDefaultActionSwagger, getInterfaceCollection } from './helpers';
import { getPluginsSwagger, getSwaggerDocument, loadSwagger } from './loader'; import { getPluginsSwagger, getSwaggerDocument, loadSwagger } from './loader';
import { merge } from './merge'; import { merge } from './merge';
import collectionToSwaggerObject from './collections';
export class SwaggerManager { export class SwaggerManager {
private plugin: APIDocPlugin; private plugin: APIDocPlugin;
@ -81,6 +81,10 @@ export class SwaggerManager {
return merge(await this.getBaseSwagger(), await loadSwagger('@nocobase/server')); return merge(await this.getBaseSwagger(), await loadSwagger('@nocobase/server'));
} }
getURL(pathname: string) {
return process.env.API_BASE_PATH + pathname;
}
async getUrls() { async getUrls() {
const plugins = await getPluginsSwagger(this.db) const plugins = await getPluginsSwagger(this.db)
.then((res) => { .then((res) => {
@ -88,7 +92,7 @@ export class SwaggerManager {
const schema = res[name]; const schema = res[name];
return { return {
name: schema.info?.title || name, name: schema.info?.title || name,
url: `/api/swagger:get?ns=${encodeURIComponent(`plugins/${name}`)}`, url: this.getURL(`swagger:get?ns=${encodeURIComponent(`plugins/${name}`)}`),
}; };
}); });
}) })
@ -102,25 +106,25 @@ export class SwaggerManager {
return [ return [
{ {
name: 'NocoBase API', name: 'NocoBase API',
url: '/api/swagger:get', url: this.getURL('swagger:get'),
}, },
{ {
name: 'NocoBase API - Core', name: 'NocoBase API - Core',
url: '/api/swagger:get?ns=core', url: this.getURL('swagger:get?ns=core'),
}, },
{ {
name: 'NocoBase API - All plugins', name: 'NocoBase API - All plugins',
url: '/api/swagger:get?ns=plugins', url: this.getURL('swagger:get?ns=plugins'),
}, },
{ {
name: 'NocoBase API - Custom collections', name: 'NocoBase API - Custom collections',
url: '/api/swagger:get?ns=collections', url: this.getURL('swagger:get?ns=collections'),
}, },
...plugins, ...plugins,
...collections.map((collection) => { ...collections.map((collection) => {
return { return {
name: `Collection API - ${collection.title}`, name: `Collection API - ${collection.title}`,
url: `/api/swagger:get?ns=${encodeURIComponent('collections/' + collection.name)}`, url: this.getURL(`swagger:get?ns=${encodeURIComponent('collections/' + collection.name)}`),
}; };
}), }),
]; ];

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "认证API 密钥", "displayName.zh-CN": "认证API 密钥",
"description": "Allows users to use API key to access application's HTTP API", "description": "Allows users to use API key to access application's HTTP API",
"description.zh-CN": "允许用户使用 API 密钥访问应用的 HTTP API", "description.zh-CN": "允许用户使用 API 密钥访问应用的 HTTP API",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/api-keys", "homepage": "https://docs.nocobase.com/handbook/api-keys",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-audit-logs", "name": "@nocobase/plugin-audit-logs",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"displayName": "Audit logs (deprecated)", "displayName": "Audit logs (deprecated)",
"displayName.zh-CN": "审计日志(废弃)", "displayName.zh-CN": "审计日志(废弃)",
"description": "This plugin is deprecated. There will be a new audit log plugin in the future.", "description": "This plugin is deprecated. There will be a new audit log plugin in the future.",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-auth", "name": "@nocobase/plugin-auth",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/auth", "homepage": "https://docs.nocobase.com/handbook/auth",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "应用的备份与还原", "displayName.zh-CN": "应用的备份与还原",
"description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.", "description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.",
"description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。", "description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/backup-restore", "homepage": "https://docs.nocobase.com/handbook/backup-restore",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-calendar", "name": "@nocobase/plugin-calendar",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"displayName": "Calendar", "displayName": "Calendar",
"displayName.zh-CN": "日历", "displayName.zh-CN": "日历",
"description": "Provides callendar collection template and block for managing date data, typically for date/time related information such as events, appointments, tasks, and so on.", "description": "Provides callendar collection template and block for managing date data, typically for date/time related information such as events, appointments, tasks, and so on.",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "认证CAS", "displayName.zh-CN": "认证CAS",
"description": "CAS authentication.", "description": "CAS authentication.",
"description.zh-CN": "通过 CAS 协议认证身份。", "description.zh-CN": "通过 CAS 协议认证身份。",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/auth-cas", "homepage": "https://docs.nocobase.com/handbook/auth-cas",

View File

@ -1,19 +1,23 @@
import React, { useEffect } from 'react';
import { LoginOutlined } from '@ant-design/icons'; import { LoginOutlined } from '@ant-design/icons';
import { Button, Space, message } from 'antd'; import { useApp } from '@nocobase/client';
import { useLocation } from 'react-router-dom';
import { getSubAppName } from '@nocobase/sdk';
import { Authenticator } from '@nocobase/plugin-auth/client'; import { Authenticator } from '@nocobase/plugin-auth/client';
import { getSubAppName } from '@nocobase/sdk';
import { Button, Space, message } from 'antd';
import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export const SigninPage = (props: { authenticator: Authenticator }) => { export const SigninPage = (props: { authenticator: Authenticator }) => {
const authenticator = props.authenticator; const authenticator = props.authenticator;
const location = useLocation(); const location = useLocation();
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search);
const redirect = params.get('redirect'); const redirect = params.get('redirect');
const app = useApp();
const app = getSubAppName() || 'main'; const appName = getSubAppName(app.getPublicPath()) || 'main';
const login = async () => { const login = async () => {
window.location.replace(`/api/cas:login?authenticator=${authenticator.name}&__appName=${app}&redirect=${redirect}`); window.location.replace(
`/api/cas:login?authenticator=${authenticator.name}&__appName=${appName}&redirect=${redirect}`,
);
}; };
useEffect(() => { useEffect(() => {

View File

@ -9,7 +9,7 @@ export const service = async (ctx: Context, next: Next) => {
if (appName && appName !== 'main') { if (appName && appName !== 'main') {
const appSupervisor = AppSupervisor.getInstance(); const appSupervisor = AppSupervisor.getInstance();
if (appSupervisor?.runningMode !== 'single') { if (appSupervisor?.runningMode !== 'single') {
prefix = `/apps/${appName}`; prefix = process.env.APP_PUBLIC_PATH + `apps/${appName}`;
} }
} }

View File

@ -19,7 +19,7 @@ export class CASAuth extends BaseAuth {
const opts = this.options || {}; const opts = this.options || {};
return { return {
...opts, ...opts,
serviceUrl: `${opts.serviceDomain}/api/cas:service`, serviceUrl: `${opts.serviceDomain}${process.env.API_BASE_PATH}cas:service`,
}; };
} }

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "图表(废弃)", "displayName.zh-CN": "图表(废弃)",
"description": "The plugin has been deprecated, please use the data visualization plugin instead.", "description": "The plugin has been deprecated, please use the data visualization plugin instead.",
"description.zh-CN": "已废弃插件,请使用数据可视化插件代替。", "description.zh-CN": "已废弃插件,请使用数据可视化插件代替。",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"devDependencies": { "devDependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-china-region", "name": "@nocobase/plugin-china-region",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"displayName": "Administrative divisions of China", "displayName": "Administrative divisions of China",
"displayName.zh-CN": "中国行政区划", "displayName.zh-CN": "中国行政区划",
"description": "Provides data and field type for administrative divisions of China.", "description": "Provides data and field type for administrative divisions of China.",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "WEB 客户端", "displayName.zh-CN": "WEB 客户端",
"description": "Provides a client interface for the NocoBase server", "description": "Provides a client interface for the NocoBase server",
"description.zh-CN": "为 NocoBase 服务端提供客户端界面", "description.zh-CN": "为 NocoBase 服务端提供客户端界面",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"devDependencies": { "devDependencies": {

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "数据源:主数据库", "displayName.zh-CN": "数据源:主数据库",
"description": "NocoBase main database, supports relational databases such as MySQL, PostgreSQL, SQLite and so on.", "description": "NocoBase main database, supports relational databases such as MySQL, PostgreSQL, SQLite and so on.",
"description.zh-CN": "NocoBase 主数据库,支持 MySQL、PostgreSQL、SQLite 等关系型数据库。", "description.zh-CN": "NocoBase 主数据库,支持 MySQL、PostgreSQL、SQLite 等关系型数据库。",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/data-source-main", "homepage": "https://docs.nocobase.com/handbook/data-source-main",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/data-source-main", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/data-source-main",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-custom-request", "name": "@nocobase/plugin-custom-request",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-custom-request", "homepage": "https://docs.nocobase.com/handbook/action-custom-request",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-data-source-manager", "name": "@nocobase/plugin-data-source-manager",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"displayName": "Data source manager", "displayName": "Data source manager",
"displayName.zh-CN": "数据源管理", "displayName.zh-CN": "数据源管理",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-data-visualization", "name": "@nocobase/plugin-data-visualization",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"displayName": "Data visualization", "displayName": "Data visualization",
"displayName.zh-CN": "数据可视化", "displayName.zh-CN": "数据可视化",
"description": "Provides data visualization feature, including chart block and chart filter block, support line charts, area charts, bar charts and more than a dozen kinds of charts, you can also extend more chart types.", "description": "Provides data visualization feature, including chart block and chart filter block, support line charts, area charts, bar charts and more than a dozen kinds of charts, you can also extend more chart types.",

View File

@ -122,6 +122,7 @@ export class Chart implements ChartType {
let xField: FieldOption; let xField: FieldOption;
let yField: FieldOption; let yField: FieldOption;
let seriesField: FieldOption; let seriesField: FieldOption;
let colorField: FieldOption;
let yFields: FieldOption[]; let yFields: FieldOption[];
const getField = (fields: FieldOption[], selected: { field: string | string[]; alias?: string }) => { const getField = (fields: FieldOption[], selected: { field: string | string[]; alias?: string }) => {
if (selected.alias) { if (selected.alias) {
@ -147,17 +148,19 @@ export class Chart implements ChartType {
xIndex = i; xIndex = i;
} }
}); });
if (xIndex) { xIndex = xIndex || 0;
// If there is a time field, the other field is used as the series field by default. xField = xField || getField(fields, dimensions[xIndex]);
const index = xIndex === 0 ? 1 : 0; const restFields = dimensions.filter((_, i) => i !== xIndex).map((i) => getField(fields, i));
seriesField = getField(fields, dimensions[index]); if (restFields.length === 1) {
} else { seriesField = restFields[0];
xField = getField(fields, dimensions[0]); colorField = restFields[0];
seriesField = getField(fields, dimensions[1]); } else if (restFields.length > 1) {
colorField = restFields[0];
seriesField = restFields[1];
} }
} }
} }
return { xField, yField, seriesField, yFields }; return { xField, yField, seriesField, colorField, yFields };
} }
/** /**

View File

@ -49,4 +49,5 @@ export default {
xField: (props: FieldConfigProps) => selectField({ name: 'xField', title: 'xField', required: true, ...props }), xField: (props: FieldConfigProps) => selectField({ name: 'xField', title: 'xField', required: true, ...props }),
yField: (props: FieldConfigProps) => selectField({ name: 'yField', title: 'yField', required: true, ...props }), yField: (props: FieldConfigProps) => selectField({ name: 'yField', title: 'yField', required: true, ...props }),
seriesField: (props: FieldConfigProps) => selectField({ name: 'seriesField', title: 'seriesField', ...props }), seriesField: (props: FieldConfigProps) => selectField({ name: 'seriesField', title: 'seriesField', ...props }),
colorField: (props: FieldConfigProps) => selectField({ name: 'colorField', title: 'colorField', ...props }),
}; };

View File

@ -2,6 +2,7 @@ import { G2PlotChart } from './g2plot';
import { ChartType, RenderProps } from '../chart'; import { ChartType, RenderProps } from '../chart';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { DualAxes as G2DualAxes } from '@ant-design/plots'; import { DualAxes as G2DualAxes } from '@ant-design/plots';
import lodash from 'lodash';
import { ChartRendererContext } from '../../renderer'; import { ChartRendererContext } from '../../renderer';
export class DualAxes extends G2PlotChart { export class DualAxes extends G2PlotChart {
@ -60,18 +61,38 @@ export class DualAxes extends G2PlotChart {
return { return {
general: { general: {
xField: xField?.value, xField: xField?.value,
yField: yFields?.map((f) => f.value).slice(0, 2) || [], yField: yFields?.map((f) => f.value) || [],
}, },
}; };
}; };
render({ data, general, advanced, fieldProps, ctx }: RenderProps) { getProps({ data, general, advanced, fieldProps, ctx }: RenderProps) {
const props = this.getProps({ data, general, advanced, fieldProps, ctx }); const props = super.getProps({ data, general, advanced, fieldProps, ctx });
const { data: _data } = props; return {
return () => ...lodash.omit(props, ['legend', 'tooltip']),
React.createElement(this.component, { children:
...props, props.yField?.map((yField: string, index: number) => {
data: [_data, _data], return {
}); type: 'line',
yField,
colorField: () => {
const props = fieldProps[yField];
const transformer = props?.transformer;
return props?.label || (transformer ? transformer(yField) : yField);
},
axis: {
y: {
title: fieldProps[yField]?.label || yField,
position: index === 0 ? 'left' : 'right',
labelFormatter: (datnum) => {
const props = fieldProps[yField];
const transformer = props?.transformer;
return transformer ? transformer(datnum) : datnum;
},
},
},
};
}) || [],
};
} }
} }

View File

@ -1,6 +1,5 @@
import { transform } from 'lodash';
import { Chart, ChartProps, ChartType, RenderProps } from '../chart'; import { Chart, ChartProps, ChartType, RenderProps } from '../chart';
import { FieldOption } from '../../hooks';
import { QueryProps } from '../../renderer';
import configs from './configs'; import configs from './configs';
export class G2PlotChart extends Chart { export class G2PlotChart extends Chart {
@ -29,15 +28,6 @@ export class G2PlotChart extends Chart {
const meta = {}; const meta = {};
// Some charts render wrong when the field name contains a dot in G2Plot // Some charts render wrong when the field name contains a dot in G2Plot
const replace = (key: string) => key.replace(/\./g, '_'); const replace = (key: string) => key.replace(/\./g, '_');
Object.entries(fieldProps).forEach(([key, props]) => {
if (key.includes('.')) {
key = replace(key);
}
meta[key] = {
formatter: props.transformer,
alias: props.label,
};
});
general = Object.entries(general).reduce((obj, [key, value]) => { general = Object.entries(general).reduce((obj, [key, value]) => {
obj[key] = value; obj[key] = value;
if (key.includes('Field')) { if (key.includes('Field')) {
@ -49,7 +39,40 @@ export class G2PlotChart extends Chart {
} }
return obj; return obj;
}, {}); }, {});
return { const config = {
legend: {
color: {
itemLabelText: (datnum) => {
const props = fieldProps[datnum.label];
const transformer = props?.transformer;
return props?.label || (transformer ? transformer(datnum.label) : datnum.label);
},
},
},
tooltip: (d, index, data, column) => {
const field = column.y.field;
const props = fieldProps[field];
const name = props?.label || field;
const transformer = props?.transformer;
const value = column.y.value[index];
return { name, value: transformer ? transformer(value) : value };
},
axis: {
x: {
labelFormatter: (datnum) => {
const props = fieldProps[general.xField];
const transformer = props?.transformer;
return transformer ? transformer(datnum) : datnum;
},
},
y: {
labelFormatter: (datnum) => {
const props = fieldProps[general.yField];
const transformer = props?.transformer;
return transformer ? transformer(datnum) : datnum;
},
},
},
data: data.map((item) => { data: data.map((item) => {
const obj = {}; const obj = {};
Object.entries(item).forEach(([key, value]) => { Object.entries(item).forEach(([key, value]) => {
@ -60,11 +83,27 @@ export class G2PlotChart extends Chart {
}); });
return obj; return obj;
}), }),
meta, theme: 'classic',
animation: false, animate: {
enter: {
type: false,
},
update: {
type: false,
},
exit: {
type: false,
},
},
colorField: general.seriesField,
stack: general.isStack,
percent: general.isPercent ? true : undefined,
...(general.smooth ? { shapeField: 'smooth' } : {}),
...general, ...general,
seriesField: general.isGroup ? general.seriesField : undefined,
...advanced, ...advanced,
}; };
return config;
} }
getReference() { getReference() {

View File

@ -1,5 +1,4 @@
import { Area, Column, Line, Scatter } from '@ant-design/plots'; import { Area, Column, Line, Scatter, Bar } from '@ant-design/plots';
import { Bar } from './bar';
import { Pie } from './pie'; import { Pie } from './pie';
import { DualAxes } from './dualAxes'; import { DualAxes } from './dualAxes';
import { G2PlotChart } from './g2plot'; import { G2PlotChart } from './g2plot';
@ -30,7 +29,12 @@ export default [
component: Column, component: Column,
config: ['isGroup', 'isStack', 'isPercent'], config: ['isGroup', 'isStack', 'isPercent'],
}), }),
new Bar(), new G2PlotChart({
name: 'bar',
title: 'Bar Chart',
component: Bar,
config: ['isGroup', 'isStack', 'isPercent'],
}),
new Pie(), new Pie(),
new DualAxes(), new DualAxes(),
new G2PlotChart({ name: 'scatter', title: 'Scatter Chart', component: Scatter }), new G2PlotChart({ name: 'scatter', title: 'Scatter Chart', component: Scatter }),

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-disable-pm-add", "name": "@nocobase/plugin-disable-pm-add",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"peerDependencies": { "peerDependencies": {
"@nocobase/client": "0.x", "@nocobase/client": "0.x",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "错误处理器", "displayName.zh-CN": "错误处理器",
"description": "Handling application errors and exceptions.", "description": "Handling application errors and exceptions.",
"description.zh-CN": "处理应用程序中的错误和异常。", "description.zh-CN": "处理应用程序中的错误和异常。",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"devDependencies": { "devDependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-excel-formula-field", "name": "@nocobase/plugin-excel-formula-field",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"description": "", "description": "",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "操作:导出记录", "displayName.zh-CN": "操作:导出记录",
"description": "Export filtered records to excel, you can configure which fields to export.", "description": "Export filtered records to excel, you can configure which fields to export.",
"description.zh-CN": "导出筛选后的记录到 Excel 中,可以配置导出哪些字段。", "description.zh-CN": "导出筛选后的记录到 Excel 中,可以配置导出哪些字段。",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-export", "homepage": "https://docs.nocobase.com/handbook/action-export",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-file-manager", "name": "@nocobase/plugin-file-manager",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"displayName": "File manager", "displayName": "File manager",
"displayName.zh-CN": "文件管理器", "displayName.zh-CN": "文件管理器",
"description": "Provides files storage services with files collection template and attachment field.", "description": "Provides files storage services with files collection template and attachment field.",

View File

@ -10,6 +10,9 @@ export class FileModel extends Model {
if (!json['thumbnailRule'] && storage?.type === 'ali-oss') { if (!json['thumbnailRule'] && storage?.type === 'ali-oss') {
json['thumbnailRule'] = '?x-oss-process=image/auto-orient,1/resize,m_fill,w_94,h_94/quality,q_90'; json['thumbnailRule'] = '?x-oss-process=image/auto-orient,1/resize,m_fill,w_94,h_94/quality,q_90';
} }
if (storage?.type === 'local' && process.env.APP_PUBLIC_PATH) {
json['url'] = process.env.APP_PUBLIC_PATH.replace(/\/$/g, '') + json.url;
}
} }
return json; return json;
} }

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表字段:公式", "displayName.zh-CN": "数据表字段:公式",
"description": "Configure and store the results of calculations between multiple field values in the same record, supporting both Math.js and Excel formula functions.", "description": "Configure and store the results of calculations between multiple field values in the same record, supporting both Math.js and Excel formula functions.",
"description.zh-CN": "可以配置并存储同一条记录的多字段值之间的计算结果,支持 Math.js 和 Excel formula functions 两种引擎", "description.zh-CN": "可以配置并存储同一条记录的多字段值之间的计算结果,支持 Math.js 和 Excel formula functions 两种引擎",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/field-formula", "homepage": "https://docs.nocobase.com/handbook/field-formula",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-gantt", "name": "@nocobase/plugin-gantt",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"displayName": "Block: Gantt", "displayName": "Block: Gantt",
"displayName.zh-CN": "区块:甘特图", "displayName.zh-CN": "区块:甘特图",
"description": "Provides Gantt block.", "description": "Provides Gantt block.",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "可视化数据表管理", "displayName.zh-CN": "可视化数据表管理",
"description": "An ER diagram-like tool. Currently only the Master database is supported.", "description": "An ER diagram-like tool. Currently only the Master database is supported.",
"description.zh-CN": "类似 ER 图的工具,目前只支持主数据库。", "description.zh-CN": "类似 ER 图的工具,目前只支持主数据库。",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/graph-collection-manager", "homepage": "https://docs.nocobase.com/handbook/graph-collection-manager",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "区块iframe", "displayName.zh-CN": "区块iframe",
"description": "Create an iframe block on the page to embed and display external web pages or content.", "description": "Create an iframe block on the page to embed and display external web pages or content.",
"description.zh-CN": "在页面上创建和管理iframe用于嵌入和展示外部网页或内容。", "description.zh-CN": "在页面上创建和管理iframe用于嵌入和展示外部网页或内容。",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/block-iframe", "homepage": "https://docs.nocobase.com/handbook/block-iframe",

View File

@ -1,5 +1,5 @@
import { observer, useField } from '@formily/react'; import { observer, useField } from '@formily/react';
import { useAPIClient } from '@nocobase/client'; import { useAPIClient, useApp } from '@nocobase/client';
import { Card } from 'antd'; import { Card } from 'antd';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -22,13 +22,16 @@ export const Iframe: any = observer(
const { t } = useTranslation(); const { t } = useTranslation();
const api = useAPIClient(); const api = useAPIClient();
const field = useField(); const field = useField();
const app = useApp();
const src = React.useMemo(() => { const src = React.useMemo(() => {
if (mode === 'html') { if (mode === 'html') {
return `/api/iframeHtml:getHtml/${htmlId}?token=${api.auth.getToken()}&v=${field.data?.v || ''}`; const options = app.getOptions();
const apiBaseURL: string = options?.apiClient?.['baseURL'];
return `${apiBaseURL}iframeHtml:getHtml/${htmlId}?token=${api.auth.getToken()}&v=${field.data?.v || ''}`;
} }
return url; return url;
}, [url, mode, htmlId, field.data?.v]); }, [app, url, mode, htmlId, field.data?.v]);
if ((mode === 'url' && !url) || (mode === 'html' && !htmlId)) { if ((mode === 'url' && !url) || (mode === 'html' && !htmlId)) {
return <Card style={{ marginBottom: 24 }}>{t('Please fill in the iframe URL')}</Card>; return <Card style={{ marginBottom: 24 }}>{t('Please fill in the iframe URL')}</Card>;

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "操作:导入记录", "displayName.zh-CN": "操作:导入记录",
"description": "Import records using excel templates. You can configure which fields to import and templates will be generated automatically.", "description": "Import records using excel templates. You can configure which fields to import and templates will be generated automatically.",
"description.zh-CN": "使用 Excel 模板导入数据,可以配置导入哪些字段,自动生成模板。", "description.zh-CN": "使用 Excel 模板导入数据,可以配置导入哪些字段,自动生成模板。",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-import", "homepage": "https://docs.nocobase.com/handbook/action-import",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-kanban", "name": "@nocobase/plugin-kanban",
"version": "0.20.0-alpha.10", "version": "0.20.0-alpha.12",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/block-kanban", "homepage": "https://docs.nocobase.com/handbook/block-kanban",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/block-kanban", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/block-kanban",

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