Merge remote-tracking branch 'upstream/main' into merge_from_upstream
This commit is contained in:
commit
f364b40e79
4
.github/workflows/nocobase-test-backend.yml
vendored
4
.github/workflows/nocobase-test-backend.yml
vendored
@ -13,6 +13,8 @@ on:
|
|||||||
- 'packages/core/acl/**'
|
- 'packages/core/acl/**'
|
||||||
- 'packages/core/actions/**'
|
- 'packages/core/actions/**'
|
||||||
- 'packages/core/database/**'
|
- 'packages/core/database/**'
|
||||||
|
- 'packages/core/resourcer/**'
|
||||||
|
- 'packages/core/data-source-manager/**'
|
||||||
- 'packages/core/server/**'
|
- 'packages/core/server/**'
|
||||||
- 'packages/core/utils/**'
|
- 'packages/core/utils/**'
|
||||||
- 'packages/plugins/**/src/server/**'
|
- 'packages/plugins/**/src/server/**'
|
||||||
@ -23,6 +25,8 @@ on:
|
|||||||
- 'packages/core/acl/**'
|
- 'packages/core/acl/**'
|
||||||
- 'packages/core/actions/**'
|
- 'packages/core/actions/**'
|
||||||
- 'packages/core/database/**'
|
- 'packages/core/database/**'
|
||||||
|
- 'packages/core/resourcer/**'
|
||||||
|
- 'packages/core/data-source-manager/**'
|
||||||
- 'packages/core/server/**'
|
- 'packages/core/server/**'
|
||||||
- 'packages/core/utils/**'
|
- 'packages/core/utils/**'
|
||||||
- 'packages/plugins/**/src/server/**'
|
- 'packages/plugins/**/src/server/**'
|
||||||
|
34
CHANGELOG.md
34
CHANGELOG.md
@ -7,6 +7,40 @@ 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.17](https://github.com/nocobase/nocobase/compare/v0.20.0-alpha.16...v0.20.0-alpha.17) - 2024-03-26
|
||||||
|
|
||||||
|
### Merged
|
||||||
|
|
||||||
|
- feat: read pretty input number field support display format config [`#3815`](https://github.com/nocobase/nocobase/pull/3815)
|
||||||
|
- fix(Table): fix invalid pagination [`#3821`](https://github.com/nocobase/nocobase/pull/3821)
|
||||||
|
- chore: add tsdoc [`#3788`](https://github.com/nocobase/nocobase/pull/3788)
|
||||||
|
- chore(test): fix agent type [`#3819`](https://github.com/nocobase/nocobase/pull/3819)
|
||||||
|
- fix: embed plugin need hooks and e2e change [`#3727`](https://github.com/nocobase/nocobase/pull/3727)
|
||||||
|
- fix(associationBlock): fix association blocks for parent collection f… [`#3813`](https://github.com/nocobase/nocobase/pull/3813)
|
||||||
|
- fix(plugin-workflow-manual): fix schema migration [`#3814`](https://github.com/nocobase/nocobase/pull/3814)
|
||||||
|
- refactor(DataBlock): table block [`#3748`](https://github.com/nocobase/nocobase/pull/3748)
|
||||||
|
- fix(Details): block template [`#3807`](https://github.com/nocobase/nocobase/pull/3807)
|
||||||
|
- chore: cascade can replace set null action [`#3812`](https://github.com/nocobase/nocobase/pull/3812)
|
||||||
|
- feat(data-vi): support multiple data sources [`#3743`](https://github.com/nocobase/nocobase/pull/3743)
|
||||||
|
- feat(plugin-workflow): support multiple data source in workflow [`#3739`](https://github.com/nocobase/nocobase/pull/3739)
|
||||||
|
- chore: add options for matching and ignoring test files in e2e and p-test commands [`#3811`](https://github.com/nocobase/nocobase/pull/3811)
|
||||||
|
- chore: file collection template preset fields should be disabled [`#3810`](https://github.com/nocobase/nocobase/pull/3810)
|
||||||
|
- fix(plugin-workflow): remove string template in condition calculation [`#3688`](https://github.com/nocobase/nocobase/pull/3688)
|
||||||
|
- fix: refresh collection name when update [`#3797`](https://github.com/nocobase/nocobase/pull/3797)
|
||||||
|
- fix: reload when data source click refresh [`#3804`](https://github.com/nocobase/nocobase/pull/3804)
|
||||||
|
- fix: plugin manager keywords [`#3809`](https://github.com/nocobase/nocobase/pull/3809)
|
||||||
|
- fix: expand action and add new action should support drag & sort [`#3808`](https://github.com/nocobase/nocobase/pull/3808)
|
||||||
|
- fix: create attachments middleware [`#3794`](https://github.com/nocobase/nocobase/pull/3794)
|
||||||
|
- fix: useExpressionScope [`#3805`](https://github.com/nocobase/nocobase/pull/3805)
|
||||||
|
- chore: set default association reference on delete action to no action [`#3722`](https://github.com/nocobase/nocobase/pull/3722)
|
||||||
|
- fix: field permission all fields should be displayed [`#3799`](https://github.com/nocobase/nocobase/pull/3799)
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- chore(versions): 😊 publish v0.20.0-alpha.17 [`3398222`](https://github.com/nocobase/nocobase/commit/339822241f2f641656f64107318b793d63d0b2c9)
|
||||||
|
- fix: description [`0dc0d32`](https://github.com/nocobase/nocobase/commit/0dc0d329f80c268672bd80fc6cb0190c3cef964d)
|
||||||
|
- chore: update changelog [`35a6514`](https://github.com/nocobase/nocobase/commit/35a6514993bede12b952ce13641f7258fe6c76d2)
|
||||||
|
|
||||||
## [v0.20.0-alpha.16](https://github.com/nocobase/nocobase/compare/v0.20.0-alpha.15...v0.20.0-alpha.16) - 2024-03-23
|
## [v0.20.0-alpha.16](https://github.com/nocobase/nocobase/compare/v0.20.0-alpha.15...v0.20.0-alpha.16) - 2024-03-23
|
||||||
|
|
||||||
### Merged
|
### Merged
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"npmClientArgs": ["--ignore-engines"],
|
"npmClientArgs": ["--ignore-engines"],
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/acl",
|
"name": "@nocobase/acl",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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.16",
|
"@nocobase/resourcer": "0.20.0-alpha.17",
|
||||||
"@nocobase/utils": "0.20.0-alpha.16",
|
"@nocobase/utils": "0.20.0-alpha.17",
|
||||||
"minimatch": "^5.1.1"
|
"minimatch": "^5.1.1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -45,14 +45,40 @@ interface CanArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ACL extends EventEmitter {
|
export class ACL extends EventEmitter {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
public availableStrategy = new Map<string, ACLAvailableStrategy>();
|
public availableStrategy = new Map<string, ACLAvailableStrategy>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
public allowManager = new AllowManager(this);
|
public allowManager = new AllowManager(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
public snippetManager = new SnippetManager();
|
public snippetManager = new SnippetManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
roles = new Map<string, ACLRole>();
|
roles = new Map<string, ACLRole>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
actionAlias = new Map<string, string>();
|
actionAlias = new Map<string, string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
configResources: string[] = [];
|
configResources: string[] = [];
|
||||||
|
|
||||||
protected availableActions = new Map<string, ACLAvailableAction>();
|
protected availableActions = new Map<string, ACLAvailableAction>();
|
||||||
|
|
||||||
protected fixedParamsManager = new FixedParamsManager();
|
protected fixedParamsManager = new FixedParamsManager();
|
||||||
|
|
||||||
protected middlewares: Toposort<any>;
|
protected middlewares: Toposort<any>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -114,14 +140,23 @@ export class ACL extends EventEmitter {
|
|||||||
return this.roles.delete(name);
|
return this.roles.delete(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
registerConfigResources(names: string[]) {
|
registerConfigResources(names: string[]) {
|
||||||
names.forEach((name) => this.registerConfigResource(name));
|
names.forEach((name) => this.registerConfigResource(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
registerConfigResource(name: string) {
|
registerConfigResource(name: string) {
|
||||||
this.configResources.push(name);
|
this.configResources.push(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
isConfigResource(name: string) {
|
isConfigResource(name: string) {
|
||||||
return this.configResources.includes(name);
|
return this.configResources.includes(name);
|
||||||
}
|
}
|
||||||
@ -227,6 +262,9 @@ export class ACL extends EventEmitter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
public resolveActionAlias(action: string) {
|
public resolveActionAlias(action: string) {
|
||||||
return this.actionAlias.get(action) ? this.actionAlias.get(action) : action;
|
return this.actionAlias.get(action) ? this.actionAlias.get(action) : action;
|
||||||
}
|
}
|
||||||
@ -242,6 +280,9 @@ export class ACL extends EventEmitter {
|
|||||||
return this.skip(resourceName, actionNames, condition);
|
return this.skip(resourceName, actionNames, condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
skip(resourceName: string, actionNames: string[] | string, condition?: string | ConditionFunc) {
|
skip(resourceName: string, actionNames: string[] | string, condition?: string | ConditionFunc) {
|
||||||
if (!Array.isArray(actionNames)) {
|
if (!Array.isArray(actionNames)) {
|
||||||
actionNames = [actionNames];
|
actionNames = [actionNames];
|
||||||
@ -252,6 +293,9 @@ export class ACL extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async parseJsonTemplate(json: any, ctx: any) {
|
async parseJsonTemplate(json: any, ctx: any) {
|
||||||
if (json.filter) {
|
if (json.filter) {
|
||||||
ctx.logger?.info?.('parseJsonTemplate.raw', JSON.parse(JSON.stringify(json.filter)));
|
ctx.logger?.info?.('parseJsonTemplate.raw', JSON.parse(JSON.stringify(json.filter)));
|
||||||
@ -295,6 +339,9 @@ export class ACL extends EventEmitter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async getActionParams(ctx) {
|
async getActionParams(ctx) {
|
||||||
const roleName = ctx.state.currentRole || 'anonymous';
|
const roleName = ctx.state.currentRole || 'anonymous';
|
||||||
const { resourceName, actionName } = ctx.action;
|
const { resourceName, actionName } = ctx.action;
|
||||||
@ -322,6 +369,9 @@ export class ACL extends EventEmitter {
|
|||||||
this.snippetManager.register(snippet);
|
this.snippetManager.register(snippet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
filterParams(ctx, resourceName, params) {
|
filterParams(ctx, resourceName, params) {
|
||||||
if (params?.filter?.createdById) {
|
if (params?.filter?.createdById) {
|
||||||
const collection = ctx.db.getCollection(resourceName);
|
const collection = ctx.db.getCollection(resourceName);
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/actions",
|
"name": "@nocobase/actions",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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.16",
|
"@nocobase/cache": "0.20.0-alpha.17",
|
||||||
"@nocobase/database": "0.20.0-alpha.16",
|
"@nocobase/database": "0.20.0-alpha.17",
|
||||||
"@nocobase/resourcer": "0.20.0-alpha.16"
|
"@nocobase/resourcer": "0.20.0-alpha.17"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/app",
|
"name": "@nocobase/app",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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.16",
|
"@nocobase/database": "0.20.0-alpha.17",
|
||||||
"@nocobase/preset-nocobase": "0.20.0-alpha.16",
|
"@nocobase/preset-nocobase": "0.20.0-alpha.17",
|
||||||
"@nocobase/server": "0.20.0-alpha.16"
|
"@nocobase/server": "0.20.0-alpha.17"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nocobase/client": "0.20.0-alpha.16"
|
"@nocobase/client": "0.20.0-alpha.17"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/auth",
|
"name": "@nocobase/auth",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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.16",
|
"@nocobase/actions": "0.20.0-alpha.17",
|
||||||
"@nocobase/cache": "0.20.0-alpha.16",
|
"@nocobase/cache": "0.20.0-alpha.17",
|
||||||
"@nocobase/database": "0.20.0-alpha.16",
|
"@nocobase/database": "0.20.0-alpha.17",
|
||||||
"@nocobase/resourcer": "0.20.0-alpha.16",
|
"@nocobase/resourcer": "0.20.0-alpha.17",
|
||||||
"@nocobase/utils": "0.20.0-alpha.16",
|
"@nocobase/utils": "0.20.0-alpha.17",
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^8.5.8",
|
||||||
"jsonwebtoken": "^8.5.1"
|
"jsonwebtoken": "^8.5.1"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/build",
|
"name": "@nocobase/build",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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",
|
||||||
|
@ -14,16 +14,22 @@ import { buildClient } from './buildClient';
|
|||||||
import { buildCjs } from './buildCjs';
|
import { buildCjs } from './buildCjs';
|
||||||
import { buildPlugin } from './buildPlugin';
|
import { buildPlugin } from './buildPlugin';
|
||||||
import { buildDeclaration } from './buildDeclaration';
|
import { buildDeclaration } from './buildDeclaration';
|
||||||
import { PkgLog, getPkgLog, toUnixPath, getPackageJson, getUserConfig, UserConfig } from './utils';
|
import { PkgLog, getPkgLog, toUnixPath, getPackageJson, getUserConfig, UserConfig, writeToCache, readFromCache } from './utils';
|
||||||
import { getPackages } from './utils/getPackages';
|
import { getPackages } from './utils/getPackages';
|
||||||
import { Package } from '@lerna/package';
|
import { Package } from '@lerna/package';
|
||||||
import { tarPlugin } from './tarPlugin'
|
import { tarPlugin } from './tarPlugin'
|
||||||
|
|
||||||
|
const BUILD_ERROR = 'build-error';
|
||||||
|
|
||||||
export async function build(pkgs: string[]) {
|
export async function build(pkgs: string[]) {
|
||||||
const isDev = process.argv.includes('--development');
|
const isDev = process.argv.includes('--development');
|
||||||
process.env.NODE_ENV = isDev ? 'development' : 'production';
|
process.env.NODE_ENV = isDev ? 'development' : 'production';
|
||||||
|
|
||||||
const packages = getPackages(pkgs);
|
let packages = getPackages(pkgs);
|
||||||
|
const cachePkg = readFromCache(BUILD_ERROR);
|
||||||
|
if (process.argv.includes('--retry') && cachePkg?.pkg) {
|
||||||
|
packages = packages.slice(packages.findIndex((item) => item.name === cachePkg.pkg));
|
||||||
|
}
|
||||||
if (packages.length === 0) {
|
if (packages.length === 0) {
|
||||||
let msg = '';
|
let msg = '';
|
||||||
if (pkgs.length) {
|
if (pkgs.length) {
|
||||||
@ -59,6 +65,7 @@ export async function build(pkgs: string[]) {
|
|||||||
APP_ROOT: path.join(CORE_APP, 'client'),
|
APP_ROOT: path.join(CORE_APP, 'client'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
writeToCache(BUILD_ERROR, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function buildPackages(
|
export async function buildPackages(
|
||||||
@ -67,6 +74,7 @@ export async function buildPackages(
|
|||||||
doBuildPackage: (cwd: string, userConfig: UserConfig, sourcemap: boolean, log?: PkgLog) => Promise<any>,
|
doBuildPackage: (cwd: string, userConfig: UserConfig, sourcemap: boolean, log?: PkgLog) => Promise<any>,
|
||||||
) {
|
) {
|
||||||
for await (const pkg of packages) {
|
for await (const pkg of packages) {
|
||||||
|
writeToCache(BUILD_ERROR, { pkg: pkg.name })
|
||||||
await buildPackage(pkg, targetDir, doBuildPackage);
|
await buildPackage(pkg, targetDir, doBuildPackage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ export const EsbuildSupportExts = [
|
|||||||
'.data',
|
'.data',
|
||||||
];
|
];
|
||||||
export const ROOT_PATH = path.join(__dirname, '../../../../');
|
export const ROOT_PATH = path.join(__dirname, '../../../../');
|
||||||
|
export const NODE_MODULES = path.join(ROOT_PATH, 'node_modules');
|
||||||
export const PACKAGES_PATH = path.join(ROOT_PATH, 'packages');
|
export const PACKAGES_PATH = path.join(ROOT_PATH, 'packages');
|
||||||
export const PLUGINS_DIR = ['plugins', 'samples', 'pro-plugins']
|
export const PLUGINS_DIR = ['plugins', 'samples', 'pro-plugins']
|
||||||
.concat((process.env.PLUGINS_DIRS || '').split(','))
|
.concat((process.env.PLUGINS_DIRS || '').split(','))
|
||||||
|
@ -10,6 +10,7 @@ import { toUnixPath } from './utils';
|
|||||||
* 获取构建包的绝对路径,支持项目路径和 npm 两种形式
|
* 获取构建包的绝对路径,支持项目路径和 npm 两种形式
|
||||||
* @example
|
* @example
|
||||||
* yarn build packages/core/client @nocobase/acl => ['/home/xx/packages/core/client', '/home/xx/packages/core/acl']
|
* yarn build packages/core/client @nocobase/acl => ['/home/xx/packages/core/client', '/home/xx/packages/core/acl']
|
||||||
|
* yarn build packages/plugins/* => ['/home/xx/packages/plugins/a', '/home/xx/packages/plugins/b']
|
||||||
* yarn build => all packages
|
* yarn build => all packages
|
||||||
*/
|
*/
|
||||||
function getPackagesPath(pkgs: string[]) {
|
function getPackagesPath(pkgs: string[]) {
|
||||||
@ -24,7 +25,6 @@ function getPackagesPath(pkgs: string[]) {
|
|||||||
return allPackageJson
|
return allPackageJson
|
||||||
.map(toUnixPath).map(item => path.dirname(item));
|
.map(toUnixPath).map(item => path.dirname(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
const allPackageInfo = allPackageJson
|
const allPackageInfo = allPackageJson
|
||||||
.map(packageJsonPath => ({ name: require(packageJsonPath).name, path: path.dirname(toUnixPath(packageJsonPath)) }))
|
.map(packageJsonPath => ({ name: require(packageJsonPath).name, path: path.dirname(toUnixPath(packageJsonPath)) }))
|
||||||
.reduce((acc, cur) => {
|
.reduce((acc, cur) => {
|
||||||
@ -37,7 +37,9 @@ function getPackagesPath(pkgs: string[]) {
|
|||||||
const relativePaths = pkgNames.length ? pkgs.filter(item => !pkgNames.includes(item)) : pkgs;
|
const relativePaths = pkgNames.length ? pkgs.filter(item => !pkgNames.includes(item)) : pkgs;
|
||||||
const pkgPaths = pkgs.map(item => allPackageInfo[item])
|
const pkgPaths = pkgs.map(item => allPackageInfo[item])
|
||||||
const absPaths = allPackagePaths.filter(absPath => relativePaths.some((relativePath) => absPath.endsWith(relativePath)));
|
const absPaths = allPackagePaths.filter(absPath => relativePaths.some((relativePath) => absPath.endsWith(relativePath)));
|
||||||
return [...pkgPaths, ...absPaths];
|
const dirPaths = fg.sync(pkgs, { onlyDirectories: true, absolute: true, cwd: ROOT_PATH });
|
||||||
|
const dirMatchPaths = allPackagePaths.filter(pkgPath => dirPaths.some(dirPath => pkgPath.startsWith(dirPath)));
|
||||||
|
return [...new Set([...pkgPaths, ...absPaths, ...dirMatchPaths])];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPackages(pkgs: string[]) {
|
export function getPackages(pkgs: string[]) {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
|
||||||
import fg from 'fast-glob';
|
import fg from 'fast-glob';
|
||||||
|
import fs from 'fs-extra';
|
||||||
import { Options as TsupConfig } from 'tsup'
|
import { Options as TsupConfig } from 'tsup'
|
||||||
import { InlineConfig as ViteConfig } from 'vite'
|
import { InlineConfig as ViteConfig } from 'vite'
|
||||||
import { register } from 'esbuild-register/dist/node';
|
import { register } from 'esbuild-register/dist/node';
|
||||||
|
import { NODE_MODULES } from '../constant';
|
||||||
|
|
||||||
let previousColor = '';
|
let previousColor = '';
|
||||||
function randomColor() {
|
function randomColor() {
|
||||||
@ -79,3 +80,18 @@ export function getUserConfig(cwd: string) {
|
|||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CACHE_DIR = path.join(NODE_MODULES, '.cache', 'nocobase');
|
||||||
|
export function writeToCache(key: string, data: Record<string, any>) {
|
||||||
|
const cachePath = path.join(CACHE_DIR, `${key}.json`);
|
||||||
|
fs.ensureDirSync(path.dirname(cachePath));
|
||||||
|
fs.writeJsonSync(cachePath, data, { spaces: 2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readFromCache(key: string) {
|
||||||
|
const cachePath = path.join(CACHE_DIR, `${key}.json`);
|
||||||
|
if (fs.existsSync(cachePath)) {
|
||||||
|
return fs.readJsonSync(cachePath);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
2
packages/core/cache/package.json
vendored
2
packages/core/cache/package.json
vendored
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/cache",
|
"name": "@nocobase/cache",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/cli",
|
"name": "@nocobase/cli",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"main": "./src/index.js",
|
"main": "./src/index.js",
|
||||||
@ -8,7 +8,7 @@
|
|||||||
"nocobase": "./bin/index.js"
|
"nocobase": "./bin/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/app": "0.20.0-alpha.16",
|
"@nocobase/app": "0.20.0-alpha.17",
|
||||||
"@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",
|
||||||
@ -25,7 +25,7 @@
|
|||||||
"tsx": "^4.6.2"
|
"tsx": "^4.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nocobase/devtools": "0.20.0-alpha.16"
|
"@nocobase/devtools": "0.20.0-alpha.17"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -13,6 +13,7 @@ module.exports = (cli) => {
|
|||||||
.argument('[packages...]')
|
.argument('[packages...]')
|
||||||
.option('-v, --version', 'print version')
|
.option('-v, --version', 'print version')
|
||||||
.option('-c, --compile', 'compile the @nocobase/build package')
|
.option('-c, --compile', 'compile the @nocobase/build package')
|
||||||
|
.option('-r, --retry', 'retry the last failed package')
|
||||||
.option('-w, --watch', 'watch compile the @nocobase/build package')
|
.option('-w, --watch', 'watch compile the @nocobase/build package')
|
||||||
.option('-s, --sourcemap', 'generate sourcemap')
|
.option('-s, --sourcemap', 'generate sourcemap')
|
||||||
.option('--no-dts', 'not generate dts')
|
.option('--no-dts', 'not generate dts')
|
||||||
@ -24,12 +25,14 @@ module.exports = (cli) => {
|
|||||||
});
|
});
|
||||||
if (options.watch) return;
|
if (options.watch) return;
|
||||||
}
|
}
|
||||||
|
process.env['VITE_CJS_IGNORE_WARNING'] = 'true';
|
||||||
|
|
||||||
await run('nocobase-build', [
|
await run('nocobase-build', [
|
||||||
...pkgs,
|
...pkgs,
|
||||||
options.version ? '--version' : '',
|
options.version ? '--version' : '',
|
||||||
!options.dts ? '--no-dts' : '',
|
!options.dts ? '--no-dts' : '',
|
||||||
options.sourcemap ? '--sourcemap' : '',
|
options.sourcemap ? '--sourcemap' : '',
|
||||||
|
options.retry ? '--retry' : '',
|
||||||
]);
|
]);
|
||||||
buildIndexHtml(true);
|
buildIndexHtml(true);
|
||||||
});
|
});
|
||||||
|
@ -243,6 +243,12 @@ module.exports = (cli) => {
|
|||||||
.option('--stop-on-error')
|
.option('--stop-on-error')
|
||||||
.option('--build')
|
.option('--build')
|
||||||
.option('--concurrency [concurrency]', '', os.cpus().length)
|
.option('--concurrency [concurrency]', '', os.cpus().length)
|
||||||
|
.option(
|
||||||
|
'--match [match]',
|
||||||
|
'Only the files matching one of these patterns are executed as test files. Matching is performed against the absolute file path. Strings are treated as glob patterns.',
|
||||||
|
'packages/**/__e2e__/**/*.test.ts',
|
||||||
|
)
|
||||||
|
.option('--ignore [ignore]', 'Skip tests that match the pattern. Strings are treated as glob patterns.', undefined)
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
process.env.__E2E__ = true;
|
process.env.__E2E__ = true;
|
||||||
if (options.build) {
|
if (options.build) {
|
||||||
|
@ -60,7 +60,8 @@ exports.pTest = async (options) => {
|
|||||||
fs.mkdirSync(dir, { recursive: true });
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = glob.sync('packages/**/__e2e__/**/*.test.ts', {
|
const files = glob.sync(options.match, {
|
||||||
|
ignore: options.ignore,
|
||||||
root: process.cwd(),
|
root: process.cwd(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ function addTestCommand(name, cli) {
|
|||||||
.arguments('[paths...]')
|
.arguments('[paths...]')
|
||||||
.allowUnknownOption()
|
.allowUnknownOption()
|
||||||
.action(async (paths, opts) => {
|
.action(async (paths, opts) => {
|
||||||
|
process.argv.push('--disable-console-intercept');
|
||||||
if (name === 'test:server') {
|
if (name === 'test:server') {
|
||||||
process.env.TEST_ENV = 'server-side';
|
process.env.TEST_ENV = 'server-side';
|
||||||
} else if (name === 'test:client') {
|
} else if (name === 'test:client') {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/client",
|
"name": "@nocobase/client",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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.16",
|
"@nocobase/evaluators": "0.20.0-alpha.17",
|
||||||
"@nocobase/sdk": "0.20.0-alpha.16",
|
"@nocobase/sdk": "0.20.0-alpha.17",
|
||||||
"@nocobase/utils": "0.20.0-alpha.16",
|
"@nocobase/utils": "0.20.0-alpha.17",
|
||||||
"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",
|
||||||
|
@ -11,7 +11,7 @@ export const ADMIN_SETTINGS_PATH = '/admin/settings/';
|
|||||||
export const SNIPPET_PREFIX = 'pm.';
|
export const SNIPPET_PREFIX = 'pm.';
|
||||||
|
|
||||||
export interface PluginSettingOptions {
|
export interface PluginSettingOptions {
|
||||||
title: string | React.ReactElement;
|
title: any;
|
||||||
/**
|
/**
|
||||||
* @default Outlet
|
* @default Outlet
|
||||||
*/
|
*/
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SchemaComponent, SchemaComponentProvider } from '../../../schema-component';
|
import { SchemaComponent, SchemaComponentProvider } from '../../../schema-component';
|
||||||
import { render, screen, sleep, userEvent, waitFor } from '@nocobase/test/client';
|
import { render } from '@nocobase/test/client';
|
||||||
import { withDynamicSchemaProps } from '../../hoc';
|
import { withDynamicSchemaProps } from '../../hoc';
|
||||||
|
|
||||||
const HelloComponent = withDynamicSchemaProps((props: any) => (
|
const HelloComponent = withDynamicSchemaProps((props: any) => (
|
||||||
@ -66,7 +66,7 @@ describe('withDynamicSchemaProps', () => {
|
|||||||
|
|
||||||
const Demo = withTestDemo(schema, scopes);
|
const Demo = withTestDemo(schema, scopes);
|
||||||
const { getByTestId } = render(<Demo />);
|
const { getByTestId } = render(<Demo />);
|
||||||
expect(getByTestId('component')).toHaveTextContent(JSON.stringify({ a: 'a', b: 'b' }));
|
expect(getByTestId('component')).toHaveTextContent(JSON.stringify({ b: 'b', a: 'a' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('x-use-decorator-props', () => {
|
test('x-use-decorator-props', () => {
|
||||||
@ -101,7 +101,7 @@ describe('withDynamicSchemaProps', () => {
|
|||||||
|
|
||||||
const Demo = withTestDemo(schema, scopes);
|
const Demo = withTestDemo(schema, scopes);
|
||||||
const { getByTestId } = render(<Demo />);
|
const { getByTestId } = render(<Demo />);
|
||||||
expect(getByTestId('decorator')).toHaveTextContent(JSON.stringify({ a: 'a', b: 'b' }));
|
expect(getByTestId('decorator')).toHaveTextContent(JSON.stringify({ b: 'b', a: 'a' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('x-use-component-props and x-use-decorator-props exist simultaneously', () => {
|
test('x-use-component-props and x-use-decorator-props exist simultaneously', () => {
|
||||||
@ -130,8 +130,8 @@ describe('withDynamicSchemaProps', () => {
|
|||||||
|
|
||||||
const Demo = withTestDemo(schema, scopes);
|
const Demo = withTestDemo(schema, scopes);
|
||||||
const { getByTestId } = render(<Demo />);
|
const { getByTestId } = render(<Demo />);
|
||||||
expect(getByTestId('decorator')).toHaveTextContent(JSON.stringify({ a: 'a', b: 'b' }));
|
expect(getByTestId('decorator')).toHaveTextContent(JSON.stringify({ b: 'b', a: 'a' }));
|
||||||
expect(getByTestId('component')).toHaveTextContent(JSON.stringify({ c: 'c', d: 'd' }));
|
expect(getByTestId('component')).toHaveTextContent(JSON.stringify({ d: 'd', c: 'c' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('no register scope', () => {
|
test('no register scope', () => {
|
||||||
@ -142,4 +142,23 @@ describe('withDynamicSchemaProps', () => {
|
|||||||
const { getByTestId } = render(<Demo />);
|
const { getByTestId } = render(<Demo />);
|
||||||
expect(getByTestId('component')).toHaveTextContent(JSON.stringify({}));
|
expect(getByTestId('component')).toHaveTextContent(JSON.stringify({}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('x-use-component-props should override x-component-props', () => {
|
||||||
|
function useComponentProps() {
|
||||||
|
return {
|
||||||
|
a: 'a',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const schema = {
|
||||||
|
'x-use-component-props': 'useComponentProps',
|
||||||
|
'x-component-props': {
|
||||||
|
a: 'b',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const scopes = { useComponentProps };
|
||||||
|
|
||||||
|
const Demo = withTestDemo(schema, scopes);
|
||||||
|
const { getByTestId } = render(<Demo />);
|
||||||
|
expect(getByTestId('component')).toHaveTextContent(JSON.stringify({ a: 'a' }));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
import { useExpressionScope } from '@formily/react';
|
||||||
import { merge, omit } from 'lodash';
|
import { merge, omit } from 'lodash';
|
||||||
import React, { ComponentType, useMemo } from 'react';
|
import React, { ComponentType, useMemo } from 'react';
|
||||||
import { useDesignable, useSchemaComponentContext } from '../../schema-component';
|
import { useDesignable } from '../../schema-component';
|
||||||
|
|
||||||
const useDefaultSchemaProps = () => undefined;
|
const useDefaultSchemaProps = () => undefined;
|
||||||
|
|
||||||
@ -8,11 +9,11 @@ interface WithSchemaHookOptions {
|
|||||||
displayName?: string;
|
displayName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withDynamicSchemaProps<T = any>(Component: ComponentType<T>, options: WithSchemaHookOptions = {}) {
|
export function withDynamicSchemaProps<T = any>(Component: any, options: WithSchemaHookOptions = {}) {
|
||||||
const displayName = options.displayName || Component.displayName || Component.name;
|
const displayName = options.displayName || Component.displayName || Component.name;
|
||||||
const ComponentWithProps: ComponentType<T> = (props) => {
|
const ComponentWithProps: ComponentType<T> = (props) => {
|
||||||
const { dn, findComponent } = useDesignable();
|
const { dn, findComponent } = useDesignable();
|
||||||
const { scope } = useSchemaComponentContext();
|
const scope = useExpressionScope();
|
||||||
const useComponentPropsStr = useMemo(() => {
|
const useComponentPropsStr = useMemo(() => {
|
||||||
const xComponent = dn.getSchemaAttribute('x-component');
|
const xComponent = dn.getSchemaAttribute('x-component');
|
||||||
const xDecorator = dn.getSchemaAttribute('x-decorator');
|
const xDecorator = dn.getSchemaAttribute('x-decorator');
|
||||||
@ -40,7 +41,7 @@ export function withDynamicSchemaProps<T = any>(Component: ComponentType<T>, opt
|
|||||||
const schemaProps = useSchemaProps(props);
|
const schemaProps = useSchemaProps(props);
|
||||||
|
|
||||||
const memoProps = useMemo(() => {
|
const memoProps = useMemo(() => {
|
||||||
return merge(omit(schemaProps, 'children'), omit(props, 'children'));
|
return merge(omit(props, 'children'), omit(schemaProps, 'children'));
|
||||||
}, [schemaProps, props]);
|
}, [schemaProps, props]);
|
||||||
|
|
||||||
return <Component {...memoProps}>{props.children}</Component>;
|
return <Component {...memoProps}>{props.children}</Component>;
|
||||||
|
@ -59,31 +59,6 @@ export const useBlockResource = () => {
|
|||||||
return useContext(BlockResourceContext) || resource;
|
return useContext(BlockResourceContext) || resource;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface UseResourceProps {
|
|
||||||
resource: any;
|
|
||||||
association?: any;
|
|
||||||
useSourceId?: any;
|
|
||||||
collection?: any;
|
|
||||||
dataSource?: any;
|
|
||||||
block?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useAssociation = (props) => {
|
|
||||||
const { association } = props;
|
|
||||||
const { getCollectionField } = useCollectionManager_deprecated();
|
|
||||||
if (typeof association === 'string') {
|
|
||||||
return getCollectionField(association);
|
|
||||||
} else if (association?.collectionName && association?.name) {
|
|
||||||
return getCollectionField(`${association?.collectionName}.${association?.name}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const useActionParams = (props) => {
|
|
||||||
const { useParams } = props;
|
|
||||||
const params = useParams?.() || {};
|
|
||||||
return { ...props.params, ...params };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MaybeCollectionProvider = (props) => {
|
export const MaybeCollectionProvider = (props) => {
|
||||||
const { collection } = props;
|
const { collection } = props;
|
||||||
return collection ? (
|
return collection ? (
|
||||||
@ -218,6 +193,22 @@ export const useBlockContext = () => {
|
|||||||
return useContext(BlockContext);
|
return useContext(BlockContext);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于兼容旧版本 Schema
|
||||||
|
*/
|
||||||
|
const useCompatDataBlockSourceId = (props) => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
|
||||||
|
// 如果存在 x-use-decorator-props,说明是新版 Schema
|
||||||
|
if (fieldSchema['x-use-decorator-props']) {
|
||||||
|
return props.sourceId;
|
||||||
|
} else {
|
||||||
|
// 是否存在 x-use-decorator-props 是固定不变的,所以这里可以使用 hooks
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
return useDataBlockSourceId(props);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated use `DataBlockProvider` instead
|
* @deprecated use `DataBlockProvider` instead
|
||||||
*/
|
*/
|
||||||
@ -236,8 +227,11 @@ export const BlockProvider = (props: {
|
|||||||
useParams?: any;
|
useParams?: any;
|
||||||
}) => {
|
}) => {
|
||||||
const { name, dataSource, association, useParams, parentRecord } = props;
|
const { name, dataSource, association, useParams, parentRecord } = props;
|
||||||
const sourceId = useDataBlockSourceId({ association });
|
const sourceId = useCompatDataBlockSourceId(props);
|
||||||
|
|
||||||
|
// 新版(1.0)已弃用 useParams,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||||
const paramsFromHook = useParams?.();
|
const paramsFromHook = useParams?.();
|
||||||
|
|
||||||
const { getAssociationAppends } = useAssociationNames(dataSource);
|
const { getAssociationAppends } = useAssociationNames(dataSource);
|
||||||
const { appends, updateAssociationValues } = getAssociationAppends();
|
const { appends, updateAssociationValues } = getAssociationAppends();
|
||||||
const params = useMemo(() => {
|
const params = useMemo(() => {
|
||||||
@ -330,7 +324,9 @@ export const useParamsFromRecord = () => {
|
|||||||
const { fields } = useCollection_deprecated();
|
const { fields } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { getCollectionJoinField } = useCollectionManager_deprecated();
|
const { getCollectionJoinField } = useCollectionManager_deprecated();
|
||||||
const collectionField = getCollectionJoinField(fieldSchema?.['x-decorator-props']?.resource);
|
const collectionField = getCollectionJoinField(
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association,
|
||||||
|
);
|
||||||
const filterFields = fields
|
const filterFields = fields
|
||||||
.filter((v) => {
|
.filter((v) => {
|
||||||
return ['boolean', 'date', 'integer', 'radio', 'sort', 'string', 'time', 'uid', 'uuid'].includes(v.type);
|
return ['boolean', 'date', 'integer', 'radio', 'sort', 'string', 'time', 'uid', 'uuid'].includes(v.type);
|
||||||
|
@ -10,11 +10,13 @@ import { DetailsBlockProvider, useDetailsBlockProps } from './DetailsBlockProvid
|
|||||||
import { FilterFormBlockProvider } from './FilterFormBlockProvider';
|
import { FilterFormBlockProvider } from './FilterFormBlockProvider';
|
||||||
import { FormBlockProvider, useFormBlockProps } from './FormBlockProvider';
|
import { FormBlockProvider, useFormBlockProps } from './FormBlockProvider';
|
||||||
import { FormFieldProvider, useFormFieldProps } from './FormFieldProvider';
|
import { FormFieldProvider, useFormFieldProps } from './FormFieldProvider';
|
||||||
import { TableBlockProvider, useTableBlockProps } from './TableBlockProvider';
|
import { TableBlockProvider } from './TableBlockProvider';
|
||||||
|
import { useTableBlockProps } from '../modules/blocks/data-blocks/table/hooks/useTableBlockProps';
|
||||||
import { TableFieldProvider, useTableFieldProps } from './TableFieldProvider';
|
import { TableFieldProvider, useTableFieldProps } from './TableFieldProvider';
|
||||||
import { TableSelectorProvider, useTableSelectorProps } from './TableSelectorProvider';
|
import { TableSelectorProvider, useTableSelectorProps } from './TableSelectorProvider';
|
||||||
import * as bp from './hooks';
|
import * as bp from './hooks';
|
||||||
import { BlockSchemaToolbar } from '../modules/blocks/BlockSchemaToolbar';
|
import { BlockSchemaToolbar } from '../modules/blocks/BlockSchemaToolbar';
|
||||||
|
import { useTableBlockDecoratorProps } from '../modules/blocks/data-blocks/table/hooks/useTableBlockDecoratorProps';
|
||||||
|
|
||||||
// TODO: delete this, replaced by `BlockSchemaComponentPlugin`
|
// TODO: delete this, replaced by `BlockSchemaComponentPlugin`
|
||||||
export const BlockSchemaComponentProvider: React.FC = (props) => {
|
export const BlockSchemaComponentProvider: React.FC = (props) => {
|
||||||
@ -41,6 +43,7 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
|
|||||||
useTableFieldProps,
|
useTableFieldProps,
|
||||||
useTableBlockProps,
|
useTableBlockProps,
|
||||||
useTableSelectorProps,
|
useTableSelectorProps,
|
||||||
|
useTableBlockDecoratorProps,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
@ -84,6 +87,7 @@ export class BlockSchemaComponentPlugin extends Plugin {
|
|||||||
useTableFieldProps,
|
useTableFieldProps,
|
||||||
useTableBlockProps,
|
useTableBlockProps,
|
||||||
useTableSelectorProps,
|
useTableSelectorProps,
|
||||||
|
useTableBlockDecoratorProps,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { ArrayField, createForm } from '@formily/core';
|
import { createForm } from '@formily/core';
|
||||||
import { FormContext, useField, useFieldSchema } from '@formily/react';
|
import { FormContext, useField, useFieldSchema } from '@formily/react';
|
||||||
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
import React, { createContext, useContext, useMemo, useState } from 'react';
|
||||||
import { useCollectionManager_deprecated } from '../collection-manager';
|
import { useCollectionManager_deprecated } from '../collection-manager';
|
||||||
import { useFilterBlock } from '../filter-provider/FilterProvider';
|
import { FixedBlockWrapper, SchemaComponentOptions } from '../schema-component';
|
||||||
import { mergeFilter } from '../filter-provider/utils';
|
|
||||||
import { FixedBlockWrapper, SchemaComponentOptions, removeNullCondition } from '../schema-component';
|
|
||||||
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
|
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
|
||||||
import { findFilterTargets, useParsedFilter } from './hooks';
|
import { useParsedFilter } from './hooks';
|
||||||
|
import { useTableBlockParams } from '../modules/blocks/data-blocks/table/hooks/useTableBlockDecoratorProps';
|
||||||
|
import { withDynamicSchemaProps } from '../application/hoc/withDynamicSchemaProps';
|
||||||
|
|
||||||
export const TableBlockContext = createContext<any>({});
|
export const TableBlockContext = createContext<any>({});
|
||||||
TableBlockContext.displayName = 'TableBlockContext';
|
TableBlockContext.displayName = 'TableBlockContext';
|
||||||
@ -74,16 +74,37 @@ const InternalTableBlockProvider = (props: Props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TableBlockProvider = (props) => {
|
/**
|
||||||
const resourceName = props.resource;
|
* 用于兼容旧版本的 schema,当不需要兼容时可直接移除该方法
|
||||||
const params = useMemo(() => ({ ...props.params }), [props.params]);
|
* @param props
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const useTableBlockParamsCompat = (props) => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
|
||||||
|
let params;
|
||||||
|
// 1. 新版本的 schema 存在 x-use-decorator-props 属性
|
||||||
|
if (fieldSchema['x-use-decorator-props']) {
|
||||||
|
params = props.params;
|
||||||
|
} else {
|
||||||
|
// 2. 旧版本的 schema 不存在 x-use-decorator-props 属性
|
||||||
|
// 因为 schema 中是否存在 x-use-decorator-props 是固定不变的,所以这里可以使用 hooks
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
params = useTableBlockParams(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TableBlockProvider = withDynamicSchemaProps((props) => {
|
||||||
|
const resourceName = props.resource || props.association;
|
||||||
|
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { getCollection, getCollectionField } = useCollectionManager_deprecated(props.dataSource);
|
const { getCollection, getCollectionField } = useCollectionManager_deprecated(props.dataSource);
|
||||||
const collection = getCollection(props.collection, props.dataSource);
|
const collection = getCollection(props.collection, props.dataSource);
|
||||||
const { treeTable, dragSortBy } = fieldSchema?.['x-decorator-props'] || {};
|
const { treeTable } = fieldSchema?.['x-decorator-props'] || {};
|
||||||
if (props.dragSort && dragSortBy) {
|
const params = useTableBlockParamsCompat(props);
|
||||||
params['sort'] = dragSortBy;
|
|
||||||
}
|
|
||||||
let childrenColumnName = 'children';
|
let childrenColumnName = 'children';
|
||||||
if (collection?.tree && treeTable !== false) {
|
if (collection?.tree && treeTable !== false) {
|
||||||
if (resourceName?.includes('.')) {
|
if (resourceName?.includes('.')) {
|
||||||
@ -101,140 +122,18 @@ export const TableBlockProvider = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const form = useMemo(() => createForm(), [treeTable]);
|
const form = useMemo(() => createForm(), [treeTable]);
|
||||||
const { filter: parsedFilter } = useParsedFilter({
|
|
||||||
filterOption: params?.filter,
|
|
||||||
});
|
|
||||||
const paramsWithFilter = useMemo(() => {
|
|
||||||
return {
|
|
||||||
...params,
|
|
||||||
filter: parsedFilter,
|
|
||||||
};
|
|
||||||
}, [parsedFilter, params]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaComponentOptions scope={{ treeTable }}>
|
<SchemaComponentOptions scope={{ treeTable }}>
|
||||||
<FormContext.Provider value={form}>
|
<FormContext.Provider value={form}>
|
||||||
<BlockProvider name={props.name || 'table'} {...props} params={paramsWithFilter} runWhenParamsChanged>
|
<BlockProvider name={props.name || 'table'} {...props} params={params} runWhenParamsChanged>
|
||||||
<InternalTableBlockProvider {...props} childrenColumnName={childrenColumnName} params={paramsWithFilter} />
|
<InternalTableBlockProvider {...props} childrenColumnName={childrenColumnName} params={params} />
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
</FormContext.Provider>
|
</FormContext.Provider>
|
||||||
</SchemaComponentOptions>
|
</SchemaComponentOptions>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export const useTableBlockContext = () => {
|
export const useTableBlockContext = () => {
|
||||||
return useContext(TableBlockContext);
|
return useContext(TableBlockContext);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTableBlockProps = () => {
|
|
||||||
const field = useField<ArrayField>();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const ctx = useTableBlockContext();
|
|
||||||
const globalSort = fieldSchema.parent?.['x-decorator-props']?.['params']?.['sort'];
|
|
||||||
const { getDataBlocks } = useFilterBlock();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ctx?.service?.loading) {
|
|
||||||
field.value = [];
|
|
||||||
field.value = ctx?.service?.data?.data;
|
|
||||||
field?.setInitialValue(ctx?.service?.data?.data);
|
|
||||||
field.data = field.data || {};
|
|
||||||
field.data.selectedRowKeys = ctx?.field?.data?.selectedRowKeys;
|
|
||||||
field.componentProps.pagination = field.componentProps.pagination || {};
|
|
||||||
field.componentProps.pagination.pageSize = ctx?.service?.data?.meta?.pageSize;
|
|
||||||
field.componentProps.pagination.total = ctx?.service?.data?.meta?.count;
|
|
||||||
field.componentProps.pagination.current = ctx?.service?.data?.meta?.page;
|
|
||||||
}
|
|
||||||
}, [ctx?.service?.data, ctx?.service?.loading]); // 这里如果依赖了 ctx?.field?.data?.selectedRowKeys 的话,会导致这个问题:
|
|
||||||
return {
|
|
||||||
childrenColumnName: ctx.childrenColumnName,
|
|
||||||
loading: ctx?.service?.loading,
|
|
||||||
showIndex: ctx.showIndex,
|
|
||||||
dragSort: ctx.dragSort && ctx.dragSortBy,
|
|
||||||
rowKey: ctx.rowKey || 'id',
|
|
||||||
pagination:
|
|
||||||
ctx?.params?.paginate !== false
|
|
||||||
? {
|
|
||||||
defaultCurrent: ctx?.params?.page || 1,
|
|
||||||
defaultPageSize: ctx?.params?.pageSize,
|
|
||||||
}
|
|
||||||
: false,
|
|
||||||
onRowSelectionChange(selectedRowKeys) {
|
|
||||||
ctx.field.data = ctx?.field?.data || {};
|
|
||||||
ctx.field.data.selectedRowKeys = selectedRowKeys;
|
|
||||||
ctx?.field?.onRowSelect?.(selectedRowKeys);
|
|
||||||
},
|
|
||||||
async onRowDragEnd({ from, to }) {
|
|
||||||
await ctx.resource.move({
|
|
||||||
sourceId: from[ctx.rowKey || 'id'],
|
|
||||||
targetId: to[ctx.rowKey || 'id'],
|
|
||||||
sortField: ctx.dragSort && ctx.dragSortBy,
|
|
||||||
});
|
|
||||||
ctx.service.refresh();
|
|
||||||
},
|
|
||||||
onChange({ current, pageSize }, filters, sorter) {
|
|
||||||
const sort = sorter.order ? (sorter.order === `ascend` ? [sorter.field] : [`-${sorter.field}`]) : globalSort;
|
|
||||||
ctx.service.run({ ...ctx.service.params?.[0], page: current, pageSize, sort });
|
|
||||||
},
|
|
||||||
onClickRow(record, setSelectedRow, selectedRow) {
|
|
||||||
const { targets, uid } = findFilterTargets(fieldSchema);
|
|
||||||
const dataBlocks = getDataBlocks();
|
|
||||||
|
|
||||||
// 如果是之前创建的区块是没有 x-filter-targets 属性的,所以这里需要判断一下避免报错
|
|
||||||
if (!targets || !targets.some((target) => dataBlocks.some((dataBlock) => dataBlock.uid === target.uid))) {
|
|
||||||
// 当用户已经点击过某一行,如果此时再把相连接的区块给删除的话,行的高亮状态就会一直保留。
|
|
||||||
// 这里暂时没有什么比较好的方法,只是在用户再次点击的时候,把高亮状态给清除掉。
|
|
||||||
setSelectedRow((prev) => (prev.length ? [] : prev));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = [record[ctx.rowKey]];
|
|
||||||
|
|
||||||
dataBlocks.forEach((block) => {
|
|
||||||
const target = targets.find((target) => target.uid === block.uid);
|
|
||||||
if (!target) return;
|
|
||||||
|
|
||||||
const param = block.service.params?.[0] || {};
|
|
||||||
// 保留原有的 filter
|
|
||||||
const storedFilter = block.service.params?.[1]?.filters || {};
|
|
||||||
|
|
||||||
if (selectedRow.includes(record[ctx.rowKey])) {
|
|
||||||
if (block.dataLoadingMode === 'manual') {
|
|
||||||
return block.clearData();
|
|
||||||
}
|
|
||||||
delete storedFilter[uid];
|
|
||||||
} else {
|
|
||||||
storedFilter[uid] = {
|
|
||||||
$and: [
|
|
||||||
{
|
|
||||||
[target.field || ctx.rowKey]: {
|
|
||||||
[target.field ? '$in' : '$eq']: value,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedFilter = mergeFilter([
|
|
||||||
...Object.values(storedFilter).map((filter) => removeNullCondition(filter)),
|
|
||||||
block.defaultFilter,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return block.doFilter(
|
|
||||||
{
|
|
||||||
...param,
|
|
||||||
page: 1,
|
|
||||||
filter: mergedFilter,
|
|
||||||
},
|
|
||||||
{ filters: storedFilter },
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新表格的选中状态
|
|
||||||
setSelectedRow((prev) => (prev?.includes(record[ctx.rowKey]) ? [] : [...value]));
|
|
||||||
},
|
|
||||||
onExpand(expanded, record) {
|
|
||||||
ctx?.field.onExpandClick?.(expanded, record);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { useFieldSchema } from '@formily/react';
|
import {
|
||||||
import { useCollection, useCollectionManager, useCollectionParentRecordData, useCollectionRecordData } from '../..';
|
InheritanceCollectionMixin,
|
||||||
|
useCollection,
|
||||||
|
useCollectionManager,
|
||||||
|
useCollectionParentRecordData,
|
||||||
|
useCollectionRecordData,
|
||||||
|
} from '../..';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注意:这里有一个需要更改 schema 才能解决的问题,就是在获取 sourceId 的时候无法确定(在关系字段和当前表同表时)
|
* 注意:这里有一个需要更改 schema 才能解决的问题,就是在获取 sourceId 的时候无法确定(在关系字段和当前表同表时)
|
||||||
@ -12,14 +17,17 @@ export const useDataBlockSourceId = ({ association }: { association: string }) =
|
|||||||
const recordData = useCollectionRecordData();
|
const recordData = useCollectionRecordData();
|
||||||
const parentRecordData = useCollectionParentRecordData();
|
const parentRecordData = useCollectionParentRecordData();
|
||||||
const cm = useCollectionManager();
|
const cm = useCollectionManager();
|
||||||
const collectionOutsideBlock = useCollection();
|
const collectionOutsideBlock = useCollection<InheritanceCollectionMixin>();
|
||||||
|
|
||||||
if (!association) return;
|
if (!association) return;
|
||||||
|
|
||||||
const associationField = cm.getCollectionField(association);
|
const associationField = cm.getCollectionField(association);
|
||||||
const associationCollection = cm.getCollection(associationField.collectionName);
|
const associationCollection = cm.getCollection<InheritanceCollectionMixin>(associationField.collectionName);
|
||||||
|
|
||||||
if (collectionOutsideBlock.name === associationCollection.name) {
|
if (
|
||||||
|
collectionOutsideBlock.name === associationCollection.name ||
|
||||||
|
collectionOutsideBlock.getParentCollectionsName?.().includes(associationCollection.name)
|
||||||
|
) {
|
||||||
return recordData?.[
|
return recordData?.[
|
||||||
associationField.sourceKey ||
|
associationField.sourceKey ||
|
||||||
associationCollection.filterTargetKey ||
|
associationCollection.filterTargetKey ||
|
||||||
|
@ -193,6 +193,10 @@ export const PresetFields = observer(
|
|||||||
rowSelection={{
|
rowSelection={{
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
|
getCheckboxProps: (record) => ({
|
||||||
|
disabled: form.values.template === 'file', // Column configuration not to be checked
|
||||||
|
name: record.name,
|
||||||
|
}),
|
||||||
onChange: (_, selectedRows) => {
|
onChange: (_, selectedRows) => {
|
||||||
const fields = getDefaultCollectionFields(selectedRows, form.values);
|
const fields = getDefaultCollectionFields(selectedRows, form.values);
|
||||||
const config = {
|
const config = {
|
||||||
|
@ -11,6 +11,7 @@ export interface DataSourceOptions {
|
|||||||
collections?: CollectionOptions[];
|
collections?: CollectionOptions[];
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
status?: 'loaded' | 'loading-failed' | 'loading' | 'reloading';
|
status?: 'loaded' | 'loading-failed' | 'loading' | 'reloading';
|
||||||
|
isDBInstance?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DataSourceFactory = new (options: DataSourceOptions, dataSourceManager: DataSourceManager) => DataSource;
|
export type DataSourceFactory = new (options: DataSourceOptions, dataSourceManager: DataSourceManager) => DataSource;
|
||||||
|
@ -910,5 +910,14 @@
|
|||||||
"Second": "秒",
|
"Second": "秒",
|
||||||
"Unix Timestamp": "Unix 时间戳",
|
"Unix Timestamp": "Unix 时间戳",
|
||||||
"Field value do not meet the requirements": "字符不符合要求",
|
"Field value do not meet the requirements": "字符不符合要求",
|
||||||
"Field value size is": "字符长度要求"
|
"Field value size is": "字符长度要求",
|
||||||
|
"Style": "风格",
|
||||||
|
"Unit conversion": "单位换算",
|
||||||
|
"Separator": "分隔符",
|
||||||
|
"Prefix": "前缀",
|
||||||
|
"Suffix": "后缀",
|
||||||
|
"Multiply by":"乘以",
|
||||||
|
"Divide by":"除以",
|
||||||
|
"Scientifix notation":"科学计数法",
|
||||||
|
"Normal":"常规"
|
||||||
}
|
}
|
||||||
|
@ -177,7 +177,8 @@ export const multiDataDetailsBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'Details',
|
componentName: 'Details',
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
|
@ -16,7 +16,8 @@ export const singleDataDetailsBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
insertAdjacentPosition: 'beforeEnd',
|
insertAdjacentPosition: 'beforeEnd',
|
||||||
componentName: 'ReadPrettyFormItem',
|
componentName: 'ReadPrettyFormItem',
|
||||||
|
@ -51,7 +51,8 @@ export const creationFormBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'FormItem',
|
componentName: 'FormItem',
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
@ -117,7 +118,8 @@ export const createFormBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'FormItem',
|
componentName: 'FormItem',
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
|
@ -50,7 +50,8 @@ export const editFormBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'FormItem',
|
componentName: 'FormItem',
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
|
@ -257,7 +257,8 @@ export const gridCardBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
componentName: 'GridCard',
|
componentName: 'GridCard',
|
||||||
|
@ -196,7 +196,8 @@ export const listBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
componentName: 'List',
|
componentName: 'List',
|
||||||
|
@ -2,9 +2,9 @@ import { TableOutlined } from '@ant-design/icons';
|
|||||||
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application/schema-initializer/context';
|
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application/schema-initializer/context';
|
||||||
import { useCollectionManager_deprecated } from '../../../../collection-manager/hooks/useCollectionManager_deprecated';
|
import { useCollectionManager_deprecated } from '../../../../collection-manager/hooks/useCollectionManager_deprecated';
|
||||||
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
|
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
|
||||||
import { createTableBlockSchema } from '../../../../schema-initializer/utils';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
|
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
|
||||||
|
import { createTableBlockUISchema } from './createTableBlockUISchema';
|
||||||
|
|
||||||
export const TableBlockInitializer = ({
|
export const TableBlockInitializer = ({
|
||||||
filterCollections,
|
filterCollections,
|
||||||
@ -42,8 +42,8 @@ export const TableBlockInitializer = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const collection = getCollection(item.name, item.dataSource);
|
const collection = getCollection(item.name, item.dataSource);
|
||||||
const schema = createTableBlockSchema({
|
const schema = createTableBlockUISchema({
|
||||||
collection: item.name,
|
collectionName: item.name,
|
||||||
dataSource: item.dataSource,
|
dataSource: item.dataSource,
|
||||||
rowKey: collection.filterTargetKey || 'id',
|
rowKey: collection.filterTargetKey || 'id',
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { createBlockInPage, expect, oneEmptyTable, test } from '@nocobase/test/e2e';
|
import { createBlockInPage, expect, oneEmptyTable, test } from '@nocobase/test/e2e';
|
||||||
|
import { T3686 } from './templatesOfBug';
|
||||||
|
|
||||||
test.describe('where table block can be added', () => {
|
test.describe('where table block can be added', () => {
|
||||||
test('page', async ({ page, mockPage }) => {
|
test('page', async ({ page, mockPage }) => {
|
||||||
@ -9,7 +10,45 @@ test.describe('where table block can be added', () => {
|
|||||||
await expect(page.getByLabel('block-item-CardItem-users-table')).toBeVisible();
|
await expect(page.getByLabel('block-item-CardItem-users-table')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('popup', async () => {});
|
test('popup', async ({ page, mockPage, mockRecord }) => {
|
||||||
|
await mockPage(T3686).goto();
|
||||||
|
await mockRecord('parentCollection');
|
||||||
|
const childRecord = await mockRecord('childCollection');
|
||||||
|
|
||||||
|
// 打开弹窗
|
||||||
|
await page.getByLabel('action-Action.Link-View').click();
|
||||||
|
|
||||||
|
// 添加当前表关系区块
|
||||||
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'childAssociationField ->' }).click();
|
||||||
|
await page
|
||||||
|
.getByTestId('drawer-Action.Container-childCollection-View record')
|
||||||
|
.getByLabel('schema-initializer-TableV2-')
|
||||||
|
.hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'childTargetText' }).click();
|
||||||
|
|
||||||
|
// 添加父表关系区块
|
||||||
|
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'parentAssociationField ->' }).click();
|
||||||
|
await page.getByLabel('schema-initializer-TableV2-table:configureColumns-parentTargetCollection').hover();
|
||||||
|
await page.getByRole('menuitem', { name: 'parentTargetText' }).click();
|
||||||
|
|
||||||
|
// 普通关系区块应该显示正常
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByLabel('block-item-CardItem-childTargetCollection-table')
|
||||||
|
.getByText(childRecord.childAssociationField[0].childTargetText),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// 父表关系区块应该显示正常
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByLabel('block-item-CardItem-parentTargetCollection-table')
|
||||||
|
.getByText(childRecord.parentAssociationField[0].parentTargetText),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('configure actions', () => {
|
test.describe('configure actions', () => {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { PageConfig } from '@nocobase/test/e2e';
|
||||||
|
|
||||||
export const T2183 = {
|
export const T2183 = {
|
||||||
pageSchema: {
|
pageSchema: {
|
||||||
_isJSONSchemaObject: true,
|
_isJSONSchemaObject: true,
|
||||||
@ -670,3 +672,260 @@ export const T2187 = {
|
|||||||
'x-index': 1,
|
'x-index': 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const T3686: PageConfig = {
|
||||||
|
collections: [
|
||||||
|
{
|
||||||
|
name: 'parentTargetCollection',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'parentTargetText',
|
||||||
|
interface: 'input',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'childTargetCollection',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'childTargetText',
|
||||||
|
interface: 'input',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'childCollection',
|
||||||
|
inherits: ['parentCollection'],
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'childAssociationField',
|
||||||
|
interface: 'm2m',
|
||||||
|
target: 'childTargetCollection',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parentCollection',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'parentAssociationField',
|
||||||
|
interface: 'm2m',
|
||||||
|
target: 'parentTargetCollection',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageSchema: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Page',
|
||||||
|
properties: {
|
||||||
|
fa2wzem9pud: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'page:addBlock',
|
||||||
|
properties: {
|
||||||
|
'14kr5bu1min': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
properties: {
|
||||||
|
uc0jyubx2p3: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
properties: {
|
||||||
|
k22vt5rvlf8: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableBlockProvider',
|
||||||
|
'x-acl-action': 'childCollection:list',
|
||||||
|
'x-use-decorator-props': 'useTableBlockDecoratorProps',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection: 'childCollection',
|
||||||
|
dataSource: 'main',
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
rowKey: 'id',
|
||||||
|
showIndex: true,
|
||||||
|
dragSort: 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': 'table:configureActions',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 'var(--nb-spacing)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '42xfns3215b',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
mv0fpgnz9x4: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'array',
|
||||||
|
'x-initializer': 'table:configureColumns',
|
||||||
|
'x-component': 'TableV2',
|
||||||
|
'x-use-component-props': 'useTableBlockProps',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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': 'table:configureItemActions',
|
||||||
|
properties: {
|
||||||
|
'4pr5w722wko': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'DndContext',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
'0z54f36l29g': {
|
||||||
|
'x-uid': '6ga7ofdmqac',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: 'View record',
|
||||||
|
'x-action': 'view',
|
||||||
|
'x-toolbar': 'ActionSchemaToolbar',
|
||||||
|
'x-settings': 'actionSettings:view',
|
||||||
|
'x-component': 'Action.Link',
|
||||||
|
'x-component-props': {
|
||||||
|
openMode: 'drawer',
|
||||||
|
danger: false,
|
||||||
|
},
|
||||||
|
'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': 'popup:common:addBlock',
|
||||||
|
'x-uid': '8isg655oydv',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'avctzq7wpne',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '8mimixsn47i',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'l491lw6ud7u',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '0y6h0doaa8s',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'cusyvu100n5',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'kp6yhoecxt4',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '6fsjzz00845',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'gbcojp8p120',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '18neqdk2pbg',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '7przcz662jy',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'teroosp0elp',
|
||||||
|
'x-async': true,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
import { createTableBlockUISchema } from '../createTableBlockUISchema';
|
||||||
|
|
||||||
|
vi.mock('@formily/shared', () => {
|
||||||
|
return {
|
||||||
|
uid: () => 'mocked-uid',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createTableBLockSchemaV2', () => {
|
||||||
|
it('should create a default table block schema with minimum options', () => {
|
||||||
|
const options = { dataSource: 'abc', collectionName: 'users', association: 'users.roles', rowKey: 'rowKey' };
|
||||||
|
const schema = createTableBlockUISchema(options);
|
||||||
|
|
||||||
|
expect(schema).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"actions": {
|
||||||
|
"properties": {},
|
||||||
|
"type": "void",
|
||||||
|
"x-component": "ActionBar",
|
||||||
|
"x-component-props": {
|
||||||
|
"style": {
|
||||||
|
"marginBottom": "var(--nb-spacing)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"x-initializer": "table:configureActions",
|
||||||
|
},
|
||||||
|
"mocked-uid": {
|
||||||
|
"properties": {
|
||||||
|
"actions": {
|
||||||
|
"properties": {
|
||||||
|
"mocked-uid": {
|
||||||
|
"type": "void",
|
||||||
|
"x-component": "Space",
|
||||||
|
"x-component-props": {
|
||||||
|
"split": "|",
|
||||||
|
},
|
||||||
|
"x-decorator": "DndContext",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": "{{ t("Actions") }}",
|
||||||
|
"type": "void",
|
||||||
|
"x-action-column": "actions",
|
||||||
|
"x-component": "TableV2.Column",
|
||||||
|
"x-decorator": "TableV2.Column.ActionBar",
|
||||||
|
"x-designer": "TableV2.ActionColumnDesigner",
|
||||||
|
"x-initializer": "table:configureItemActions",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"x-component": "TableV2",
|
||||||
|
"x-component-props": {
|
||||||
|
"rowKey": "id",
|
||||||
|
"rowSelection": {
|
||||||
|
"type": "checkbox",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"x-initializer": "table:configureColumns",
|
||||||
|
"x-use-component-props": "useTableBlockProps",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "void",
|
||||||
|
"x-acl-action": "users:list",
|
||||||
|
"x-component": "CardItem",
|
||||||
|
"x-decorator": "TableBlockProvider",
|
||||||
|
"x-decorator-props": {
|
||||||
|
"action": "list",
|
||||||
|
"association": "users.roles",
|
||||||
|
"collection": "users",
|
||||||
|
"dataSource": "abc",
|
||||||
|
"dragSort": false,
|
||||||
|
"params": {
|
||||||
|
"pageSize": 20,
|
||||||
|
},
|
||||||
|
"rowKey": "rowKey",
|
||||||
|
"showIndex": true,
|
||||||
|
},
|
||||||
|
"x-filter-targets": [],
|
||||||
|
"x-settings": "blockSettings:table",
|
||||||
|
"x-toolbar": "BlockSchemaToolbar",
|
||||||
|
"x-use-decorator-props": "useTableBlockDecoratorProps",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,84 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
|
||||||
|
export const createTableBlockUISchema = (options: {
|
||||||
|
dataSource: string;
|
||||||
|
collectionName?: string;
|
||||||
|
rowKey?: string;
|
||||||
|
association?: string;
|
||||||
|
}): ISchema => {
|
||||||
|
const { collectionName, dataSource, rowKey, association } = options;
|
||||||
|
|
||||||
|
if (!dataSource) {
|
||||||
|
throw new Error('dataSource is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableBlockProvider',
|
||||||
|
'x-acl-action': `${collectionName}:list`,
|
||||||
|
'x-use-decorator-props': 'useTableBlockDecoratorProps',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection: collectionName,
|
||||||
|
association,
|
||||||
|
dataSource,
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
rowKey,
|
||||||
|
showIndex: true,
|
||||||
|
dragSort: false,
|
||||||
|
},
|
||||||
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
|
'x-settings': 'blockSettings:table',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-filter-targets': [],
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
'x-initializer': 'table:configureActions',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 'var(--nb-spacing)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
[uid()]: {
|
||||||
|
type: 'array',
|
||||||
|
'x-initializer': 'table:configureColumns',
|
||||||
|
'x-component': 'TableV2',
|
||||||
|
'x-use-component-props': 'useTableBlockProps',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Actions") }}',
|
||||||
|
'x-action-column': 'actions',
|
||||||
|
'x-decorator': 'TableV2.Column.ActionBar',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-designer': 'TableV2.ActionColumnDesigner',
|
||||||
|
'x-initializer': 'table:configureItemActions',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'DndContext',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,51 @@
|
|||||||
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import { useParsedFilter } from '../../../../../block-provider/hooks/useParsedFilter';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useDataBlockSourceId } from '../../../../../block-provider/hooks/useDataBlockSourceId';
|
||||||
|
|
||||||
|
export const useTableBlockDecoratorProps = (props) => {
|
||||||
|
const params = useTableBlockParams(props);
|
||||||
|
const sourceId = useTableBlockSourceId(props);
|
||||||
|
|
||||||
|
return {
|
||||||
|
params,
|
||||||
|
sourceId,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useTableBlockParams(props) {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { filter: parsedFilter } = useParsedFilter({
|
||||||
|
filterOption: props.params?.filter,
|
||||||
|
});
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const params = props.params || {};
|
||||||
|
|
||||||
|
// 1. sort
|
||||||
|
const { dragSortBy } = fieldSchema?.['x-decorator-props'] || {};
|
||||||
|
if (props.dragSort && dragSortBy) {
|
||||||
|
params['sort'] = dragSortBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. filter
|
||||||
|
const paramsWithFilter = {
|
||||||
|
...params,
|
||||||
|
filter: parsedFilter,
|
||||||
|
};
|
||||||
|
|
||||||
|
return paramsWithFilter;
|
||||||
|
}, [fieldSchema, parsedFilter, props.dragSort, props.params]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useTableBlockSourceId(props) {
|
||||||
|
let sourceId: string | undefined;
|
||||||
|
|
||||||
|
// 因为 association 是固定不变的,所以在条件中使用 hooks 是安全的
|
||||||
|
if (props.association) {
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
sourceId = useDataBlockSourceId({ association: props.association });
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceId;
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
import { ArrayField } from '@formily/core';
|
||||||
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useFilterBlock } from '../../../../../filter-provider/FilterProvider';
|
||||||
|
import { mergeFilter } from '../../../../../filter-provider/utils';
|
||||||
|
import { removeNullCondition } from '../../../../../schema-component';
|
||||||
|
import { findFilterTargets } from '../../../../../block-provider/hooks';
|
||||||
|
import { useTableBlockContext } from '../../../../../block-provider/TableBlockProvider';
|
||||||
|
|
||||||
|
export const useTableBlockProps = () => {
|
||||||
|
const field = useField<ArrayField>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const ctx = useTableBlockContext();
|
||||||
|
const globalSort = fieldSchema.parent?.['x-decorator-props']?.['params']?.['sort'];
|
||||||
|
const { getDataBlocks } = useFilterBlock();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ctx?.service?.loading) {
|
||||||
|
field.value = [];
|
||||||
|
field.value = ctx?.service?.data?.data;
|
||||||
|
field?.setInitialValue(ctx?.service?.data?.data);
|
||||||
|
field.data = field.data || {};
|
||||||
|
field.data.selectedRowKeys = ctx?.field?.data?.selectedRowKeys;
|
||||||
|
field.componentProps.pagination = field.componentProps.pagination || {};
|
||||||
|
field.componentProps.pagination.pageSize = ctx?.service?.data?.meta?.pageSize;
|
||||||
|
field.componentProps.pagination.total = ctx?.service?.data?.meta?.count;
|
||||||
|
field.componentProps.pagination.current = ctx?.service?.data?.meta?.page;
|
||||||
|
}
|
||||||
|
}, [ctx?.service?.data, ctx?.service?.loading]); // 这里如果依赖了 ctx?.field?.data?.selectedRowKeys 的话,会导致这个问题:
|
||||||
|
return {
|
||||||
|
childrenColumnName: ctx.childrenColumnName,
|
||||||
|
loading: ctx?.service?.loading,
|
||||||
|
showIndex: ctx.showIndex,
|
||||||
|
dragSort: ctx.dragSort && ctx.dragSortBy,
|
||||||
|
rowKey: ctx.rowKey || 'id',
|
||||||
|
pagination:
|
||||||
|
ctx?.params?.paginate !== false
|
||||||
|
? {
|
||||||
|
defaultCurrent: ctx?.params?.page || 1,
|
||||||
|
defaultPageSize: ctx?.params?.pageSize,
|
||||||
|
}
|
||||||
|
: false,
|
||||||
|
onRowSelectionChange(selectedRowKeys) {
|
||||||
|
ctx.field.data = ctx?.field?.data || {};
|
||||||
|
ctx.field.data.selectedRowKeys = selectedRowKeys;
|
||||||
|
ctx?.field?.onRowSelect?.(selectedRowKeys);
|
||||||
|
},
|
||||||
|
async onRowDragEnd({ from, to }) {
|
||||||
|
await ctx.resource.move({
|
||||||
|
sourceId: from[ctx.rowKey || 'id'],
|
||||||
|
targetId: to[ctx.rowKey || 'id'],
|
||||||
|
sortField: ctx.dragSort && ctx.dragSortBy,
|
||||||
|
});
|
||||||
|
ctx.service.refresh();
|
||||||
|
},
|
||||||
|
onChange({ current, pageSize }, filters, sorter) {
|
||||||
|
const sort = sorter.order ? (sorter.order === `ascend` ? [sorter.field] : [`-${sorter.field}`]) : globalSort;
|
||||||
|
ctx.service.run({ ...ctx.service.params?.[0], page: current, pageSize, sort });
|
||||||
|
},
|
||||||
|
onClickRow(record, setSelectedRow, selectedRow) {
|
||||||
|
const { targets, uid } = findFilterTargets(fieldSchema);
|
||||||
|
const dataBlocks = getDataBlocks();
|
||||||
|
|
||||||
|
// 如果是之前创建的区块是没有 x-filter-targets 属性的,所以这里需要判断一下避免报错
|
||||||
|
if (!targets || !targets.some((target) => dataBlocks.some((dataBlock) => dataBlock.uid === target.uid))) {
|
||||||
|
// 当用户已经点击过某一行,如果此时再把相连接的区块给删除的话,行的高亮状态就会一直保留。
|
||||||
|
// 这里暂时没有什么比较好的方法,只是在用户再次点击的时候,把高亮状态给清除掉。
|
||||||
|
setSelectedRow((prev) => (prev.length ? [] : prev));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = [record[ctx.rowKey]];
|
||||||
|
|
||||||
|
dataBlocks.forEach((block) => {
|
||||||
|
const target = targets.find((target) => target.uid === block.uid);
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
const param = block.service.params?.[0] || {};
|
||||||
|
// 保留原有的 filter
|
||||||
|
const storedFilter = block.service.params?.[1]?.filters || {};
|
||||||
|
|
||||||
|
if (selectedRow.includes(record[ctx.rowKey])) {
|
||||||
|
if (block.dataLoadingMode === 'manual') {
|
||||||
|
return block.clearData();
|
||||||
|
}
|
||||||
|
delete storedFilter[uid];
|
||||||
|
} else {
|
||||||
|
storedFilter[uid] = {
|
||||||
|
$and: [
|
||||||
|
{
|
||||||
|
[target.field || ctx.rowKey]: {
|
||||||
|
[target.field ? '$in' : '$eq']: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedFilter = mergeFilter([
|
||||||
|
...Object.values(storedFilter).map((filter) => removeNullCondition(filter)),
|
||||||
|
block.defaultFilter,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return block.doFilter(
|
||||||
|
{
|
||||||
|
...param,
|
||||||
|
page: 1,
|
||||||
|
filter: mergedFilter,
|
||||||
|
},
|
||||||
|
{ filters: storedFilter },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新表格的选中状态
|
||||||
|
setSelectedRow((prev) => (prev?.includes(record[ctx.rowKey]) ? [] : [...value]));
|
||||||
|
},
|
||||||
|
onExpand(expanded, record) {
|
||||||
|
ctx?.field.onExpandClick?.(expanded, record);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -5,3 +5,5 @@ export * from './TableColumnSchemaToolbar';
|
|||||||
export * from './tableBlockSettings';
|
export * from './tableBlockSettings';
|
||||||
export * from './tableColumnSettings';
|
export * from './tableColumnSettings';
|
||||||
export * from './TableColumnInitializers';
|
export * from './TableColumnInitializers';
|
||||||
|
export * from './createTableBlockUISchema';
|
||||||
|
export * from './hooks/useTableBlockDecoratorProps';
|
||||||
|
@ -359,7 +359,8 @@ export const tableBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'Table',
|
componentName: 'Table',
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
|
@ -22,7 +22,8 @@ export const filterCollapseBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
componentName: 'FilterCollapse',
|
componentName: 'FilterCollapse',
|
||||||
|
@ -23,7 +23,8 @@ export const filterFormBlockSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'FilterFormItem',
|
componentName: 'FilterFormItem',
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import { useField, useFieldSchema, useForm } from '@formily/react';
|
||||||
|
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||||
|
import { SchemaSettingsNumberFormat } from '../../../../schema-settings/SchemaSettingsNumberFormat';
|
||||||
|
import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator';
|
||||||
|
import { useIsFieldReadPretty } from '../../../../schema-component/antd/form-item/FormItem.Settings';
|
||||||
|
export const inputNumberComponentFieldSettings = new SchemaSettings({
|
||||||
|
name: 'fieldSettings:component:InputNumber',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'displayFormat',
|
||||||
|
Component: SchemaSettingsNumberFormat as any,
|
||||||
|
useComponentProps() {
|
||||||
|
const schema = useFieldSchema();
|
||||||
|
const { fieldSchema: tableColumnSchema } = useColumnSchema();
|
||||||
|
const fieldSchema = tableColumnSchema || schema;
|
||||||
|
return {
|
||||||
|
fieldSchema,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
useVisible() {
|
||||||
|
const isFieldReadPretty = useIsFieldReadPretty();
|
||||||
|
return isFieldReadPretty;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
@ -92,7 +92,7 @@ const LocalPlugins = () => {
|
|||||||
const keyWordsfilterList = useMemo(() => {
|
const keyWordsfilterList = useMemo(() => {
|
||||||
const list = keyWordlists.map((i) => {
|
const list = keyWordlists.map((i) => {
|
||||||
if (i === 'Others') {
|
if (i === 'Others') {
|
||||||
const result = data?.data.filter((v) => !v.keywords);
|
const result = data?.data.filter((v) => !v.keywords || !v.keywords.every((k) => keyWordlists.includes(k)));
|
||||||
return {
|
return {
|
||||||
key: i,
|
key: i,
|
||||||
list: result,
|
list: result,
|
||||||
|
@ -11,7 +11,11 @@ import { usePlugin } from '../../../application/hooks';
|
|||||||
import { SchemaSettingOptions, SchemaSettings } from '../../../application/schema-settings';
|
import { SchemaSettingOptions, SchemaSettings } from '../../../application/schema-settings';
|
||||||
import { useSchemaToolbar } from '../../../application/schema-toolbar';
|
import { useSchemaToolbar } from '../../../application/schema-toolbar';
|
||||||
import { useFormBlockContext } from '../../../block-provider';
|
import { useFormBlockContext } from '../../../block-provider';
|
||||||
import { useCollectionManager_deprecated, useCollection_deprecated } from '../../../collection-manager';
|
import {
|
||||||
|
joinCollectionName,
|
||||||
|
useCollectionManager_deprecated,
|
||||||
|
useCollection_deprecated,
|
||||||
|
} from '../../../collection-manager';
|
||||||
import { FlagProvider } from '../../../flag-provider';
|
import { FlagProvider } from '../../../flag-provider';
|
||||||
import { SaveMode } from '../../../modules/actions/submit/createSubmitActionSettings';
|
import { SaveMode } from '../../../modules/actions/submit/createSubmitActionSettings';
|
||||||
import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items';
|
import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items';
|
||||||
@ -28,6 +32,7 @@ import {
|
|||||||
import { DefaultValueProvider } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
import { DefaultValueProvider } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||||
import { useLinkageAction } from './hooks';
|
import { useLinkageAction } from './hooks';
|
||||||
import { requestSettingsSchema } from './utils';
|
import { requestSettingsSchema } from './utils';
|
||||||
|
import { DataSourceProvider, useDataSourceKey } from '../../../data-source';
|
||||||
|
|
||||||
const MenuGroup = (props) => {
|
const MenuGroup = (props) => {
|
||||||
return props.children;
|
return props.children;
|
||||||
@ -327,7 +332,8 @@ function WorkflowSelect({ actionType, direct = false, ...props }) {
|
|||||||
const { setValuesIn } = useForm();
|
const { setValuesIn } = useForm();
|
||||||
const baseCollection = useCollection_deprecated();
|
const baseCollection = useCollection_deprecated();
|
||||||
const { getCollection } = useCollectionManager_deprecated();
|
const { getCollection } = useCollectionManager_deprecated();
|
||||||
const [workflowCollection, setWorkflowCollection] = useState(baseCollection.name);
|
const dataSourceKey = useDataSourceKey();
|
||||||
|
const [workflowCollection, setWorkflowCollection] = useState(joinCollectionName(dataSourceKey, baseCollection.name));
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
|
|
||||||
const workflowPlugin = usePlugin('workflow') as any;
|
const workflowPlugin = usePlugin('workflow') as any;
|
||||||
@ -351,11 +357,11 @@ function WorkflowSelect({ actionType, direct = false, ...props }) {
|
|||||||
const path = paths[i];
|
const path = paths[i];
|
||||||
const associationField = collection.fields.find((f) => f.name === path);
|
const associationField = collection.fields.find((f) => f.name === path);
|
||||||
if (associationField) {
|
if (associationField) {
|
||||||
collection = getCollection(associationField.target);
|
collection = getCollection(associationField.target, dataSourceKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setWorkflowCollection(collection.name);
|
setWorkflowCollection(joinCollectionName(dataSourceKey, collection.name));
|
||||||
setValuesIn(`group[${index}].workflowKey`, null);
|
setValuesIn(`group[${index}].workflowKey`, null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -375,6 +381,7 @@ function WorkflowSelect({ actionType, direct = false, ...props }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<DataSourceProvider dataSource="main">
|
||||||
<RemoteSelect
|
<RemoteSelect
|
||||||
manual={false}
|
manual={false}
|
||||||
placeholder={t('Select workflow', { ns: 'workflow' })}
|
placeholder={t('Select workflow', { ns: 'workflow' })}
|
||||||
@ -407,6 +414,7 @@ function WorkflowSelect({ actionType, direct = false, ...props }) {
|
|||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
</DataSourceProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,7 +422,7 @@ export function WorkflowConfig() {
|
|||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { name: collection } = useCollection_deprecated();
|
const collection = useCollection_deprecated();
|
||||||
// TODO(refactor): should refactor for getting certain action type, better from 'x-action'.
|
// TODO(refactor): should refactor for getting certain action type, better from 'x-action'.
|
||||||
const formBlock = useFormBlockContext();
|
const formBlock = useFormBlockContext();
|
||||||
const actionType = formBlock?.type || fieldSchema['x-action'];
|
const actionType = formBlock?.type || fieldSchema['x-action'];
|
||||||
@ -483,7 +491,9 @@ export function WorkflowConfig() {
|
|||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
placeholder: t('Select context', { ns: 'workflow' }),
|
placeholder: t('Select context', { ns: 'workflow' }),
|
||||||
popupMatchSelectWidth: false,
|
popupMatchSelectWidth: false,
|
||||||
collection,
|
collection: `${
|
||||||
|
collection.dataSource && collection.dataSource !== 'main' ? `${collection.dataSource}:` : ''
|
||||||
|
}${collection.name}`,
|
||||||
filter: '{{ fieldFilter }}',
|
filter: '{{ fieldFilter }}',
|
||||||
rootOption: {
|
rootOption: {
|
||||||
label: t('Full form data', { ns: 'workflow' }),
|
label: t('Full form data', { ns: 'workflow' }),
|
||||||
|
@ -3,7 +3,12 @@ import { Tag, TreeSelect } from 'antd';
|
|||||||
import type { DefaultOptionType, TreeSelectProps } from 'rc-tree-select/es/TreeSelect';
|
import type { DefaultOptionType, TreeSelectProps } from 'rc-tree-select/es/TreeSelect';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { CollectionFieldOptions_deprecated, useCollectionManager_deprecated, useCompile } from '../../..';
|
import {
|
||||||
|
CollectionFieldOptions_deprecated,
|
||||||
|
parseCollectionName,
|
||||||
|
useCollectionManager_deprecated,
|
||||||
|
useCompile,
|
||||||
|
} from '../../..';
|
||||||
|
|
||||||
export type AppendsTreeSelectProps = {
|
export type AppendsTreeSelectProps = {
|
||||||
value: string[] | string;
|
value: string[] | string;
|
||||||
@ -27,7 +32,7 @@ function usePropsCollection({ collection }) {
|
|||||||
|
|
||||||
type CallScope = {
|
type CallScope = {
|
||||||
compile?(value: string): string;
|
compile?(value: string): string;
|
||||||
getCollectionFields?(name: any): CollectionFieldOptions_deprecated[];
|
getCollectionFields?(name: any, dataSource?: string): CollectionFieldOptions_deprecated[];
|
||||||
filter(field): boolean;
|
filter(field): boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,7 +57,8 @@ function trueFilter(field) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCollectionFieldOptions(this: CallScope, collection, parentNode?): TreeOptionType[] {
|
function getCollectionFieldOptions(this: CallScope, collection, parentNode?): TreeOptionType[] {
|
||||||
const fields = this.getCollectionFields(collection).filter(isAssociation);
|
const [dataSourceName, collectionName] = parseCollectionName(collection);
|
||||||
|
const fields = this.getCollectionFields(collectionName, dataSourceName).filter(isAssociation);
|
||||||
const boundLoadChildren = loadChildren.bind(this);
|
const boundLoadChildren = loadChildren.bind(this);
|
||||||
return fields.filter(this.filter).map((field) => {
|
return fields.filter(this.filter).map((field) => {
|
||||||
const key = parentNode ? `${parentNode.value ? `${parentNode.value}.` : ''}${field.name}` : field.name;
|
const key = parentNode ? `${parentNode.value ? `${parentNode.value}.` : ''}${field.name}` : field.name;
|
||||||
@ -84,11 +90,12 @@ export const AppendsTreeSelect: React.FC<TreeSelectProps & AppendsTreeSelectProp
|
|||||||
loadData: propsLoadData,
|
loadData: propsLoadData,
|
||||||
...restProps
|
...restProps
|
||||||
} = props;
|
} = props;
|
||||||
const { getCollectionFields } = useCollectionManager_deprecated();
|
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [optionsMap, setOptionsMap] = useState({});
|
const [optionsMap, setOptionsMap] = useState({});
|
||||||
const baseCollection = useCollection({ collection });
|
const collectionString = useCollection({ collection });
|
||||||
|
const [dataSourceName, collectionName] = parseCollectionName(collectionString);
|
||||||
|
const { getCollectionFields } = useCollectionManager_deprecated(dataSourceName);
|
||||||
const treeData = Object.values(optionsMap);
|
const treeData = Object.values(optionsMap);
|
||||||
const value: string | DefaultOptionType[] = useMemo(() => {
|
const value: string | DefaultOptionType[] = useMemo(() => {
|
||||||
if (props.multiple) {
|
if (props.multiple) {
|
||||||
@ -111,7 +118,7 @@ export const AppendsTreeSelect: React.FC<TreeSelectProps & AppendsTreeSelectProp
|
|||||||
},
|
},
|
||||||
[propsLoadData],
|
[propsLoadData],
|
||||||
);
|
);
|
||||||
|
// NOTE:
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const parentNode = rootOption
|
const parentNode = rootOption
|
||||||
? {
|
? {
|
||||||
@ -123,17 +130,19 @@ export const AppendsTreeSelect: React.FC<TreeSelectProps & AppendsTreeSelectProp
|
|||||||
isLeaf: false,
|
isLeaf: false,
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
const treeData =
|
const tData =
|
||||||
propsLoadData === null
|
propsLoadData === null
|
||||||
? []
|
? []
|
||||||
: getCollectionFieldOptions.call({ compile, getCollectionFields, filter }, baseCollection, parentNode);
|
: getCollectionFieldOptions.call({ compile, getCollectionFields, filter }, collectionString, parentNode);
|
||||||
const map = treeData.reduce((result, item) => Object.assign(result, { [item.value]: item }), {});
|
|
||||||
|
const map = tData.reduce((result, item) => Object.assign(result, { [item.value]: item }), {});
|
||||||
if (parentNode) {
|
if (parentNode) {
|
||||||
map[parentNode.value] = parentNode;
|
map[parentNode.value] = parentNode;
|
||||||
}
|
}
|
||||||
setOptionsMap(map);
|
setOptionsMap(map);
|
||||||
}, [collection, baseCollection, rootOption, filter, propsLoadData]);
|
}, [collectionString, rootOption, filter, propsLoadData]);
|
||||||
|
|
||||||
|
// NOTE: preload options in value
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const arr = (props.multiple ? propsValue : propsValue ? [propsValue] : []) as string[];
|
const arr = (props.multiple ? propsValue : propsValue ? [propsValue] : []) as string[];
|
||||||
if (!arr?.length || arr.every((v) => Boolean(optionsMap[v]))) {
|
if (!arr?.length || arr.every((v) => Boolean(optionsMap[v]))) {
|
||||||
|
@ -18,7 +18,8 @@ export const AssociationFilterBlockDesigner = () => {
|
|||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import React from 'react';
|
import React, { forwardRef, createRef } from 'react';
|
||||||
|
import { composeRef } from 'rc-util/lib/ref';
|
||||||
import { useCompile } from '../../hooks';
|
import { useCompile } from '../../hooks';
|
||||||
import { useTableBlockContext, useTableSelectorContext } from '../../../block-provider';
|
import { useTableBlockContext, useTableSelectorContext } from '../../../block-provider';
|
||||||
import { Icon } from '../../../icon';
|
import { Icon } from '../../../icon';
|
||||||
@ -46,7 +47,7 @@ const actionDesignerCss = css`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ExpandAction = (props) => {
|
const InternalExpandAction = (props, ref) => {
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
const ctxSelector = useTableSelectorContext();
|
const ctxSelector = useTableSelectorContext();
|
||||||
const ctxBlock = useTableBlockContext();
|
const ctxBlock = useTableBlockContext();
|
||||||
@ -54,8 +55,11 @@ export const ExpandAction = (props) => {
|
|||||||
const ctx = isTableSelector ? ctxSelector : ctxBlock;
|
const ctx = isTableSelector ? ctxSelector : ctxBlock;
|
||||||
const { titleExpand, titleCollapse, iconExpand, iconCollapse } = schema['x-component-props'] || {};
|
const { titleExpand, titleCollapse, iconExpand, iconCollapse } = schema['x-component-props'] || {};
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
|
const internalRef = createRef<HTMLButtonElement | HTMLAnchorElement>();
|
||||||
|
const buttonRef = composeRef(ref, internalRef);
|
||||||
return (
|
return (
|
||||||
<div className={actionDesignerCss}>
|
//@ts-ignore
|
||||||
|
<div className={actionDesignerCss} ref={buttonRef as React.Ref<HTMLButtonElement>}>
|
||||||
{ctx?.params['tree'] && (
|
{ctx?.params['tree'] && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -63,6 +67,7 @@ export const ExpandAction = (props) => {
|
|||||||
}}
|
}}
|
||||||
icon={<Icon type={ctx?.expandFlag ? iconCollapse : iconExpand} />}
|
icon={<Icon type={ctx?.expandFlag ? iconCollapse : iconExpand} />}
|
||||||
type={props.type}
|
type={props.type}
|
||||||
|
style={props?.style}
|
||||||
>
|
>
|
||||||
{props.children?.[1]}
|
{props.children?.[1]}
|
||||||
<span style={{ marginLeft: 10 }}>{ctx?.expandFlag ? compile(titleCollapse) : compile(titleExpand)}</span>
|
<span style={{ marginLeft: 10 }}>{ctx?.expandFlag ? compile(titleCollapse) : compile(titleExpand)}</span>
|
||||||
@ -71,3 +76,5 @@ export const ExpandAction = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ExpandAction = forwardRef<HTMLButtonElement | HTMLAnchorElement, any>(InternalExpandAction);
|
||||||
|
@ -62,7 +62,8 @@ export const formSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'FormItem',
|
componentName: 'FormItem',
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
@ -103,7 +104,8 @@ export const readPrettyFormSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
insertAdjacentPosition: 'beforeEnd',
|
insertAdjacentPosition: 'beforeEnd',
|
||||||
componentName: 'ReadPrettyFormItem',
|
componentName: 'ReadPrettyFormItem',
|
||||||
@ -294,7 +296,8 @@ export const formDetailsSettings = new SchemaSettings({
|
|||||||
useComponentProps() {
|
useComponentProps() {
|
||||||
const { name } = useCollection_deprecated();
|
const { name } = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
return {
|
return {
|
||||||
componentName: 'Details',
|
componentName: 'Details',
|
||||||
collectionName: name,
|
collectionName: name,
|
||||||
|
@ -39,7 +39,8 @@ export const GridCardDesigner = () => {
|
|||||||
const sortFields = useSortFields(name);
|
const sortFields = useSortFields(name);
|
||||||
const record = useRecord();
|
const record = useRecord();
|
||||||
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
const columnCount = field.decoratorProps.columnCount || defaultColumnCount;
|
const columnCount = field.decoratorProps.columnCount || defaultColumnCount;
|
||||||
|
|
||||||
const columnCountSchema = useMemo(() => {
|
const columnCountSchema = useMemo(() => {
|
||||||
|
@ -2,21 +2,125 @@ import { isValid } from '@formily/shared';
|
|||||||
import { toFixedByStep } from '@nocobase/utils/client';
|
import { toFixedByStep } from '@nocobase/utils/client';
|
||||||
import type { InputProps } from 'antd/es/input';
|
import type { InputProps } from 'antd/es/input';
|
||||||
import type { InputNumberProps } from 'antd/es/input-number';
|
import type { InputNumberProps } from 'antd/es/input-number';
|
||||||
import React from 'react';
|
import { format } from 'd3-format';
|
||||||
|
import * as math from 'mathjs';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
function countDecimalPlaces(value) {
|
||||||
|
const number = Number(value);
|
||||||
|
if (!Number.isFinite(number)) return 0;
|
||||||
|
|
||||||
|
const decimalPart = String(number).split('.')[1];
|
||||||
|
return decimalPart ? decimalPart.length : 0;
|
||||||
|
}
|
||||||
|
const separators = {
|
||||||
|
'0,0.00': { thousands: ',', decimal: '.' },
|
||||||
|
'0.0,00': { thousands: '.', decimal: ',' },
|
||||||
|
'0 0,00': { thousands: ' ', decimal: '.' },
|
||||||
|
'0.00': { thousands: '', decimal: '.' }, // 没有千位分隔符
|
||||||
|
};
|
||||||
|
//分隔符换算
|
||||||
|
export function formatNumberWithSeparator(number, format = '0.00', step = 1) {
|
||||||
|
let formattedNumber = '';
|
||||||
|
|
||||||
|
if (separators[format]) {
|
||||||
|
const { thousands, decimal } = separators[format];
|
||||||
|
formattedNumber = number
|
||||||
|
.toLocaleString('en-US', {
|
||||||
|
style: 'decimal',
|
||||||
|
minimumFractionDigits: step,
|
||||||
|
maximumFractionDigits: step,
|
||||||
|
})
|
||||||
|
.replace(/,/g, 'comma_placeholder')
|
||||||
|
.replace(/\./g, 'dot_placeholder')
|
||||||
|
.replace(/comma_placeholder/g, thousands)
|
||||||
|
.replace(/dot_placeholder/g, decimal);
|
||||||
|
} else {
|
||||||
|
formattedNumber = number.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
//单位换算
|
||||||
|
export function formatUnitConversion(value, operator = '*', multiplier) {
|
||||||
|
if (!multiplier) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
let result;
|
||||||
|
|
||||||
|
if (operator === '*') {
|
||||||
|
result = value * multiplier;
|
||||||
|
} else if (operator === '/') {
|
||||||
|
if (multiplier !== 0) {
|
||||||
|
result = value / multiplier;
|
||||||
|
} else {
|
||||||
|
console.error('Error: Division by zero.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("Error: Invalid operator. Use '*' for multiplication or '/' for division.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return math.round(result, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
//科学计数法显示
|
||||||
|
export function scientificNotation(number, decimalPlaces, separator = '.') {
|
||||||
|
const formatter = format(`.${decimalPlaces}e`);
|
||||||
|
const formattedNumber = formatter(number).replace('.', separator);
|
||||||
|
|
||||||
|
// 匹配科学计数法中的指数部分,判断正负情况
|
||||||
|
const result = formattedNumber.replace(/e([+-]?\d+)/, (match, exponent) => {
|
||||||
|
if (exponent.startsWith('+')) {
|
||||||
|
// 正数指数,不显示符号
|
||||||
|
return `×10<sup>${exponent.slice(1)}</sup>`;
|
||||||
|
} else {
|
||||||
|
// 负数指数,显示 "-" 符号
|
||||||
|
return `×10<sup>-${exponent.slice(1)}</sup>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatNumber(props) {
|
||||||
|
const { step, formatStyle, value, unitConversion, unitConversionType, separator = '0.00' } = props;
|
||||||
|
|
||||||
|
if (!isValid(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//单位换算
|
||||||
|
const unitData = formatUnitConversion(value, unitConversionType, unitConversion);
|
||||||
|
//精度换算
|
||||||
|
const preciationData = toFixedByStep(unitData, step);
|
||||||
|
let result;
|
||||||
|
//分隔符换算
|
||||||
|
result = formatNumberWithSeparator(Number(preciationData), separator, countDecimalPlaces(step));
|
||||||
|
if (formatStyle === 'scientifix') {
|
||||||
|
//科学计数显示
|
||||||
|
result = scientificNotation(Number(unitData), countDecimalPlaces(step), separators?.[separator]?.['decimal']);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export const ReadPretty: React.FC<InputProps & InputNumberProps> = (props: any) => {
|
export const ReadPretty: React.FC<InputProps & InputNumberProps> = (props: any) => {
|
||||||
const { step, value, addonBefore, addonAfter } = props;
|
const { step, formatStyle, value, addonBefore, addonAfter, unitConversion, unitConversionType, separator } = props;
|
||||||
if (!isValid(props.value)) {
|
|
||||||
return null;
|
const result = useMemo(() => {
|
||||||
}
|
return formatNumber({ step, formatStyle, value, unitConversion, unitConversionType, separator });
|
||||||
const result = toFixedByStep(value, step);
|
}, [step, formatStyle, value, unitConversion, unitConversionType, separator]);
|
||||||
if (isNaN(result)) {
|
|
||||||
|
if (!isValid(result)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'nb-read-pretty-input-number'}>
|
<div className={'nb-read-pretty-input-number'}>
|
||||||
{addonBefore}
|
{addonBefore}
|
||||||
{result}
|
<span dangerouslySetInnerHTML={{ __html: result }} />
|
||||||
{addonAfter}
|
{addonAfter}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { fireEvent, render, screen } from '@nocobase/test/client';
|
import { fireEvent, render, screen } from '@nocobase/test/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { formatNumberWithSeparator, formatUnitConversion, scientificNotation } from '../ReadPretty';
|
||||||
import App2 from '../demos/addonBefore&addonAfter';
|
import App2 from '../demos/addonBefore&addonAfter';
|
||||||
import App3 from '../demos/highPrecisionDecimals';
|
import App3 from '../demos/highPrecisionDecimals';
|
||||||
import App1 from '../demos/inputNumber';
|
import App1 from '../demos/inputNumber';
|
||||||
@ -43,7 +44,7 @@ describe('InputNumber: addonBefore/addonAfter', () => {
|
|||||||
fireEvent.change(input, { target: { value: 1 } });
|
fireEvent.change(input, { target: { value: 1 } });
|
||||||
expect(input.value).toBe('1');
|
expect(input.value).toBe('1');
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
expect(screen.getByText('¥1万元')).toBeInTheDocument();
|
expect(screen.getByText('¥')).toBeInTheDocument();
|
||||||
|
|
||||||
// empty value
|
// empty value
|
||||||
fireEvent.change(input, { target: { value: '' } });
|
fireEvent.change(input, { target: { value: '' } });
|
||||||
@ -67,7 +68,7 @@ describe('InputNumber: High precision decimals', () => {
|
|||||||
fireEvent.change(input, { target: { value: 1 } });
|
fireEvent.change(input, { target: { value: 1 } });
|
||||||
expect(input.value).toBe('1.00');
|
expect(input.value).toBe('1.00');
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
expect(screen.getByText('1.00%')).toBeInTheDocument();
|
expect(screen.getByText('1.00')).toBeInTheDocument();
|
||||||
|
|
||||||
// empty value
|
// empty value
|
||||||
fireEvent.change(input, { target: { value: '' } });
|
fireEvent.change(input, { target: { value: '' } });
|
||||||
@ -75,3 +76,48 @@ describe('InputNumber: High precision decimals', () => {
|
|||||||
expect(screen.queryByText('NaN')).toBeNull();
|
expect(screen.queryByText('NaN')).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ReadPretty:formatNumberWithSeparator', () => {
|
||||||
|
// Test case 1: Format a number with default format '0,0.00'
|
||||||
|
test('Format number with default separator', () => {
|
||||||
|
const formatted = formatNumberWithSeparator(1234567.89);
|
||||||
|
expect(formatted).toBe('1234567.9');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test case 2: Format a number with custom format '0.00'
|
||||||
|
test('Format number with custom separator', () => {
|
||||||
|
const formatted = formatNumberWithSeparator(1234567.89, '0,0.00', 1);
|
||||||
|
expect(formatted).toBe('1,234,567.9');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('ReadPretty:formatUnitConversion', () => {
|
||||||
|
// Test case 1: Multiply a value by 2
|
||||||
|
test('Multiply value by 2', () => {
|
||||||
|
const result = formatUnitConversion(10, '*', 2);
|
||||||
|
expect(result).toBe(20);
|
||||||
|
});
|
||||||
|
// Test case 2: Divide a value by 0 (error case)
|
||||||
|
test('Divide value by zero', () => {
|
||||||
|
const result = formatUnitConversion(10, '/', 0);
|
||||||
|
expect(result).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('0.1*0.2', () => {
|
||||||
|
const result = formatUnitConversion(0.1, '*', 0.2);
|
||||||
|
expect(result).toBe(0.02);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ReadPretty:scientificNotation', () => {
|
||||||
|
// Test case 1: Format a number into scientific notation with 2 decimal places
|
||||||
|
test('Format number into scientific notation', () => {
|
||||||
|
const formatted = scientificNotation(1234567.89, 2);
|
||||||
|
expect(formatted).toBe('1.23×10<sup>6</sup>');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test case 2: Format a number into scientific notation with custom separator '.'
|
||||||
|
test('Format number into scientific notation with custom separator', () => {
|
||||||
|
const formatted = scientificNotation(1234567.89, 2, '.');
|
||||||
|
expect(formatted).toBe('1.23×10<sup>6</sup>');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -35,7 +35,8 @@ export const ListDesigner = () => {
|
|||||||
const sortFields = useSortFields(name);
|
const sortFields = useSortFields(name);
|
||||||
const record = useRecord();
|
const record = useRecord();
|
||||||
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
const sort = defaultSort?.map((item: string) => {
|
const sort = defaultSort?.map((item: string) => {
|
||||||
return item.startsWith('-')
|
return item.startsWith('-')
|
||||||
? {
|
? {
|
||||||
|
@ -25,6 +25,7 @@ import {
|
|||||||
useTableBlockContext,
|
useTableBlockContext,
|
||||||
useTableSelectorContext,
|
useTableSelectorContext,
|
||||||
} from '../../../';
|
} from '../../../';
|
||||||
|
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
|
||||||
import { useACLFieldWhitelist } from '../../../acl/ACLProvider';
|
import { useACLFieldWhitelist } from '../../../acl/ACLProvider';
|
||||||
import { useToken } from '../__builtins__';
|
import { useToken } from '../__builtins__';
|
||||||
import { SubFormProvider } from '../association-field/hooks';
|
import { SubFormProvider } from '../association-field/hooks';
|
||||||
@ -242,7 +243,8 @@ const usePaginationProps = (pagination1, pagination2) => {
|
|||||||
return result.total <= result.pageSize ? false : result;
|
return result.total <= result.pageSize ? false : result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Table: any = observer(
|
export const Table: any = withDynamicSchemaProps(
|
||||||
|
observer(
|
||||||
(props: {
|
(props: {
|
||||||
useProps?: () => any;
|
useProps?: () => any;
|
||||||
onChange?: (pagination, filters, sorter, extra) => void;
|
onChange?: (pagination, filters, sorter, extra) => void;
|
||||||
@ -259,8 +261,11 @@ export const Table: any = observer(
|
|||||||
isSubTable?: boolean;
|
isSubTable?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
const { pagination: pagination1, useProps, onChange, ...others1 } = props;
|
const { pagination: pagination1, useProps, ...others1 } = props;
|
||||||
const { pagination: pagination2, onClickRow, ...others2 } = useProps?.() || {};
|
|
||||||
|
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||||
|
const { pagination: pagination2, ...others2 } = useProps?.() || {};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dragSort = false,
|
dragSort = false,
|
||||||
showIndex = true,
|
showIndex = true,
|
||||||
@ -270,6 +275,7 @@ export const Table: any = observer(
|
|||||||
rowKey,
|
rowKey,
|
||||||
required,
|
required,
|
||||||
onExpand,
|
onExpand,
|
||||||
|
onClickRow,
|
||||||
...others
|
...others
|
||||||
} = { ...others1, ...others2 } as any;
|
} = { ...others1, ...others2 } as any;
|
||||||
const field = useArrayField(others);
|
const field = useArrayField(others);
|
||||||
@ -566,6 +572,10 @@ export const Table: any = observer(
|
|||||||
className={css`
|
className={css`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
.ant-table-thead {
|
||||||
|
.ant-table-cell {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.ant-table-wrapper {
|
.ant-table-wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
.ant-spin-nested-loading {
|
.ant-spin-nested-loading {
|
||||||
@ -574,11 +584,6 @@ export const Table: any = observer(
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.ant-table-thead {
|
|
||||||
.ant-table-cell {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -627,5 +632,6 @@ export const Table: any = observer(
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
{ displayName: 'Table' },
|
{ displayName: 'Table' },
|
||||||
);
|
);
|
||||||
|
@ -78,7 +78,8 @@ export const TableBlockDesigner = () => {
|
|||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
|
|
||||||
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource =
|
||||||
|
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
|
||||||
const supportTemplate = !fieldSchema?.['x-decorator-props']?.disableTemplate;
|
const supportTemplate = !fieldSchema?.['x-decorator-props']?.disableTemplate;
|
||||||
const sort = defaultSort?.map((item: string) => {
|
const sort = defaultSort?.map((item: string) => {
|
||||||
return item?.startsWith('-')
|
return item?.startsWith('-')
|
||||||
|
@ -2,7 +2,8 @@ import { DownOutlined } from '@ant-design/icons';
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
|
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
|
||||||
import { Button, Dropdown, MenuProps } from 'antd';
|
import { Button, Dropdown, MenuProps } from 'antd';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState, forwardRef, createRef } from 'react';
|
||||||
|
import { composeRef } from 'rc-util/lib/ref';
|
||||||
import { useDesignable } from '../../';
|
import { useDesignable } from '../../';
|
||||||
import { useACLRolesCheck, useRecordPkValue } from '../../acl/ACLProvider';
|
import { useACLRolesCheck, useRecordPkValue } from '../../acl/ACLProvider';
|
||||||
import {
|
import {
|
||||||
@ -96,8 +97,7 @@ function useAclCheckFn() {
|
|||||||
return actionAclCheck;
|
return actionAclCheck;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreateRecordAction = observer(
|
const InternalCreateRecordAction = (props: any, ref) => {
|
||||||
(props: any) => {
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const collection = useCollection_deprecated();
|
const collection = useCollection_deprecated();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
@ -125,8 +125,11 @@ export const CreateRecordAction = observer(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [field, linkageRules, localVariables, variables]);
|
}, [field, linkageRules, localVariables, variables]);
|
||||||
|
const internalRef = createRef<HTMLButtonElement | HTMLAnchorElement>();
|
||||||
|
const buttonRef = composeRef(ref, internalRef);
|
||||||
return (
|
return (
|
||||||
<div className={actionDesignerCss}>
|
//@ts-ignore
|
||||||
|
<div className={actionDesignerCss} ref={buttonRef as React.Ref<HTMLButtonElement>}>
|
||||||
<ActionContextProvider value={{ ...ctx, visible, setVisible }}>
|
<ActionContextProvider value={{ ...ctx, visible, setVisible }}>
|
||||||
<CreateAction
|
<CreateAction
|
||||||
{...props}
|
{...props}
|
||||||
@ -142,9 +145,7 @@ export const CreateRecordAction = observer(
|
|||||||
</ActionContextProvider>
|
</ActionContextProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
{ displayName: 'CreateRecordAction' },
|
|
||||||
);
|
|
||||||
|
|
||||||
function getLinkageCollection(str, form, field) {
|
function getLinkageCollection(str, form, field) {
|
||||||
const variablesCtx = { $form: form.values, $iteration: form.values };
|
const variablesCtx = { $form: form.values, $iteration: form.values };
|
||||||
@ -283,6 +284,7 @@ function FinallyButton({
|
|||||||
designable: boolean;
|
designable: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { getCollection } = useCollectionManager_deprecated();
|
const { getCollection } = useCollectionManager_deprecated();
|
||||||
|
|
||||||
if (inheritsCollections?.length > 0) {
|
if (inheritsCollections?.length > 0) {
|
||||||
if (!linkageFromForm) {
|
if (!linkageFromForm) {
|
||||||
return allowAddToCurrent === undefined || allowAddToCurrent ? (
|
return allowAddToCurrent === undefined || allowAddToCurrent ? (
|
||||||
@ -360,6 +362,7 @@ function FinallyButton({
|
|||||||
onClick?.(collection);
|
onClick?.(collection);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
|
...props?.style,
|
||||||
display: !designable && field?.data?.hidden && 'none',
|
display: !designable && field?.data?.hidden && 'none',
|
||||||
opacity: designable && field?.data?.hidden && 0.1,
|
opacity: designable && field?.data?.hidden && 0.1,
|
||||||
}}
|
}}
|
||||||
@ -368,3 +371,5 @@ function FinallyButton({
|
|||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CreateRecordAction = forwardRef<HTMLButtonElement | HTMLAnchorElement, any>(InternalCreateRecordAction);
|
||||||
|
@ -10,6 +10,7 @@ export const CreateFilterActionInitializer = (props) => {
|
|||||||
'x-designer': 'Action.Designer',
|
'x-designer': 'Action.Designer',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
|
htmlType: 'submit',
|
||||||
useProps: '{{ useFilterBlockActionProps }}',
|
useProps: '{{ useFilterBlockActionProps }}',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -3,8 +3,9 @@ import { TableOutlined } from '@ant-design/icons';
|
|||||||
|
|
||||||
import { useCollectionManager_deprecated } from '../../collection-manager';
|
import { useCollectionManager_deprecated } from '../../collection-manager';
|
||||||
import { useSchemaTemplateManager } from '../../schema-templates';
|
import { useSchemaTemplateManager } from '../../schema-templates';
|
||||||
import { createTableBlockSchema, useRecordCollectionDataSourceItems } from '../utils';
|
import { useRecordCollectionDataSourceItems } from '../utils';
|
||||||
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
|
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
|
||||||
|
import { createTableBlockUISchema } from '../../modules/blocks/data-blocks/table/createTableBlockUISchema';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
@ -17,7 +18,7 @@ export const RecordAssociationBlockInitializer = () => {
|
|||||||
const { getCollection } = useCollectionManager_deprecated();
|
const { getCollection } = useCollectionManager_deprecated();
|
||||||
const field = itemConfig.field;
|
const field = itemConfig.field;
|
||||||
const collection = getCollection(field.target);
|
const collection = getCollection(field.target);
|
||||||
const resource = `${field.collectionName}.${field.name}`;
|
const association = `${field.collectionName}.${field.name}`;
|
||||||
return (
|
return (
|
||||||
<SchemaInitializerItem
|
<SchemaInitializerItem
|
||||||
icon={<TableOutlined />}
|
icon={<TableOutlined />}
|
||||||
@ -28,17 +29,15 @@ export const RecordAssociationBlockInitializer = () => {
|
|||||||
insert(s);
|
insert(s);
|
||||||
} else {
|
} else {
|
||||||
insert(
|
insert(
|
||||||
createTableBlockSchema({
|
createTableBlockUISchema({
|
||||||
rowKey: collection.filterTargetKey,
|
rowKey: collection.filterTargetKey,
|
||||||
collection: field.target,
|
|
||||||
dataSource: collection.dataSource,
|
dataSource: collection.dataSource,
|
||||||
resource,
|
association: association,
|
||||||
association: resource,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
items={useRecordCollectionDataSourceItems('Table', itemConfig, field.target, resource)}
|
items={useRecordCollectionDataSourceItems('Table', itemConfig, field.target, association)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -53,9 +52,8 @@ export function useCreateAssociationTableBlock() {
|
|||||||
const collection = getCollection(field.target);
|
const collection = getCollection(field.target);
|
||||||
|
|
||||||
insert(
|
insert(
|
||||||
createTableBlockSchema({
|
createTableBlockUISchema({
|
||||||
rowKey: collection.filterTargetKey,
|
rowKey: collection.filterTargetKey,
|
||||||
collection: field.target,
|
|
||||||
dataSource: collection.dataSource,
|
dataSource: collection.dataSource,
|
||||||
association: `${field.collectionName}.${field.name}`,
|
association: `${field.collectionName}.${field.name}`,
|
||||||
}),
|
}),
|
||||||
|
@ -1181,8 +1181,6 @@ export const createFormBlockSchema = (options) => {
|
|||||||
resource: resourceName,
|
resource: resourceName,
|
||||||
collection,
|
collection,
|
||||||
association,
|
association,
|
||||||
// action: 'get',
|
|
||||||
// useParams: '{{ useParamsFromRecord }}',
|
|
||||||
},
|
},
|
||||||
'x-toolbar': 'BlockSchemaToolbar',
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
...(settings ? { 'x-settings': settings } : { 'x-designer': designer }),
|
...(settings ? { 'x-settings': settings } : { 'x-designer': designer }),
|
||||||
@ -1357,6 +1355,12 @@ export const createReadPrettyFormBlockSchema = (options) => {
|
|||||||
return schema;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* 已弃用,可以使用 createTableBlockUISchema 替换
|
||||||
|
* @param options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export const createTableBlockSchema = (options) => {
|
export const createTableBlockSchema = (options) => {
|
||||||
const {
|
const {
|
||||||
collection,
|
collection,
|
||||||
@ -1688,6 +1692,15 @@ function useAssociationFields({
|
|||||||
const targetCollection = cm.getCollection(field.target);
|
const targetCollection = cm.getCollection(field.target);
|
||||||
const title = `${compile(field.uiSchema.title || field.name)} -> ${compile(targetCollection.title)}`;
|
const title = `${compile(field.uiSchema.title || field.name)} -> ${compile(targetCollection.title)}`;
|
||||||
const templates = getTemplatesByCollection(dataSource, field.target).filter((template) => {
|
const templates = getTemplatesByCollection(dataSource, field.target).filter((template) => {
|
||||||
|
// 针对弹窗中的详情区块
|
||||||
|
if (componentName === 'ReadPrettyFormItem') {
|
||||||
|
if (['hasOne', 'belongsTo'].includes(field.type)) {
|
||||||
|
return template.componentName === 'ReadPrettyFormItem';
|
||||||
|
} else {
|
||||||
|
return template.componentName === 'Details';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
componentName &&
|
componentName &&
|
||||||
template.componentName === componentName &&
|
template.componentName === componentName &&
|
||||||
|
@ -56,13 +56,22 @@ export interface GeneralSchemaDesignerProps {
|
|||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
draggable?: boolean;
|
draggable?: boolean;
|
||||||
|
showDataSource?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated use `SchemaToolbar` instead
|
* @deprecated use `SchemaToolbar` instead
|
||||||
*/
|
*/
|
||||||
export const GeneralSchemaDesigner: FC<GeneralSchemaDesignerProps> = (props: any) => {
|
export const GeneralSchemaDesigner: FC<GeneralSchemaDesignerProps> = (props: any) => {
|
||||||
const { disableInitializer, title, template, schemaSettings, contextValue, draggable = true } = props;
|
const {
|
||||||
|
disableInitializer,
|
||||||
|
title,
|
||||||
|
template,
|
||||||
|
schemaSettings,
|
||||||
|
contextValue,
|
||||||
|
draggable = true,
|
||||||
|
showDataSource = true,
|
||||||
|
} = props;
|
||||||
const { dn, designable } = useDesignable();
|
const { dn, designable } = useDesignable();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -112,7 +121,9 @@ export const GeneralSchemaDesigner: FC<GeneralSchemaDesignerProps> = (props: any
|
|||||||
<div className={classNames('general-schema-designer-title', titleCss)}>
|
<div className={classNames('general-schema-designer-title', titleCss)}>
|
||||||
<Space size={2}>
|
<Space size={2}>
|
||||||
<span className={'title-tag'}>
|
<span className={'title-tag'}>
|
||||||
{dataSource ? `${compile(dataSource?.displayName)} > ${compile(title)}` : compile(title)}
|
{showDataSource && dataSource
|
||||||
|
? `${compile(dataSource?.displayName)} > ${compile(title)}`
|
||||||
|
: compile(title)}
|
||||||
</span>
|
</span>
|
||||||
{template && (
|
{template && (
|
||||||
<span className={'title-tag'}>
|
<span className={'title-tag'}>
|
||||||
|
@ -293,7 +293,7 @@ export const SchemaSettingsTemplate = function Template(props) {
|
|||||||
|
|
||||||
const findGridSchema = (fieldSchema) => {
|
const findGridSchema = (fieldSchema) => {
|
||||||
return fieldSchema.reduceProperties((buf, s) => {
|
return fieldSchema.reduceProperties((buf, s) => {
|
||||||
if (s['x-component'] === 'FormV2') {
|
if (s['x-component'] === 'FormV2' || s['x-component'] === 'Details') {
|
||||||
const f = s.reduceProperties((buf, s) => {
|
const f = s.reduceProperties((buf, s) => {
|
||||||
if (s['x-component'] === 'Grid' || s['x-component'] === 'BlockTemplate') {
|
if (s['x-component'] === 'Grid' || s['x-component'] === 'BlockTemplate') {
|
||||||
return s;
|
return s;
|
||||||
@ -310,7 +310,7 @@ const findGridSchema = (fieldSchema) => {
|
|||||||
|
|
||||||
const findBlockTemplateSchema = (fieldSchema) => {
|
const findBlockTemplateSchema = (fieldSchema) => {
|
||||||
return fieldSchema.reduceProperties((buf, s) => {
|
return fieldSchema.reduceProperties((buf, s) => {
|
||||||
if (s['x-component'] === 'FormV2') {
|
if (s['x-component'] === 'FormV2' || s['x-component'] === 'Details') {
|
||||||
const f = s.reduceProperties((buf, s) => {
|
const f = s.reduceProperties((buf, s) => {
|
||||||
if (s['x-component'] === 'BlockTemplate') {
|
if (s['x-component'] === 'BlockTemplate') {
|
||||||
return s;
|
return s;
|
||||||
|
@ -0,0 +1,156 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { ISchema, Schema, useField, useForm } from '@formily/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Select } from 'antd';
|
||||||
|
import { useCollectionManager_deprecated, useDesignable } from '..';
|
||||||
|
import { SchemaSettingsModalItem } from './SchemaSettings';
|
||||||
|
|
||||||
|
const UnitConversion = ({ unitConversionType }) => {
|
||||||
|
const form = useForm();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
defaultValue={unitConversionType || '*'}
|
||||||
|
style={{ width: 160 }}
|
||||||
|
onChange={(value) => {
|
||||||
|
form.setValuesIn('unitConversionType', value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Option value="*">{t('Multiply by')}</Select.Option>
|
||||||
|
<Select.Option value="/">{t('Divide by')}</Select.Option>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SchemaSettingsNumberFormat = function NumberFormatConfig(props: { fieldSchema: Schema }) {
|
||||||
|
const { fieldSchema } = props;
|
||||||
|
const field = useField();
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { getCollectionJoinField } = useCollectionManager_deprecated();
|
||||||
|
const collectionField = getCollectionJoinField(fieldSchema?.['x-collection-field']) || {};
|
||||||
|
const { formatStyle, unitConversion, unitConversionType, separator, step, addonBefore, addonAfter } =
|
||||||
|
fieldSchema['x-component-props'] || {};
|
||||||
|
const { step: prescition } = collectionField?.uiSchema['x-component-props'] || {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaSettingsModalItem
|
||||||
|
title={t('Format')}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
formatStyle: {
|
||||||
|
type: 'string',
|
||||||
|
default: formatStyle || 'normal',
|
||||||
|
enum: [
|
||||||
|
{
|
||||||
|
value: 'normal',
|
||||||
|
label: t('Normal'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'scientifix',
|
||||||
|
label: t('Scientifix notation'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
title: "{{t('Style')}}",
|
||||||
|
},
|
||||||
|
unitConversion: {
|
||||||
|
type: 'number',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
title: "{{t('Unit conversion')}}",
|
||||||
|
default: unitConversion,
|
||||||
|
'x-component-props': {
|
||||||
|
style: { width: '100%' },
|
||||||
|
addonBefore: <UnitConversion unitConversionType={unitConversionType} />,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
type: 'string',
|
||||||
|
default: separator || '0,0.00',
|
||||||
|
enum: [
|
||||||
|
{
|
||||||
|
value: '0,0.00',
|
||||||
|
label: t('100,000.00'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '0.0,00',
|
||||||
|
label: t('100.000,00'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '0 0,00',
|
||||||
|
label: t('100 000.00'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '0.00',
|
||||||
|
label: t('100000.00'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
title: "{{t('Separator')}}",
|
||||||
|
},
|
||||||
|
step: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Precision")}}',
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
default: step || prescition || '1',
|
||||||
|
enum: [
|
||||||
|
{ value: '1', label: '1' },
|
||||||
|
{ value: '0.1', label: '1.0' },
|
||||||
|
{ value: '0.01', label: '1.00' },
|
||||||
|
{ value: '0.001', label: '1.000' },
|
||||||
|
{ value: '0.0001', label: '1.0000' },
|
||||||
|
{ value: '0.00001', label: '1.00000' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
addonBefore: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Prefix")}}',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
default: addonBefore,
|
||||||
|
},
|
||||||
|
addonAfter: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Suffix")}}',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
default: addonAfter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={(data) => {
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
schema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||||
|
fieldSchema['x-component-props'] = {
|
||||||
|
...(fieldSchema['x-component-props'] || {}),
|
||||||
|
...data,
|
||||||
|
};
|
||||||
|
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||||
|
field.componentProps = fieldSchema['x-component-props'];
|
||||||
|
//子表格/表格区块
|
||||||
|
const parts = (field.path.entire as string).split('.');
|
||||||
|
parts.pop();
|
||||||
|
const modifiedString = parts.join('.');
|
||||||
|
field.query(`${modifiedString}.*[0:].${fieldSchema.name}`).forEach((f) => {
|
||||||
|
if (f.props.name === fieldSchema.name) {
|
||||||
|
f.setComponentProps({ ...data });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -45,6 +45,7 @@ import { selectComponentFieldSettings } from '../modules/fields/component/Select
|
|||||||
import { subTablePopoverComponentFieldSettings } from '../modules/fields/component/SubTable/subTablePopoverComponentFieldSettings';
|
import { subTablePopoverComponentFieldSettings } from '../modules/fields/component/SubTable/subTablePopoverComponentFieldSettings';
|
||||||
import { tagComponentFieldSettings } from '../modules/fields/component/Tag/tagComponentFieldSettings';
|
import { tagComponentFieldSettings } from '../modules/fields/component/Tag/tagComponentFieldSettings';
|
||||||
import { unixTimestampComponentFieldSettings } from '../modules/fields/component/UnixTimestamp/unixTimestampComponentFieldSettings';
|
import { unixTimestampComponentFieldSettings } from '../modules/fields/component/UnixTimestamp/unixTimestampComponentFieldSettings';
|
||||||
|
import { inputNumberComponentFieldSettings } from '../modules/fields/component/InputNumber/inputNumberComponentFieldSettings';
|
||||||
|
|
||||||
export class SchemaSettingsPlugin extends Plugin {
|
export class SchemaSettingsPlugin extends Plugin {
|
||||||
async load() {
|
async load() {
|
||||||
@ -95,6 +96,7 @@ export class SchemaSettingsPlugin extends Plugin {
|
|||||||
this.schemaSettingsManager.add(subTablePopoverComponentFieldSettings);
|
this.schemaSettingsManager.add(subTablePopoverComponentFieldSettings);
|
||||||
this.schemaSettingsManager.add(datePickerComponentFieldSettings);
|
this.schemaSettingsManager.add(datePickerComponentFieldSettings);
|
||||||
this.schemaSettingsManager.add(unixTimestampComponentFieldSettings);
|
this.schemaSettingsManager.add(unixTimestampComponentFieldSettings);
|
||||||
|
this.schemaSettingsManager.add(inputNumberComponentFieldSettings);
|
||||||
|
|
||||||
this.schemaSettingsManager.add(fileManagerComponentFieldSettings);
|
this.schemaSettingsManager.add(fileManagerComponentFieldSettings);
|
||||||
this.schemaSettingsManager.add(tagComponentFieldSettings);
|
this.schemaSettingsManager.add(tagComponentFieldSettings);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "create-nocobase-app",
|
"name": "create-nocobase-app",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/data-source-manager",
|
"name": "@nocobase/data-source-manager",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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.16",
|
"@nocobase/actions": "0.20.0-alpha.17",
|
||||||
"@nocobase/cache": "0.20.0-alpha.16",
|
"@nocobase/cache": "0.20.0-alpha.17",
|
||||||
"@nocobase/database": "0.20.0-alpha.16",
|
"@nocobase/database": "0.20.0-alpha.17",
|
||||||
"@nocobase/resourcer": "0.20.0-alpha.16",
|
"@nocobase/resourcer": "0.20.0-alpha.17",
|
||||||
"@nocobase/utils": "0.20.0-alpha.16",
|
"@nocobase/utils": "0.20.0-alpha.17",
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^8.5.8",
|
||||||
"jsonwebtoken": "^8.5.1"
|
"jsonwebtoken": "^8.5.1"
|
||||||
},
|
},
|
||||||
|
@ -7,3 +7,10 @@ export function parseCollectionName(collection: string) {
|
|||||||
const dataSourceName = dataSourceCollection[0] ?? 'main';
|
const dataSourceName = dataSourceCollection[0] ?? 'main';
|
||||||
return [dataSourceName, collectionName];
|
return [dataSourceName, collectionName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function joinCollectionName(dataSourceName: string, collectionName: string) {
|
||||||
|
if (!dataSourceName || dataSourceName === 'main') {
|
||||||
|
return collectionName;
|
||||||
|
}
|
||||||
|
return `${dataSourceName}:${collectionName}`;
|
||||||
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/database",
|
"name": "@nocobase/database",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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.16",
|
"@nocobase/logger": "0.20.0-alpha.17",
|
||||||
"@nocobase/utils": "0.20.0-alpha.16",
|
"@nocobase/utils": "0.20.0-alpha.17",
|
||||||
"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",
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
import { Database } from '../../database';
|
||||||
|
import { mockDatabase } from '../index';
|
||||||
|
describe('association references', () => {
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = mockDatabase();
|
||||||
|
|
||||||
|
await db.clean({ drop: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add reference with default priority', async () => {
|
||||||
|
const User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [{ type: 'hasOne', name: 'profile' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const Profile = db.collection({
|
||||||
|
name: 'profiles',
|
||||||
|
fields: [{ type: 'belongsTo', name: 'user' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.sync();
|
||||||
|
|
||||||
|
const references = db.referenceMap.getReferences('users');
|
||||||
|
|
||||||
|
expect(references[0].priority).toBe('default');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add reference with user defined priority', async () => {
|
||||||
|
const User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [{ type: 'hasOne', name: 'profile', onDelete: 'CASCADE' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const Profile = db.collection({
|
||||||
|
name: 'profiles',
|
||||||
|
fields: [{ type: 'belongsTo', name: 'user' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.sync();
|
||||||
|
|
||||||
|
const references = db.referenceMap.getReferences('users');
|
||||||
|
|
||||||
|
expect(references.length).toBe(1);
|
||||||
|
expect(references[0].priority).toBe('user');
|
||||||
|
});
|
||||||
|
});
|
@ -14,6 +14,36 @@ describe('belongs to field', () => {
|
|||||||
await db.close();
|
await db.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should load with no action', async () => {
|
||||||
|
const User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [{ type: 'string', name: 'name', unique: true }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const Post = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'title' },
|
||||||
|
{ type: 'belongsTo', name: 'user', onDelete: 'NO ACTION' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.sync();
|
||||||
|
|
||||||
|
const u1 = await User.repository.create({ values: { name: 'u1' } });
|
||||||
|
const p1 = await Post.repository.create({ values: { title: 'p1', user: u1.id } });
|
||||||
|
|
||||||
|
// delete u1
|
||||||
|
await User.repository.destroy({ filterByTk: u1.id });
|
||||||
|
|
||||||
|
// list posts with user
|
||||||
|
const post = await Post.repository.findOne({
|
||||||
|
appends: ['user'],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(post.user).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw error when associated with item that null with target key', async () => {
|
it('should throw error when associated with item that null with target key', async () => {
|
||||||
const User = db.collection({
|
const User = db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
|
@ -27,7 +27,7 @@ import { CollectionFactory } from './collection-factory';
|
|||||||
import { CollectionGroupManager } from './collection-group-manager';
|
import { CollectionGroupManager } from './collection-group-manager';
|
||||||
import { ImporterReader, ImportFileExtension } from './collection-importer';
|
import { ImporterReader, ImportFileExtension } from './collection-importer';
|
||||||
import DatabaseUtils from './database-utils';
|
import DatabaseUtils from './database-utils';
|
||||||
import ReferencesMap from './features/ReferencesMap';
|
import ReferencesMap from './features/references-map';
|
||||||
import { referentialIntegrityCheck } from './features/referential-integrity-check';
|
import { referentialIntegrityCheck } from './features/referential-integrity-check';
|
||||||
import { ArrayFieldRepository } from './field-repository/array-field-repository';
|
import { ArrayFieldRepository } from './field-repository/array-field-repository';
|
||||||
import * as FieldTypes from './fields';
|
import * as FieldTypes from './fields';
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
export interface Reference {
|
|
||||||
sourceCollectionName: string;
|
|
||||||
sourceField: string;
|
|
||||||
targetField: string;
|
|
||||||
targetCollectionName: string;
|
|
||||||
onDelete: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReferencesMap {
|
|
||||||
protected map: Map<string, Reference[]> = new Map();
|
|
||||||
|
|
||||||
addReference(reference: Reference) {
|
|
||||||
if (!reference.onDelete) {
|
|
||||||
reference.onDelete = 'SET NULL';
|
|
||||||
}
|
|
||||||
|
|
||||||
reference.onDelete = reference.onDelete.toUpperCase();
|
|
||||||
|
|
||||||
const existReference = this.existReference(reference);
|
|
||||||
|
|
||||||
if (existReference && existReference.onDelete !== reference.onDelete) {
|
|
||||||
if (reference.onDelete === 'SET NULL') {
|
|
||||||
// using existing reference
|
|
||||||
return;
|
|
||||||
} else if (existReference.onDelete === 'SET NULL') {
|
|
||||||
existReference.onDelete = reference.onDelete;
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`On Delete Conflict, exist reference ${JSON.stringify(existReference)}, new reference ${JSON.stringify(
|
|
||||||
reference,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!existReference) {
|
|
||||||
this.map.set(reference.targetCollectionName, [
|
|
||||||
...(this.map.get(reference.targetCollectionName) || []),
|
|
||||||
reference,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getReferences(collectionName) {
|
|
||||||
return this.map.get(collectionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
existReference(reference: Reference) {
|
|
||||||
const references = this.map.get(reference.targetCollectionName);
|
|
||||||
|
|
||||||
if (!references) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = Object.keys(reference).filter((k) => k !== 'onDelete');
|
|
||||||
|
|
||||||
return references.find((ref) => keys.every((key) => ref[key] === reference[key]));
|
|
||||||
}
|
|
||||||
|
|
||||||
removeReference(reference: Reference) {
|
|
||||||
const references = this.map.get(reference.targetCollectionName);
|
|
||||||
|
|
||||||
if (!references) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = ['sourceCollectionName', 'sourceField', 'targetField', 'targetCollectionName'];
|
|
||||||
|
|
||||||
this.map.set(
|
|
||||||
reference.targetCollectionName,
|
|
||||||
references.filter((ref) => !keys.every((key) => ref[key] === reference[key])),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ReferencesMap;
|
|
107
packages/core/database/src/features/references-map.ts
Normal file
107
packages/core/database/src/features/references-map.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
export type ReferencePriority = 'default' | 'user';
|
||||||
|
|
||||||
|
export interface Reference {
|
||||||
|
sourceCollectionName: string;
|
||||||
|
sourceField: string;
|
||||||
|
targetField: string;
|
||||||
|
targetCollectionName: string;
|
||||||
|
onDelete: string;
|
||||||
|
priority: ReferencePriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_ON_DELETE = 'NO ACTION';
|
||||||
|
|
||||||
|
export function buildReference(options: Partial<Reference>): Reference {
|
||||||
|
const { sourceCollectionName, sourceField, targetField, targetCollectionName, onDelete, priority } = options;
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourceCollectionName,
|
||||||
|
sourceField,
|
||||||
|
targetField,
|
||||||
|
targetCollectionName,
|
||||||
|
onDelete: (onDelete || DEFAULT_ON_DELETE).toUpperCase(),
|
||||||
|
priority: assignPriority(priority, onDelete),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignPriority(priority: string | undefined, onDelete: string | undefined): ReferencePriority {
|
||||||
|
if (priority) {
|
||||||
|
return priority as ReferencePriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
return onDelete ? 'user' : 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRIORITY_MAP = {
|
||||||
|
default: 1,
|
||||||
|
user: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ReferencesMap {
|
||||||
|
protected map: Map<string, Reference[]> = new Map();
|
||||||
|
|
||||||
|
addReference(reference: Reference) {
|
||||||
|
const existReference = this.existReference(reference);
|
||||||
|
|
||||||
|
if (existReference && existReference.onDelete !== reference.onDelete) {
|
||||||
|
// check two references onDelete priority, using the higher priority, if both are the same, throw error
|
||||||
|
const existPriority = PRIORITY_MAP[existReference.priority];
|
||||||
|
const newPriority = PRIORITY_MAP[reference.priority];
|
||||||
|
|
||||||
|
if (newPriority > existPriority) {
|
||||||
|
existReference.onDelete = reference.onDelete;
|
||||||
|
existReference.priority = reference.priority;
|
||||||
|
} else if (newPriority === existPriority && newPriority === PRIORITY_MAP['user']) {
|
||||||
|
if (existReference.onDelete === 'SET NULL' && reference.onDelete === 'CASCADE') {
|
||||||
|
existReference.onDelete = reference.onDelete;
|
||||||
|
} else {
|
||||||
|
console.error(new Error(
|
||||||
|
`On Delete Conflict, exist reference ${JSON.stringify(existReference)}, new reference ${JSON.stringify(
|
||||||
|
reference,
|
||||||
|
)}`,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existReference) {
|
||||||
|
this.map.set(reference.targetCollectionName, [
|
||||||
|
...(this.map.get(reference.targetCollectionName) || []),
|
||||||
|
reference,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getReferences(collectionName) {
|
||||||
|
return this.map.get(collectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
existReference(reference: Reference) {
|
||||||
|
const references = this.map.get(reference.targetCollectionName);
|
||||||
|
|
||||||
|
if (!references) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = Object.keys(reference).filter((k) => k !== 'onDelete' && k !== 'priority');
|
||||||
|
|
||||||
|
return references.find((ref) => keys.every((key) => ref[key] === reference[key]));
|
||||||
|
}
|
||||||
|
|
||||||
|
removeReference(reference: Reference) {
|
||||||
|
const references = this.map.get(reference.targetCollectionName);
|
||||||
|
|
||||||
|
if (!references) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = ['sourceCollectionName', 'sourceField', 'targetField', 'targetCollectionName'];
|
||||||
|
|
||||||
|
this.map.set(
|
||||||
|
reference.targetCollectionName,
|
||||||
|
references.filter((ref) => !keys.every((key) => ref[key] === reference[key])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ReferencesMap;
|
@ -21,6 +21,11 @@ export async function referentialIntegrityCheck(options: ReferentialIntegrityChe
|
|||||||
|
|
||||||
for (const reference of references) {
|
for (const reference of references) {
|
||||||
const { sourceCollectionName, sourceField, targetField, onDelete } = reference;
|
const { sourceCollectionName, sourceField, targetField, onDelete } = reference;
|
||||||
|
|
||||||
|
if (onDelete === 'NO ACTION') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const sourceCollection = db.collections.get(sourceCollectionName);
|
const sourceCollection = db.collections.get(sourceCollectionName);
|
||||||
const sourceRepository = sourceCollection.repository;
|
const sourceRepository = sourceCollection.repository;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import lodash, { omit } from 'lodash';
|
import lodash, { omit } from 'lodash';
|
||||||
import { BelongsToOptions as SequelizeBelongsToOptions, Utils } from 'sequelize';
|
import { BelongsToOptions as SequelizeBelongsToOptions, Utils } from 'sequelize';
|
||||||
import { Reference } from '../features/ReferencesMap';
|
import { buildReference, Reference, ReferencePriority } from '../features/references-map';
|
||||||
import { checkIdentifier } from '../utils';
|
import { checkIdentifier } from '../utils';
|
||||||
import { BaseRelationFieldOptions, RelationField } from './relation-field';
|
import { BaseRelationFieldOptions, RelationField } from './relation-field';
|
||||||
|
|
||||||
@ -16,20 +16,26 @@ export class BelongsToField extends RelationField {
|
|||||||
return target || Utils.pluralize(name);
|
return target || Utils.pluralize(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static toReference(db, association, onDelete) {
|
static toReference(db, association, onDelete, priority: ReferencePriority = 'default'): Reference {
|
||||||
const targetKey = association.targetKey;
|
const targetKey = association.targetKey;
|
||||||
|
|
||||||
return {
|
return buildReference({
|
||||||
sourceCollectionName: db.modelCollection.get(association.source).name,
|
sourceCollectionName: db.modelCollection.get(association.source).name,
|
||||||
sourceField: association.foreignKey,
|
sourceField: association.foreignKey,
|
||||||
targetField: targetKey,
|
targetField: targetKey,
|
||||||
targetCollectionName: db.modelCollection.get(association.target).name,
|
targetCollectionName: db.modelCollection.get(association.target).name,
|
||||||
onDelete: onDelete,
|
onDelete: onDelete,
|
||||||
};
|
priority: priority,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
reference(association): Reference {
|
reference(association): Reference {
|
||||||
return BelongsToField.toReference(this.database, association, this.options.onDelete);
|
return BelongsToField.toReference(
|
||||||
|
this.database,
|
||||||
|
association,
|
||||||
|
this.options.onDelete,
|
||||||
|
this.options.onDelete ? 'user' : 'default',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAssociationKeys() {
|
checkAssociationKeys() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import { AssociationScope, BelongsToManyOptions as SequelizeBelongsToManyOptions, Utils } from 'sequelize';
|
import { AssociationScope, BelongsToManyOptions as SequelizeBelongsToManyOptions, Utils } from 'sequelize';
|
||||||
import { Collection } from '../collection';
|
import { Collection } from '../collection';
|
||||||
import { Reference } from '../features/ReferencesMap';
|
import { Reference } from '../features/references-map';
|
||||||
import { checkIdentifier } from '../utils';
|
import { checkIdentifier } from '../utils';
|
||||||
import { BelongsToField } from './belongs-to-field';
|
import { BelongsToField } from './belongs-to-field';
|
||||||
import { MultipleRelationFieldOptions, RelationField } from './relation-field';
|
import { MultipleRelationFieldOptions, RelationField } from './relation-field';
|
||||||
@ -32,6 +32,8 @@ export class BelongsToManyField extends RelationField {
|
|||||||
|
|
||||||
const onDelete = this.options.onDelete || 'CASCADE';
|
const onDelete = this.options.onDelete || 'CASCADE';
|
||||||
|
|
||||||
|
const priority = this.options.onDelete ? 'user' : 'default';
|
||||||
|
|
||||||
const targetAssociation = association.toTarget;
|
const targetAssociation = association.toTarget;
|
||||||
|
|
||||||
if (association.targetKey) {
|
if (association.targetKey) {
|
||||||
@ -45,8 +47,8 @@ export class BelongsToManyField extends RelationField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
BelongsToField.toReference(db, targetAssociation, onDelete),
|
BelongsToField.toReference(db, targetAssociation, onDelete, priority),
|
||||||
BelongsToField.toReference(db, sourceAssociation, onDelete),
|
BelongsToField.toReference(db, sourceAssociation, onDelete, priority),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,10 +151,6 @@ export class BelongsToManyField extends RelationField {
|
|||||||
Object.defineProperty(Through.model, 'isThrough', { value: true });
|
Object.defineProperty(Through.model, 'isThrough', { value: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.options.onDelete) {
|
|
||||||
this.options.onDelete = 'CASCADE';
|
|
||||||
}
|
|
||||||
|
|
||||||
const belongsToManyOptions = {
|
const belongsToManyOptions = {
|
||||||
constraints: false,
|
constraints: false,
|
||||||
...omit(this.options, ['name', 'type', 'target']),
|
...omit(this.options, ['name', 'type', 'target']),
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
Utils,
|
Utils,
|
||||||
} from 'sequelize';
|
} from 'sequelize';
|
||||||
import { Collection } from '../collection';
|
import { Collection } from '../collection';
|
||||||
import { Reference } from '../features/ReferencesMap';
|
import { buildReference, Reference } from '../features/references-map';
|
||||||
import { checkIdentifier } from '../utils';
|
import { checkIdentifier } from '../utils';
|
||||||
import { MultipleRelationFieldOptions, RelationField } from './relation-field';
|
import { MultipleRelationFieldOptions, RelationField } from './relation-field';
|
||||||
|
|
||||||
@ -89,13 +89,13 @@ export class HasManyField extends RelationField {
|
|||||||
reference(association): Reference {
|
reference(association): Reference {
|
||||||
const sourceKey = association.sourceKey;
|
const sourceKey = association.sourceKey;
|
||||||
|
|
||||||
return {
|
return buildReference({
|
||||||
sourceCollectionName: this.database.modelCollection.get(association.target).name,
|
sourceCollectionName: this.database.modelCollection.get(association.target).name,
|
||||||
sourceField: association.foreignKey,
|
sourceField: association.foreignKey,
|
||||||
targetField: sourceKey,
|
targetField: sourceKey,
|
||||||
targetCollectionName: this.database.modelCollection.get(association.source).name,
|
targetCollectionName: this.database.modelCollection.get(association.source).name,
|
||||||
onDelete: this.options.onDelete,
|
onDelete: this.options.onDelete,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAssociationKeys() {
|
checkAssociationKeys() {
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
Utils,
|
Utils,
|
||||||
} from 'sequelize';
|
} from 'sequelize';
|
||||||
import { Collection } from '../collection';
|
import { Collection } from '../collection';
|
||||||
import { Reference } from '../features/ReferencesMap';
|
import { buildReference, Reference } from '../features/references-map';
|
||||||
import { checkIdentifier } from '../utils';
|
import { checkIdentifier } from '../utils';
|
||||||
import { BaseRelationFieldOptions, RelationField } from './relation-field';
|
import { BaseRelationFieldOptions, RelationField } from './relation-field';
|
||||||
|
|
||||||
@ -98,13 +98,13 @@ export class HasOneField extends RelationField {
|
|||||||
reference(association): Reference {
|
reference(association): Reference {
|
||||||
const sourceKey = association.sourceKey;
|
const sourceKey = association.sourceKey;
|
||||||
|
|
||||||
return {
|
return buildReference({
|
||||||
sourceCollectionName: this.database.modelCollection.get(association.target).name,
|
sourceCollectionName: this.database.modelCollection.get(association.target).name,
|
||||||
sourceField: association.foreignKey,
|
sourceField: association.foreignKey,
|
||||||
targetField: sourceKey,
|
targetField: sourceKey,
|
||||||
targetCollectionName: this.database.modelCollection.get(association.source).name,
|
targetCollectionName: this.database.modelCollection.get(association.source).name,
|
||||||
onDelete: this.options.onDelete,
|
onDelete: this.options.onDelete,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAssociationKeys() {
|
checkAssociationKeys() {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/devtools",
|
"name": "@nocobase/devtools",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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.16",
|
"@nocobase/build": "0.20.0-alpha.17",
|
||||||
"@nocobase/client": "0.20.0-alpha.16",
|
"@nocobase/client": "0.20.0-alpha.17",
|
||||||
"@nocobase/test": "0.20.0-alpha.16",
|
"@nocobase/test": "0.20.0-alpha.17",
|
||||||
"@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",
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/evaluators",
|
"name": "@nocobase/evaluators",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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.16",
|
"@nocobase/utils": "0.20.0-alpha.17",
|
||||||
"mathjs": "^10.6.0"
|
"mathjs": "^10.6.0"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/logger",
|
"name": "@nocobase/logger",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"description": "nocobase logging library",
|
"description": "nocobase logging library",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/resourcer",
|
"name": "@nocobase/resourcer",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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.16",
|
"@nocobase/utils": "0.20.0-alpha.17",
|
||||||
"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",
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { assign, MergeStrategies, prePerfHooksWrap, requireModule } from '@nocobase/utils';
|
import { assign, MergeStrategies, requireModule } from '@nocobase/utils';
|
||||||
import compose from 'koa-compose';
|
import compose from 'koa-compose';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Middleware, { MiddlewareType } from './middleware';
|
import Middleware, { MiddlewareType } from './middleware';
|
||||||
import Resource from './resource';
|
import Resource from './resource';
|
||||||
import { HandlerType } from './resourcer';
|
import { HandlerType } from './resourcer';
|
||||||
import { RecordableHistogram, performance } from 'perf_hooks';
|
|
||||||
|
|
||||||
export type ActionType = string | HandlerType | ActionOptions;
|
export type ActionType = string | HandlerType | ActionOptions;
|
||||||
|
|
||||||
@ -161,27 +160,38 @@ export interface ActionParams {
|
|||||||
*/
|
*/
|
||||||
values?: any;
|
values?: any;
|
||||||
/**
|
/**
|
||||||
* 当前资源的主体,对应的表名或 Model 名称
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link action.resourceName.split(',')[0]} instead.
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
resourceName?: string;
|
resourceName?: string;
|
||||||
/**
|
/**
|
||||||
* 资源标识符
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link filterByTk} instead.
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
resourceIndex?: string;
|
resourceIndex?: string;
|
||||||
/**
|
/**
|
||||||
* 资源的从属关系
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link action.resourceName.split(',')[1]} instead.
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
associatedName?: string;
|
associatedName?: string;
|
||||||
/**
|
/**
|
||||||
* 从属关系的标识符
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link action.sourceId} instead.
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
associatedIndex?: string;
|
associatedIndex?: string;
|
||||||
/**
|
/**
|
||||||
* 从属关系的当前实例
|
* This method is deprecated and should not be used.
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
associated?: any;
|
associated?: any;
|
||||||
/**
|
/**
|
||||||
* 资源提供哪些行为或方法
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link action.actionName} instead.
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
actionName?: string;
|
actionName?: string;
|
||||||
/**
|
/**
|
||||||
@ -204,11 +214,23 @@ export class Action {
|
|||||||
public params: ActionParams = {};
|
public params: ActionParams = {};
|
||||||
|
|
||||||
public actionName: string;
|
public actionName: string;
|
||||||
|
|
||||||
public resourceName: string;
|
public resourceName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link this.sourceId} instead.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
public resourceOf: any;
|
public resourceOf: any;
|
||||||
|
|
||||||
|
public sourceId: any;
|
||||||
|
|
||||||
public readonly middlewares: Array<Middleware> = [];
|
public readonly middlewares: Array<Middleware> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
constructor(options: ActionOptions) {
|
constructor(options: ActionOptions) {
|
||||||
options = requireModule(options);
|
options = requireModule(options);
|
||||||
if (typeof options === 'function') {
|
if (typeof options === 'function') {
|
||||||
@ -221,15 +243,22 @@ export class Action {
|
|||||||
this.mergeParams(params);
|
this.mergeParams(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
actionName: this.actionName,
|
actionName: this.actionName,
|
||||||
resourceName: this.resourceName,
|
resourceName: this.resourceName,
|
||||||
resourceOf: this.resourceOf,
|
resourceOf: this.sourceId,
|
||||||
|
sourceId: this.sourceId,
|
||||||
params: this.params,
|
params: this.params,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
clone() {
|
clone() {
|
||||||
const options = _.cloneDeep(this.options);
|
const options = _.cloneDeep(this.options);
|
||||||
delete options.middleware;
|
delete options.middleware;
|
||||||
@ -241,6 +270,9 @@ export class Action {
|
|||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
setContext(context: any) {
|
setContext(context: any) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
@ -266,34 +298,55 @@ export class Action {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
setResource(resource: Resource) {
|
setResource(resource: Resource) {
|
||||||
this.resource = resource;
|
this.resource = resource;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getResource() {
|
getResource() {
|
||||||
return this.resource;
|
return this.resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getOptions(): ActionOptions {
|
getOptions(): ActionOptions {
|
||||||
return this.options;
|
return this.options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
setName(name: ActionName) {
|
setName(name: ActionName) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getName() {
|
getName() {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getMiddlewareHandlers() {
|
getMiddlewareHandlers() {
|
||||||
return this.middlewares
|
return this.middlewares
|
||||||
.filter((middleware) => middleware.canAccess(this.name))
|
.filter((middleware) => middleware.canAccess(this.name))
|
||||||
.map((middleware) => middleware.getHandler());
|
.map((middleware) => middleware.getHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getHandler() {
|
getHandler() {
|
||||||
const handler = requireModule(this.handler || this.resource.resourcer.getRegisteredHandler(this.name));
|
const handler = requireModule(this.handler || this.resource.resourcer.getRegisteredHandler(this.name));
|
||||||
if (typeof handler !== 'function') {
|
if (typeof handler !== 'function') {
|
||||||
@ -303,6 +356,9 @@ export class Action {
|
|||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getHandlers() {
|
getHandlers() {
|
||||||
const handlers = [
|
const handlers = [
|
||||||
...this.resource.resourcer.getMiddlewares(),
|
...this.resource.resourcer.getMiddlewares(),
|
||||||
@ -315,10 +371,16 @@ export class Action {
|
|||||||
return handlers;
|
return handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async execute(context: any, next?: any) {
|
async execute(context: any, next?: any) {
|
||||||
return await compose(this.getHandlers())(context, next);
|
return await compose(this.getHandlers())(context, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
static toInstanceMap(actions: object, resource?: Resource) {
|
static toInstanceMap(actions: object, resource?: Resource) {
|
||||||
return new Map(
|
return new Map(
|
||||||
Object.entries(actions).map(([key, options]) => {
|
Object.entries(actions).map(([key, options]) => {
|
||||||
|
@ -150,6 +150,9 @@ export interface ImportOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Resourcer {
|
export class Resourcer {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
public readonly options: ResourcerOptions;
|
public readonly options: ResourcerOptions;
|
||||||
protected resources = new Map<string, Resource>();
|
protected resources = new Map<string, Resource>();
|
||||||
/**
|
/**
|
||||||
@ -173,6 +176,7 @@ export class Resourcer {
|
|||||||
* @param {object} [options]
|
* @param {object} [options]
|
||||||
* @param {string} [options.directory] 指定配置所在路径
|
* @param {string} [options.directory] 指定配置所在路径
|
||||||
* @param {array} [options.extensions = ['js', 'ts', 'json']] 文件后缀
|
* @param {array} [options.extensions = ['js', 'ts', 'json']] 文件后缀
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public async import(options: ImportOptions): Promise<Map<string, Resource>> {
|
public async import(options: ImportOptions): Promise<Map<string, Resource>> {
|
||||||
const { extensions = ['js', 'ts', 'json'], directory } = options;
|
const { extensions = ['js', 'ts', 'json'], directory } = options;
|
||||||
@ -206,14 +210,27 @@ export class Resourcer {
|
|||||||
return this.resources.has(name);
|
return this.resources.has(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
removeResource(name) {
|
removeResource(name) {
|
||||||
return this.resources.delete(name);
|
return this.resources.delete(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link this.registerActionHandler()} instead.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
registerAction(name: ActionName, handler: HandlerType) {
|
registerAction(name: ActionName, handler: HandlerType) {
|
||||||
this.registerActionHandler(name, handler);
|
this.registerActionHandler(name, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link this.registerActionHandlers()} instead.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
registerActions(handlers: Handlers) {
|
registerActions(handlers: Handlers) {
|
||||||
this.registerActionHandlers(handlers);
|
this.registerActionHandlers(handlers);
|
||||||
}
|
}
|
||||||
@ -233,14 +250,23 @@ export class Resourcer {
|
|||||||
this.actionHandlers.set(name, handler);
|
this.actionHandlers.set(name, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getRegisteredHandler(name: ActionName) {
|
getRegisteredHandler(name: ActionName) {
|
||||||
return this.actionHandlers.get(name);
|
return this.actionHandlers.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getRegisteredHandlers() {
|
getRegisteredHandlers() {
|
||||||
return this.actionHandlers;
|
return this.actionHandlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getResource(name: string): Resource {
|
getResource(name: string): Resource {
|
||||||
if (!this.resources.has(name)) {
|
if (!this.resources.has(name)) {
|
||||||
throw new Error(`${name} resource does not exist`);
|
throw new Error(`${name} resource does not exist`);
|
||||||
@ -248,6 +274,9 @@ export class Resourcer {
|
|||||||
return this.resources.get(name);
|
return this.resources.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getAction(name: string, action: ActionName): Action {
|
getAction(name: string, action: ActionName): Action {
|
||||||
// 支持注册局部 action
|
// 支持注册局部 action
|
||||||
if (this.actionHandlers.has(`${name}:${action}`)) {
|
if (this.actionHandlers.has(`${name}:${action}`)) {
|
||||||
@ -256,6 +285,9 @@ export class Resourcer {
|
|||||||
return this.getResource(name).getAction(action);
|
return this.getResource(name).getAction(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getMiddlewares() {
|
getMiddlewares() {
|
||||||
return this.middlewares.nodes;
|
return this.middlewares.nodes;
|
||||||
}
|
}
|
||||||
@ -264,6 +296,11 @@ export class Resourcer {
|
|||||||
this.middlewares.add(middlewares, options);
|
this.middlewares.add(middlewares, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link this.middleware()} instead.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
restApiMiddleware({ prefix, accessors, skipIfDataSourceExists = false }: KoaMiddlewareOptions = {}) {
|
restApiMiddleware({ prefix, accessors, skipIfDataSourceExists = false }: KoaMiddlewareOptions = {}) {
|
||||||
return async (ctx: ResourcerContext, next: () => Promise<any>) => {
|
return async (ctx: ResourcerContext, next: () => Promise<any>) => {
|
||||||
if (skipIfDataSourceExists) {
|
if (skipIfDataSourceExists) {
|
||||||
@ -317,6 +354,7 @@ export class Resourcer {
|
|||||||
|
|
||||||
ctx.action.setContext(ctx);
|
ctx.action.setContext(ctx);
|
||||||
ctx.action.actionName = params.actionName;
|
ctx.action.actionName = params.actionName;
|
||||||
|
ctx.action.sourceId = params.associatedIndex;
|
||||||
ctx.action.resourceOf = params.associatedIndex;
|
ctx.action.resourceOf = params.associatedIndex;
|
||||||
ctx.action.resourceName = params.associatedName
|
ctx.action.resourceName = params.associatedName
|
||||||
? `${params.associatedName}.${params.resourceName}`
|
? `${params.associatedName}.${params.resourceName}`
|
||||||
@ -349,11 +387,7 @@ export class Resourcer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 实验性 API
|
* @internal
|
||||||
*
|
|
||||||
* @param options
|
|
||||||
* @param context
|
|
||||||
* @param next
|
|
||||||
*/
|
*/
|
||||||
async execute(options: ExecuteOptions, context: ResourcerContext = {}, next?: any) {
|
async execute(options: ExecuteOptions, context: ResourcerContext = {}, next?: any) {
|
||||||
const { resource, action } = options;
|
const { resource, action } = options;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/sdk",
|
"name": "@nocobase/sdk",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/server",
|
"name": "@nocobase/server",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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.16",
|
"@nocobase/acl": "0.20.0-alpha.17",
|
||||||
"@nocobase/actions": "0.20.0-alpha.16",
|
"@nocobase/actions": "0.20.0-alpha.17",
|
||||||
"@nocobase/auth": "0.20.0-alpha.16",
|
"@nocobase/auth": "0.20.0-alpha.17",
|
||||||
"@nocobase/cache": "0.20.0-alpha.16",
|
"@nocobase/cache": "0.20.0-alpha.17",
|
||||||
"@nocobase/data-source-manager": "0.20.0-alpha.16",
|
"@nocobase/data-source-manager": "0.20.0-alpha.17",
|
||||||
"@nocobase/database": "0.20.0-alpha.16",
|
"@nocobase/database": "0.20.0-alpha.17",
|
||||||
"@nocobase/evaluators": "0.20.0-alpha.16",
|
"@nocobase/evaluators": "0.20.0-alpha.17",
|
||||||
"@nocobase/logger": "0.20.0-alpha.16",
|
"@nocobase/logger": "0.20.0-alpha.17",
|
||||||
"@nocobase/resourcer": "0.20.0-alpha.16",
|
"@nocobase/resourcer": "0.20.0-alpha.17",
|
||||||
"@nocobase/sdk": "0.20.0-alpha.16",
|
"@nocobase/sdk": "0.20.0-alpha.17",
|
||||||
"@nocobase/telemetry": "0.20.0-alpha.16",
|
"@nocobase/telemetry": "0.20.0-alpha.17",
|
||||||
"@nocobase/utils": "0.20.0-alpha.16",
|
"@nocobase/utils": "0.20.0-alpha.17",
|
||||||
"@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",
|
||||||
|
@ -142,19 +142,41 @@ export type MaintainingCommandStatus = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class Application<StateT = DefaultState, ContextT = DefaultContext> extends Koa implements AsyncEmitter {
|
export class Application<StateT = DefaultState, ContextT = DefaultContext> extends Koa implements AsyncEmitter {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
declare middleware: any;
|
declare middleware: any;
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
stopped = false;
|
stopped = false;
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
ready = false;
|
ready = false;
|
||||||
declare emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
|
declare emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
public rawOptions: ApplicationOptions;
|
public rawOptions: ApplicationOptions;
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
public activatedCommand: {
|
public activatedCommand: {
|
||||||
name: string;
|
name: string;
|
||||||
} = null;
|
} = null;
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
public running = false;
|
public running = false;
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
public perfHistograms = new Map<string, RecordableHistogram>();
|
public perfHistograms = new Map<string, RecordableHistogram>();
|
||||||
protected plugins = new Map<string, Plugin>();
|
protected plugins = new Map<string, Plugin>();
|
||||||
protected _appSupervisor: AppSupervisor = AppSupervisor.getInstance();
|
protected _appSupervisor: AppSupervisor = AppSupervisor.getInstance();
|
||||||
protected _started: boolean;
|
protected _started: boolean;
|
||||||
|
protected _logger: SystemLogger;
|
||||||
private _authenticated = false;
|
private _authenticated = false;
|
||||||
private _maintaining = false;
|
private _maintaining = false;
|
||||||
private _maintainingCommandStatus: MaintainingCommandStatus;
|
private _maintainingCommandStatus: MaintainingCommandStatus;
|
||||||
@ -193,12 +215,18 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
|
|
||||||
protected _loaded: boolean;
|
protected _loaded: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
get loaded() {
|
get loaded() {
|
||||||
return this._loaded;
|
return this._loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _maintainingMessage: string;
|
private _maintainingMessage: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
get maintainingMessage() {
|
get maintainingMessage() {
|
||||||
return this._maintainingMessage;
|
return this._maintainingMessage;
|
||||||
}
|
}
|
||||||
@ -222,8 +250,6 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
return this.mainDataSource.collectionManager.db;
|
return this.mainDataSource.collectionManager.db;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _logger: SystemLogger;
|
|
||||||
|
|
||||||
get logger() {
|
get logger() {
|
||||||
return this._logger;
|
return this._logger;
|
||||||
}
|
}
|
||||||
@ -244,6 +270,9 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
return this._cache;
|
return this._cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
set cache(cache: Cache) {
|
set cache(cache: Cache) {
|
||||||
this._cache = cache;
|
this._cache = cache;
|
||||||
}
|
}
|
||||||
@ -278,6 +307,11 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
|
|
||||||
protected _locales: Locale;
|
protected _locales: Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link #localeManager} instead.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
get locales() {
|
get locales() {
|
||||||
return this._locales;
|
return this._locales;
|
||||||
}
|
}
|
||||||
@ -312,10 +346,16 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
return this._dataSourceManager;
|
return this._dataSourceManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getMaintaining() {
|
getMaintaining() {
|
||||||
return this._maintainingCommandStatus;
|
return this._maintainingCommandStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
setMaintaining(_maintainingCommandStatus: MaintainingCommandStatus) {
|
setMaintaining(_maintainingCommandStatus: MaintainingCommandStatus) {
|
||||||
this._maintainingCommandStatus = _maintainingCommandStatus;
|
this._maintainingCommandStatus = _maintainingCommandStatus;
|
||||||
|
|
||||||
@ -329,6 +369,9 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
this._maintaining = true;
|
this._maintaining = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
setMaintainingMessage(message: string) {
|
setMaintainingMessage(message: string) {
|
||||||
this._maintainingMessage = message;
|
this._maintainingMessage = message;
|
||||||
|
|
||||||
@ -338,11 +381,18 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link #this.version.get()} instead.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
getVersion() {
|
getVersion() {
|
||||||
return packageJson.version;
|
return packageJson.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link #this.pm.addPreset()} instead.
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
plugin<O = any>(pluginClass: any, options?: O) {
|
plugin<O = any>(pluginClass: any, options?: O) {
|
||||||
@ -359,6 +409,9 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
callback() {
|
callback() {
|
||||||
const fn = compose(this.middleware.nodes);
|
const fn = compose(this.middleware.nodes);
|
||||||
|
|
||||||
@ -372,14 +425,29 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link #this.db.collection()} instead.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
collection(options: CollectionOptions) {
|
collection(options: CollectionOptions) {
|
||||||
return this.db.collection(options);
|
return this.db.collection(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link #this.resourcer.define()} instead.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
resource(options: ResourceOptions) {
|
resource(options: ResourceOptions) {
|
||||||
return this.resourcer.define(options);
|
return this.resourcer.define(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link #this.resourcer.registerActions()} instead.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
actions(handlers: any, options?: ActionsOptions) {
|
actions(handlers: any, options?: ActionsOptions) {
|
||||||
return this.resourcer.registerActions(handlers);
|
return this.resourcer.registerActions(handlers);
|
||||||
}
|
}
|
||||||
@ -392,11 +460,9 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
return (this.cli as any)._findCommand(name);
|
return (this.cli as any)._findCommand(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async preload() {
|
/**
|
||||||
// load core collections
|
* @internal
|
||||||
// load plugin commands
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
async reInit() {
|
async reInit() {
|
||||||
if (!this._loaded) {
|
if (!this._loaded) {
|
||||||
return;
|
return;
|
||||||
@ -500,12 +566,19 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link this.pm.get()} instead.
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
getPlugin<P extends Plugin>(name: string | typeof Plugin) {
|
getPlugin<P extends Plugin>(name: string | typeof Plugin) {
|
||||||
return this.pm.get(name) as P;
|
return this.pm.get(name) as P;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is deprecated and should not be used.
|
||||||
|
* Use {@link this.runAsCLI()} instead.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
async parse(argv = process.argv) {
|
async parse(argv = process.argv) {
|
||||||
return this.runAsCLI(argv);
|
return this.runAsCLI(argv);
|
||||||
}
|
}
|
||||||
@ -528,7 +601,7 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
return await this.runAsCLI([command, ...args], { from: 'user', throwError: true });
|
return await this.runAsCLI([command, ...args], { from: 'user', throwError: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
createCli() {
|
protected createCLI() {
|
||||||
const command = new AppCommand('nocobase')
|
const command = new AppCommand('nocobase')
|
||||||
.usage('[command] [options]')
|
.usage('[command] [options]')
|
||||||
.hook('preAction', async (_, actionCommand) => {
|
.hook('preAction', async (_, actionCommand) => {
|
||||||
@ -568,6 +641,9 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async loadMigrations(options) {
|
async loadMigrations(options) {
|
||||||
const { directory, context, namespace } = options;
|
const { directory, context, namespace } = options;
|
||||||
const migrations = {
|
const migrations = {
|
||||||
@ -594,6 +670,9 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
return migrations;
|
return migrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async loadCoreMigrations() {
|
async loadCoreMigrations() {
|
||||||
const migrations = await this.loadMigrations({
|
const migrations = await this.loadMigrations({
|
||||||
directory: resolve(__dirname, 'migrations'),
|
directory: resolve(__dirname, 'migrations'),
|
||||||
@ -624,11 +703,17 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async loadPluginCommands() {
|
async loadPluginCommands() {
|
||||||
this.log.debug('load plugin commands');
|
this.log.debug('load plugin commands');
|
||||||
await this.pm.loadCommands();
|
await this.pm.loadCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async runAsCLI(
|
async runAsCLI(
|
||||||
argv = process.argv,
|
argv = process.argv,
|
||||||
options?: ParseOptions & { throwError?: boolean; reqId?: string },
|
options?: ParseOptions & { throwError?: boolean; reqId?: string },
|
||||||
@ -732,6 +817,9 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
this.stopped = false;
|
this.stopped = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async emitStartedEvent(options: StartOptions = {}) {
|
async emitStartedEvent(options: StartOptions = {}) {
|
||||||
await this.emitAsync('__started', this, {
|
await this.emitAsync('__started', this, {
|
||||||
maintainingStatus: lodash.cloneDeep(this._maintainingCommandStatus),
|
maintainingStatus: lodash.cloneDeep(this._maintainingCommandStatus),
|
||||||
@ -743,6 +831,9 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
return this._started;
|
return this._started;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async tryReloadOrRestart(options: StartOptions = {}) {
|
async tryReloadOrRestart(options: StartOptions = {}) {
|
||||||
if (this._started) {
|
if (this._started) {
|
||||||
await this.restart(options);
|
await this.restart(options);
|
||||||
@ -941,6 +1032,9 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
reInitEvents() {
|
reInitEvents() {
|
||||||
for (const eventName of this.eventNames()) {
|
for (const eventName of this.eventNames()) {
|
||||||
for (const listener of this.listeners(eventName)) {
|
for (const listener of this.listeners(eventName)) {
|
||||||
@ -986,7 +1080,7 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
|
|
||||||
this._cronJobManager = new CronJobManager(this);
|
this._cronJobManager = new CronJobManager(this);
|
||||||
|
|
||||||
this._cli = this.createCli();
|
this._cli = this.createCLI();
|
||||||
this._i18n = createI18n(options);
|
this._i18n = createI18n(options);
|
||||||
this.context.db = this.db;
|
this.context.db = this.db;
|
||||||
|
|
||||||
|
@ -67,6 +67,9 @@ export class Locale {
|
|||||||
|
|
||||||
async getCacheResources(lang: string) {
|
async getCacheResources(lang: string) {
|
||||||
this.resourceCached.set(lang, true);
|
this.resourceCached.set(lang, true);
|
||||||
|
if (process.env.APP_ENV !== 'production') {
|
||||||
|
await this.cache.reset();
|
||||||
|
}
|
||||||
return await this.wrapCache(`resources:${lang}`, () => this.getResources(lang));
|
return await this.wrapCache(`resources:${lang}`, () => this.getResources(lang));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,10 @@ export const getResource = (packageName: string, lang: string, isPlugin = true)
|
|||||||
for (const prefix of prefixes) {
|
for (const prefix of prefixes) {
|
||||||
try {
|
try {
|
||||||
const file = `${packageName}/${prefix}/locale/${lang}`;
|
const file = `${packageName}/${prefix}/locale/${lang}`;
|
||||||
require.resolve(file);
|
const f = require.resolve(file);
|
||||||
|
if (process.env.APP_ENV !== 'production') {
|
||||||
|
delete require.cache[f];
|
||||||
|
}
|
||||||
const resource = requireModule(file);
|
const resource = requireModule(file);
|
||||||
resources.push(resource);
|
resources.push(resource);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -3,12 +3,21 @@ import lodash from 'lodash';
|
|||||||
import { PluginManager } from './plugin-manager';
|
import { PluginManager } from './plugin-manager';
|
||||||
|
|
||||||
export class PluginManagerRepository extends Repository {
|
export class PluginManagerRepository extends Repository {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
pm: PluginManager;
|
pm: PluginManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
setPluginManager(pm: PluginManager) {
|
setPluginManager(pm: PluginManager) {
|
||||||
this.pm = pm;
|
this.pm = pm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
async remove(name: string | string[]) {
|
async remove(name: string | string[]) {
|
||||||
await this.destroy({
|
await this.destroy({
|
||||||
filter: {
|
filter: {
|
||||||
@ -17,6 +26,9 @@ export class PluginManagerRepository extends Repository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
async enable(name: string | string[]) {
|
async enable(name: string | string[]) {
|
||||||
const pluginNames = lodash.castArray(name);
|
const pluginNames = lodash.castArray(name);
|
||||||
const plugins = pluginNames.map((name) => this.pm.get(name));
|
const plugins = pluginNames.map((name) => this.pm.get(name));
|
||||||
@ -56,6 +68,9 @@ export class PluginManagerRepository extends Repository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
async disable(name: string | string[]) {
|
async disable(name: string | string[]) {
|
||||||
name = lodash.cloneDeep(name);
|
name = lodash.cloneDeep(name);
|
||||||
|
|
||||||
|
@ -45,12 +45,39 @@ export interface InstallOptions {
|
|||||||
export class AddPresetError extends Error {}
|
export class AddPresetError extends Error {}
|
||||||
|
|
||||||
export class PluginManager {
|
export class PluginManager {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
app: Application;
|
app: Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
collection: Collection;
|
collection: Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
pluginInstances = new Map<typeof Plugin, Plugin>();
|
pluginInstances = new Map<typeof Plugin, Plugin>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
pluginAliases = new Map<string, Plugin>();
|
pluginAliases = new Map<string, Plugin>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
server: net.Server;
|
server: net.Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_repository: PluginManagerRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
constructor(public options: PluginManagerOptions) {
|
constructor(public options: PluginManagerOptions) {
|
||||||
this.app = options.app;
|
this.app = options.app;
|
||||||
this.app.db.registerRepositories({
|
this.app.db.registerRepositories({
|
||||||
@ -76,18 +103,22 @@ export class PluginManager {
|
|||||||
this.app.resourcer.use(uploadMiddleware);
|
this.app.resourcer.use(uploadMiddleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
_repository: PluginManagerRepository;
|
|
||||||
|
|
||||||
get repository() {
|
get repository() {
|
||||||
return this.app.db.getRepository('applicationPlugins') as PluginManagerRepository;
|
return this.app.db.getRepository('applicationPlugins') as PluginManagerRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
static async getPackageJson(packageName: string) {
|
static async getPackageJson(packageName: string) {
|
||||||
const file = await fs.promises.realpath(resolve(process.env.NODE_MODULES_PATH, packageName, 'package.json'));
|
const file = await fs.promises.realpath(resolve(process.env.NODE_MODULES_PATH, packageName, 'package.json'));
|
||||||
const data = await fs.promises.readFile(file, { encoding: 'utf-8' });
|
const data = await fs.promises.readFile(file, { encoding: 'utf-8' });
|
||||||
return JSON.parse(data);
|
return JSON.parse(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
static async getPackageName(name: string) {
|
static async getPackageName(name: string) {
|
||||||
const prefixes = this.getPluginPkgPrefix();
|
const prefixes = this.getPluginPkgPrefix();
|
||||||
for (const prefix of prefixes) {
|
for (const prefix of prefixes) {
|
||||||
@ -100,12 +131,18 @@ export class PluginManager {
|
|||||||
throw new Error(`${name} plugin does not exist`);
|
throw new Error(`${name} plugin does not exist`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
static getPluginPkgPrefix() {
|
static getPluginPkgPrefix() {
|
||||||
return (process.env.PLUGIN_PACKAGE_PREFIX || '@nocobase/plugin-,@nocobase/preset-,@nocobase/plugin-pro-').split(
|
return (process.env.PLUGIN_PACKAGE_PREFIX || '@nocobase/plugin-,@nocobase/preset-,@nocobase/plugin-pro-').split(
|
||||||
',',
|
',',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
static async findPackage(name: string) {
|
static async findPackage(name: string) {
|
||||||
try {
|
try {
|
||||||
const packageName = this.getPackageName(name);
|
const packageName = this.getPackageName(name);
|
||||||
@ -130,6 +167,9 @@ export class PluginManager {
|
|||||||
throw new Error(`No available packages found, ${name} plugin does not exist`);
|
throw new Error(`No available packages found, ${name} plugin does not exist`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
static clearCache(packageName: string) {
|
static clearCache(packageName: string) {
|
||||||
return;
|
return;
|
||||||
const packageNamePath = packageName.replace('/', sep);
|
const packageNamePath = packageName.replace('/', sep);
|
||||||
@ -140,6 +180,9 @@ export class PluginManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
static async resolvePlugin(pluginName: string | typeof Plugin, isUpgrade = false, isPkg = false) {
|
static async resolvePlugin(pluginName: string | typeof Plugin, isUpgrade = false, isPkg = false) {
|
||||||
if (typeof pluginName === 'string') {
|
if (typeof pluginName === 'string') {
|
||||||
const packageName = isPkg ? pluginName : await this.getPackageName(pluginName);
|
const packageName = isPkg ? pluginName : await this.getPackageName(pluginName);
|
||||||
@ -296,11 +339,17 @@ export class PluginManager {
|
|||||||
await instance.afterAdd();
|
await instance.afterAdd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async initPlugins() {
|
async initPlugins() {
|
||||||
await this.initPresetPlugins();
|
await this.initPresetPlugins();
|
||||||
await this.initOtherPlugins();
|
await this.initOtherPlugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async loadCommands() {
|
async loadCommands() {
|
||||||
this.app.log.debug('load commands');
|
this.app.log.debug('load commands');
|
||||||
const items = await this.repository.find({
|
const items = await this.repository.find({
|
||||||
@ -619,6 +668,9 @@ export class PluginManager {
|
|||||||
await execa('yarn', ['nocobase', 'refresh']);
|
await execa('yarn', ['nocobase', 'refresh']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
async loadOne(plugin: Plugin) {
|
async loadOne(plugin: Plugin) {
|
||||||
this.app.setMaintainingMessage(`loading plugin ${plugin.name}...`);
|
this.app.setMaintainingMessage(`loading plugin ${plugin.name}...`);
|
||||||
if (plugin.state.loaded || !plugin.enabled) {
|
if (plugin.state.loaded || !plugin.enabled) {
|
||||||
@ -637,6 +689,9 @@ export class PluginManager {
|
|||||||
this.app.setMaintainingMessage(`loaded plugin ${plugin.name}`);
|
this.app.setMaintainingMessage(`loaded plugin ${plugin.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async addViaCLI(urlOrName: string, options?: PluginData) {
|
async addViaCLI(urlOrName: string, options?: PluginData) {
|
||||||
if (isURL(urlOrName)) {
|
if (isURL(urlOrName)) {
|
||||||
await this.addByCompressedFileUrl({
|
await this.addByCompressedFileUrl({
|
||||||
@ -679,6 +734,9 @@ export class PluginManager {
|
|||||||
await execa('yarn', ['nocobase', 'postinstall']);
|
await execa('yarn', ['nocobase', 'postinstall']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async addByNpm(options: { packageName: string; name?: string; registry: string; authToken?: string }) {
|
async addByNpm(options: { packageName: string; name?: string; registry: string; authToken?: string }) {
|
||||||
let { name = '', registry, packageName, authToken } = options;
|
let { name = '', registry, packageName, authToken } = options;
|
||||||
name = name.trim();
|
name = name.trim();
|
||||||
@ -693,6 +751,9 @@ export class PluginManager {
|
|||||||
return this.addByCompressedFileUrl({ name, compressedFileUrl, registry, authToken, type: 'npm' });
|
return this.addByCompressedFileUrl({ name, compressedFileUrl, registry, authToken, type: 'npm' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async addByFile(options: { file: string; registry?: string; authToken?: string; type?: string; name?: string }) {
|
async addByFile(options: { file: string; registry?: string; authToken?: string; type?: string; name?: string }) {
|
||||||
const { file, authToken } = options;
|
const { file, authToken } = options;
|
||||||
|
|
||||||
@ -708,6 +769,9 @@ export class PluginManager {
|
|||||||
return this.add(name, { packageName }, true);
|
return this.add(name, { packageName }, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async addByCompressedFileUrl(options: {
|
async addByCompressedFileUrl(options: {
|
||||||
compressedFileUrl: string;
|
compressedFileUrl: string;
|
||||||
registry?: string;
|
registry?: string;
|
||||||
@ -748,6 +812,9 @@ export class PluginManager {
|
|||||||
await this.app.upgrade();
|
await this.app.upgrade();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async upgradeByNpm(values: PluginData) {
|
async upgradeByNpm(values: PluginData) {
|
||||||
const name = values.name;
|
const name = values.name;
|
||||||
const plugin = this.get(name);
|
const plugin = this.get(name);
|
||||||
@ -769,6 +836,9 @@ export class PluginManager {
|
|||||||
return this.upgradeByCompressedFileUrl({ compressedFileUrl, name, version, registry, authToken });
|
return this.upgradeByCompressedFileUrl({ compressedFileUrl, name, version, registry, authToken });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async upgradeByCompressedFileUrl(options: PluginData) {
|
async upgradeByCompressedFileUrl(options: PluginData) {
|
||||||
const { name, compressedFileUrl, authToken } = options;
|
const { name, compressedFileUrl, authToken } = options;
|
||||||
const data = await this.repository.findOne({ filter: { name } });
|
const data = await this.repository.findOne({ filter: { name } });
|
||||||
@ -780,6 +850,9 @@ export class PluginManager {
|
|||||||
await this.add(name, { version, packageName: data.packageName }, true, true);
|
await this.add(name, { version, packageName: data.packageName }, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
getNameByPackageName(packageName: string) {
|
getNameByPackageName(packageName: string) {
|
||||||
const prefixes = PluginManager.getPluginPkgPrefix();
|
const prefixes = PluginManager.getPluginPkgPrefix();
|
||||||
const prefix = prefixes.find((prefix) => packageName.startsWith(prefix));
|
const prefix = prefixes.find((prefix) => packageName.startsWith(prefix));
|
||||||
@ -808,12 +881,18 @@ export class PluginManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async getNpmVersionList(name: string) {
|
async getNpmVersionList(name: string) {
|
||||||
const plugin = this.get(name);
|
const plugin = this.get(name);
|
||||||
const npmInfo = await getNpmInfo(plugin.options.packageName, plugin.options.registry, plugin.options.authToken);
|
const npmInfo = await getNpmInfo(plugin.options.packageName, plugin.options.registry, plugin.options.authToken);
|
||||||
return Object.keys(npmInfo.versions);
|
return Object.keys(npmInfo.versions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async loadPresetMigrations() {
|
async loadPresetMigrations() {
|
||||||
const migrations = {
|
const migrations = {
|
||||||
beforeLoad: [],
|
beforeLoad: [],
|
||||||
@ -854,6 +933,9 @@ export class PluginManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async loadOtherMigrations() {
|
async loadOtherMigrations() {
|
||||||
const migrations = {
|
const migrations = {
|
||||||
beforeLoad: [],
|
beforeLoad: [],
|
||||||
@ -897,6 +979,9 @@ export class PluginManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async loadPresetPlugins() {
|
async loadPresetPlugins() {
|
||||||
await this.initPresetPlugins();
|
await this.initPresetPlugins();
|
||||||
await this.load();
|
await this.load();
|
||||||
@ -931,6 +1016,9 @@ export class PluginManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async initOtherPlugins() {
|
async initOtherPlugins() {
|
||||||
if (this['_initOtherPlugins']) {
|
if (this['_initOtherPlugins']) {
|
||||||
return;
|
return;
|
||||||
@ -939,6 +1027,9 @@ export class PluginManager {
|
|||||||
this['_initOtherPlugins'] = true;
|
this['_initOtherPlugins'] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async initPresetPlugins() {
|
async initPresetPlugins() {
|
||||||
if (this['_initPresetPlugins']) {
|
if (this['_initPresetPlugins']) {
|
||||||
return;
|
return;
|
||||||
|
@ -33,9 +33,22 @@ export interface PluginOptions {
|
|||||||
export abstract class Plugin<O = any> implements PluginInterface {
|
export abstract class Plugin<O = any> implements PluginInterface {
|
||||||
options: any;
|
options: any;
|
||||||
app: Application;
|
app: Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
model: Model;
|
model: Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
state: any = {};
|
state: any = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
private _sourceDir: string;
|
||||||
|
|
||||||
constructor(app: Application, options?: any) {
|
constructor(app: Application, options?: any) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.setOptions(options);
|
this.setOptions(options);
|
||||||
@ -80,10 +93,6 @@ export abstract class Plugin<O = any> implements PluginInterface {
|
|||||||
return this.options.isPreset;
|
return this.options.isPreset;
|
||||||
}
|
}
|
||||||
|
|
||||||
setOptions(options: any) {
|
|
||||||
this.options = options || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
return (this.options as any).name;
|
return (this.options as any).name;
|
||||||
}
|
}
|
||||||
@ -92,64 +101,6 @@ export abstract class Plugin<O = any> implements PluginInterface {
|
|||||||
return this.app.createLogger(options);
|
return this.app.createLogger(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _sourceDir: string;
|
|
||||||
|
|
||||||
protected async getSourceDir() {
|
|
||||||
if (this._sourceDir) {
|
|
||||||
return this._sourceDir;
|
|
||||||
}
|
|
||||||
if (await this.isDev()) {
|
|
||||||
return (this._sourceDir = 'src');
|
|
||||||
}
|
|
||||||
if (basename(__dirname) === 'src') {
|
|
||||||
return (this._sourceDir = 'src');
|
|
||||||
}
|
|
||||||
return (this._sourceDir = this.isPreset ? 'lib' : 'dist');
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadCommands() {
|
|
||||||
const extensions = ['js', 'ts'];
|
|
||||||
const directory = resolve(
|
|
||||||
process.env.NODE_MODULES_PATH,
|
|
||||||
this.options.packageName,
|
|
||||||
await this.getSourceDir(),
|
|
||||||
'server/commands',
|
|
||||||
);
|
|
||||||
const patten = `${directory}/*.{${extensions.join(',')}}`;
|
|
||||||
const files = glob.sync(patten, {
|
|
||||||
ignore: ['**/*.d.ts'],
|
|
||||||
});
|
|
||||||
for (const file of files) {
|
|
||||||
let filename = basename(file);
|
|
||||||
filename = filename.substring(0, filename.lastIndexOf('.')) || filename;
|
|
||||||
const callback = await importModule(file);
|
|
||||||
callback(this.app);
|
|
||||||
}
|
|
||||||
if (files.length) {
|
|
||||||
this.app.log.debug(`load commands [${this.name}]`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadMigrations() {
|
|
||||||
this.app.log.debug(`load plugin migrations [${this.name}]`);
|
|
||||||
if (!this.options.packageName) {
|
|
||||||
return { beforeLoad: [], afterSync: [], afterLoad: [] };
|
|
||||||
}
|
|
||||||
const directory = resolve(
|
|
||||||
process.env.NODE_MODULES_PATH,
|
|
||||||
this.options.packageName,
|
|
||||||
await this.getSourceDir(),
|
|
||||||
'server/migrations',
|
|
||||||
);
|
|
||||||
return await this.app.loadMigrations({
|
|
||||||
directory,
|
|
||||||
namespace: this.options.packageName,
|
|
||||||
context: {
|
|
||||||
plugin: this,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
afterAdd() {}
|
afterAdd() {}
|
||||||
|
|
||||||
beforeLoad() {}
|
beforeLoad() {}
|
||||||
@ -172,13 +123,86 @@ export abstract class Plugin<O = any> implements PluginInterface {
|
|||||||
|
|
||||||
async afterRemove() {}
|
async afterRemove() {}
|
||||||
|
|
||||||
async importCollections(collectionsPath: string) {
|
/**
|
||||||
// await this.db.import({
|
* @deprecated
|
||||||
// directory: collectionsPath,
|
*/
|
||||||
// from: `plugin:${this.getName()}`,
|
async importCollections(collectionsPath: string) {}
|
||||||
// });
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
setOptions(options: any) {
|
||||||
|
this.options = options || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected async getSourceDir() {
|
||||||
|
if (this._sourceDir) {
|
||||||
|
return this._sourceDir;
|
||||||
|
}
|
||||||
|
if (await this.isDev()) {
|
||||||
|
return (this._sourceDir = 'src');
|
||||||
|
}
|
||||||
|
if (basename(__dirname) === 'src') {
|
||||||
|
return (this._sourceDir = 'src');
|
||||||
|
}
|
||||||
|
return (this._sourceDir = this.isPreset ? 'lib' : 'dist');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
async loadCommands() {
|
||||||
|
const extensions = ['js', 'ts'];
|
||||||
|
const directory = resolve(
|
||||||
|
process.env.NODE_MODULES_PATH,
|
||||||
|
this.options.packageName,
|
||||||
|
await this.getSourceDir(),
|
||||||
|
'server/commands',
|
||||||
|
);
|
||||||
|
const patten = `${directory}/*.{${extensions.join(',')}}`;
|
||||||
|
const files = glob.sync(patten, {
|
||||||
|
ignore: ['**/*.d.ts'],
|
||||||
|
});
|
||||||
|
for (const file of files) {
|
||||||
|
let filename = basename(file);
|
||||||
|
filename = filename.substring(0, filename.lastIndexOf('.')) || filename;
|
||||||
|
const callback = await importModule(file);
|
||||||
|
callback(this.app);
|
||||||
|
}
|
||||||
|
if (files.length) {
|
||||||
|
this.app.log.debug(`load commands [${this.name}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
async loadMigrations() {
|
||||||
|
this.app.log.debug(`load plugin migrations [${this.name}]`);
|
||||||
|
if (!this.options.packageName) {
|
||||||
|
return { beforeLoad: [], afterSync: [], afterLoad: [] };
|
||||||
|
}
|
||||||
|
const directory = resolve(
|
||||||
|
process.env.NODE_MODULES_PATH,
|
||||||
|
this.options.packageName,
|
||||||
|
await this.getSourceDir(),
|
||||||
|
'server/migrations',
|
||||||
|
);
|
||||||
|
return await this.app.loadMigrations({
|
||||||
|
directory,
|
||||||
|
namespace: this.options.packageName,
|
||||||
|
context: {
|
||||||
|
plugin: this,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
async loadCollections() {
|
async loadCollections() {
|
||||||
if (!this.options.packageName) {
|
if (!this.options.packageName) {
|
||||||
return;
|
return;
|
||||||
@ -197,6 +221,9 @@ export abstract class Plugin<O = any> implements PluginInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
requiredPlugins() {
|
requiredPlugins() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -205,6 +232,9 @@ export abstract class Plugin<O = any> implements PluginInterface {
|
|||||||
return this.app.i18n.t(text, { ns: this.options['packageName'], ...(options as any) });
|
return this.app.i18n.t(text, { ns: this.options['packageName'], ...(options as any) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
protected async isDev() {
|
protected async isDev() {
|
||||||
if (!this.options.packageName) {
|
if (!this.options.packageName) {
|
||||||
return false;
|
return false;
|
||||||
@ -218,6 +248,9 @@ export abstract class Plugin<O = any> implements PluginInterface {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
async toJSON(options: any = {}) {
|
async toJSON(options: any = {}) {
|
||||||
const { locale = 'en-US' } = options;
|
const { locale = 'en-US' } = options;
|
||||||
const { name, packageName, packageJson } = this.options;
|
const { name, packageName, packageJson } = this.options;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/telemetry",
|
"name": "@nocobase/telemetry",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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.16",
|
"@nocobase/utils": "0.20.0-alpha.17",
|
||||||
"@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",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/test",
|
"name": "@nocobase/test",
|
||||||
"version": "0.20.0-alpha.16",
|
"version": "0.20.0-alpha.17",
|
||||||
"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,9 +40,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@faker-js/faker": "8.1.0",
|
"@faker-js/faker": "8.1.0",
|
||||||
"@nocobase/server": "0.20.0-alpha.16",
|
"@nocobase/server": "0.20.0-alpha.17",
|
||||||
"@playwright/test": "^1.42.1",
|
"@playwright/test": "^1.42.1",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
|
"@testing-library/react-hooks": "^8.0.1",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
"@types/supertest": "^2.0.11",
|
"@types/supertest": "^2.0.11",
|
||||||
"@vitejs/plugin-react": "^4.0.0",
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
@ -56,7 +57,7 @@
|
|||||||
"sqlite3": "^5.0.8",
|
"sqlite3": "^5.0.8",
|
||||||
"supertest": "^6.1.6",
|
"supertest": "^6.1.6",
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
"vitest": "^1.0.0",
|
"vitest": "^1.4.0",
|
||||||
"vitest-dom": "^0.1.1",
|
"vitest-dom": "^0.1.1",
|
||||||
"ws": "^8.13.0"
|
"ws": "^8.13.0"
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
|
export { renderHook } from '@testing-library/react-hooks';
|
||||||
|
|
||||||
function customRender(ui: React.ReactElement, options = {}) {
|
function customRender(ui: React.ReactElement, options = {}) {
|
||||||
return render(ui, {
|
return render(ui, {
|
||||||
|
@ -21,7 +21,7 @@ export const defineConfig = (config?: PlaywrightTestConfig) => {
|
|||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
|
|
||||||
// Retry on CI only.
|
// Retry on CI only.
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: 2,
|
||||||
|
|
||||||
// Opt out of parallel tests on CI.
|
// Opt out of parallel tests on CI.
|
||||||
// workers: process.env.CI ? 1 : undefined,
|
// workers: process.env.CI ? 1 : undefined,
|
||||||
@ -52,7 +52,14 @@ export const defineConfig = (config?: PlaywrightTestConfig) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'chromium',
|
name: 'chromium',
|
||||||
use: { ...devices['Desktop Chrome'], storageState: process.env.PLAYWRIGHT_AUTH_FILE },
|
use: {
|
||||||
|
...devices['Desktop Chrome'],
|
||||||
|
storageState: process.env.PLAYWRIGHT_AUTH_FILE,
|
||||||
|
contextOptions: {
|
||||||
|
// chromium-specific permissions
|
||||||
|
permissions: ['clipboard-read', 'clipboard-write'],
|
||||||
|
},
|
||||||
|
},
|
||||||
dependencies: ['authSetup'],
|
dependencies: ['authSetup'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user