feat(variable): lazy load association fields (#2382)
* refactor: remove useless code * feat: should always show user variable * style: optimize style of variable input * feat: change maxDepth of user varialbe to 3 * refactor: migrate getFieldDefaultValue to SchemaSettings.tsx * feat: support to parse variables * feat: support to parse array fields * refactor: make testing more stable * chore: fix CI * feat: support to parse current user * fix: fix tests * feat: support to register variable * feat: support to parse "$context" * refactor: remove useless code * fix: fix build * fix: fix build * test: make stable * docs: add doc * fix: fix T-1267 * fix: fix T-1268 * fix: fix T-1269 * fix: fix T-1271 * fix: fix T-1274 * refactor: improve code * feat(variable): support local variable * fix: fix T-1280 * refactor: close T-881 * refactor: remove useLazyLoadAssociation * fix: fix T-1289,T-1290 * fix: fix T-1296 * refactor(AssignedField): replace user variable * refactor: extract useBaseVariable * refactor(AssignedField): replace variables * feat(useVariables): support to use array as local variables * refactor: improve variables (T-1295) * feat: limit user choice (T-1297) * fix: fix T-1305 * refactor: extract as SchemaSettings.DataScope * refactor: improve linkage rules * refactor: improve linkage rules of Action * chore: fix build * refactor: improve data scope of association fields * chore: fix build * feat: select limit * refactor: use VariableInput to replace other * fix: fix can not input * fix(AssignedField): fix cannot close variables list * test: fix errors * refactor: migrate to new file * fix: uniq * fix: filter empty values * fix: transform variables value * fix: should sum numbers (T-1330) * fix: fix Select (T-1337) * refactor: use useMemo * refactor: use hooks * refactor: use hooks * refactor: use hooks * refactor: remove useless code * refactor: rename `$form` to `$nForm` and `$record` to `$nRecord` * fix: fix T-1334 * refactor(LinkageRuleActionGroup): use hooks * refactor(FormButtonLinkageRuleAction): use hooks * refactor: add type * fix: avoid crashing * refactor: rename `$date` to `$nDate` * fix: fix T-1348 * perf: avoid duplicate requests * test: fix error * fix: fix T-1355 * fix: fix T-1355 * fix: fix T-1345 * fix: fix T-1346 * refactor(useVariableOptions): replace query with parameter * fix: fix T-1335 * chore: fix build * fix: fix T-1453 * refactor: remove useless code * fix: fix T-1447 * fix: should update when seting default value * fix: fix T-1454 * chore(eslint): ignore docker * fix: fix T-1440 * refactor: use hooks * fix: fix T-1332 * fix(defaultValue): should use Select mode * fix(defaultValue): fix Checkbox * refactor: rename and comment * refactor: rename file name and hook name * feat(subForm): support lazy load association fields * fix: filter empty values * fix(defaultValue): schema type of Checkbox should be void * fix: fix T-1479 * fix: fix T-1491 * fix(ToOneNester): should use RecordProvider * fix: fix T-1493 * chore: add comment * fix: fix T-1495 * chore: fix build * test: make pass * fix: fix T-1503 * fix: fix T-1504 * fix: fix transformVariableValue * fix: disable unsupport options * refactor(useVariableOptions): add noDisabled params * fix: data scope support to select to-many fields * chore: add comment * refactor: improve default value * refactor: extract as useIsAllowToSetDefaultValue * fix: should not support to set default value in edit mode * fix: only the default values for sub-forms and sub-tables are valid * fix: limit default value of association fields * fix: fix '$nForm' variable * fix: allow all inherit chain * fix: optimize default value * fix: optimize json field * fix(currentObject): avoid error * fix: should not show variable string * fix: active fields * fix: avoid errors * feat: support special case * fix: fix CI * chore: fix build * fix: json * refactor: rename flag name * fix: fix T-1559 * fix: fix T-1515 * fix: avoid crash * fix: fix T-1520 * fix: fix T-1521 * fix: fix T-1523 * fix: fix T-1563 * fix: fix T-1565 * fix: fix T-1525 * fix: fix T-1524 * fix: fix T-1603 * fix: fix T-1332 * fix: fix T-1559 * fix: fix T-1602 * fix: fix T-1608 * fix: fix T-1612 T-1609 * fix: fix T-1681 * fix: fix T-1711 * fix: fix T-1713 * fix: fix T-1680 * chore: close T-1550 * fix: fix T-1507 * chore: avoid crashing * fix: fix T-1557 * perf: close T-1726 * fix: fix T-1727 * fix: fix T-1741 * fix: fix T-1740 * chore(workflow): compact * chore: should not to support to set default value in assign field values * perf: compat and perf (T-1736) * fix: fix T-1872 * fix: fix T-1877 * fix: fix quarter variables * chore: add README * chore: front-end should not depend on back-end code * chore: should disable all variable options if no field is selected * chore: should be a leaf if it has no children * chore: fix build error * fix: compat $form * fix: compat $form * chore: fix build * chore: fix build * fix: typeError: Cannot read properties of undefined (reading 'template') * chore: fix test * chore: add test * fix: fix T-2072 * chore: optimize eslint * perf: reduce number of requests * fix: fix JSON * test: fix error * fix(e2e): avoid error --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
c4f572fda8
commit
573cde0a21
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -30,7 +30,7 @@
|
|||||||
"name": "Debug Client Tests",
|
"name": "Debug Client Tests",
|
||||||
"runtimeExecutable": "yarn",
|
"runtimeExecutable": "yarn",
|
||||||
"runtimeArgs": ["run", "test:client", "${file}"],
|
"runtimeArgs": ["run", "test:client", "${file}"],
|
||||||
"skipFiles": ["<node_internals>/**"],
|
"skipFiles": ["<node_internals>/**", "**/node_modules/**", "**/dist/**", "**/lib/**", "**/es/**"],
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"internalConsoleOptions": "neverOpen",
|
"internalConsoleOptions": "neverOpen",
|
||||||
"windows": {
|
"windows": {
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import path from 'path';
|
import react from '@vitejs/plugin-react';
|
||||||
import fg from 'fast-glob';
|
import fg from 'fast-glob';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { build as viteBuild } from 'vite';
|
import path from 'path';
|
||||||
import { build as tsupBuild } from 'tsup';
|
import { build as tsupBuild } from 'tsup';
|
||||||
|
import { build as viteBuild } from 'vite';
|
||||||
import { libInjectCss } from 'vite-plugin-lib-inject-css';
|
import { libInjectCss } from 'vite-plugin-lib-inject-css';
|
||||||
import react from '@vitejs/plugin-react';
|
|
||||||
|
|
||||||
import { PkgLog, UserConfig } from './utils';
|
|
||||||
import { globExcludeFiles } from './constant';
|
import { globExcludeFiles } from './constant';
|
||||||
|
import { PkgLog, UserConfig } from './utils';
|
||||||
|
|
||||||
export async function buildClient(cwd: string, userConfig: UserConfig, sourcemap: boolean = false, log: PkgLog) {
|
export async function buildClient(cwd: string, userConfig: UserConfig, sourcemap: boolean = false, log: PkgLog) {
|
||||||
log('build client');
|
log('build client');
|
||||||
@ -19,7 +18,7 @@ export async function buildClient(cwd: string, userConfig: UserConfig, sourcemap
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
};
|
||||||
await buildEsm(cwd, userConfig, sourcemap, external, log);
|
await buildEsm(cwd, userConfig, sourcemap, external, log);
|
||||||
await buildLib(cwd, userConfig, sourcemap, external, log);
|
await buildLib(cwd, userConfig, sourcemap, external, log);
|
||||||
await buildLocale(cwd, userConfig, log);
|
await buildLocale(cwd, userConfig, log);
|
||||||
@ -31,10 +30,12 @@ export function buildEsm(cwd: string, userConfig: UserConfig, sourcemap: boolean
|
|||||||
log('build client esm');
|
log('build client esm');
|
||||||
const entry = path.join(cwd, 'src/index.ts').replaceAll(/\\/g, '/');
|
const entry = path.join(cwd, 'src/index.ts').replaceAll(/\\/g, '/');
|
||||||
const outDir = path.resolve(cwd, 'es');
|
const outDir = path.resolve(cwd, 'es');
|
||||||
return viteBuild(userConfig.modifyViteConfig({
|
return viteBuild(
|
||||||
|
userConfig.modifyViteConfig({
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
define: {
|
define: {
|
||||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||||
|
'process.env.__TEST__': false,
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
minify: false,
|
minify: false,
|
||||||
@ -55,22 +56,30 @@ export function buildEsm(cwd: string, userConfig: UserConfig, sourcemap: boolean
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [react(), libInjectCss()],
|
plugins: [react(), libInjectCss()],
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function buildLib(cwd: string, userConfig: UserConfig, sourcemap: boolean, external: External, log: PkgLog) {
|
export async function buildLib(
|
||||||
|
cwd: string,
|
||||||
|
userConfig: UserConfig,
|
||||||
|
sourcemap: boolean,
|
||||||
|
external: External,
|
||||||
|
log: PkgLog,
|
||||||
|
) {
|
||||||
log('build client lib');
|
log('build client lib');
|
||||||
const outDir = path.resolve(cwd, 'lib');
|
const outDir = path.resolve(cwd, 'lib');
|
||||||
const esDir = path.resolve(cwd, 'es');
|
const esDir = path.resolve(cwd, 'es');
|
||||||
const entry = path.join(esDir, 'index.ts')
|
const entry = path.join(esDir, 'index.ts');
|
||||||
|
|
||||||
fs.removeSync(entry);
|
fs.removeSync(entry);
|
||||||
fs.linkSync(path.join(cwd, 'es/index.mjs'), entry);
|
fs.linkSync(path.join(cwd, 'es/index.mjs'), entry);
|
||||||
|
|
||||||
await viteBuild(userConfig.modifyViteConfig({
|
await viteBuild(
|
||||||
|
userConfig.modifyViteConfig({
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
esbuild: {
|
esbuild: {
|
||||||
format: 'cjs'
|
format: 'cjs',
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir,
|
outDir,
|
||||||
@ -85,7 +94,8 @@ export async function buildLib(cwd: string, userConfig: UserConfig, sourcemap: b
|
|||||||
external,
|
external,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
fs.removeSync(entry);
|
fs.removeSync(entry);
|
||||||
|
|
||||||
@ -100,7 +110,8 @@ export function buildLocale(cwd: string, userConfig: UserConfig, log: PkgLog) {
|
|||||||
|
|
||||||
const entry = fg.globSync(['src/locale/**', ...globExcludeFiles], { cwd, absolute: true });
|
const entry = fg.globSync(['src/locale/**', ...globExcludeFiles], { cwd, absolute: true });
|
||||||
const outDir = path.resolve(cwd, 'lib', 'locale');
|
const outDir = path.resolve(cwd, 'lib', 'locale');
|
||||||
return tsupBuild(userConfig.modifyTsupConfig({
|
return tsupBuild(
|
||||||
|
userConfig.modifyTsupConfig({
|
||||||
entry,
|
entry,
|
||||||
splitting: false,
|
splitting: false,
|
||||||
clean: false,
|
clean: false,
|
||||||
@ -112,5 +123,6 @@ export function buildLocale(cwd: string, userConfig: UserConfig, log: PkgLog) {
|
|||||||
outDir,
|
outDir,
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
skipNodeModulesBundle: true,
|
skipNodeModulesBundle: true,
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -303,6 +303,7 @@ export async function buildPluginClient(cwd: string, userConfig: UserConfig, sou
|
|||||||
mode: 'production',
|
mode: 'production',
|
||||||
define: {
|
define: {
|
||||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||||
|
'process.env.__TEST__': false,
|
||||||
},
|
},
|
||||||
logLevel: 'warn',
|
logLevel: 'warn',
|
||||||
build: {
|
build: {
|
||||||
|
803
packages/core/client/src/__tests__/e2e/variables.test.ts
Normal file
803
packages/core/client/src/__tests__/e2e/variables.test.ts
Normal file
@ -0,0 +1,803 @@
|
|||||||
|
import { test } from '@nocobase/test/client';
|
||||||
|
|
||||||
|
const t2072Config = {
|
||||||
|
collections: [
|
||||||
|
{
|
||||||
|
key: 'etlrv6xojl4',
|
||||||
|
name: 't_cyjvg3xxak7',
|
||||||
|
title: '人工>组织[普通表]',
|
||||||
|
inherit: false,
|
||||||
|
hidden: false,
|
||||||
|
description: null,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'e4jfv35cotm',
|
||||||
|
name: 'id',
|
||||||
|
type: 'bigInt',
|
||||||
|
interface: 'id',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_cyjvg3xxak7',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: '{{t("ID")}}',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '778zti7timg',
|
||||||
|
name: 'f_jnf1x0qxsnz',
|
||||||
|
type: 'bigInt',
|
||||||
|
interface: 'integer',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_cyjvg3xxak7',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
isForeignKey: true,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: 'f_jnf1x0qxsnz',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2nuvtsxlzvh',
|
||||||
|
name: 'f_zx8j9897cv5',
|
||||||
|
type: 'bigInt',
|
||||||
|
interface: 'integer',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_cyjvg3xxak7',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
isForeignKey: true,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: 'f_zx8j9897cv5',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'q491jgqygpg',
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'date',
|
||||||
|
interface: 'createdAt',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_cyjvg3xxak7',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
field: 'createdAt',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'datetime',
|
||||||
|
title: '{{t("Created at")}}',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'q2uungrabre',
|
||||||
|
name: 'createdBy',
|
||||||
|
type: 'belongsTo',
|
||||||
|
interface: 'createdBy',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_cyjvg3xxak7',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
target: 'users',
|
||||||
|
foreignKey: 'createdById',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'object',
|
||||||
|
title: '{{t("Created by")}}',
|
||||||
|
'x-component': 'AssociationField',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
value: 'id',
|
||||||
|
label: 'nickname',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
targetKey: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'cizz53lon1q',
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'date',
|
||||||
|
interface: 'updatedAt',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_cyjvg3xxak7',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
field: 'updatedAt',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Last updated at")}}',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '9dd8bohloh2',
|
||||||
|
name: 'updatedBy',
|
||||||
|
type: 'belongsTo',
|
||||||
|
interface: 'updatedBy',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_cyjvg3xxak7',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
target: 'users',
|
||||||
|
foreignKey: 'updatedById',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'object',
|
||||||
|
title: '{{t("Last updated by")}}',
|
||||||
|
'x-component': 'AssociationField',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
value: 'id',
|
||||||
|
label: 'nickname',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
targetKey: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'p7649gl5tic',
|
||||||
|
name: 'f_wrlootg9xu9',
|
||||||
|
type: 'hasMany',
|
||||||
|
interface: 'o2m',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_cyjvg3xxak7',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
foreignKey: 'f_jnf1x0qxsnz',
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component': 'AssociationField',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: true,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'id',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: '部门(one to many)',
|
||||||
|
},
|
||||||
|
target: 't_cyjvg3xxak7',
|
||||||
|
targetKey: 'id',
|
||||||
|
sourceKey: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '6etvnvsplrv',
|
||||||
|
name: 'f_4xulfnosvlx',
|
||||||
|
type: 'belongsTo',
|
||||||
|
interface: 'm2o',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_cyjvg3xxak7',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
foreignKey: 'f_zx8j9897cv5',
|
||||||
|
onDelete: 'SET NULL',
|
||||||
|
uiSchema: {
|
||||||
|
'x-component': 'AssociationField',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: false,
|
||||||
|
fieldNames: {
|
||||||
|
label: 'id',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: '负责人(many to one)',
|
||||||
|
},
|
||||||
|
target: 't_ymx99ljfetl',
|
||||||
|
targetKey: 'id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
category: [],
|
||||||
|
logging: true,
|
||||||
|
autoGenId: true,
|
||||||
|
createdBy: true,
|
||||||
|
updatedBy: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
sortable: true,
|
||||||
|
template: 'general',
|
||||||
|
view: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'gmsav2kkbk3',
|
||||||
|
name: 't_ymx99ljfetl',
|
||||||
|
title: '人员',
|
||||||
|
inherit: false,
|
||||||
|
hidden: false,
|
||||||
|
description: null,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'dhonulr1xxc',
|
||||||
|
name: 'id',
|
||||||
|
type: 'bigInt',
|
||||||
|
interface: 'id',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_ymx99ljfetl',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
allowNull: false,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: '{{t("ID")}}',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fh09uvcibsq',
|
||||||
|
name: 'f_qojsgjsrjie',
|
||||||
|
type: 'bigInt',
|
||||||
|
interface: 'integer',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_ymx99ljfetl',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
isForeignKey: true,
|
||||||
|
uiSchema: {
|
||||||
|
type: 'number',
|
||||||
|
title: 'f_qojsgjsrjie',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'xzvwq5g75gi',
|
||||||
|
name: 'createdAt',
|
||||||
|
type: 'date',
|
||||||
|
interface: 'createdAt',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_ymx99ljfetl',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
field: 'createdAt',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'datetime',
|
||||||
|
title: '{{t("Created at")}}',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'oc580r95evm',
|
||||||
|
name: 'createdBy',
|
||||||
|
type: 'belongsTo',
|
||||||
|
interface: 'createdBy',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_ymx99ljfetl',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
target: 'users',
|
||||||
|
foreignKey: 'createdById',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'object',
|
||||||
|
title: '{{t("Created by")}}',
|
||||||
|
'x-component': 'AssociationField',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
value: 'id',
|
||||||
|
label: 'nickname',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
targetKey: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '9zi17bh97yl',
|
||||||
|
name: 'updatedAt',
|
||||||
|
type: 'date',
|
||||||
|
interface: 'updatedAt',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_ymx99ljfetl',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
field: 'updatedAt',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Last updated at")}}',
|
||||||
|
'x-component': 'DatePicker',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2xbcmman6gz',
|
||||||
|
name: 'updatedBy',
|
||||||
|
type: 'belongsTo',
|
||||||
|
interface: 'updatedBy',
|
||||||
|
description: null,
|
||||||
|
collectionName: 't_ymx99ljfetl',
|
||||||
|
parentKey: null,
|
||||||
|
reverseKey: null,
|
||||||
|
target: 'users',
|
||||||
|
foreignKey: 'updatedById',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'object',
|
||||||
|
title: '{{t("Last updated by")}}',
|
||||||
|
'x-component': 'AssociationField',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
value: 'id',
|
||||||
|
label: 'nickname',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
targetKey: 'id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
category: [],
|
||||||
|
logging: true,
|
||||||
|
autoGenId: true,
|
||||||
|
createdBy: true,
|
||||||
|
updatedBy: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
sortable: true,
|
||||||
|
template: 'general',
|
||||||
|
view: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageSchema: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Page',
|
||||||
|
properties: {
|
||||||
|
'3fsvxxy8klh': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'BlockInitializers',
|
||||||
|
properties: {
|
||||||
|
kwm0euv1w2z: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
properties: {
|
||||||
|
'4wk59ijgdzx': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
properties: {
|
||||||
|
vsgx66mbgpm: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableBlockProvider',
|
||||||
|
'x-acl-action': 't_cyjvg3xxak7:list',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection: 't_cyjvg3xxak7',
|
||||||
|
resource: 't_cyjvg3xxak7',
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
rowKey: 'id',
|
||||||
|
showIndex: true,
|
||||||
|
dragSort: false,
|
||||||
|
disableTemplate: false,
|
||||||
|
},
|
||||||
|
'x-designer': 'TableBlockDesigner',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-filter-targets': [],
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-initializer': 'TableActionInitializers',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 'var(--nb-spacing)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
mb64tcykcw3: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Add record")}}',
|
||||||
|
'x-designer': 'Action.Designer',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-action': 'customize:create',
|
||||||
|
'x-component-props': {
|
||||||
|
openMode: 'drawer',
|
||||||
|
icon: 'PlusOutlined',
|
||||||
|
},
|
||||||
|
'x-align': 'right',
|
||||||
|
'x-decorator': 'ACLActionProvider',
|
||||||
|
'x-acl-action': 'create',
|
||||||
|
'x-acl-action-props': {
|
||||||
|
skipScopeCheck: true,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Add 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': 'TabPaneInitializersForCreateFormBlock',
|
||||||
|
properties: {
|
||||||
|
tab1: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Add record")}}',
|
||||||
|
'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': 'CusomeizeCreateFormBlockInitializers',
|
||||||
|
properties: {
|
||||||
|
'4sdqrhx1egh': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
properties: {
|
||||||
|
m1gl7ido4xg: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
properties: {
|
||||||
|
f0srs8s7mq6: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-acl-action-props': {
|
||||||
|
skipScopeCheck: true,
|
||||||
|
},
|
||||||
|
'x-acl-action': 't_cyjvg3xxak7:create',
|
||||||
|
'x-decorator': 'FormBlockProvider',
|
||||||
|
'x-decorator-props': {
|
||||||
|
isCusomeizeCreate: true,
|
||||||
|
resource: 't_cyjvg3xxak7',
|
||||||
|
collection: 't_cyjvg3xxak7',
|
||||||
|
},
|
||||||
|
'x-designer': 'FormV2.Designer',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-component-props': {},
|
||||||
|
properties: {
|
||||||
|
l3kfu43aq17: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'FormV2',
|
||||||
|
'x-component-props': {
|
||||||
|
useProps: '{{ useFormBlockProps }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
grid: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'FormItemInitializers',
|
||||||
|
properties: {
|
||||||
|
j57m9wdhidh: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
properties: {
|
||||||
|
'8l5ut3li3my': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
properties: {
|
||||||
|
f_wrlootg9xu9: {
|
||||||
|
'x-uid': 'enypaahwvzs',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'string',
|
||||||
|
'x-designer': 'FormItem.Designer',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-collection-field':
|
||||||
|
't_cyjvg3xxak7.f_wrlootg9xu9',
|
||||||
|
'x-component-props': {
|
||||||
|
mode: 'SubTable',
|
||||||
|
},
|
||||||
|
default: null,
|
||||||
|
properties: {
|
||||||
|
svaxqhd7v29: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component':
|
||||||
|
'AssociationField.SubTable',
|
||||||
|
'x-initializer':
|
||||||
|
'TableColumnInitializers',
|
||||||
|
'x-initializer-props': {
|
||||||
|
action: false,
|
||||||
|
},
|
||||||
|
'x-index': 1,
|
||||||
|
properties: {
|
||||||
|
eevxwy50bcp: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator':
|
||||||
|
'TableV2.Column.Decorator',
|
||||||
|
'x-designer':
|
||||||
|
'TableV2.Column.Designer',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
properties: {
|
||||||
|
f_4xulfnosvlx: {
|
||||||
|
'x-uid': 'ho3gwsra9qk',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
'x-collection-field':
|
||||||
|
't_cyjvg3xxak7.f_4xulfnosvlx',
|
||||||
|
'x-component':
|
||||||
|
'CollectionField',
|
||||||
|
'x-component-props': {
|
||||||
|
ellipsis: true,
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-decorator-props': {
|
||||||
|
labelStyle: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default:
|
||||||
|
'{{$context.f_4xulfnosvlx}}',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '6ym1xpsxmyf',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'nmbnils3epp',
|
||||||
|
'x-async': false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'e69smhckndt',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'puxwfyhomzf',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'rziak1zqrgj',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-initializer': 'FormActionInitializers',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
layout: 'one-column',
|
||||||
|
style: {
|
||||||
|
marginTop: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'ohkberml3rb',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 't2zwt2anafy',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '9nr7zawsp77',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'sy2eoiiguig',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '3dpkedn8cdb',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'vkzbih8qgqz',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '88pixfaj3wm',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'khgiclrgqhp',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'p5j5303aqdv',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'jcdnkjqfof4',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'wmcdk2bf0rk',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
ooh4p68w3wy: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'array',
|
||||||
|
'x-initializer': 'TableColumnInitializers',
|
||||||
|
'x-component': 'TableV2',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
useProps: '{{ useTableBlockProps }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Actions") }}',
|
||||||
|
'x-action-column': 'actions',
|
||||||
|
'x-decorator': 'TableV2.Column.ActionBar',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-designer': 'TableV2.ActionColumnDesigner',
|
||||||
|
'x-initializer': 'TableActionColumnInitializers',
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'DndContext',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
'x-uid': 'zf80uv8uhol',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'lrb33ra71kx',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
bi9qnla6pey: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-designer': 'TableV2.Column.Designer',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
properties: {
|
||||||
|
f_4xulfnosvlx: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
'x-collection-field': 't_cyjvg3xxak7.f_4xulfnosvlx',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-component-props': {
|
||||||
|
ellipsis: true,
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': null,
|
||||||
|
'x-decorator-props': {
|
||||||
|
labelStyle: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '9a1xgqcn8g8',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'ipt9o4tqqkw',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'a89hczfm5nt',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'sghu92e6os9',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '8bhcc7f8dfb',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'l4nrtmguqpv',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'jfcdfwu92mu',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'cfjrdgvlm68',
|
||||||
|
'x-async': true,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test.describe.skip('variables', () => {
|
||||||
|
test('T-2072', async ({ page, mockPage }) => {
|
||||||
|
await mockPage(t2072Config).goto();
|
||||||
|
|
||||||
|
await page.getByTestId('table-index-1').hover();
|
||||||
|
await page.getByRole('cell', { name: '1' }).getByLabel('').check();
|
||||||
|
await page.getByTestId('table-index-2').hover();
|
||||||
|
await page.getByRole('cell', { name: '2' }).getByLabel('').check();
|
||||||
|
await page.getByTestId('table-index-3').hover();
|
||||||
|
await page.getByRole('cell', { name: '3' }).getByLabel('').check();
|
||||||
|
await page.getByTestId('customize:create-action').click();
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,5 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Field } from '@formily/core';
|
import { Field, GeneralField } from '@formily/core';
|
||||||
import { RecursionField, useField, useFieldSchema } from '@formily/react';
|
import { RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||||
import { useRequest } from 'ahooks';
|
import { useRequest } from 'ahooks';
|
||||||
import { Col, Row } from 'antd';
|
import { Col, Row } from 'antd';
|
||||||
@ -24,7 +24,16 @@ import { useAssociationNames } from './hooks';
|
|||||||
|
|
||||||
export const BlockResourceContext = createContext(null);
|
export const BlockResourceContext = createContext(null);
|
||||||
export const BlockAssociationContext = createContext(null);
|
export const BlockAssociationContext = createContext(null);
|
||||||
export const BlockRequestContext = createContext<any>({});
|
export const BlockRequestContext = createContext<{
|
||||||
|
block?: string;
|
||||||
|
props?: any;
|
||||||
|
field?: GeneralField;
|
||||||
|
service?: any;
|
||||||
|
resource?: any;
|
||||||
|
allowedActions?: any;
|
||||||
|
__parent?: any;
|
||||||
|
updateAssociationValues?: any[];
|
||||||
|
}>({});
|
||||||
|
|
||||||
export const useBlockResource = () => {
|
export const useBlockResource = () => {
|
||||||
return useContext(BlockResourceContext);
|
return useContext(BlockResourceContext);
|
||||||
@ -56,7 +65,7 @@ const useResource = (props: UseResourceProps) => {
|
|||||||
const isCreateAction = fieldSchema?.['x-action'] === 'create';
|
const isCreateAction = fieldSchema?.['x-action'] === 'create';
|
||||||
const association = useAssociation(props);
|
const association = useAssociation(props);
|
||||||
const sourceId = useSourceId?.();
|
const sourceId = useSourceId?.();
|
||||||
const field = useField<Field>();
|
const field = useField();
|
||||||
const withoutTableFieldResource = useContext(WithoutTableFieldResource);
|
const withoutTableFieldResource = useContext(WithoutTableFieldResource);
|
||||||
const __parent = useContext(BlockRequestContext);
|
const __parent = useContext(BlockRequestContext);
|
||||||
if (block === 'TableField') {
|
if (block === 'TableField') {
|
||||||
@ -96,7 +105,7 @@ const useActionParams = (props) => {
|
|||||||
return { ...props.params, ...params };
|
return { ...props.params, ...params };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useResourceAction = (props, opts = {}) => {
|
const useResourceAction = (props, opts = {}) => {
|
||||||
/**
|
/**
|
||||||
* fieldName: 来自 TableFieldProvider
|
* fieldName: 来自 TableFieldProvider
|
||||||
*/
|
*/
|
||||||
@ -168,7 +177,7 @@ export const MaybeCollectionProvider = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const BlockRequestProvider = (props) => {
|
export const BlockRequestProvider = (props) => {
|
||||||
const field = useField();
|
const field = useField<Field>();
|
||||||
const resource = useBlockResource();
|
const resource = useBlockResource();
|
||||||
const [allowedActions, setAllowedActions] = useState({});
|
const [allowedActions, setAllowedActions] = useState({});
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import { RecordProvider, useRecord } from '../record-provider';
|
|||||||
import { useActionContext, useDesignable } from '../schema-component';
|
import { useActionContext, useDesignable } from '../schema-component';
|
||||||
import { Templates as DataTemplateSelect } from '../schema-component/antd/form-v2/Templates';
|
import { Templates as DataTemplateSelect } from '../schema-component/antd/form-v2/Templates';
|
||||||
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
||||||
|
import { FormActiveFieldsProvider } from './hooks';
|
||||||
|
|
||||||
export const FormBlockContext = createContext<any>({});
|
export const FormBlockContext = createContext<any>({});
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ const InternalFormBlockProvider = (props) => {
|
|||||||
createForm({
|
createForm({
|
||||||
readPretty,
|
readPretty,
|
||||||
}),
|
}),
|
||||||
[],
|
[readPretty],
|
||||||
);
|
);
|
||||||
const { resource, service, updateAssociationValues } = useBlockRequestContext();
|
const { resource, service, updateAssociationValues } = useBlockRequestContext();
|
||||||
const formBlockRef = useRef();
|
const formBlockRef = useRef();
|
||||||
@ -27,6 +28,7 @@ const InternalFormBlockProvider = (props) => {
|
|||||||
if (service.loading && Object.keys(form?.initialValues)?.length === 0 && action) {
|
if (service.loading && Object.keys(form?.initialValues)?.length === 0 && action) {
|
||||||
return <Spin />;
|
return <Spin />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormBlockContext.Provider
|
<FormBlockContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@ -83,7 +85,9 @@ export const FormBlockProvider = (props) => {
|
|||||||
return (
|
return (
|
||||||
(detailFlag || createFlag || isCusomeizeCreate) && (
|
(detailFlag || createFlag || isCusomeizeCreate) && (
|
||||||
<BlockProvider data-testid={props['data-testid'] || 'form-block'} {...props} block={'form'}>
|
<BlockProvider data-testid={props['data-testid'] || 'form-block'} {...props} block={'form'}>
|
||||||
|
<FormActiveFieldsProvider name="form">
|
||||||
<InternalFormBlockProvider {...props} />
|
<InternalFormBlockProvider {...props} />
|
||||||
|
</FormActiveFieldsProvider>
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -91,7 +91,7 @@ export const useGanttBlockProps = () => {
|
|||||||
const { getPrimaryKey, name, template, writableView } = useCollection();
|
const { getPrimaryKey, name, template, writableView } = useCollection();
|
||||||
const { parseAction } = useACLRoleContext();
|
const { parseAction } = useACLRoleContext();
|
||||||
const primaryKey = getPrimaryKey();
|
const primaryKey = getPrimaryKey();
|
||||||
const checkPermassion = (record) => {
|
const checkPermission = (record) => {
|
||||||
const actionPath = `${name}:update`;
|
const actionPath = `${name}:update`;
|
||||||
const schema = {};
|
const schema = {};
|
||||||
const recordPkValue = record?.[primaryKey];
|
const recordPkValue = record?.[primaryKey];
|
||||||
@ -106,13 +106,13 @@ export const useGanttBlockProps = () => {
|
|||||||
ctx.field.data = tasksData;
|
ctx.field.data = tasksData;
|
||||||
};
|
};
|
||||||
const expandAndCollapseAll = (flag) => {
|
const expandAndCollapseAll = (flag) => {
|
||||||
const data = formatData(ctx.service.data?.data, ctx.fieldNames, [], undefined, flag, checkPermassion);
|
const data = formatData(ctx.service.data?.data, ctx.fieldNames, [], undefined, flag, checkPermission);
|
||||||
setTasks(data);
|
setTasks(data);
|
||||||
ctx.field.data = data;
|
ctx.field.data = data;
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ctx?.service?.loading) {
|
if (!ctx?.service?.loading) {
|
||||||
const data = formatData(ctx.service.data?.data, ctx.fieldNames, [], undefined, false, checkPermassion);
|
const data = formatData(ctx.service.data?.data, ctx.fieldNames, [], undefined, false, checkPermission);
|
||||||
setTasks(data);
|
setTasks(data);
|
||||||
ctx.field.data = data;
|
ctx.field.data = data;
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,10 @@ interface Props {
|
|||||||
rowKey?: string;
|
rowKey?: string;
|
||||||
childrenColumnName: any;
|
childrenColumnName: any;
|
||||||
fieldNames?: any;
|
fieldNames?: any;
|
||||||
|
/**
|
||||||
|
* Table 区块的 collection name
|
||||||
|
*/
|
||||||
|
collection?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InternalTableBlockProvider = (props: Props) => {
|
const InternalTableBlockProvider = (props: Props) => {
|
||||||
@ -43,6 +47,7 @@ const InternalTableBlockProvider = (props: Props) => {
|
|||||||
return keys || [];
|
return keys || [];
|
||||||
}
|
}
|
||||||
}, [service?.loading]);
|
}, [service?.loading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FixedBlockWrapper>
|
<FixedBlockWrapper>
|
||||||
<TableBlockContext.Provider
|
<TableBlockContext.Provider
|
||||||
@ -94,6 +99,7 @@ export const TableBlockProvider = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const form = useMemo(() => createForm(), [treeTable]);
|
const form = useMemo(() => createForm(), [treeTable]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaComponentOptions scope={{ treeTable }}>
|
<SchemaComponentOptions scope={{ treeTable }}>
|
||||||
<FormContext.Provider value={form}>
|
<FormContext.Provider value={form}>
|
||||||
@ -142,7 +148,6 @@ export const useTableBlockProps = () => {
|
|||||||
}
|
}
|
||||||
: false,
|
: false,
|
||||||
onRowSelectionChange(selectedRowKeys) {
|
onRowSelectionChange(selectedRowKeys) {
|
||||||
console.log(selectedRowKeys);
|
|
||||||
ctx.field.data = ctx?.field?.data || {};
|
ctx.field.data = ctx?.field?.data || {};
|
||||||
ctx.field.data.selectedRowKeys = selectedRowKeys;
|
ctx.field.data.selectedRowKeys = selectedRowKeys;
|
||||||
ctx?.field?.onRowSelect?.(selectedRowKeys);
|
ctx?.field?.onRowSelect?.(selectedRowKeys);
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { SchemaExpressionScopeContext, useField, useFieldSchema, useForm } from '@formily/react';
|
import { SchemaExpressionScopeContext, useField, useFieldSchema, useForm } from '@formily/react';
|
||||||
import { isURL, parse } from '@nocobase/utils/client';
|
import { isURL, parse } from '@nocobase/utils/client';
|
||||||
import { App, message } from 'antd';
|
import { App, message } from 'antd';
|
||||||
import { cloneDeep } from 'lodash';
|
import _, { cloneDeep } from 'lodash';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
import { ChangeEvent, useContext, useEffect } from 'react';
|
import { ChangeEvent, useContext, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useReactToPrint } from 'react-to-print';
|
import { useReactToPrint } from 'react-to-print';
|
||||||
import { AssociationFilter, useFormBlockContext, useTableBlockContext } from '../..';
|
import { AssociationFilter, useFormActiveFields, useFormBlockContext, useTableBlockContext } from '../..';
|
||||||
import { useAPIClient, useRequest } from '../../api-client';
|
import { useAPIClient, useRequest } from '../../api-client';
|
||||||
import { useCollection, useCollectionManager } from '../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../collection-manager';
|
||||||
import { useFilterBlock } from '../../filter-provider/FilterProvider';
|
import { useFilterBlock } from '../../filter-provider/FilterProvider';
|
||||||
@ -17,11 +17,16 @@ import { useRecord } from '../../record-provider';
|
|||||||
import { removeNullCondition, useActionContext, useCompile } from '../../schema-component';
|
import { removeNullCondition, useActionContext, useCompile } from '../../schema-component';
|
||||||
import { BulkEditFormItemValueType } from '../../schema-initializer/components';
|
import { BulkEditFormItemValueType } from '../../schema-initializer/components';
|
||||||
import { useCurrentUserContext } from '../../user';
|
import { useCurrentUserContext } from '../../user';
|
||||||
|
import { useLocalVariables, useVariables } from '../../variables';
|
||||||
|
import { isVariable } from '../../variables/utils/isVariable';
|
||||||
|
import { transformVariableValue } from '../../variables/utils/transformVariableValue';
|
||||||
import { useBlockRequestContext, useFilterByTk, useParamsFromRecord } from '../BlockProvider';
|
import { useBlockRequestContext, useFilterByTk, useParamsFromRecord } from '../BlockProvider';
|
||||||
import { useDetailsBlockContext } from '../DetailsBlockProvider';
|
import { useDetailsBlockContext } from '../DetailsBlockProvider';
|
||||||
import { mergeFilter } from '../SharedFilterProvider';
|
import { mergeFilter } from '../SharedFilterProvider';
|
||||||
import { TableFieldResource } from '../TableFieldProvider';
|
import { TableFieldResource } from '../TableFieldProvider';
|
||||||
|
|
||||||
|
export * from './useFormActiveFields';
|
||||||
|
|
||||||
export const usePickActionProps = () => {
|
export const usePickActionProps = () => {
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
return {
|
return {
|
||||||
@ -61,13 +66,28 @@ const filterValue = (value) => {
|
|||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getFormValues(filterByTk, field, form, fieldNames, getField, resource) {
|
function getFormValues({
|
||||||
|
filterByTk,
|
||||||
|
field,
|
||||||
|
form,
|
||||||
|
fieldNames,
|
||||||
|
getField,
|
||||||
|
resource,
|
||||||
|
actionFields,
|
||||||
|
}: {
|
||||||
|
filterByTk;
|
||||||
|
field;
|
||||||
|
form;
|
||||||
|
fieldNames;
|
||||||
|
getField;
|
||||||
|
resource;
|
||||||
|
actionFields: any[];
|
||||||
|
}) {
|
||||||
if (filterByTk) {
|
if (filterByTk) {
|
||||||
const actionFields = field?.data?.activeFields as Set<string>;
|
|
||||||
if (actionFields) {
|
if (actionFields) {
|
||||||
const keys = Object.keys(form.values).filter((key) => {
|
const keys = Object.keys(form.values).filter((key) => {
|
||||||
const f = getField(key);
|
const f = getField(key);
|
||||||
return !actionFields.has(key) && ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(f?.type);
|
return !actionFields.includes(key) && ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(f?.type);
|
||||||
});
|
});
|
||||||
return omit({ ...form.values }, keys);
|
return omit({ ...form.values }, keys);
|
||||||
}
|
}
|
||||||
@ -122,17 +142,17 @@ export const useCreateActionProps = () => {
|
|||||||
const { field, resource, __parent } = useBlockRequestContext();
|
const { field, resource, __parent } = useBlockRequestContext();
|
||||||
const { setVisible, fieldSchema } = useActionContext();
|
const { setVisible, fieldSchema } = useActionContext();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation();
|
|
||||||
const actionSchema = useFieldSchema();
|
const actionSchema = useFieldSchema();
|
||||||
const actionField = useField();
|
const actionField = useField();
|
||||||
const { fields, getField, getTreeParentField } = useCollection();
|
const { fields, getField, getTreeParentField, name } = useCollection();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const filterByTk = useFilterByTk();
|
const filterByTk = useFilterByTk();
|
||||||
const currentRecord = useRecord();
|
const currentRecord = useRecord();
|
||||||
const currentUserContext = useCurrentUserContext();
|
|
||||||
const { modal } = App.useApp();
|
const { modal } = App.useApp();
|
||||||
|
const variables = useVariables();
|
||||||
|
const localVariables = useLocalVariables({ currentForm: form });
|
||||||
|
const { getActiveFieldsName } = useFormActiveFields();
|
||||||
|
|
||||||
const currentUser = currentUserContext?.data?.data;
|
|
||||||
const action = actionField.componentProps.saveMode || 'create';
|
const action = actionField.componentProps.saveMode || 'create';
|
||||||
const filterKeys = actionField.componentProps.filterKeys?.checked || [];
|
const filterKeys = actionField.componentProps.filterKeys?.checked || [];
|
||||||
return {
|
return {
|
||||||
@ -146,11 +166,41 @@ export const useCreateActionProps = () => {
|
|||||||
triggerWorkflows,
|
triggerWorkflows,
|
||||||
} = actionSchema?.['x-action-settings'] ?? {};
|
} = actionSchema?.['x-action-settings'] ?? {};
|
||||||
const addChild = fieldSchema?.['x-component-props']?.addChild;
|
const addChild = fieldSchema?.['x-component-props']?.addChild;
|
||||||
const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
|
|
||||||
|
const assignedValues = {};
|
||||||
|
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
||||||
|
const value = originalAssignedValues[key];
|
||||||
|
const collectionField = getField(key);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
if (!collectionField) {
|
||||||
|
throw new Error(`useCreateActionProps: field "${key}" not found in collection "${name}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVariable(value)) {
|
||||||
|
const result = await variables?.parseVariable(value, localVariables);
|
||||||
|
if (result) {
|
||||||
|
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||||
|
}
|
||||||
|
} else if (value != null && value !== '') {
|
||||||
|
assignedValues[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(waitList);
|
||||||
|
|
||||||
if (!skipValidator) {
|
if (!skipValidator) {
|
||||||
await form.submit();
|
await form.submit();
|
||||||
}
|
}
|
||||||
const values = getFormValues(filterByTk, field, form, fieldNames, getField, resource);
|
const values = getFormValues({
|
||||||
|
filterByTk,
|
||||||
|
field,
|
||||||
|
form,
|
||||||
|
fieldNames,
|
||||||
|
getField,
|
||||||
|
resource,
|
||||||
|
actionFields: getActiveFieldsName('form'),
|
||||||
|
});
|
||||||
// const values = omitBy(formValues, (value) => isEqual(JSON.stringify(value), '[{}]'));
|
// const values = omitBy(formValues, (value) => isEqual(JSON.stringify(value), '[{}]'));
|
||||||
if (addChild) {
|
if (addChild) {
|
||||||
const treeParentField = getTreeParentField();
|
const treeParentField = getTreeParentField();
|
||||||
@ -209,12 +259,14 @@ export const useAssociationCreateActionProps = () => {
|
|||||||
const { setVisible, fieldSchema } = useActionContext();
|
const { setVisible, fieldSchema } = useActionContext();
|
||||||
const actionSchema = useFieldSchema();
|
const actionSchema = useFieldSchema();
|
||||||
const actionField = useField();
|
const actionField = useField();
|
||||||
const { fields, getField, getTreeParentField } = useCollection();
|
const { fields, getField, getTreeParentField, name } = useCollection();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const filterByTk = useFilterByTk();
|
const filterByTk = useFilterByTk();
|
||||||
const currentRecord = useRecord();
|
const currentRecord = useRecord();
|
||||||
const currentUserContext = useCurrentUserContext();
|
const variables = useVariables();
|
||||||
const currentUser = currentUserContext?.data?.data;
|
const localVariables = useLocalVariables({ currentForm: form });
|
||||||
|
const { getActiveFieldsName } = useFormActiveFields();
|
||||||
|
|
||||||
const action = actionField.componentProps.saveMode || 'create';
|
const action = actionField.componentProps.saveMode || 'create';
|
||||||
const filterKeys = actionField.componentProps.filterKeys?.checked || [];
|
const filterKeys = actionField.componentProps.filterKeys?.checked || [];
|
||||||
return {
|
return {
|
||||||
@ -228,11 +280,41 @@ export const useAssociationCreateActionProps = () => {
|
|||||||
triggerWorkflows,
|
triggerWorkflows,
|
||||||
} = actionSchema?.['x-action-settings'] ?? {};
|
} = actionSchema?.['x-action-settings'] ?? {};
|
||||||
const addChild = fieldSchema?.['x-component-props']?.addChild;
|
const addChild = fieldSchema?.['x-component-props']?.addChild;
|
||||||
const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
|
|
||||||
|
const assignedValues = {};
|
||||||
|
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
||||||
|
const value = originalAssignedValues[key];
|
||||||
|
const collectionField = getField(key);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
if (!collectionField) {
|
||||||
|
throw new Error(`useAssociationCreateActionProps: field "${key}" not found in collection "${name}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVariable(value)) {
|
||||||
|
const result = await variables?.parseVariable(value, localVariables);
|
||||||
|
if (result) {
|
||||||
|
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||||
|
}
|
||||||
|
} else if (value != null && value !== '') {
|
||||||
|
assignedValues[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(waitList);
|
||||||
|
|
||||||
if (!skipValidator) {
|
if (!skipValidator) {
|
||||||
await form.submit();
|
await form.submit();
|
||||||
}
|
}
|
||||||
const values = getFormValues(filterByTk, field, form, fieldNames, getField, resource);
|
const values = getFormValues({
|
||||||
|
filterByTk,
|
||||||
|
field,
|
||||||
|
form,
|
||||||
|
fieldNames,
|
||||||
|
getField,
|
||||||
|
resource,
|
||||||
|
actionFields: getActiveFieldsName('form'),
|
||||||
|
});
|
||||||
if (addChild) {
|
if (addChild) {
|
||||||
const treeParentField = getTreeParentField();
|
const treeParentField = getTreeParentField();
|
||||||
values[treeParentField?.name ?? 'parent'] = currentRecord;
|
values[treeParentField?.name ?? 'parent'] = currentRecord;
|
||||||
@ -405,13 +487,13 @@ export const useCustomizeUpdateActionProps = () => {
|
|||||||
const { resource, __parent, service } = useBlockRequestContext();
|
const { resource, __parent, service } = useBlockRequestContext();
|
||||||
const filterByTk = useFilterByTk();
|
const filterByTk = useFilterByTk();
|
||||||
const actionSchema = useFieldSchema();
|
const actionSchema = useFieldSchema();
|
||||||
const currentRecord = useRecord();
|
|
||||||
const currentUserContext = useCurrentUserContext();
|
|
||||||
const currentUser = currentUserContext?.data?.data;
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
const { modal } = App.useApp();
|
const { modal } = App.useApp();
|
||||||
|
const variables = useVariables();
|
||||||
|
const localVariables = useLocalVariables({ currentForm: form });
|
||||||
|
const { name, getField } = useCollection();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async onClick() {
|
async onClick() {
|
||||||
@ -420,7 +502,29 @@ export const useCustomizeUpdateActionProps = () => {
|
|||||||
onSuccess,
|
onSuccess,
|
||||||
skipValidator,
|
skipValidator,
|
||||||
} = actionSchema?.['x-action-settings'] ?? {};
|
} = actionSchema?.['x-action-settings'] ?? {};
|
||||||
const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
|
|
||||||
|
const assignedValues = {};
|
||||||
|
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
||||||
|
const value = originalAssignedValues[key];
|
||||||
|
const collectionField = getField(key);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
if (!collectionField) {
|
||||||
|
throw new Error(`useCustomizeUpdateActionProps: field "${key}" not found in collection "${name}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVariable(value)) {
|
||||||
|
const result = await variables?.parseVariable(value, localVariables);
|
||||||
|
if (result) {
|
||||||
|
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||||
|
}
|
||||||
|
} else if (value != null && value !== '') {
|
||||||
|
assignedValues[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(waitList);
|
||||||
|
|
||||||
if (skipValidator === false) {
|
if (skipValidator === false) {
|
||||||
await form.submit();
|
await form.submit();
|
||||||
}
|
}
|
||||||
@ -463,13 +567,14 @@ export const useCustomizeBulkUpdateActionProps = () => {
|
|||||||
const { rowKey } = tableBlockContext;
|
const { rowKey } = tableBlockContext;
|
||||||
const selectedRecordKeys =
|
const selectedRecordKeys =
|
||||||
tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? {};
|
tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? {};
|
||||||
const currentUserContext = useCurrentUserContext();
|
|
||||||
const currentUser = currentUserContext?.data?.data;
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const actionField = useField();
|
const actionField = useField();
|
||||||
const { modal } = App.useApp();
|
const { modal } = App.useApp();
|
||||||
|
const variables = useVariables();
|
||||||
|
const localVariables = useLocalVariables();
|
||||||
|
const { name, getField } = useCollection();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async onClick() {
|
async onClick() {
|
||||||
@ -480,7 +585,29 @@ export const useCustomizeBulkUpdateActionProps = () => {
|
|||||||
} = actionSchema?.['x-action-settings'] ?? {};
|
} = actionSchema?.['x-action-settings'] ?? {};
|
||||||
actionField.data = field.data || {};
|
actionField.data = field.data || {};
|
||||||
actionField.data.loading = true;
|
actionField.data.loading = true;
|
||||||
const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentUser });
|
|
||||||
|
const assignedValues = {};
|
||||||
|
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
||||||
|
const value = originalAssignedValues[key];
|
||||||
|
const collectionField = getField(key);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
if (!collectionField) {
|
||||||
|
throw new Error(`useCustomizeBulkUpdateActionProps: field "${key}" not found in collection "${name}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVariable(value)) {
|
||||||
|
const result = await variables?.parseVariable(value, localVariables);
|
||||||
|
if (result) {
|
||||||
|
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||||
|
}
|
||||||
|
} else if (value != null && value !== '') {
|
||||||
|
assignedValues[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(waitList);
|
||||||
|
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
title: t('Bulk update'),
|
title: t('Bulk update'),
|
||||||
content: updateMode === 'selected' ? t('Update selected data?') : t('Update all data?'),
|
content: updateMode === 'selected' ? t('Update selected data?') : t('Update all data?'),
|
||||||
@ -644,6 +771,7 @@ export const useCustomizeRequestActionProps = () => {
|
|||||||
const actionField = useField();
|
const actionField = useField();
|
||||||
const { setVisible } = useActionContext();
|
const { setVisible } = useActionContext();
|
||||||
const { modal } = App.useApp();
|
const { modal } = App.useApp();
|
||||||
|
const { getActiveFieldsName } = useFormActiveFields();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async onClick() {
|
async onClick() {
|
||||||
@ -662,7 +790,15 @@ export const useCustomizeRequestActionProps = () => {
|
|||||||
const methods = ['POST', 'PUT', 'PATCH'];
|
const methods = ['POST', 'PUT', 'PATCH'];
|
||||||
if (xAction === 'customize:form:request' && methods.includes(requestSettings['method'])) {
|
if (xAction === 'customize:form:request' && methods.includes(requestSettings['method'])) {
|
||||||
const fieldNames = fields.map((field) => field.name);
|
const fieldNames = fields.map((field) => field.name);
|
||||||
const values = getFormValues(filterByTk, field, form, fieldNames, getField, resource);
|
const values = getFormValues({
|
||||||
|
filterByTk,
|
||||||
|
field,
|
||||||
|
form,
|
||||||
|
fieldNames,
|
||||||
|
getField,
|
||||||
|
resource,
|
||||||
|
actionFields: getActiveFieldsName('form'),
|
||||||
|
});
|
||||||
Object.assign(data, values);
|
Object.assign(data, values);
|
||||||
}
|
}
|
||||||
const requestBody = {
|
const requestBody = {
|
||||||
@ -715,20 +851,19 @@ export const useCustomizeRequestActionProps = () => {
|
|||||||
export const useUpdateActionProps = () => {
|
export const useUpdateActionProps = () => {
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
const filterByTk = useFilterByTk();
|
const filterByTk = useFilterByTk();
|
||||||
const { field, resource, __parent, service } = useBlockRequestContext();
|
const { field, resource, __parent } = useBlockRequestContext();
|
||||||
const { setVisible } = useActionContext();
|
const { setVisible } = useActionContext();
|
||||||
const actionSchema = useFieldSchema();
|
const actionSchema = useFieldSchema();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { fields, getField } = useCollection();
|
const { fields, getField, name } = useCollection();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const actionField = useField();
|
const actionField = useField();
|
||||||
const { updateAssociationValues } = useFormBlockContext();
|
const { updateAssociationValues } = useFormBlockContext();
|
||||||
const currentRecord = useRecord();
|
|
||||||
const currentUserContext = useCurrentUserContext();
|
|
||||||
const { modal } = App.useApp();
|
const { modal } = App.useApp();
|
||||||
|
|
||||||
const currentUser = currentUserContext?.data?.data;
|
|
||||||
const data = useParamsFromRecord();
|
const data = useParamsFromRecord();
|
||||||
|
const variables = useVariables();
|
||||||
|
const localVariables = useLocalVariables({ currentForm: form });
|
||||||
|
const { getActiveFieldsName } = useFormActiveFields();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async onClick() {
|
async onClick() {
|
||||||
@ -739,12 +874,42 @@ export const useUpdateActionProps = () => {
|
|||||||
skipValidator,
|
skipValidator,
|
||||||
triggerWorkflows,
|
triggerWorkflows,
|
||||||
} = actionSchema?.['x-action-settings'] ?? {};
|
} = actionSchema?.['x-action-settings'] ?? {};
|
||||||
const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
|
|
||||||
|
const assignedValues = {};
|
||||||
|
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
||||||
|
const value = originalAssignedValues[key];
|
||||||
|
const collectionField = getField(key);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
if (!collectionField) {
|
||||||
|
throw new Error(`useUpdateActionProps: field "${key}" not found in collection "${name}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVariable(value)) {
|
||||||
|
const result = await variables?.parseVariable(value, localVariables);
|
||||||
|
if (result) {
|
||||||
|
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||||
|
}
|
||||||
|
} else if (value != null && value !== '') {
|
||||||
|
assignedValues[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(waitList);
|
||||||
|
|
||||||
if (!skipValidator) {
|
if (!skipValidator) {
|
||||||
await form.submit();
|
await form.submit();
|
||||||
}
|
}
|
||||||
const fieldNames = fields.map((field) => field.name);
|
const fieldNames = fields.map((field) => field.name);
|
||||||
const values = getFormValues(filterByTk, field, form, fieldNames, getField, resource);
|
const values = getFormValues({
|
||||||
|
filterByTk,
|
||||||
|
field,
|
||||||
|
form,
|
||||||
|
fieldNames,
|
||||||
|
getField,
|
||||||
|
resource,
|
||||||
|
actionFields: getActiveFieldsName('form'),
|
||||||
|
});
|
||||||
actionField.data = field.data || {};
|
actionField.data = field.data || {};
|
||||||
actionField.data.loading = true;
|
actionField.data.loading = true;
|
||||||
try {
|
try {
|
||||||
@ -1125,6 +1290,25 @@ export const useAssociationNames = () => {
|
|||||||
const isAssociationSubfield = s.name.includes('.');
|
const isAssociationSubfield = s.name.includes('.');
|
||||||
const isAssociationField =
|
const isAssociationField =
|
||||||
collectionfield && ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(collectionfield.type);
|
collectionfield && ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(collectionfield.type);
|
||||||
|
|
||||||
|
// 根据联动规则中条件的字段获取一些 appends
|
||||||
|
if (s['x-linkage-rules']) {
|
||||||
|
const rules = s['x-linkage-rules'];
|
||||||
|
rules.forEach(({ condition }) => {
|
||||||
|
const type = Object.keys(condition)[0] || '$and';
|
||||||
|
const list = condition[type];
|
||||||
|
|
||||||
|
list.forEach((item) => {
|
||||||
|
const fieldNames = getTargetField(item);
|
||||||
|
|
||||||
|
// 只应该收集关系字段,只有大于 1 的时候才是关系字段
|
||||||
|
if (fieldNames.length > 1) {
|
||||||
|
appends.add(fieldNames.join('.'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const isTreeCollection = isAssociationField && getCollection(collectionfield.target)?.template === 'tree';
|
const isTreeCollection = isAssociationField && getCollection(collectionfield.target)?.template === 'tree';
|
||||||
if (collectionfield && (isAssociationField || isAssociationSubfield) && s['x-component'] !== 'TableField') {
|
if (collectionfield && (isAssociationField || isAssociationSubfield) && s['x-component'] !== 'TableField') {
|
||||||
const fieldPath = !isAssociationField && isAssociationSubfield ? getAssociationPath(s.name) : s.name;
|
const fieldPath = !isAssociationField && isAssociationSubfield ? getAssociationPath(s.name) : s.name;
|
||||||
@ -1161,3 +1345,30 @@ export const useAssociationNames = () => {
|
|||||||
getAssociationAppends(fieldSchema, '');
|
getAssociationAppends(fieldSchema, '');
|
||||||
return { appends: [...appends], updateAssociationValues: [...updateAssociationValues] };
|
return { appends: [...appends], updateAssociationValues: [...updateAssociationValues] };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getTargetField(obj) {
|
||||||
|
function getAllKeys(obj) {
|
||||||
|
const keys = [];
|
||||||
|
function traverse(o) {
|
||||||
|
Object.keys(o)
|
||||||
|
.sort()
|
||||||
|
.forEach(function (key) {
|
||||||
|
keys.push(key);
|
||||||
|
if (o[key] && typeof o[key] === 'object') {
|
||||||
|
traverse(o[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
traverse(obj);
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = getAllKeys(obj);
|
||||||
|
const index = _.findIndex(keys, (key: string, index: number) => {
|
||||||
|
if (key.includes('$') && index > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const result = keys.slice(0, index);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
import React, { createContext, useContext, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
|
interface FormActiveFieldsProviderValue {
|
||||||
|
getActiveFieldsName: (name: string) => string[];
|
||||||
|
addActiveFieldName: (fieldName: string) => void;
|
||||||
|
removeActiveFieldName: (fieldName: string) => void;
|
||||||
|
name: string;
|
||||||
|
parent: FormActiveFieldsProviderValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormActiveFieldsContext = createContext<FormActiveFieldsProviderValue>(null);
|
||||||
|
|
||||||
|
interface ProviderProps extends Partial<FormActiveFieldsProviderValue> {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FormActiveFieldsProvider = ({
|
||||||
|
children,
|
||||||
|
name,
|
||||||
|
getActiveFieldsName,
|
||||||
|
addActiveFieldName,
|
||||||
|
removeActiveFieldName,
|
||||||
|
}: ProviderProps) => {
|
||||||
|
const activeFieldsNameRef = useRef<Set<string>>(new Set());
|
||||||
|
const upLevelContext = useContext(FormActiveFieldsContext);
|
||||||
|
|
||||||
|
const value = useMemo(() => {
|
||||||
|
return {
|
||||||
|
getActiveFieldsName:
|
||||||
|
getActiveFieldsName ||
|
||||||
|
((_name: string) => {
|
||||||
|
if (!_name || _name === name) {
|
||||||
|
return Array.from(activeFieldsNameRef.current);
|
||||||
|
}
|
||||||
|
return upLevelContext?.getActiveFieldsName(_name);
|
||||||
|
}),
|
||||||
|
addActiveFieldName:
|
||||||
|
addActiveFieldName ||
|
||||||
|
((field: string) => {
|
||||||
|
activeFieldsNameRef.current.add(field);
|
||||||
|
}),
|
||||||
|
removeActiveFieldName:
|
||||||
|
removeActiveFieldName ||
|
||||||
|
((field: string) => {
|
||||||
|
activeFieldsNameRef.current.delete(field);
|
||||||
|
}),
|
||||||
|
name,
|
||||||
|
parent: upLevelContext,
|
||||||
|
};
|
||||||
|
}, [addActiveFieldName, getActiveFieldsName, name, removeActiveFieldName, upLevelContext]);
|
||||||
|
|
||||||
|
return <FormActiveFieldsContext.Provider value={value}>{children}</FormActiveFieldsContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于获取和更新当前表单区块中已显示出来的字段
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const useFormActiveFields = () => {
|
||||||
|
return useContext(FormActiveFieldsContext);
|
||||||
|
};
|
@ -6,7 +6,7 @@ export const CollectionManagerContext = createContext<CollectionManagerOptions>(
|
|||||||
interfaces: {},
|
interfaces: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CollectionContext = createContext<CollectionOptions>({});
|
export const CollectionContext = createContext<CollectionOptions>({} as CollectionOptions);
|
||||||
|
|
||||||
export const CollectionFieldContext = createContext<CollectionFieldOptions>({});
|
export const CollectionFieldContext = createContext<CollectionFieldOptions>({});
|
||||||
|
|
||||||
|
@ -4,11 +4,12 @@ import _, { reduce, unionBy, uniq, uniqBy } from 'lodash';
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useCompile } from '../../schema-component';
|
import { useCompile } from '../../schema-component';
|
||||||
import { CollectionManagerContext } from '../context';
|
import { CollectionManagerContext } from '../context';
|
||||||
import { CollectionFieldOptions } from '../types';
|
import { CollectionFieldOptions, CollectionOptions } from '../types';
|
||||||
|
|
||||||
export const useCollectionManager = () => {
|
export const useCollectionManager = () => {
|
||||||
const { refreshCM, updateCollection, service, interfaces, collections, templates } =
|
const { refreshCM, updateCollection, service, interfaces, collections, templates } = useContext(
|
||||||
useContext(CollectionManagerContext);
|
CollectionManagerContext,
|
||||||
|
);
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const getInheritedFields = (name) => {
|
const getInheritedFields = (name) => {
|
||||||
const inheritKeys = getInheritCollections(name);
|
const inheritKeys = getInheritCollections(name);
|
||||||
@ -192,7 +193,7 @@ export const useCollectionManager = () => {
|
|||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCollection = (name: any) => {
|
const getCollection = (name: any): CollectionOptions => {
|
||||||
if (typeof name !== 'string') {
|
if (typeof name !== 'string') {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@ -290,7 +291,7 @@ export const useCollectionManager = () => {
|
|||||||
getCollectionFieldsOptions,
|
getCollectionFieldsOptions,
|
||||||
getCurrentCollectionFields,
|
getCurrentCollectionFields,
|
||||||
getCollection,
|
getCollection,
|
||||||
getCollectionJoinField(name: string) {
|
getCollectionJoinField(name: string): CollectionFieldOptions {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FormItem, FormLayout } from '@formily/antd-v5';
|
import { FormItem, FormLayout } from '@formily/antd-v5';
|
||||||
import { registerValidateRules } from '@formily/core';
|
import { registerValidateRules } from '@formily/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defaultProps } from './properties';
|
import { defaultProps, operators } from './properties';
|
||||||
import { IField } from './types';
|
import { IField } from './types';
|
||||||
|
|
||||||
registerValidateRules({
|
registerValidateRules({
|
||||||
@ -59,5 +59,7 @@ export const json: IField = {
|
|||||||
'x-disabled': `{{ disabledJSONB }}`,
|
'x-disabled': `{{ disabledJSONB }}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
filterable: {},
|
filterable: {
|
||||||
|
operators: operators.string,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -138,5 +138,4 @@ export const linkTo: IField = {
|
|||||||
// },
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
invariable: true,
|
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import {
|
import { defaultProps, relationshipType, reverseFieldProperties } from './properties';
|
||||||
defaultProps,
|
|
||||||
relationshipType,
|
|
||||||
reverseFieldProperties
|
|
||||||
} from './properties';
|
|
||||||
import { IField } from './types';
|
import { IField } from './types';
|
||||||
|
|
||||||
export const m2m: IField = {
|
export const m2m: IField = {
|
||||||
@ -246,5 +242,4 @@ export const m2m: IField = {
|
|||||||
// },
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
invariable: true,
|
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import {
|
import { constraintsProps, relationshipType, reverseFieldProperties } from './properties';
|
||||||
constraintsProps,
|
|
||||||
relationshipType,
|
|
||||||
reverseFieldProperties
|
|
||||||
} from './properties';
|
|
||||||
import { IField } from './types';
|
import { IField } from './types';
|
||||||
|
|
||||||
export const m2o: IField = {
|
export const m2o: IField = {
|
||||||
@ -178,5 +174,4 @@ export const m2o: IField = {
|
|||||||
// },
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
invariable: true,
|
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import { i18n } from '../../i18n';
|
import { i18n } from '../../i18n';
|
||||||
import { defaultProps } from './properties';
|
import { defaultProps, operators } from './properties';
|
||||||
import { IField } from './types';
|
import { IField } from './types';
|
||||||
|
|
||||||
export const markdown: IField = {
|
export const markdown: IField = {
|
||||||
@ -69,4 +69,7 @@ export const markdown: IField = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
filterable: {
|
||||||
|
operators: operators.string,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -198,5 +198,4 @@ export const o2m: IField = {
|
|||||||
// },
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
invariable: true,
|
|
||||||
};
|
};
|
||||||
|
@ -178,7 +178,6 @@ export const o2o: IField = {
|
|||||||
// },
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
invariable: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const oho: IField = {
|
export const oho: IField = {
|
||||||
@ -349,7 +348,6 @@ export const oho: IField = {
|
|||||||
// },
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
invariable: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const obo: IField = {
|
export const obo: IField = {
|
||||||
@ -524,5 +522,4 @@ export const obo: IField = {
|
|||||||
// },
|
// },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
invariable: true,
|
|
||||||
};
|
};
|
||||||
|
@ -45,6 +45,11 @@ export const array = [
|
|||||||
{ label: '{{t("is not empty")}}', value: '$notEmpty', noValue: true },
|
{ label: '{{t("is not empty")}}', value: '$notEmpty', noValue: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const object = [
|
||||||
|
{ label: '{{t("is")}}', value: '$eq', selected: true },
|
||||||
|
{ label: '{{t("is not")}}', value: '$ne' },
|
||||||
|
];
|
||||||
|
|
||||||
export const datetime = [
|
export const datetime = [
|
||||||
{ label: "{{ t('is') }}", value: '$dateOn', selected: true },
|
{ label: "{{ t('is') }}", value: '$dateOn', selected: true },
|
||||||
{ label: "{{ t('is not') }}", value: '$dateNotOn' },
|
{ label: "{{ t('is not') }}", value: '$dateNotOn' },
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { ISchema } from '@formily/react';
|
import type { ISchema } from '@formily/react';
|
||||||
import { defaultProps } from './properties';
|
|
||||||
import { IField } from './types';
|
|
||||||
import { i18n } from '../../i18n';
|
import { i18n } from '../../i18n';
|
||||||
|
import { defaultProps, operators } from './properties';
|
||||||
|
import { IField } from './types';
|
||||||
|
|
||||||
export const richText: IField = {
|
export const richText: IField = {
|
||||||
name: 'richText',
|
name: 'richText',
|
||||||
@ -70,4 +70,7 @@ export const richText: IField = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
filterable: {
|
||||||
|
operators: operators.string,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import { defaultProps } from './properties';
|
|
||||||
import { IField } from './types';
|
|
||||||
import { i18n } from '../../i18n';
|
import { i18n } from '../../i18n';
|
||||||
|
import { defaultProps, operators } from './properties';
|
||||||
|
import { IField } from './types';
|
||||||
|
|
||||||
export const textarea: IField = {
|
export const textarea: IField = {
|
||||||
name: 'textarea',
|
name: 'textarea',
|
||||||
@ -70,4 +70,7 @@ export const textarea: IField = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
filterable: {
|
||||||
|
operators: operators.string,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -9,9 +9,21 @@ interface IDefault {
|
|||||||
export interface IField extends ISchema {
|
export interface IField extends ISchema {
|
||||||
default?: IDefault;
|
default?: IDefault;
|
||||||
operators?: any[];
|
operators?: any[];
|
||||||
filterable?: any;
|
/**
|
||||||
/** 不支持使用变量的值进行设置 */
|
* - 如果该值为空,则在 Filter 组件中该字段会被过滤掉
|
||||||
invariable?: boolean;
|
* - 如果该值为空,则不会在变量列表中看到该字段
|
||||||
|
*/
|
||||||
|
filterable?: {
|
||||||
|
/**
|
||||||
|
* 字段所支持的操作符,会在 Filter 组件中显示,比如设置 `数据范围` 的时候可以看见
|
||||||
|
*/
|
||||||
|
operators?: any[];
|
||||||
|
/**
|
||||||
|
* 为当前字段添加子选项,这个子选项会在 Filter 组件中显示,比如设置 `数据范围` 的时候可以看见
|
||||||
|
*/
|
||||||
|
children?: any[];
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
// NOTE: set to `true` means field could be used as a title field
|
// NOTE: set to `true` means field could be used as a title field
|
||||||
titleUsable?: boolean;
|
titleUsable?: boolean;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import { defaultProps } from './properties';
|
import { defaultProps, operators } from './properties';
|
||||||
import { IField } from './types';
|
import { IField } from './types';
|
||||||
|
|
||||||
export const url: IField = {
|
export const url: IField = {
|
||||||
@ -20,4 +20,7 @@ export const url: IField = {
|
|||||||
...defaultProps,
|
...defaultProps,
|
||||||
},
|
},
|
||||||
titleUsable: true,
|
titleUsable: true,
|
||||||
|
filterable: {
|
||||||
|
operators: operators.string,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -40,7 +40,7 @@ const PreviewCom = (props) => {
|
|||||||
sourceCollections.forEach((item) => {
|
sourceCollections.forEach((item) => {
|
||||||
const collection = getCollection(item);
|
const collection = getCollection(item);
|
||||||
const inherits = getInheritCollections(item);
|
const inherits = getInheritCollections(item);
|
||||||
const result = inherits.map((v) => {
|
const result: any[] = inherits.map((v) => {
|
||||||
const fields = getParentCollectionFields(v, item);
|
const fields = getParentCollectionFields(v, item);
|
||||||
return {
|
return {
|
||||||
type: 'group',
|
type: 'group',
|
||||||
@ -59,7 +59,7 @@ const PreviewCom = (props) => {
|
|||||||
const children = collection.fields
|
const children = collection.fields
|
||||||
.filter((v) => !['hasOne', 'hasMany', 'belongsToMany'].includes(v?.type))
|
.filter((v) => !['hasOne', 'hasMany', 'belongsToMany'].includes(v?.type))
|
||||||
?.map((v) => {
|
?.map((v) => {
|
||||||
return { value: v.name, label: t(v.uiSchema?.title) };
|
return { value: v.name, key: v.name, label: t(v.uiSchema?.title) };
|
||||||
})
|
})
|
||||||
.concat(result);
|
.concat(result);
|
||||||
data.push({
|
data.push({
|
||||||
|
@ -1,36 +1,66 @@
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
type dumpable = 'required' | 'optional' | 'skip';
|
||||||
|
type CollectionSortable = string | boolean | { name?: string; scopeKey?: string };
|
||||||
|
|
||||||
|
export interface CollectionOptions {
|
||||||
|
name: string;
|
||||||
|
title?: string;
|
||||||
|
namespace?: string;
|
||||||
|
/**
|
||||||
|
* Used for @nocobase/plugin-duplicator
|
||||||
|
* @see packages/core/database/src/collection-group-manager.tss
|
||||||
|
*
|
||||||
|
* @prop {'required' | 'optional' | 'skip'} dumpable - Determine whether the collection is dumped
|
||||||
|
* @prop {string[] | string} [with] - Collections dumped with this collection
|
||||||
|
* @prop {any} [delayRestore] - A function to execute after all collections are restored
|
||||||
|
*/
|
||||||
|
duplicator?:
|
||||||
|
| dumpable
|
||||||
|
| {
|
||||||
|
dumpable: dumpable;
|
||||||
|
with?: string[] | string;
|
||||||
|
delayRestore?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
tableName?: string;
|
||||||
|
inherits?: string[] | string;
|
||||||
|
viewName?: string;
|
||||||
|
writableView?: boolean;
|
||||||
|
|
||||||
|
filterTargetKey?: string;
|
||||||
|
fields?: FieldOptions[];
|
||||||
|
model?: any;
|
||||||
|
repository?: any;
|
||||||
|
sortable?: CollectionSortable;
|
||||||
|
/**
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
autoGenId?: boolean;
|
||||||
|
/**
|
||||||
|
* @default 'options'
|
||||||
|
*/
|
||||||
|
magicAttribute?: string;
|
||||||
|
|
||||||
|
tree?: string;
|
||||||
|
|
||||||
|
template?: string;
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CollectionManagerOptions {
|
export interface CollectionManagerOptions {
|
||||||
service?: any;
|
service?: any;
|
||||||
interfaces?: any;
|
interfaces?: any;
|
||||||
collections?: any[];
|
collections?: CollectionOptions[];
|
||||||
templates?: any;
|
templates?: any;
|
||||||
refreshCM?: () => Promise<void>;
|
refreshCM?: () => Promise<void>;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
updateCollection?: (collection: any) => void;
|
updateCollection?: (collection: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FieldOptions {
|
export type FieldOptions = any;
|
||||||
type: string;
|
|
||||||
interface?: string;
|
|
||||||
uiSchema?: ISchema;
|
|
||||||
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CollectionOptions {
|
|
||||||
name?: string;
|
|
||||||
title?: string;
|
|
||||||
filterTargetKey?: string;
|
|
||||||
targetKey?: string;
|
|
||||||
sortable?: any;
|
|
||||||
fields?: FieldOptions[];
|
|
||||||
inherits?: string[];
|
|
||||||
tree?: string;
|
|
||||||
template?: string;
|
|
||||||
writableView?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICollectionProviderProps {
|
export interface ICollectionProviderProps {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
@ -3,11 +3,11 @@ import { uniqBy } from 'lodash';
|
|||||||
import React, { createContext, useEffect, useRef } from 'react';
|
import React, { createContext, useEffect, useRef } from 'react';
|
||||||
import { useBlockRequestContext } from '../block-provider/BlockProvider';
|
import { useBlockRequestContext } from '../block-provider/BlockProvider';
|
||||||
import { SharedFilter, mergeFilter } from '../block-provider/SharedFilterProvider';
|
import { SharedFilter, mergeFilter } from '../block-provider/SharedFilterProvider';
|
||||||
import { CollectionFieldOptions, FieldOptions, useCollection } from '../collection-manager';
|
import { CollectionFieldOptions, useCollection } from '../collection-manager';
|
||||||
import { removeNullCondition } from '../schema-component';
|
import { removeNullCondition } from '../schema-component';
|
||||||
import { useAssociatedFields } from './utils';
|
import { useAssociatedFields } from './utils';
|
||||||
|
|
||||||
export interface ForeignKeyField extends FieldOptions {
|
export interface ForeignKeyField {
|
||||||
/** 外键字段所在的数据表的名称 */
|
/** 外键字段所在的数据表的名称 */
|
||||||
collectionName: string;
|
collectionName: string;
|
||||||
isForeignKey: boolean;
|
isForeignKey: boolean;
|
||||||
@ -15,6 +15,8 @@ export interface ForeignKeyField extends FieldOptions {
|
|||||||
name: string;
|
name: string;
|
||||||
parentKey: null | string;
|
parentKey: null | string;
|
||||||
reverseKey: null | string;
|
reverseKey: null | string;
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Collection = ReturnType<typeof useCollection>;
|
type Collection = ReturnType<typeof useCollection>;
|
||||||
|
31
packages/core/client/src/flag-provider/FlagProvider.tsx
Normal file
31
packages/core/client/src/flag-provider/FlagProvider.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
export interface FlagProviderProps {
|
||||||
|
/**
|
||||||
|
* 字段是否存在于 `字段赋值` 弹窗中
|
||||||
|
*/
|
||||||
|
isInAssignFieldValues?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否存在于 `设置默认值` 弹窗中
|
||||||
|
*/
|
||||||
|
isInSetDefaultValueDialog?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否存在于 `表单数据模板` 中
|
||||||
|
*/
|
||||||
|
isInFormDataTemplate?: boolean;
|
||||||
|
children: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FlagContext = React.createContext<Omit<FlagProviderProps, 'children'>>(null);
|
||||||
|
|
||||||
|
export const FlagProvider = (props: FlagProviderProps) => {
|
||||||
|
const value = useMemo(() => {
|
||||||
|
return {
|
||||||
|
isInAssignFieldValues: props.isInAssignFieldValues,
|
||||||
|
isInSetDefaultValueDialog: props.isInSetDefaultValueDialog,
|
||||||
|
isInFormDataTemplate: props.isInFormDataTemplate,
|
||||||
|
};
|
||||||
|
}, [props.isInAssignFieldValues, props.isInFormDataTemplate, props.isInSetDefaultValueDialog]);
|
||||||
|
|
||||||
|
return <FlagContext.Provider value={value}>{props.children}</FlagContext.Provider>;
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { renderHook } from 'testUtils';
|
||||||
|
import { FlagProvider } from '../FlagProvider';
|
||||||
|
import { useFlag } from '../hooks/useFlag';
|
||||||
|
|
||||||
|
describe('FlagProvider', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
const { result } = renderHook(() => useFlag(), {
|
||||||
|
wrapper: ({ children }) => {
|
||||||
|
return <FlagProvider isInAssignFieldValues={true}>{children}</FlagProvider>;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.isInAssignFieldValues).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
6
packages/core/client/src/flag-provider/hooks/useFlag.ts
Normal file
6
packages/core/client/src/flag-provider/hooks/useFlag.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { FlagContext } from '../FlagProvider';
|
||||||
|
|
||||||
|
export const useFlag = () => {
|
||||||
|
return useContext(FlagContext);
|
||||||
|
};
|
3
packages/core/client/src/flag-provider/index.tsx
Normal file
3
packages/core/client/src/flag-provider/index.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './FlagProvider';
|
||||||
|
|
||||||
|
export * from './hooks/useFlag';
|
@ -25,6 +25,7 @@ import {
|
|||||||
import { Plugin } from '../../../application/Plugin';
|
import { Plugin } from '../../../application/Plugin';
|
||||||
import { useAppSpin } from '../../../application/hooks/useAppSpin';
|
import { useAppSpin } from '../../../application/hooks/useAppSpin';
|
||||||
import { useCollectionManager } from '../../../collection-manager';
|
import { useCollectionManager } from '../../../collection-manager';
|
||||||
|
import { VariablesProvider } from '../../../variables';
|
||||||
|
|
||||||
const filterByACL = (schema, options) => {
|
const filterByACL = (schema, options) => {
|
||||||
const { allowAll, allowMenuItemIds = [] } = options;
|
const { allowAll, allowMenuItemIds = [] } = options;
|
||||||
@ -355,7 +356,9 @@ export const AdminProvider = (props) => {
|
|||||||
<NavigateIfNotSignIn>
|
<NavigateIfNotSignIn>
|
||||||
<RemoteSchemaTemplateManagerProvider>
|
<RemoteSchemaTemplateManagerProvider>
|
||||||
<RemoteCollectionManagerProvider>
|
<RemoteCollectionManagerProvider>
|
||||||
|
<VariablesProvider>
|
||||||
<ACLRolesCheckProvider>{props.children}</ACLRolesCheckProvider>
|
<ACLRolesCheckProvider>{props.children}</ACLRolesCheckProvider>
|
||||||
|
</VariablesProvider>
|
||||||
</RemoteCollectionManagerProvider>
|
</RemoteCollectionManagerProvider>
|
||||||
</RemoteSchemaTemplateManagerProvider>
|
</RemoteSchemaTemplateManagerProvider>
|
||||||
</NavigateIfNotSignIn>
|
</NavigateIfNotSignIn>
|
||||||
|
@ -7,12 +7,14 @@ import { cloneDeep } from 'lodash';
|
|||||||
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 { RemoteSelect, useCompile, useDesignable } from '../..';
|
import { RemoteSelect, useCompile, useDesignable } from '../..';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { CollectionOptions, useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
|
import { FlagProvider } from '../../../flag-provider';
|
||||||
import { useRecord } from '../../../record-provider';
|
import { useRecord } from '../../../record-provider';
|
||||||
import { OpenModeSchemaItems } from '../../../schema-items';
|
import { OpenModeSchemaItems } from '../../../schema-items';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useCollectionState } from '../../../schema-settings/DataTemplates/hooks/useCollectionState';
|
import { useCollectionState } from '../../../schema-settings/DataTemplates/hooks/useCollectionState';
|
||||||
import { useSyncFromForm } from '../../../schema-settings/DataTemplates/utils';
|
import { useSyncFromForm } from '../../../schema-settings/DataTemplates/utils';
|
||||||
|
import { DefaultValueProvider } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||||
import { useLinkageAction } from './hooks';
|
import { useLinkageAction } from './hooks';
|
||||||
import { requestSettingsSchema } from './utils';
|
import { requestSettingsSchema } from './utils';
|
||||||
|
|
||||||
@ -481,15 +483,8 @@ function AssignedFieldValues() {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
const actionType = fieldSchema['x-action'] ?? '';
|
const actionType = fieldSchema['x-action'] ?? '';
|
||||||
|
const onSubmit = useCallback(
|
||||||
return (
|
(assignedValues) => {
|
||||||
<SchemaSettings.ActionModalItem
|
|
||||||
title={t('Assign field values')}
|
|
||||||
initialSchema={initialSchema}
|
|
||||||
initialValues={fieldSchema?.['x-action-settings']?.assignedValues}
|
|
||||||
modalTip={tips[actionType]}
|
|
||||||
uid={fieldSchema?.['x-action-settings']?.schemaUid}
|
|
||||||
onSubmit={(assignedValues) => {
|
|
||||||
fieldSchema['x-action-settings']['assignedValues'] = assignedValues;
|
fieldSchema['x-action-settings']['assignedValues'] = assignedValues;
|
||||||
dn.emit('patch', {
|
dn.emit('patch', {
|
||||||
schema: {
|
schema: {
|
||||||
@ -497,9 +492,23 @@ function AssignedFieldValues() {
|
|||||||
'x-action-settings': fieldSchema['x-action-settings'],
|
'x-action-settings': fieldSchema['x-action-settings'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
dn.refresh();
|
},
|
||||||
}}
|
[dn, fieldSchema],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlagProvider isInAssignFieldValues={true}>
|
||||||
|
<DefaultValueProvider isAllowToSetDefaultValue={() => false}>
|
||||||
|
<SchemaSettings.ActionModalItem
|
||||||
|
title={t('Assign field values')}
|
||||||
|
initialSchema={initialSchema}
|
||||||
|
initialValues={fieldSchema?.['x-action-settings']?.assignedValues}
|
||||||
|
modalTip={tips[actionType]}
|
||||||
|
uid={fieldSchema?.['x-action-settings']?.schemaUid}
|
||||||
|
onSubmit={onSubmit}
|
||||||
/>
|
/>
|
||||||
|
</DefaultValueProvider>
|
||||||
|
</FlagProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -653,7 +662,7 @@ function FormWorkflowSelect(props) {
|
|||||||
const [workflowCollection, setWorkflowCollection] = useState(baseCollection.name);
|
const [workflowCollection, setWorkflowCollection] = useState(baseCollection.name);
|
||||||
useFormEffects(() => {
|
useFormEffects(() => {
|
||||||
onFieldValueChange(`group[${index}].context`, (field) => {
|
onFieldValueChange(`group[${index}].context`, (field) => {
|
||||||
let collection = baseCollection;
|
let collection: CollectionOptions = baseCollection;
|
||||||
if (field.value) {
|
if (field.value) {
|
||||||
const paths = field.value.split('.');
|
const paths = field.value.split('.');
|
||||||
for (let i = 0; i < paths.length && collection; i++) {
|
for (let i = 0; i < paths.length && collection; i++) {
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
|
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
|
||||||
|
import { isPortalInBody } from '@nocobase/utils/client';
|
||||||
import { App, Button, Popover } from 'antd';
|
import { App, Button, Popover } from 'antd';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useActionContext } from '../..';
|
import { useActionContext } from '../..';
|
||||||
import { useDesignable } from '../../';
|
import { useDesignable } from '../../';
|
||||||
import { Icon } from '../../../icon';
|
import { Icon } from '../../../icon';
|
||||||
import { useRecord } from '../../../record-provider';
|
import { useRecord } from '../../../record-provider';
|
||||||
|
import { useLocalVariables, useVariables } from '../../../variables';
|
||||||
import { SortableItem } from '../../common';
|
import { SortableItem } from '../../common';
|
||||||
import { useCompile, useComponent, useDesigner } from '../../hooks';
|
import { useCompile, useComponent, useDesigner } from '../../hooks';
|
||||||
import { useProps } from '../../hooks/useProps';
|
import { useProps } from '../../hooks/useProps';
|
||||||
@ -51,44 +53,44 @@ export const Action: ComposedAction = observer(
|
|||||||
const values = useRecord();
|
const values = useRecord();
|
||||||
const designerProps = fieldSchema['x-designer-props'];
|
const designerProps = fieldSchema['x-designer-props'];
|
||||||
const openMode = fieldSchema?.['x-component-props']?.['openMode'];
|
const openMode = fieldSchema?.['x-component-props']?.['openMode'];
|
||||||
const disabled = form.disabled || field.disabled || props.disabled;
|
const disabled = form.disabled || field.disabled || field.data?.disabled || props.disabled;
|
||||||
const openSize = fieldSchema?.['x-component-props']?.['openSize'];
|
const openSize = fieldSchema?.['x-component-props']?.['openSize'];
|
||||||
const linkageRules = fieldSchema?.['x-linkage-rules'] || [];
|
const linkageRules = fieldSchema?.['x-linkage-rules'] || [];
|
||||||
const { designable } = useDesignable();
|
const { designable } = useDesignable();
|
||||||
const tarComponent = useComponent(component) || component;
|
const tarComponent = useComponent(component) || component;
|
||||||
const { modal } = App.useApp();
|
const { modal } = App.useApp();
|
||||||
|
const variables = useVariables();
|
||||||
|
const localVariables = useLocalVariables({ currentForm: { values } as any });
|
||||||
|
|
||||||
let actionTitle = title || compile(fieldSchema.title);
|
let actionTitle = title || compile(fieldSchema.title);
|
||||||
actionTitle = lodash.isString(actionTitle) ? t(actionTitle) : actionTitle;
|
actionTitle = lodash.isString(actionTitle) ? t(actionTitle) : actionTitle;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
field.linkageProperty = {};
|
|
||||||
linkageRules
|
linkageRules
|
||||||
.filter((k) => !k.disabled)
|
.filter((k) => !k.disabled)
|
||||||
.forEach((v) => {
|
.forEach((v) => {
|
||||||
return v.actions?.map((h) => {
|
v.actions?.forEach((h) => {
|
||||||
linkageAction(h.operator, field, v.condition, form.initialValues);
|
linkageAction({
|
||||||
|
operator: h.operator,
|
||||||
|
field,
|
||||||
|
condition: v.condition,
|
||||||
|
values,
|
||||||
|
variables,
|
||||||
|
localVariables,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [linkageRules, values, designable]);
|
});
|
||||||
|
}, [JSON.stringify(linkageRules), values, designable, field]);
|
||||||
|
|
||||||
const renderButton = () => {
|
const handleButtonClick = useCallback(
|
||||||
if (!designable && field?.data?.hidden) {
|
(e: React.MouseEvent) => {
|
||||||
return null;
|
if (isPortalInBody(e.target as Element)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<SortableItem
|
|
||||||
data-testid={`${fieldSchema['x-action'] || fieldSchema.name}-action`}
|
|
||||||
{...others}
|
|
||||||
loading={field?.data?.loading}
|
|
||||||
icon={icon ? <Icon type={icon} /> : null}
|
|
||||||
disabled={disabled}
|
|
||||||
style={{
|
|
||||||
...others.style,
|
|
||||||
opacity: designable && field?.data?.hidden && 0.1,
|
|
||||||
}}
|
|
||||||
onClick={(e: React.MouseEvent) => {
|
|
||||||
if (!disabled) {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (!disabled) {
|
||||||
const onOk = () => {
|
const onOk = () => {
|
||||||
onClick?.(e);
|
onClick?.(e);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
@ -103,9 +105,33 @@ export const Action: ComposedAction = observer(
|
|||||||
onOk();
|
onOk();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
},
|
||||||
|
[confirm, disabled, modal, onClick, run],
|
||||||
|
);
|
||||||
|
|
||||||
|
const buttonStyle = useMemo(() => {
|
||||||
|
return {
|
||||||
|
...others.style,
|
||||||
|
opacity: designable && field?.data?.hidden && 0.1,
|
||||||
|
};
|
||||||
|
}, [designable, field?.data?.hidden, others.style]);
|
||||||
|
|
||||||
|
const renderButton = () => {
|
||||||
|
if (!designable && field?.data?.hidden) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SortableItem
|
||||||
|
data-testid={`${fieldSchema['x-action'] || fieldSchema.name}-action`}
|
||||||
|
{...others}
|
||||||
|
loading={field?.data?.loading}
|
||||||
|
icon={icon ? <Icon type={icon} /> : null}
|
||||||
|
disabled={disabled}
|
||||||
|
style={buttonStyle}
|
||||||
|
onClick={handleButtonClick}
|
||||||
component={tarComponent || Button}
|
component={tarComponent || Button}
|
||||||
className={classnames(componentCls, hashId, className)}
|
className={classnames(componentCls, hashId, className, 'nb-action')}
|
||||||
type={props.type === 'danger' ? undefined : props.type}
|
type={props.type === 'danger' ? undefined : props.type}
|
||||||
>
|
>
|
||||||
{actionTitle}
|
{actionTitle}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { ISchema } from '@formily/react';
|
import type { ISchema } from '@formily/react';
|
||||||
import { last } from 'lodash';
|
|
||||||
import { conditionAnalyse } from '../../common/utils/uitls';
|
|
||||||
import { ActionType } from '../../../schema-settings/LinkageRules/type';
|
import { ActionType } from '../../../schema-settings/LinkageRules/type';
|
||||||
|
import { VariableOption, VariablesContextType } from '../../../variables/types';
|
||||||
|
import { conditionAnalyses } from '../../common/utils/uitls';
|
||||||
const validateJSON = {
|
const validateJSON = {
|
||||||
validator: `{{(value, rule)=> {
|
validator: `{{(value, rule)=> {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@ -69,52 +69,69 @@ export const requestSettingsSchema: ISchema = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const linkageAction = (operator, field, condition, values) => {
|
export const linkageAction = async ({
|
||||||
const disableResult = field?.linkageProperty?.disabled || [false];
|
operator,
|
||||||
const displayResult = field?.linkageProperty?.display || ['visible'];
|
field,
|
||||||
|
condition,
|
||||||
|
values,
|
||||||
|
variables,
|
||||||
|
localVariables,
|
||||||
|
}: {
|
||||||
|
operator;
|
||||||
|
field;
|
||||||
|
condition;
|
||||||
|
values;
|
||||||
|
variables: VariablesContextType;
|
||||||
|
localVariables: VariableOption[];
|
||||||
|
}) => {
|
||||||
|
field.data = field.data || {};
|
||||||
|
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
case ActionType.Visible:
|
case ActionType.Visible:
|
||||||
if (conditionAnalyse(condition, values)) {
|
reset(field);
|
||||||
displayResult.push(operator);
|
if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
|
||||||
field.data = field.data || {};
|
field._display = '_display' in field ? field._display : field.display;
|
||||||
field.data.hidden = false;
|
field.display = operator;
|
||||||
|
} else {
|
||||||
|
field.display = field._display;
|
||||||
}
|
}
|
||||||
field.linkageProperty = {
|
|
||||||
...field.linkageProperty,
|
|
||||||
display: displayResult,
|
|
||||||
};
|
|
||||||
field.display = last(displayResult);
|
|
||||||
break;
|
break;
|
||||||
case ActionType.Hidden:
|
case ActionType.Hidden:
|
||||||
if (conditionAnalyse(condition, values)) {
|
reset(field);
|
||||||
field.data = field.data || {};
|
if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
|
||||||
field.data.hidden = true;
|
field.data.hidden = true;
|
||||||
} else {
|
} else {
|
||||||
field.data = field.data || {};
|
|
||||||
field.data.hidden = false;
|
field.data.hidden = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ActionType.Disabled:
|
case ActionType.Disabled:
|
||||||
if (conditionAnalyse(condition, values)) {
|
reset(field);
|
||||||
disableResult.push(true);
|
if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
|
||||||
|
field.data.disabled = true;
|
||||||
|
field.disabled = true;
|
||||||
|
} else {
|
||||||
|
field.data.disabled = false;
|
||||||
|
field.disabled = false;
|
||||||
}
|
}
|
||||||
field.linkageProperty = {
|
|
||||||
...field.linkageProperty,
|
|
||||||
disabled: disableResult,
|
|
||||||
};
|
|
||||||
field.disabled = last(disableResult);
|
|
||||||
break;
|
break;
|
||||||
case ActionType.Active:
|
case ActionType.Active:
|
||||||
if (conditionAnalyse(condition, values)) {
|
reset(field);
|
||||||
disableResult.push(false);
|
if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
|
||||||
|
field.data.disabled = false;
|
||||||
|
field.disabled = false;
|
||||||
|
} else {
|
||||||
|
field.data.disabled = true;
|
||||||
|
field.disabled = true;
|
||||||
}
|
}
|
||||||
field.linkageProperty = {
|
|
||||||
...field.linkageProperty,
|
|
||||||
disabled: disableResult,
|
|
||||||
};
|
|
||||||
field.disabled = last(disableResult);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function reset(field: any) {
|
||||||
|
field.display = field._display;
|
||||||
|
field.data.hidden = false;
|
||||||
|
field.data.disabled = false;
|
||||||
|
field.disabled = false;
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { isFunction } from 'mathjs';
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RecordProvider, useAPIClient } from '../../../';
|
import { RecordProvider, useAPIClient } from '../../../';
|
||||||
import { isVariable } from '../../common/utils/uitls';
|
import { isVariable } from '../../../variables/utils/isVariable';
|
||||||
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
||||||
import useServiceOptions, { useAssociationFieldContext } from './hooks';
|
import useServiceOptions, { useAssociationFieldContext } from './hooks';
|
||||||
|
|
||||||
@ -110,3 +110,26 @@ interface AssociationSelectInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AssociationSelect = InternalAssociationSelect as unknown as AssociationSelectInterface;
|
export const AssociationSelect = InternalAssociationSelect as unknown as AssociationSelectInterface;
|
||||||
|
|
||||||
|
export const AssociationSelectReadPretty = connect(
|
||||||
|
(props: any) => {
|
||||||
|
const service = useServiceOptions(props);
|
||||||
|
if (props.fieldNames) {
|
||||||
|
return <RemoteSelect.ReadPretty {...props} service={service}></RemoteSelect.ReadPretty>;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
mapProps(
|
||||||
|
{
|
||||||
|
dataSource: 'options',
|
||||||
|
loading: true,
|
||||||
|
},
|
||||||
|
(props, field) => {
|
||||||
|
return {
|
||||||
|
...props,
|
||||||
|
fieldNames: props.fieldNames && { ...props.fieldNames, ...field.componentProps.fieldNames },
|
||||||
|
suffixIcon: field?.['loading'] || field?.['validating'] ? <LoadingOutlined /> : props.suffixIcon,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
@ -167,7 +167,7 @@ const InternalFileManager = (props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordPickerProvider {...pickerProps}>
|
<RecordPickerProvider {...pickerProps}>
|
||||||
<CollectionProvider name={collectionField.target}>
|
<CollectionProvider name={collectionField?.target}>
|
||||||
<FormProvider>
|
<FormProvider>
|
||||||
<TableSelectorParamsProvider params={{ filter: getFilter() }}>
|
<TableSelectorParamsProvider params={{ filter: getFilter() }}>
|
||||||
<SchemaComponentOptions scope={{ usePickActionProps, useTableSelectorProps }}>
|
<SchemaComponentOptions scope={{ usePickActionProps, useTableSelectorProps }}>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { observer, useField, connect, createSchemaField, FormProvider, useFieldSchema, Field } from '@formily/react';
|
|
||||||
import { createForm, onFormValuesChange } from '@formily/core';
|
|
||||||
import { uid } from '@formily/shared';
|
|
||||||
import { Space, Tag, Spin, Select as AntdSelect, Input } from 'antd';
|
|
||||||
import { ArrayItems, FormItem } from '@formily/antd-v5';
|
import { ArrayItems, FormItem } from '@formily/antd-v5';
|
||||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
import { createForm, onFormValuesChange } from '@formily/core';
|
||||||
|
import { FormProvider, connect, createSchemaField, observer, useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import { Select as AntdSelect, Input, Space, Spin, Tag } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCompile, SchemaComponent } from '../../../schema-component';
|
|
||||||
import { useAPIClient, useCollectionManager } from '../../../';
|
import { useAPIClient, useCollectionManager } from '../../../';
|
||||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
||||||
|
import { SchemaComponent, useCompile } from '../../../schema-component';
|
||||||
import useServiceOptions, { useAssociationFieldContext } from './hooks';
|
import useServiceOptions, { useAssociationFieldContext } from './hooks';
|
||||||
|
|
||||||
const EMPTY = 'N/A';
|
const EMPTY = 'N/A';
|
||||||
@ -73,7 +73,7 @@ const CascadeSelect = connect((props) => {
|
|||||||
if (Array.isArray(label)) {
|
if (Array.isArray(label)) {
|
||||||
label = label
|
label = label
|
||||||
.map((item, index) => {
|
.map((item, index) => {
|
||||||
const option = targetField.uiSchema.enum.find((i) => i.value === item);
|
const option = (targetField.uiSchema.enum as any).find?.((i) => i.value === item);
|
||||||
if (option) {
|
if (option) {
|
||||||
return (
|
return (
|
||||||
<Tag key={index} color={option.color} style={{ marginRight: 3 }}>
|
<Tag key={index} color={option.color} style={{ marginRight: 3 }}>
|
||||||
@ -86,7 +86,7 @@ const CascadeSelect = connect((props) => {
|
|||||||
})
|
})
|
||||||
.reverse();
|
.reverse();
|
||||||
} else {
|
} else {
|
||||||
const item = targetField.uiSchema.enum.find((i) => i.value === label);
|
const item = (targetField.uiSchema.enum as any).find?.((i) => i.value === label);
|
||||||
if (item) {
|
if (item) {
|
||||||
label = <Tag color={item.color}>{item.label}</Tag>;
|
label = <Tag color={item.color}>{item.label}</Tag>;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { RecursionField, SchemaOptionsContext, observer, useField, useFieldSchem
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { ACLCollectionProvider, useACLActionParamsContext } from '../../../acl';
|
import { ACLCollectionProvider, useACLActionParamsContext } from '../../../acl';
|
||||||
import { CollectionProvider } from '../../../collection-manager';
|
import { CollectionProvider } from '../../../collection-manager';
|
||||||
import { useSchemaOptionsContext } from '../../../schema-component';
|
import { FormItem, useSchemaOptionsContext } from '../../../schema-component';
|
||||||
import Select from '../select/Select';
|
import Select from '../select/Select';
|
||||||
import { useAssociationFieldContext, useInsertSchema } from './hooks';
|
import { useAssociationFieldContext, useInsertSchema } from './hooks';
|
||||||
import schema from './schema';
|
import schema from './schema';
|
||||||
@ -24,8 +24,9 @@ export const InternalSubTable = observer(
|
|||||||
const option = useSchemaOptionsContext();
|
const option = useSchemaOptionsContext();
|
||||||
const components = {
|
const components = {
|
||||||
...option.components,
|
...option.components,
|
||||||
'Radio.Group': (props) => <Select data-testid="antd-select" {...props} />,
|
FormItem,
|
||||||
'Checkbox.Group': (props) => <Select data-testid="antd-select" multiple={true} mode="multiple" {...props} />,
|
'Radio.Group': Select,
|
||||||
|
'Checkbox.Group': (props) => <Select multiple={true} mode="multiple" {...props} />,
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<CollectionProvider name={options.target}>
|
<CollectionProvider name={options.target}>
|
||||||
|
@ -57,7 +57,7 @@ export const ReadPrettyInternalViewer: React.FC = observer(
|
|||||||
);
|
);
|
||||||
const text = getLabelFormatValue(compile(labelUiSchema), val, true);
|
const text = getLabelFormatValue(compile(labelUiSchema), val, true);
|
||||||
return (
|
return (
|
||||||
<Fragment key={`${record.id}_${index}`}>
|
<Fragment key={`${record?.id}_${index}`}>
|
||||||
<span>
|
<span>
|
||||||
{snapshot ? (
|
{snapshot ? (
|
||||||
text
|
text
|
||||||
|
@ -2,15 +2,23 @@ import { CloseOutlined, PlusOutlined } from '@ant-design/icons';
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { ArrayField } from '@formily/core';
|
import { ArrayField } from '@formily/core';
|
||||||
import { spliceArrayState } from '@formily/core/esm/shared/internals';
|
import { spliceArrayState } from '@formily/core/esm/shared/internals';
|
||||||
import { observer, RecursionField, useFieldSchema } from '@formily/react';
|
import { RecursionField, observer, useFieldSchema } from '@formily/react';
|
||||||
import { action } from '@formily/reactive';
|
import { action } from '@formily/reactive';
|
||||||
import { each } from '@formily/shared';
|
import { each } from '@formily/shared';
|
||||||
import { Button, Card, Divider, Tooltip } from 'antd';
|
import { Button, Card, Divider, Tooltip } from 'antd';
|
||||||
import React, { useContext } from 'react';
|
import _ from 'lodash';
|
||||||
|
import React, { useCallback, useContext } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FormActiveFieldsProvider } from '../../../block-provider';
|
||||||
|
import { RecordIndexProvider, RecordProvider, useRecord } from '../../../record-provider';
|
||||||
|
import { isPatternDisabled, isSystemField } from '../../../schema-settings';
|
||||||
|
import {
|
||||||
|
DefaultValueProvider,
|
||||||
|
interfacesOfUnsupportedDefaultValue,
|
||||||
|
} from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||||
|
import { LocalVariablesProvider } from '../../../variables/hooks/useLocalVariables';
|
||||||
import { AssociationFieldContext } from './context';
|
import { AssociationFieldContext } from './context';
|
||||||
import { useAssociationFieldContext } from './hooks';
|
import { useAssociationFieldContext } from './hooks';
|
||||||
import { RecordProvider, useRecord } from '../../../record-provider';
|
|
||||||
|
|
||||||
export const Nester = (props) => {
|
export const Nester = (props) => {
|
||||||
const { options } = useContext(AssociationFieldContext);
|
const { options } = useContext(AssociationFieldContext);
|
||||||
@ -24,7 +32,46 @@ export const Nester = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ToOneNester = (props) => {
|
const ToOneNester = (props) => {
|
||||||
return <Card bordered={true}>{props.children}</Card>;
|
const { field } = useAssociationFieldContext<ArrayField>();
|
||||||
|
const record = useRecord();
|
||||||
|
|
||||||
|
// 保存原始数据,用于判断是否允许设置默认值
|
||||||
|
const recordRef = React.useRef(_.omit(record, '__parent'));
|
||||||
|
|
||||||
|
const isAllowToSetDefaultValue = useCallback(({ form, fieldSchema, collectionField, getInterface }) => {
|
||||||
|
// 当 Field component 不是下列组件时,不允许设置默认值
|
||||||
|
if (
|
||||||
|
collectionField.target &&
|
||||||
|
fieldSchema['x-component-props']?.mode &&
|
||||||
|
!['Picker', 'Select'].includes(fieldSchema['x-component-props'].mode)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasOne 和 belongsTo 类型的字段只能有一个值,不会新增值,所以在编辑状态下不允许设置默认值
|
||||||
|
if (!_.isEmpty(recordRef.current)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
!form?.readPretty &&
|
||||||
|
!isPatternDisabled(fieldSchema) &&
|
||||||
|
!interfacesOfUnsupportedDefaultValue.includes(collectionField?.interface) &&
|
||||||
|
!isSystemField(collectionField, getInterface)
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormActiveFieldsProvider name="nester">
|
||||||
|
<RecordProvider record={field.value}>
|
||||||
|
<LocalVariablesProvider iterationCtx={field.value}>
|
||||||
|
<DefaultValueProvider isAllowToSetDefaultValue={isAllowToSetDefaultValue}>
|
||||||
|
<Card bordered={true}>{props.children}</Card>
|
||||||
|
</DefaultValueProvider>
|
||||||
|
</LocalVariablesProvider>
|
||||||
|
</RecordProvider>
|
||||||
|
</FormActiveFieldsProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ToManyNester = observer(
|
const ToManyNester = observer(
|
||||||
@ -32,7 +79,30 @@ const ToManyNester = observer(
|
|||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { options, field, allowMultiple, allowDissociate } = useAssociationFieldContext<ArrayField>();
|
const { options, field, allowMultiple, allowDissociate } = useAssociationFieldContext<ArrayField>();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (field.value || []).length > 0 ? (
|
|
||||||
|
if (!Array.isArray(field.value)) {
|
||||||
|
field.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAllowToSetDefaultValue = useCallback(({ form, fieldSchema, collectionField, getInterface }) => {
|
||||||
|
// 当 Field component 不是下列组件时,不允许设置默认值
|
||||||
|
if (
|
||||||
|
collectionField.target &&
|
||||||
|
fieldSchema['x-component-props']?.mode &&
|
||||||
|
!['Picker', 'Select'].includes(fieldSchema['x-component-props'].mode)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
!form?.readPretty &&
|
||||||
|
!isPatternDisabled(fieldSchema) &&
|
||||||
|
!interfacesOfUnsupportedDefaultValue.includes(collectionField?.interface) &&
|
||||||
|
!isSystemField(collectionField, getInterface)
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return field.value.length > 0 ? (
|
||||||
<Card
|
<Card
|
||||||
bordered={true}
|
bordered={true}
|
||||||
style={{ position: 'relative' }}
|
style={{ position: 'relative' }}
|
||||||
@ -42,7 +112,7 @@ const ToManyNester = observer(
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{(field.value || []).map((value, index) => {
|
{field.value.map((value, index) => {
|
||||||
let allowed = allowDissociate;
|
let allowed = allowDissociate;
|
||||||
if (!allowDissociate) {
|
if (!allowDissociate) {
|
||||||
allowed = !value?.[options.targetKey];
|
allowed = !value?.[options.targetKey];
|
||||||
@ -55,7 +125,7 @@ const ToManyNester = observer(
|
|||||||
<PlusOutlined
|
<PlusOutlined
|
||||||
style={{ zIndex: 1000, marginRight: '10px', color: '#a8a3a3' }}
|
style={{ zIndex: 1000, marginRight: '10px', color: '#a8a3a3' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
action(() => {
|
void action(() => {
|
||||||
if (!Array.isArray(field.value)) {
|
if (!Array.isArray(field.value)) {
|
||||||
field.value = [];
|
field.value = [];
|
||||||
}
|
}
|
||||||
@ -80,7 +150,7 @@ const ToManyNester = observer(
|
|||||||
<CloseOutlined
|
<CloseOutlined
|
||||||
style={{ zIndex: 1000, color: '#a8a3a3' }}
|
style={{ zIndex: 1000, color: '#a8a3a3' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
action(() => {
|
void action(() => {
|
||||||
spliceArrayState(field as any, {
|
spliceArrayState(field as any, {
|
||||||
startIndex: index,
|
startIndex: index,
|
||||||
deleteCount: 1,
|
deleteCount: 1,
|
||||||
@ -93,9 +163,21 @@ const ToManyNester = observer(
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<FormActiveFieldsProvider name="nester">
|
||||||
<RecordProvider record={value}>
|
<RecordProvider record={value}>
|
||||||
<RecursionField onlyRenderProperties basePath={field.address.concat(index)} schema={fieldSchema} />
|
<LocalVariablesProvider iterationCtx={value}>
|
||||||
|
<RecordIndexProvider index={index}>
|
||||||
|
<DefaultValueProvider isAllowToSetDefaultValue={isAllowToSetDefaultValue}>
|
||||||
|
<RecursionField
|
||||||
|
onlyRenderProperties
|
||||||
|
basePath={field.address.concat(index)}
|
||||||
|
schema={fieldSchema}
|
||||||
|
/>
|
||||||
|
</DefaultValueProvider>
|
||||||
|
</RecordIndexProvider>
|
||||||
|
</LocalVariablesProvider>
|
||||||
</RecordProvider>
|
</RecordProvider>
|
||||||
|
</FormActiveFieldsProvider>
|
||||||
<Divider />
|
<Divider />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
@ -7,12 +7,15 @@ import { action } from '@formily/reactive';
|
|||||||
import { isArr } from '@formily/shared';
|
import { isArr } from '@formily/shared';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { FormActiveFieldsProvider } from '../../../block-provider';
|
||||||
|
import { useSubTableSpecialCase } from '../form-item/hooks/useSpecialCase';
|
||||||
import { Table } from '../table-v2/Table';
|
import { Table } from '../table-v2/Table';
|
||||||
import { useAssociationFieldContext } from './hooks';
|
import { useAssociationFieldContext } from './hooks';
|
||||||
|
|
||||||
export const SubTable: any = observer(
|
export const SubTable: any = observer(
|
||||||
(props: any) => {
|
(props: any) => {
|
||||||
const { field } = useAssociationFieldContext<ArrayField>();
|
const { field } = useAssociationFieldContext<ArrayField>();
|
||||||
|
useSubTableSpecialCase({ field });
|
||||||
const move = (fromIndex: number, toIndex: number) => {
|
const move = (fromIndex: number, toIndex: number) => {
|
||||||
if (toIndex === undefined) return;
|
if (toIndex === undefined) return;
|
||||||
if (!isArr(field.value)) return;
|
if (!isArr(field.value)) return;
|
||||||
@ -59,6 +62,7 @@ export const SubTable: any = observer(
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
|
<FormActiveFieldsProvider name="nester">
|
||||||
<Table
|
<Table
|
||||||
className={css`
|
className={css`
|
||||||
.ant-formily-item.ant-formily-item-feedback-layout-loose {
|
.ant-formily-item.ant-formily-item-feedback-layout-loose {
|
||||||
@ -87,7 +91,7 @@ export const SubTable: any = observer(
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
field.value = field.value || [];
|
field.value = field.value || [];
|
||||||
field.value.push({});
|
field.value.push({});
|
||||||
field.onInput(field.value);
|
void field.onInput(field.value);
|
||||||
}}
|
}}
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
>
|
>
|
||||||
@ -97,6 +101,7 @@ export const SubTable: any = observer(
|
|||||||
}
|
}
|
||||||
isSubTable={true}
|
isSubTable={true}
|
||||||
/>
|
/>
|
||||||
|
</FormActiveFieldsProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
import { GeneralField } from '@formily/core';
|
import { GeneralField } from '@formily/core';
|
||||||
import { useField, useFieldSchema, useForm } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import flat from 'flat';
|
import { reaction } from '@formily/reactive';
|
||||||
import { isString } from 'lodash';
|
import { flatten } from '@nocobase/utils/client';
|
||||||
|
import _, { isString } from 'lodash';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import { useCallback, useContext, useMemo } from 'react';
|
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { useBlockRequestContext } from '../../../block-provider/BlockProvider';
|
import { useFormBlockContext } from '../../../block-provider';
|
||||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
import { isInFilterFormBlock } from '../../../filter-provider';
|
import { isInFilterFormBlock } from '../../../filter-provider';
|
||||||
import { useRecord } from '../../../record-provider';
|
import { useRecord } from '../../../record-provider';
|
||||||
import { getInnermostKeyAndValue } from '../../common/utils/uitls';
|
import { useParseDataScopeFilter } from '../../../schema-settings';
|
||||||
|
import { DEBOUNCE_WAIT } from '../../../variables';
|
||||||
|
import { getPath } from '../../../variables/utils/getPath';
|
||||||
|
import { isVariable } from '../../../variables/utils/isVariable';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
|
import { removeNullCondition } from '../filter';
|
||||||
import { AssociationFieldContext } from './context';
|
import { AssociationFieldContext } from './context';
|
||||||
import { extractFilterfield, extractValuesByPattern, generatePattern, parseVariables } from './util';
|
|
||||||
|
|
||||||
export const useInsertSchema = (component) => {
|
export const useInsertSchema = (component) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
@ -49,74 +53,47 @@ export default function useServiceOptions(props) {
|
|||||||
const params = service?.params || {};
|
const params = service?.params || {};
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const form = useForm();
|
|
||||||
const ctx = useBlockRequestContext();
|
|
||||||
const { getField } = useCollection();
|
const { getField } = useCollection();
|
||||||
const { getCollectionFields, getCollectionJoinField } = useCollectionManager();
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
const record = useRecord();
|
const record = useRecord();
|
||||||
const parseFilter = useCallback(
|
const { parseFilter } = useParseDataScopeFilter();
|
||||||
(rules) => {
|
const [fieldServiceFilter, setFieldServiceFilter] = useState(null);
|
||||||
if (!rules) {
|
const { form } = useFormBlockContext();
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (typeof rules === 'string') {
|
|
||||||
return rules;
|
|
||||||
}
|
|
||||||
const type = Object.keys(rules)[0] || '$and';
|
|
||||||
const conditions = rules[type];
|
|
||||||
const results = [];
|
|
||||||
conditions?.forEach((c) => {
|
|
||||||
const jsonlogic = getInnermostKeyAndValue(c);
|
|
||||||
const regex = /{{(.*?)}}/;
|
|
||||||
const matches = jsonlogic.value?.match?.(regex);
|
|
||||||
if (!matches || (!matches[1].includes('$form') && !matches[1].includes('$iteration'))) {
|
|
||||||
results.push(c);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const associationfield = extractFilterfield(matches[1]);
|
|
||||||
const filterCollectionField = getCollectionJoinField(`${ctx.props.collection}.${associationfield}`);
|
|
||||||
if (['o2m', 'm2m'].includes(filterCollectionField?.interface)) {
|
|
||||||
// 对多子表单
|
|
||||||
const pattern = generatePattern(matches?.[1], associationfield);
|
|
||||||
const parseValue: any = extractValuesByPattern(flat(form.values), pattern);
|
|
||||||
const filters = parseValue.map((v) => {
|
|
||||||
return JSON.parse(JSON.stringify(c).replace(jsonlogic.value, v));
|
|
||||||
});
|
|
||||||
results.push({ $or: filters });
|
|
||||||
} else {
|
|
||||||
const variablesCtx = { $form: form.values, $iteration: form.values };
|
|
||||||
let str = matches?.[1];
|
|
||||||
if (str.includes('$iteration')) {
|
|
||||||
const path = field.path.segments.concat([]);
|
|
||||||
path.pop();
|
|
||||||
str = str.replace('$iteration.', `$iteration.${path.join('.')}.`);
|
|
||||||
}
|
|
||||||
const parseValue = parseVariables(str, variablesCtx);
|
|
||||||
if (Array.isArray(parseValue)) {
|
|
||||||
const filters = parseValue.map((v) => {
|
|
||||||
return JSON.parse(JSON.stringify(c).replace(jsonlogic.value, v));
|
|
||||||
});
|
|
||||||
results.push({ $or: filters });
|
|
||||||
} else {
|
|
||||||
const filterObj = JSON.parse(
|
|
||||||
JSON.stringify(c).replace(jsonlogic.value, str.endsWith('id') ? parseValue ?? 0 : parseValue),
|
|
||||||
);
|
|
||||||
results.push(filterObj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return { [type]: results };
|
|
||||||
},
|
|
||||||
[ctx.props?.collection, field.path.segments, form.values, getCollectionJoinField],
|
|
||||||
);
|
|
||||||
|
|
||||||
const fieldServiceFilter = useMemo(() => {
|
useEffect(() => {
|
||||||
const filterFromSchema = isString(fieldSchema?.['x-component-props']?.service?.params?.filter)
|
const filterFromSchema = isString(fieldSchema?.['x-component-props']?.service?.params?.filter)
|
||||||
? field.componentProps?.service?.params?.filter
|
? field.componentProps?.service?.params?.filter
|
||||||
: fieldSchema?.['x-component-props']?.service?.params?.filter;
|
: fieldSchema?.['x-component-props']?.service?.params?.filter;
|
||||||
|
|
||||||
return mergeFilter([parseFilter(filterFromSchema) || service?.params?.filter]);
|
const _run = async () => {
|
||||||
}, [field.componentProps?.service?.params?.filter, fieldSchema, parseFilter, service?.params?.filter]);
|
const result = await parseFilter(mergeFilter([filterFromSchema || service?.params?.filter]));
|
||||||
|
setFieldServiceFilter(removeNullCondition(result));
|
||||||
|
};
|
||||||
|
const run = _.debounce(_run, DEBOUNCE_WAIT);
|
||||||
|
|
||||||
|
_run();
|
||||||
|
|
||||||
|
reaction(() => {
|
||||||
|
// 这一步主要是为了使 reaction 能够收集到依赖
|
||||||
|
const flat = flatten(filterFromSchema, {
|
||||||
|
breakOn({ key }) {
|
||||||
|
return key.startsWith('$') && key !== '$and' && key !== '$or';
|
||||||
|
},
|
||||||
|
transformValue(value) {
|
||||||
|
if (!isVariable(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
const result = _.get({ $nForm: form?.values }, getPath(value));
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return flat;
|
||||||
|
}, run);
|
||||||
|
}, [
|
||||||
|
field.componentProps?.service?.params?.filter,
|
||||||
|
fieldSchema?.['x-component-props']?.service?.params?.filter,
|
||||||
|
service?.params?.filter,
|
||||||
|
]);
|
||||||
|
|
||||||
const normalizeValues = useCallback(
|
const normalizeValues = useCallback(
|
||||||
(obj) => {
|
(obj) => {
|
||||||
@ -132,16 +109,21 @@ export default function useServiceOptions(props) {
|
|||||||
if (props.value === undefined || props.value === null) {
|
if (props.value === undefined || props.value === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let result: any[] = null;
|
||||||
|
|
||||||
if (Array.isArray(props.value)) {
|
if (Array.isArray(props.value)) {
|
||||||
return props.value.map(normalizeValues);
|
result = props.value.map(normalizeValues);
|
||||||
} else {
|
} else {
|
||||||
return [normalizeValues(props.value)];
|
result = [normalizeValues(props.value)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result.filter(Boolean);
|
||||||
}, [props.value, normalizeValues]);
|
}, [props.value, normalizeValues]);
|
||||||
|
|
||||||
const collectionField = useMemo(() => {
|
const collectionField = useMemo(() => {
|
||||||
return getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']);
|
return getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']);
|
||||||
}, [fieldSchema.name]);
|
}, [fieldSchema]);
|
||||||
|
|
||||||
const sourceValue = record?.[collectionField?.sourceKey];
|
const sourceValue = record?.[collectionField?.sourceKey];
|
||||||
const filter = useMemo(() => {
|
const filter = useMemo(() => {
|
||||||
@ -169,7 +151,7 @@ export default function useServiceOptions(props) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
params?.filter && value
|
params?.filter && value?.length
|
||||||
? {
|
? {
|
||||||
[fieldNames?.value]: {
|
[fieldNames?.value]: {
|
||||||
['$in']: value,
|
['$in']: value,
|
||||||
@ -179,7 +161,16 @@ export default function useServiceOptions(props) {
|
|||||||
],
|
],
|
||||||
'$or',
|
'$or',
|
||||||
);
|
);
|
||||||
}, [params?.filter, getCollectionFields, collectionField, sourceValue, value, fieldNames?.value]);
|
}, [
|
||||||
|
collectionField?.interface,
|
||||||
|
collectionField.foreignKey,
|
||||||
|
fieldSchema,
|
||||||
|
fieldServiceFilter,
|
||||||
|
sourceValue,
|
||||||
|
params?.filter,
|
||||||
|
value,
|
||||||
|
fieldNames?.value,
|
||||||
|
]);
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return {
|
return {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema, Schema } from '@formily/react';
|
||||||
import { isArr } from '@formily/shared';
|
import { isArr } from '@formily/shared';
|
||||||
import { getDefaultFormat, str2moment } from '@nocobase/utils/client';
|
import { getDefaultFormat, str2moment } from '@nocobase/utils/client';
|
||||||
import { Tag } from 'antd';
|
import { Tag } from 'antd';
|
||||||
import { get, isFunction } from 'lodash';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CollectionFieldOptions, useCollectionManager } from '../../../collection-manager';
|
import { CollectionFieldOptions, useCollectionManager } from '../../../collection-manager';
|
||||||
@ -88,43 +87,9 @@ export function isShowFilePicker(labelUiSchema) {
|
|||||||
return labelUiSchema?.['x-component'] === 'Preview';
|
return labelUiSchema?.['x-component'] === 'Preview';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toValue = (value, placeholder) => {
|
/**
|
||||||
if (value === null || value === undefined) {
|
* 当前字段的模式是否是 `子表格` 或者 `子表单`
|
||||||
return placeholder;
|
*/
|
||||||
}
|
export function isSubMode(fieldSchema: Schema) {
|
||||||
return value;
|
return ['Nester', 'SubTable', 'PopoverNester'].includes(fieldSchema['x-component-props']?.mode);
|
||||||
};
|
|
||||||
|
|
||||||
export const parseVariables = (str: string, ctx) => {
|
|
||||||
if (str) {
|
|
||||||
const result = get(ctx, str);
|
|
||||||
return isFunction(result) ? result() : result;
|
|
||||||
} else {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
export function extractFilterfield(str) {
|
|
||||||
const match = str.match(/^\$form\.([^.[\]]+)/);
|
|
||||||
if (match) {
|
|
||||||
return match[1];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extractValuesByPattern(obj, pattern) {
|
|
||||||
const regexPattern = new RegExp(pattern.replace(/\*/g, '\\d+'));
|
|
||||||
const result = [];
|
|
||||||
|
|
||||||
for (const key in obj) {
|
|
||||||
if (regexPattern.test(key)) {
|
|
||||||
const value = obj[key];
|
|
||||||
result.push(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
export function generatePattern(str, fieldName) {
|
|
||||||
const result = str.replace(`$form.${fieldName}.`, `${fieldName}.*.`);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
@ -2,26 +2,21 @@ import { ISchema, useField, useFieldSchema } from '@formily/react';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import { useFormBlockContext } from '../../../block-provider';
|
||||||
useCollection,
|
import { useCollection, useCollectionManager, useSortFields } from '../../../collection-manager';
|
||||||
useCollectionFilterOptions,
|
|
||||||
useCollectionManager,
|
|
||||||
useSortFields,
|
|
||||||
} from '../../../collection-manager';
|
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useCompile, useDesignable } from '../../hooks';
|
import { useCompile, useDesignable } from '../../hooks';
|
||||||
|
|
||||||
export const AssociationFilterItemDesigner = (props) => {
|
export const AssociationFilterItemDesigner = (props) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
|
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { getCollectionJoinField } = useCollectionManager();
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
const { getField } = useCollection();
|
const { getField } = useCollection();
|
||||||
|
|
||||||
const collectionField = getField(fieldSchema['name']!) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
const filterEnum = collectionField?.target ? useCollectionFilterOptions(collectionField?.target) : [];
|
|
||||||
const defaultFilter = fieldSchema?.['x-component-props']?.params?.filter || {};
|
|
||||||
|
|
||||||
const { getCollectionFields } = useCollectionManager();
|
const { getCollectionFields } = useCollectionManager();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
@ -30,9 +25,6 @@ export const AssociationFilterItemDesigner = (props) => {
|
|||||||
const targetFields = collectionField?.target ? getCollectionFields(collectionField?.target) : [];
|
const targetFields = collectionField?.target ? getCollectionFields(collectionField?.target) : [];
|
||||||
|
|
||||||
const options = targetFields
|
const options = targetFields
|
||||||
// .filter(
|
|
||||||
// (field) => field?.interface && ['id', 'input', 'phone', 'email', 'integer', 'number'].includes(field?.interface),
|
|
||||||
// )
|
|
||||||
.filter((field) => !field?.target && field.type !== 'boolean')
|
.filter((field) => !field?.target && field.type !== 'boolean')
|
||||||
.map((field) => ({
|
.map((field) => ({
|
||||||
value: field?.name,
|
value: field?.name,
|
||||||
@ -117,22 +109,10 @@ export const AssociationFilterItemDesigner = (props) => {
|
|||||||
dn.refresh();
|
dn.refresh();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={collectionField?.target}
|
||||||
schema={
|
defaultFilter={fieldSchema?.['x-component-props']?.params?.filter || {}}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: defaultFilter,
|
|
||||||
enum: filterEnum,
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
_.set(field.componentProps, 'params', {
|
_.set(field.componentProps, 'params', {
|
||||||
...field.componentProps?.params,
|
...field.componentProps?.params,
|
||||||
|
@ -7,20 +7,15 @@ import _ from 'lodash';
|
|||||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useFilterByTk, useFormBlockContext } from '../../../block-provider';
|
import { useFilterByTk, useFormBlockContext } from '../../../block-provider';
|
||||||
import {
|
import { useCollection, useCollectionManager, useSortFields } from '../../../collection-manager';
|
||||||
useCollection,
|
|
||||||
useCollectionFilterOptions,
|
|
||||||
useCollectionManager,
|
|
||||||
useSortFields,
|
|
||||||
} from '../../../collection-manager';
|
|
||||||
import { GeneralSchemaItems } from '../../../schema-items';
|
import { GeneralSchemaItems } from '../../../schema-items';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled } from '../../../schema-settings';
|
||||||
|
import useIsAllowToSetDefaultValue from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||||
import { useIsShowMultipleSwitch } from '../../../schema-settings/hooks/useIsShowMultipleSwitch';
|
import { useIsShowMultipleSwitch } from '../../../schema-settings/hooks/useIsShowMultipleSwitch';
|
||||||
import { useCompile, useDesignable, useFieldComponentOptions, useFieldTitle } from '../../hooks';
|
import { useCompile, useDesignable, useFieldComponentOptions, useFieldTitle } from '../../hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
||||||
import { defaultFieldNames } from '../select';
|
import { defaultFieldNames } from '../select';
|
||||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
|
|
||||||
import { ReadPretty } from './ReadPretty';
|
import { ReadPretty } from './ReadPretty';
|
||||||
import useServiceOptions from './useServiceOptions';
|
import useServiceOptions from './useServiceOptions';
|
||||||
|
|
||||||
@ -105,6 +100,7 @@ AssociationSelect.Designer = function Designer() {
|
|||||||
const { dn, refresh, insertAdjacent } = useDesignable();
|
const { dn, refresh, insertAdjacent } = useDesignable();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const IsShowMultipleSwitch = useIsShowMultipleSwitch();
|
const IsShowMultipleSwitch = useIsShowMultipleSwitch();
|
||||||
|
const { isAllowToSetDefaultValue } = useIsAllowToSetDefaultValue();
|
||||||
|
|
||||||
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
const fieldComponentOptions = useFieldComponentOptions();
|
const fieldComponentOptions = useFieldComponentOptions();
|
||||||
@ -119,8 +115,6 @@ AssociationSelect.Designer = function Designer() {
|
|||||||
const sortFields = useSortFields(collectionField?.target);
|
const sortFields = useSortFields(collectionField?.target);
|
||||||
|
|
||||||
const defaultSort = field.componentProps?.service?.params?.sort || [];
|
const defaultSort = field.componentProps?.service?.params?.sort || [];
|
||||||
const defaultFilter = field.componentProps?.service?.params?.filter || {};
|
|
||||||
const dataSource = useCollectionFilterOptions(collectionField?.target);
|
|
||||||
|
|
||||||
// TODO: 这里 fieldSchema['x-read-pretty'] 的值为 true,但是 field.readPretty 的值却为 false,不知道什么原因
|
// TODO: 这里 fieldSchema['x-read-pretty'] 的值为 true,但是 field.readPretty 的值却为 false,不知道什么原因
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -287,10 +281,7 @@ AssociationSelect.Designer = function Designer() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{form &&
|
{isAllowToSetDefaultValue() && (
|
||||||
!form?.readPretty &&
|
|
||||||
isShowDefaultValue(collectionField, getInterface) &&
|
|
||||||
!isPatternDisabled(fieldSchema) && (
|
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.ModalItem
|
||||||
title={t('Set default value')}
|
title={t('Set default value')}
|
||||||
components={{ ArrayCollapse, FormLayout }}
|
components={{ ArrayCollapse, FormLayout }}
|
||||||
@ -409,25 +400,10 @@ AssociationSelect.Designer = function Designer() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={collectionField?.target}
|
||||||
schema={
|
defaultFilter={field.componentProps?.service?.params?.filter || {}}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: defaultFilter,
|
|
||||||
// title: '数据范围',
|
|
||||||
enum: dataSource,
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {
|
|
||||||
dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
filter = removeNullCondition(filter);
|
filter = removeNullCondition(filter);
|
||||||
_.set(field.componentProps, 'service.params.filter', filter);
|
_.set(field.componentProps, 'service.params.filter', filter);
|
||||||
@ -646,8 +622,6 @@ AssociationSelect.FilterDesigner = function FilterDesigner() {
|
|||||||
const sortFields = useSortFields(collectionField?.target);
|
const sortFields = useSortFields(collectionField?.target);
|
||||||
|
|
||||||
const defaultSort = field.componentProps?.service?.params?.sort || [];
|
const defaultSort = field.componentProps?.service?.params?.sort || [];
|
||||||
const defaultFilter = field.componentProps?.service?.params?.filter || {};
|
|
||||||
const dataSource = useCollectionFilterOptions(collectionField?.target);
|
|
||||||
|
|
||||||
const sort = defaultSort?.map((item: string) => {
|
const sort = defaultSort?.map((item: string) => {
|
||||||
return item.startsWith('-')
|
return item.startsWith('-')
|
||||||
@ -839,25 +813,10 @@ AssociationSelect.FilterDesigner = function FilterDesigner() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={collectionField?.target}
|
||||||
schema={
|
defaultFilter={field.componentProps?.service?.params?.filter || {}}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: defaultFilter,
|
|
||||||
// title: '数据范围',
|
|
||||||
enum: dataSource,
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {
|
|
||||||
dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
filter = removeNullCondition(filter);
|
filter = removeNullCondition(filter);
|
||||||
_.set(field.componentProps, 'service.params.filter', filter);
|
_.set(field.componentProps, 'service.params.filter', filter);
|
||||||
|
@ -1,25 +1,22 @@
|
|||||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FixedBlockDesignerItem, removeNullCondition, useDesignable } from '../..';
|
import { FixedBlockDesignerItem, removeNullCondition, useDesignable } from '../..';
|
||||||
import { useCalendarBlockContext } from '../../../block-provider';
|
import { useCalendarBlockContext, useFormBlockContext } from '../../../block-provider';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks';
|
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
|
|
||||||
|
|
||||||
export const CalendarDesigner = () => {
|
export const CalendarDesigner = () => {
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
const { name, title } = useCollection();
|
const { name, title } = useCollection();
|
||||||
const { getCollectionFieldsOptions } = useCollectionManager();
|
const { getCollectionFieldsOptions } = useCollectionManager();
|
||||||
const dataSource = useCollectionFilterOptions(name);
|
|
||||||
const { service } = useCalendarBlockContext();
|
const { service } = useCalendarBlockContext();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
|
||||||
const fieldNames = fieldSchema?.['x-decorator-props']?.['fieldNames'] || {};
|
const fieldNames = fieldSchema?.['x-decorator-props']?.['fieldNames'] || {};
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||||
|
|
||||||
@ -105,30 +102,10 @@ export const CalendarDesigner = () => {
|
|||||||
dn.refresh();
|
dn.refresh();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={name}
|
||||||
schema={
|
defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: defaultFilter,
|
|
||||||
enum: dataSource,
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {
|
|
||||||
dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
initialValues={
|
|
||||||
{
|
|
||||||
// title: field.title,
|
|
||||||
// icon: field.componentProps.icon,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
filter = removeNullCondition(filter);
|
filter = removeNullCondition(filter);
|
||||||
const params = field.decoratorProps.params || {};
|
const params = field.decoratorProps.params || {};
|
||||||
|
@ -56,17 +56,20 @@ export const Cascader = connect(
|
|||||||
if (selectedOptions[index]) {
|
if (selectedOptions[index]) {
|
||||||
return <span key={label}>{label}</span>;
|
return <span key={label}>{label}</span>;
|
||||||
}
|
}
|
||||||
const item = toArr(value).find((item) => item[fieldNames.value] === label);
|
const item = toArr(value)
|
||||||
|
.filter(Boolean)
|
||||||
|
.find((item) => item[fieldNames.value] === label);
|
||||||
return <span key={label}>{item?.[fieldNames.label] || label}</span>;
|
return <span key={label}>{item?.[fieldNames.label] || label}</span>;
|
||||||
})}
|
})}
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const handelDropDownVisible = (value) => {
|
const handelDropDownVisible = (value) => {
|
||||||
if (value && !field.dataSource.length) {
|
if (value && !field.dataSource?.length) {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AntdCascader
|
<AntdCascader
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
@ -27,7 +27,7 @@ function useOptions({ filter, isTableOid }: CollectionSelectProps) {
|
|||||||
: collections;
|
: collections;
|
||||||
const filtered =
|
const filtered =
|
||||||
typeof filter === 'function'
|
typeof filter === 'function'
|
||||||
? (inheritCollections || currentCollections).filter(filter)
|
? ((inheritCollections || currentCollections) as any[]).filter(filter)
|
||||||
: inheritCollections || currentCollections;
|
: inheritCollections || currentCollections;
|
||||||
return filtered
|
return filtered
|
||||||
.filter((item) => !item.hidden)
|
.filter((item) => !item.hidden)
|
||||||
|
@ -37,8 +37,9 @@ const InternalRangePicker = connect(
|
|||||||
|
|
||||||
export const DatePicker = (props) => {
|
export const DatePicker = (props) => {
|
||||||
const { utc = true } = useDatePickerContext();
|
const { utc = true } = useDatePickerContext();
|
||||||
|
const value = Array.isArray(props.value) ? props.value[0] : props.value;
|
||||||
props = { utc, ...props };
|
props = { utc, ...props };
|
||||||
return <InternalDatePicker {...props} />;
|
return <InternalDatePicker {...props} value={value} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
DatePicker.ReadPretty = ReadPretty.DatePicker;
|
DatePicker.ReadPretty = ReadPretty.DatePicker;
|
||||||
|
@ -10,6 +10,18 @@ describe('getDateRanges', () => {
|
|||||||
expect(end.toISOString()).toBe(dayjs().endOf('day').toISOString());
|
expect(end.toISOString()).toBe(dayjs().endOf('day').toISOString());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('yesterday', () => {
|
||||||
|
const [start, end] = dateRanges.yesterday();
|
||||||
|
expect(dayjs(start).isSame(dayjs().subtract(1, 'day'), 'day')).toBe(true);
|
||||||
|
expect(dayjs(end).isSame(dayjs().subtract(1, 'day'), 'day')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('tomorrow', () => {
|
||||||
|
const [start, end] = dateRanges.tomorrow();
|
||||||
|
expect(dayjs(start).isSame(dayjs().add(1, 'day'), 'day')).toBe(true);
|
||||||
|
expect(dayjs(end).isSame(dayjs().add(1, 'day'), 'day')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('lastWeek', () => {
|
it('lastWeek', () => {
|
||||||
const [start, end] = dateRanges.lastWeek();
|
const [start, end] = dateRanges.lastWeek();
|
||||||
expect(start.toISOString()).toBe(dayjs().add(-1, 'week').startOf('isoWeek').toISOString());
|
expect(start.toISOString()).toBe(dayjs().add(-1, 'week').startOf('isoWeek').toISOString());
|
||||||
|
@ -6,7 +6,9 @@ const toStringByPicker = (value, picker, timezone: 'gmt' | 'local') => {
|
|||||||
if (!dayjs.isDayjs(value)) return value;
|
if (!dayjs.isDayjs(value)) return value;
|
||||||
if (timezone === 'local') {
|
if (timezone === 'local') {
|
||||||
const offset = new Date().getTimezoneOffset();
|
const offset = new Date().getTimezoneOffset();
|
||||||
return dayjs(toStringByPicker(value, picker, 'gmt')).add(offset, 'minutes').toISOString();
|
return dayjs(toStringByPicker(value, picker, 'gmt'))
|
||||||
|
.add(offset, 'minutes')
|
||||||
|
.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (picker === 'year') {
|
if (picker === 'year') {
|
||||||
@ -136,18 +138,24 @@ const getEnd = (offset: any, unit: any) => {
|
|||||||
|
|
||||||
export const getDateRanges = () => {
|
export const getDateRanges = () => {
|
||||||
return {
|
return {
|
||||||
|
now: () => dayjs(),
|
||||||
today: () => [getStart(0, 'day'), getEnd(0, 'day')],
|
today: () => [getStart(0, 'day'), getEnd(0, 'day')],
|
||||||
lastWeek: () => [getStart(-1, 'isoWeek'), getEnd(-1, 'isoWeek')],
|
yesterday: () => [getStart(-1, 'day'), getEnd(-1, 'day')],
|
||||||
|
tomorrow: () => [getStart(1, 'day'), getEnd(1, 'day')],
|
||||||
thisWeek: () => [getStart(0, 'isoWeek'), getEnd(0, 'isoWeek')],
|
thisWeek: () => [getStart(0, 'isoWeek'), getEnd(0, 'isoWeek')],
|
||||||
|
lastWeek: () => [getStart(-1, 'isoWeek'), getEnd(-1, 'isoWeek')],
|
||||||
nextWeek: () => [getStart(1, 'isoWeek'), getEnd(1, 'isoWeek')],
|
nextWeek: () => [getStart(1, 'isoWeek'), getEnd(1, 'isoWeek')],
|
||||||
lastMonth: () => [getStart(-1, 'month'), getEnd(-1, 'month')],
|
thisIsoWeek: () => [getStart(0, 'isoWeek'), getEnd(0, 'isoWeek')],
|
||||||
|
lastIsoWeek: () => [getStart(-1, 'isoWeek'), getEnd(-1, 'isoWeek')],
|
||||||
|
nextIsoWeek: () => [getStart(1, 'isoWeek'), getEnd(1, 'isoWeek')],
|
||||||
thisMonth: () => [getStart(0, 'month'), getEnd(0, 'month')],
|
thisMonth: () => [getStart(0, 'month'), getEnd(0, 'month')],
|
||||||
|
lastMonth: () => [getStart(-1, 'month'), getEnd(-1, 'month')],
|
||||||
nextMonth: () => [getStart(1, 'month'), getEnd(1, 'month')],
|
nextMonth: () => [getStart(1, 'month'), getEnd(1, 'month')],
|
||||||
lastQuarter: () => [getStart(-1, 'quarter'), getEnd(-1, 'quarter')],
|
|
||||||
thisQuarter: () => [getStart(0, 'quarter'), getEnd(0, 'quarter')],
|
thisQuarter: () => [getStart(0, 'quarter'), getEnd(0, 'quarter')],
|
||||||
|
lastQuarter: () => [getStart(-1, 'quarter'), getEnd(-1, 'quarter')],
|
||||||
nextQuarter: () => [getStart(1, 'quarter'), getEnd(1, 'quarter')],
|
nextQuarter: () => [getStart(1, 'quarter'), getEnd(1, 'quarter')],
|
||||||
lastYear: () => [getStart(-1, 'year'), getEnd(-1, 'year')],
|
|
||||||
thisYear: () => [getStart(0, 'year'), getEnd(0, 'year')],
|
thisYear: () => [getStart(0, 'year'), getEnd(0, 'year')],
|
||||||
|
lastYear: () => [getStart(-1, 'year'), getEnd(-1, 'year')],
|
||||||
nextYear: () => [getStart(1, 'year'), getEnd(1, 'year')],
|
nextYear: () => [getStart(1, 'year'), getEnd(1, 'year')],
|
||||||
last7Days: () => [getStart(-6, 'days'), getEnd(0, 'days')],
|
last7Days: () => [getStart(-6, 'days'), getEnd(0, 'days')],
|
||||||
next7Days: () => [getStart(1, 'day'), getEnd(7, 'days')],
|
next7Days: () => [getStart(1, 'day'), getEnd(7, 'days')],
|
||||||
|
@ -1,17 +1,30 @@
|
|||||||
import { createForm, onFieldValueChange } from '@formily/core';
|
import { createForm, onFieldValueChange } from '@formily/core';
|
||||||
import { FieldContext, FormContext } from '@formily/react';
|
import { FieldContext, FormContext } from '@formily/react';
|
||||||
import { merge } from '@formily/shared';
|
import { merge } from '@formily/shared';
|
||||||
import React, { useContext, useMemo } from 'react';
|
import React, { useCallback, useContext, useMemo } from 'react';
|
||||||
|
import { CollectionFieldOptions } from '../../../collection-manager';
|
||||||
import { SchemaComponent } from '../../core';
|
import { SchemaComponent } from '../../core';
|
||||||
import { useComponent } from '../../hooks';
|
import { useComponent } from '../../hooks';
|
||||||
import { FilterContext } from './context';
|
import { FilterContext } from './context';
|
||||||
|
|
||||||
const isDateComponent = {
|
export interface DynamicComponentProps {
|
||||||
'DatePicker.RangePicker': true,
|
value: any;
|
||||||
DatePicker: true,
|
/**
|
||||||
};
|
* `Filter` 组件左侧选择的字段
|
||||||
|
*/
|
||||||
|
collectionField: CollectionFieldOptions;
|
||||||
|
onChange: (value: any) => void;
|
||||||
|
renderSchemaComponent: () => React.JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
export const DynamicComponent = (props) => {
|
interface Props {
|
||||||
|
schema: any;
|
||||||
|
value: any;
|
||||||
|
collectionField: CollectionFieldOptions;
|
||||||
|
onChange: (value: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DynamicComponent = (props: Props) => {
|
||||||
const { dynamicComponent, disabled } = useContext(FilterContext);
|
const { dynamicComponent, disabled } = useContext(FilterContext);
|
||||||
const component = useComponent(dynamicComponent);
|
const component = useComponent(dynamicComponent);
|
||||||
const form = useMemo(() => {
|
const form = useMemo(() => {
|
||||||
@ -26,8 +39,8 @@ export const DynamicComponent = (props) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [JSON.stringify(props.schema), JSON.stringify(props.value)]);
|
}, [JSON.stringify(props.value)]);
|
||||||
const renderSchemaComponent = () => {
|
const renderSchemaComponent = useCallback(() => {
|
||||||
return (
|
return (
|
||||||
<FieldContext.Provider value={null}>
|
<FieldContext.Provider value={null}>
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
@ -47,13 +60,14 @@ export const DynamicComponent = (props) => {
|
|||||||
/>
|
/>
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
}, [props.schema]);
|
||||||
return (
|
return (
|
||||||
<FormContext.Provider value={form}>
|
<FormContext.Provider value={form}>
|
||||||
<div data-testid="dynamic-component-filter-item">
|
<div data-testid="dynamic-component-filter-item">
|
||||||
{component
|
{component
|
||||||
? React.createElement(component, {
|
? React.createElement<DynamicComponentProps>(component, {
|
||||||
value: props.value,
|
value: props.value,
|
||||||
|
collectionField: props.collectionField,
|
||||||
onChange: props?.onChange,
|
onChange: props?.onChange,
|
||||||
renderSchemaComponent,
|
renderSchemaComponent,
|
||||||
})
|
})
|
||||||
|
@ -3,12 +3,11 @@ import { observer, useField, useFieldSchema } from '@formily/react';
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useRequest } from '../../../api-client';
|
import { useRequest } from '../../../api-client';
|
||||||
import { useProps } from '../../hooks/useProps';
|
import { useProps } from '../../hooks/useProps';
|
||||||
import { DatePickerProvider } from '../date-picker';
|
|
||||||
import { FilterContext } from './context';
|
|
||||||
import { FilterActionDesigner } from './Filter.Action.Designer';
|
import { FilterActionDesigner } from './Filter.Action.Designer';
|
||||||
import { FilterAction } from './FilterAction';
|
import { FilterAction } from './FilterAction';
|
||||||
import { FilterGroup } from './FilterGroup';
|
import { FilterGroup } from './FilterGroup';
|
||||||
import { SaveDefaultValue } from './SaveDefaultValue';
|
import { SaveDefaultValue } from './SaveDefaultValue';
|
||||||
|
import { FilterContext } from './context';
|
||||||
|
|
||||||
const useDef = (options) => {
|
const useDef = (options) => {
|
||||||
const field = useField<ObjectFieldModel>();
|
const field = useField<ObjectFieldModel>();
|
||||||
@ -26,12 +25,12 @@ export const Filter: any = observer(
|
|||||||
field.dataSource = data?.data || [];
|
field.dataSource = data?.data || [];
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
field.initialValue = fieldSchema.defaultValue;
|
field.initialValue = fieldSchema.defaultValue;
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<DatePickerProvider value={{ utc: false }}>
|
|
||||||
<FilterContext.Provider
|
<FilterContext.Provider
|
||||||
value={{
|
value={{
|
||||||
field,
|
field,
|
||||||
@ -43,9 +42,7 @@ export const Filter: any = observer(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FilterGroup {...props} bordered={false} />
|
<FilterGroup {...props} bordered={false} />
|
||||||
{/* <pre>{JSON.stringify(field.value, null, 2)}</pre> */}
|
|
||||||
</FilterContext.Provider>
|
</FilterContext.Provider>
|
||||||
</DatePickerProvider>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@ import { CloseCircleOutlined } from '@ant-design/icons';
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { observer } from '@formily/react';
|
import { observer } from '@formily/react';
|
||||||
import { Cascader, Select, Space } from 'antd';
|
import { Cascader, Select, Space } from 'antd';
|
||||||
import React, { useContext } from 'react';
|
import React, { useCallback, useContext, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCompile } from '../..';
|
import { useCompile } from '../..';
|
||||||
import { DynamicComponent } from './DynamicComponent';
|
import { DynamicComponent } from './DynamicComponent';
|
||||||
@ -14,27 +14,56 @@ export const FilterItem = observer(
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const remove = useContext(RemoveConditionContext);
|
const remove = useContext(RemoveConditionContext);
|
||||||
const { schema, fields, operators, dataIndex, operator, setDataIndex, setOperator, value, setValue } = useValues();
|
const {
|
||||||
|
schema,
|
||||||
|
fields,
|
||||||
|
operators,
|
||||||
|
dataIndex,
|
||||||
|
operator,
|
||||||
|
setDataIndex,
|
||||||
|
setOperator,
|
||||||
|
value,
|
||||||
|
setValue,
|
||||||
|
collectionField,
|
||||||
|
} = useValues();
|
||||||
|
const style = useMemo(() => ({ marginBottom: 8 }), []);
|
||||||
|
const fieldNames = useMemo(
|
||||||
|
() => ({
|
||||||
|
label: 'title',
|
||||||
|
value: 'name',
|
||||||
|
children: 'children',
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const onChange = useCallback(
|
||||||
|
(value) => {
|
||||||
|
setDataIndex(value);
|
||||||
|
},
|
||||||
|
[setDataIndex],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onOperatorsChange = useCallback(
|
||||||
|
(value) => {
|
||||||
|
setOperator(value);
|
||||||
|
},
|
||||||
|
[setOperator],
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeStyle = useMemo(() => ({ color: '#bfbfbf' }), []);
|
||||||
return (
|
return (
|
||||||
// 添加 nc-filter-item 类名是为了帮助编写测试时更容易选中该元素
|
// 添加 nc-filter-item 类名是为了帮助编写测试时更容易选中该元素
|
||||||
<div style={{ marginBottom: 8 }} className="nc-filter-item">
|
<div style={style} className="nc-filter-item">
|
||||||
<Space>
|
<Space>
|
||||||
<Cascader
|
<Cascader
|
||||||
data-testid="antd-cascader"
|
data-testid="antd-cascader"
|
||||||
className={css`
|
className={css`
|
||||||
width: 160px;
|
width: 160px;
|
||||||
`}
|
`}
|
||||||
fieldNames={{
|
fieldNames={fieldNames}
|
||||||
label: 'title',
|
|
||||||
value: 'name',
|
|
||||||
children: 'children',
|
|
||||||
}}
|
|
||||||
changeOnSelect={false}
|
changeOnSelect={false}
|
||||||
value={dataIndex}
|
value={dataIndex}
|
||||||
options={compile(fields)}
|
options={compile(fields)}
|
||||||
onChange={(value) => {
|
onChange={onChange}
|
||||||
setDataIndex(value);
|
|
||||||
}}
|
|
||||||
placeholder={t('Select field')}
|
placeholder={t('Select field')}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
@ -45,15 +74,15 @@ export const FilterItem = observer(
|
|||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
value={operator?.value}
|
value={operator?.value}
|
||||||
options={compile(operators)}
|
options={compile(operators)}
|
||||||
onChange={(value) => {
|
onChange={onOperatorsChange}
|
||||||
setOperator(value);
|
|
||||||
}}
|
|
||||||
placeholder={t('Comparision')}
|
placeholder={t('Comparision')}
|
||||||
/>
|
/>
|
||||||
{!operator?.noValue ? <DynamicComponent value={value} schema={schema} onChange={setValue} /> : null}
|
{!operator?.noValue ? (
|
||||||
|
<DynamicComponent value={value} schema={schema} collectionField={collectionField} onChange={setValue} />
|
||||||
|
) : null}
|
||||||
{!props.disabled && (
|
{!props.disabled && (
|
||||||
<a data-testid="close-icon-button">
|
<a data-testid="close-icon-button">
|
||||||
<CloseCircleOutlined onClick={() => remove()} style={{ color: '#bfbfbf' }} />
|
<CloseCircleOutlined onClick={remove} style={removeStyle} />
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { ArrayField as ArrayFieldModel } from '@formily/core';
|
import { ArrayField as ArrayFieldModel } from '@formily/core';
|
||||||
import { ObjectField, observer, useField } from '@formily/react';
|
import { ObjectField, observer, useField } from '@formily/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { RemoveConditionContext } from './context';
|
|
||||||
import { FilterGroup } from './FilterGroup';
|
import { FilterGroup } from './FilterGroup';
|
||||||
import { FilterItem } from './FilterItem';
|
import { FilterItem } from './FilterItem';
|
||||||
|
import { RemoveConditionContext } from './context';
|
||||||
|
|
||||||
export const FilterItems = observer(
|
export const FilterItems = observer(
|
||||||
(props) => {
|
(props) => {
|
||||||
|
@ -3,7 +3,7 @@ import { Schema } from '@formily/react';
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
export interface FilterContextProps {
|
export interface FilterContextProps {
|
||||||
field?: ObjectField;
|
field?: ObjectField & { collectionName?: string };
|
||||||
fieldSchema?: Schema;
|
fieldSchema?: Schema;
|
||||||
dynamicComponent?: any;
|
dynamicComponent?: any;
|
||||||
options?: any[];
|
options?: any[];
|
||||||
|
@ -46,6 +46,7 @@ export const useGetFilterFieldOptions = () => {
|
|||||||
target: field.target,
|
target: field.target,
|
||||||
title: field?.uiSchema?.title || field.name,
|
title: field?.uiSchema?.title || field.name,
|
||||||
schema: field?.uiSchema,
|
schema: field?.uiSchema,
|
||||||
|
interface: field.interface,
|
||||||
operators:
|
operators:
|
||||||
operators?.filter?.((operator) => {
|
operators?.filter?.((operator) => {
|
||||||
return !operator?.visible || operator.visible(field);
|
return !operator?.visible || operator.visible(field);
|
||||||
|
@ -3,9 +3,22 @@ import { merge } from '@formily/shared';
|
|||||||
import flat, { unflatten } from 'flat';
|
import flat, { unflatten } from 'flat';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useContext, useEffect } from 'react';
|
import { useCallback, useContext, useEffect, useMemo } from 'react';
|
||||||
import { FilterContext } from './context';
|
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
|
import { FilterContext } from './context';
|
||||||
|
|
||||||
|
interface UseValuesReturn {
|
||||||
|
fields: any[];
|
||||||
|
collectionField: any;
|
||||||
|
dataIndex: string[];
|
||||||
|
operators: any[];
|
||||||
|
operator: any;
|
||||||
|
schema: any;
|
||||||
|
value: any;
|
||||||
|
setDataIndex: (dataIndex: string[]) => void;
|
||||||
|
setOperator: (operatorValue: string) => void;
|
||||||
|
setValue: (value: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
// import { useValues } from './useValues';
|
// import { useValues } from './useValues';
|
||||||
const findOption = (dataIndex = [], options) => {
|
const findOption = (dataIndex = [], options) => {
|
||||||
@ -21,21 +34,27 @@ const findOption = (dataIndex = [], options) => {
|
|||||||
return option;
|
return option;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useValues = () => {
|
export const useValues = (): UseValuesReturn => {
|
||||||
const field = useField<any>();
|
|
||||||
const { name } = useCollection();
|
const { name } = useCollection();
|
||||||
const { getCollectionJoinField } = useCollectionManager();
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
const ctx: any = useContext(FilterContext) || {};
|
const field = useField<any>();
|
||||||
const { options } = ctx;
|
const { options, collectionName, field: ctxField } = useContext(FilterContext) || {};
|
||||||
const data2value = () => {
|
const values: object = flat(field.value || {});
|
||||||
|
const path = Object.keys(values).shift() || '';
|
||||||
|
|
||||||
|
const collectionField = useMemo(() => {
|
||||||
|
const [fieldPath = ''] = path.split('.$');
|
||||||
|
return getCollectionJoinField(`${collectionName || name}.${fieldPath}`);
|
||||||
|
}, [name, path]);
|
||||||
|
|
||||||
|
const data2value = useCallback(() => {
|
||||||
field.value = flat.unflatten({
|
field.value = flat.unflatten({
|
||||||
[`${field.data.dataIndex?.join('.')}.${field.data?.operator?.value}`]: field.data?.value,
|
[`${field.data.dataIndex?.join('.')}.${field.data?.operator?.value}`]: field.data?.value,
|
||||||
});
|
});
|
||||||
};
|
}, [field]);
|
||||||
|
|
||||||
const value2data = () => {
|
const value2data = () => {
|
||||||
field.data = field.data || {};
|
field.data = field.data || {};
|
||||||
const values: object = flat(field.value || {});
|
|
||||||
const path = Object.keys(values).shift() || '';
|
|
||||||
if (!path || !options) {
|
if (!path || !options) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -50,20 +69,20 @@ export const useValues = () => {
|
|||||||
const fieldNames = dataIndex.concat();
|
const fieldNames = dataIndex.concat();
|
||||||
fieldNames.pop();
|
fieldNames.pop();
|
||||||
const targetField = getCollectionJoinField(`${name}.${fieldNames.join('.')}`);
|
const targetField = getCollectionJoinField(`${name}.${fieldNames.join('.')}`);
|
||||||
ctx.field.collectionName = targetField?.target;
|
ctxField.collectionName = targetField?.target;
|
||||||
} else {
|
} else {
|
||||||
ctx.field.collectionName = null;
|
ctxField.collectionName = null;
|
||||||
}
|
}
|
||||||
field.data.operators = operators;
|
field.data.operators = operators;
|
||||||
field.data.operator = operator;
|
field.data.operator = operator;
|
||||||
field.data.schema = merge(option?.schema, operator?.schema);
|
field.data.schema = merge(option?.schema, operator?.schema);
|
||||||
field.data.value = get(unflatten(field.value), `${fieldPath}.$${operatorValue}`);
|
field.data.value = get(unflatten(field.value), `${fieldPath}.$${operatorValue}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(value2data, [field.path]);
|
useEffect(value2data, [field.path]);
|
||||||
return {
|
|
||||||
fields: options,
|
const setDataIndex = useCallback(
|
||||||
...(field?.data || {}),
|
(dataIndex) => {
|
||||||
setDataIndex(dataIndex) {
|
|
||||||
const option = findOption(dataIndex, options);
|
const option = findOption(dataIndex, options);
|
||||||
const operator = option?.operators?.[0];
|
const operator = option?.operators?.[0];
|
||||||
field.data = field.data || {};
|
field.data = field.data || {};
|
||||||
@ -77,14 +96,18 @@ export const useValues = () => {
|
|||||||
const fieldNames = dataIndex.concat();
|
const fieldNames = dataIndex.concat();
|
||||||
fieldNames.pop();
|
fieldNames.pop();
|
||||||
const targetField = getCollectionJoinField(`${name}.${fieldNames.join('.')}`);
|
const targetField = getCollectionJoinField(`${name}.${fieldNames.join('.')}`);
|
||||||
ctx.field.collectionName = targetField?.target;
|
ctxField.collectionName = targetField?.target;
|
||||||
} else {
|
} else {
|
||||||
ctx.field.collectionName = null;
|
ctxField.collectionName = null;
|
||||||
}
|
}
|
||||||
field.data.value = operator?.noValue ? operator.default || true : undefined;
|
field.data.value = operator?.noValue ? operator.default || true : undefined;
|
||||||
data2value();
|
data2value();
|
||||||
},
|
},
|
||||||
setOperator(operatorValue) {
|
[data2value, field, options],
|
||||||
|
);
|
||||||
|
|
||||||
|
const setOperator = useCallback(
|
||||||
|
(operatorValue) => {
|
||||||
const operator = field.data?.operators?.find?.((item) => item.value === operatorValue);
|
const operator = field.data?.operators?.find?.((item) => item.value === operatorValue);
|
||||||
field.data.operator = operator;
|
field.data.operator = operator;
|
||||||
const option = findOption(field.data.dataIndex, options);
|
const option = findOption(field.data.dataIndex, options);
|
||||||
@ -94,9 +117,23 @@ export const useValues = () => {
|
|||||||
field.data.value = operator.noValue ? operator.default || true : undefined;
|
field.data.value = operator.noValue ? operator.default || true : undefined;
|
||||||
data2value();
|
data2value();
|
||||||
},
|
},
|
||||||
setValue(value) {
|
[data2value, field.data, options],
|
||||||
|
);
|
||||||
|
|
||||||
|
const setValue = useCallback(
|
||||||
|
(value) => {
|
||||||
field.data.value = value;
|
field.data.value = value;
|
||||||
data2value();
|
data2value();
|
||||||
},
|
},
|
||||||
|
[data2value, field.data],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
fields: options,
|
||||||
|
...(field?.data || {}),
|
||||||
|
collectionField,
|
||||||
|
setDataIndex,
|
||||||
|
setOperator,
|
||||||
|
setValue,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -3,130 +3,70 @@ import { ArrayCollapse, FormLayout, FormItem as Item } from '@formily/antd-v5';
|
|||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { ISchema, observer, useField, useFieldSchema } from '@formily/react';
|
import { ISchema, observer, useField, useFieldSchema } from '@formily/react';
|
||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ACLCollectionFieldProvider } from '../../../acl/ACLProvider';
|
import { ACLCollectionFieldProvider } from '../../../acl/ACLProvider';
|
||||||
import { useBlockRequestContext } from '../../../block-provider/BlockProvider';
|
import { useFormActiveFields } from '../../../block-provider';
|
||||||
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
|
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
|
||||||
import {
|
import { Collection, useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
Collection,
|
import { useRecord } from '../../../record-provider';
|
||||||
CollectionFieldOptions,
|
|
||||||
useCollection,
|
|
||||||
useCollectionFilterOptions,
|
|
||||||
useCollectionManager,
|
|
||||||
} from '../../../collection-manager';
|
|
||||||
import { GeneralSchemaItems } from '../../../schema-items/GeneralSchemaItems';
|
import { GeneralSchemaItems } from '../../../schema-items/GeneralSchemaItems';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled } from '../../../schema-settings';
|
||||||
|
import { VariableInput, getShouldChange } from '../../../schema-settings/VariableInput/VariableInput';
|
||||||
|
import useIsAllowToSetDefaultValue from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||||
import { useIsShowMultipleSwitch } from '../../../schema-settings/hooks/useIsShowMultipleSwitch';
|
import { useIsShowMultipleSwitch } from '../../../schema-settings/hooks/useIsShowMultipleSwitch';
|
||||||
import { isVariable, parseVariables, useVariablesCtx } from '../../common/utils/uitls';
|
import { useLocalVariables, useVariables } from '../../../variables';
|
||||||
|
import useContextVariable from '../../../variables/hooks/useContextVariable';
|
||||||
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
|
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
|
||||||
|
import { isSubMode } from '../association-field/util';
|
||||||
import { BlockItem } from '../block-item';
|
import { BlockItem } from '../block-item';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
|
import { DynamicComponentProps } from '../filter/DynamicComponent';
|
||||||
import { HTMLEncode } from '../input/shared';
|
import { HTMLEncode } from '../input/shared';
|
||||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
|
|
||||||
import { useColorFields } from '../table-v2/Table.Column.Designer';
|
import { useColorFields } from '../table-v2/Table.Column.Designer';
|
||||||
import { FilterFormDesigner } from './FormItem.FilterFormDesigner';
|
import { FilterFormDesigner } from './FormItem.FilterFormDesigner';
|
||||||
import { useEnsureOperatorsValid } from './SchemaSettingOptions';
|
import { useEnsureOperatorsValid } from './SchemaSettingOptions';
|
||||||
|
import useLazyLoadAssociationFieldOfForm from './hooks/useLazyLoadAssociationFieldOfForm';
|
||||||
|
import useLazyLoadAssociationFieldOfSubForm from './hooks/useLazyLoadAssociationFieldOfSubForm';
|
||||||
|
import useParseDefaultValue from './hooks/useParseDefaultValue';
|
||||||
|
|
||||||
export const findColumnFieldSchema = (fieldSchema, getCollectionJoinField) => {
|
|
||||||
const childsSchema = new Set();
|
|
||||||
const getAssociationAppends = (schema) => {
|
|
||||||
schema.reduceProperties((_, s) => {
|
|
||||||
const collectionfield = s['x-collection-field'] && getCollectionJoinField(s['x-collection-field']);
|
|
||||||
const isAssociationField = collectionfield && ['belongsTo'].includes(collectionfield.type);
|
|
||||||
if (collectionfield && isAssociationField && s.default?.includes?.('$context')) {
|
|
||||||
childsSchema.add(JSON.stringify({ name: s.name, default: s.default }));
|
|
||||||
} else {
|
|
||||||
getAssociationAppends(s);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
getAssociationAppends(fieldSchema);
|
|
||||||
return [...childsSchema];
|
|
||||||
};
|
|
||||||
function transformData(inputData) {
|
|
||||||
const transformedData = [];
|
|
||||||
const keys = Object.keys(inputData) || [];
|
|
||||||
const values: any[] = Object.values(inputData) || [];
|
|
||||||
for (let i = 0; i < values[0]?.length; i++) {
|
|
||||||
const newObj = {};
|
|
||||||
keys.forEach((key, index) => {
|
|
||||||
newObj[key] = values[index][i];
|
|
||||||
});
|
|
||||||
transformedData.push(newObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
return transformedData;
|
|
||||||
}
|
|
||||||
export const FormItem: any = observer(
|
export const FormItem: any = observer(
|
||||||
(props: any) => {
|
(props: any) => {
|
||||||
useEnsureOperatorsValid();
|
useEnsureOperatorsValid();
|
||||||
const field = useField<Field>();
|
const field = useField<Field>();
|
||||||
const ctx = useBlockRequestContext();
|
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
const variablesCtx = useVariablesCtx();
|
const contextVariable = useContextVariable();
|
||||||
const { getCollectionJoinField } = useCollectionManager();
|
const variables = useVariables();
|
||||||
const collectionField = getCollectionJoinField(schema['x-collection-field']);
|
const { addActiveFieldName } = useFormActiveFields() || {};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ctx?.block === 'form') {
|
variables?.registerVariable(contextVariable);
|
||||||
ctx.field.data = ctx.field.data || {};
|
}, [contextVariable]);
|
||||||
ctx.field.data.activeFields = ctx.field.data.activeFields || new Set();
|
|
||||||
ctx.field.data.activeFields.add(schema.name);
|
// 需要放在注冊完变量之后
|
||||||
// 如果默认值是一个变量,则需要解析之后再显示出来
|
useParseDefaultValue();
|
||||||
if (isVariable(schema?.default) && !schema?.default.includes('$context')) {
|
useLazyLoadAssociationFieldOfForm();
|
||||||
field.setInitialValue?.(parseVariables(schema.default, variablesCtx));
|
useLazyLoadAssociationFieldOfSubForm();
|
||||||
} else if (
|
|
||||||
isVariable(schema?.default) &&
|
useEffect(() => {
|
||||||
schema?.default?.includes('$context') &&
|
addActiveFieldName?.(schema.name as string);
|
||||||
collectionField?.interface === 'm2m'
|
}, [addActiveFieldName, schema.name]);
|
||||||
) {
|
|
||||||
// 直接对多
|
|
||||||
const contextData = parseVariables('{{$context}}', variablesCtx);
|
|
||||||
let iniValues = [];
|
|
||||||
contextData?.map((v) => {
|
|
||||||
const data = parseVariables(schema.default, { $context: v });
|
|
||||||
iniValues = iniValues.concat(data).map((v) => {
|
|
||||||
delete v[collectionField.through];
|
|
||||||
return v;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const data = _.uniqBy(iniValues, 'id');
|
|
||||||
field.setInitialValue?.(data.length > 0 ? data : [{}]);
|
|
||||||
} else if (
|
|
||||||
collectionField?.interface === 'o2m' &&
|
|
||||||
['SubTable', 'Nester'].includes(schema?.['x-component-props']?.['mode']) // 间接对多
|
|
||||||
) {
|
|
||||||
const childrenFieldWithDefault = findColumnFieldSchema(schema, getCollectionJoinField);
|
|
||||||
// 子表格/子表单中找出所有belongsTo字段的上下文默认值
|
|
||||||
if (childrenFieldWithDefault.length > 0) {
|
|
||||||
const tableData = parseVariables('{{$context}}', variablesCtx);
|
|
||||||
const contextData = {};
|
|
||||||
// 将数据拍平
|
|
||||||
childrenFieldWithDefault?.forEach((s: any) => {
|
|
||||||
const child = JSON.parse(s);
|
|
||||||
tableData?.map((v) => {
|
|
||||||
contextData[child.name] = _.uniqBy(
|
|
||||||
(contextData[child.name] || []).concat(parseVariables(child.default, { $context: v })),
|
|
||||||
'id',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const initValues = transformData(contextData);
|
|
||||||
field.setInitialValue?.(initValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
const showTitle = schema['x-decorator-props']?.showTitle ?? true;
|
const showTitle = schema['x-decorator-props']?.showTitle ?? true;
|
||||||
return (
|
const extra = useMemo(() => {
|
||||||
<ACLCollectionFieldProvider>
|
return typeof field.description === 'string' ? (
|
||||||
<BlockItem className={'nb-form-item'}>
|
<div
|
||||||
<Item
|
dangerouslySetInnerHTML={{
|
||||||
className={cx(
|
__html: HTMLEncode(field.description).split('\n').join('<br/>'),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
field.description
|
||||||
|
);
|
||||||
|
}, [field.description]);
|
||||||
|
const className = useMemo(() => {
|
||||||
|
return cx(
|
||||||
css`
|
css`
|
||||||
& .ant-space {
|
& .ant-space {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -139,20 +79,13 @@ export const FormItem: any = observer(
|
|||||||
}
|
}
|
||||||
`]: showTitle === false,
|
`]: showTitle === false,
|
||||||
},
|
},
|
||||||
)}
|
);
|
||||||
{...props}
|
}, [showTitle]);
|
||||||
extra={
|
|
||||||
typeof field.description === 'string' ? (
|
return (
|
||||||
<div
|
<ACLCollectionFieldProvider>
|
||||||
dangerouslySetInnerHTML={{
|
<BlockItem className={'nb-form-item'}>
|
||||||
__html: HTMLEncode(field.description).split('\n').join('<br/>'),
|
<Item className={className} {...props} extra={extra} />
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
field.description
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</BlockItem>
|
</BlockItem>
|
||||||
</ACLCollectionFieldProvider>
|
</ACLCollectionFieldProvider>
|
||||||
);
|
);
|
||||||
@ -161,12 +94,17 @@ export const FormItem: any = observer(
|
|||||||
);
|
);
|
||||||
|
|
||||||
FormItem.Designer = function Designer() {
|
FormItem.Designer = function Designer() {
|
||||||
let targetField;
|
const {
|
||||||
const { getCollectionFields, getInterface, getCollectionJoinField, getCollection, isTitleField } =
|
getCollectionFields,
|
||||||
useCollectionManager();
|
getInterface,
|
||||||
|
getCollectionJoinField,
|
||||||
|
getCollection,
|
||||||
|
isTitleField,
|
||||||
|
getAllCollectionsInheritChain,
|
||||||
|
} = useCollectionManager();
|
||||||
const { getField } = useCollection();
|
const { getField } = useCollection();
|
||||||
const { form } = useFormBlockContext();
|
const { form } = useFormBlockContext();
|
||||||
const ctx = useBlockRequestContext();
|
const record = useRecord();
|
||||||
const field = useField<Field>();
|
const field = useField<Field>();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -174,11 +112,10 @@ FormItem.Designer = function Designer() {
|
|||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const IsShowMultipleSwitch = useIsShowMultipleSwitch();
|
const IsShowMultipleSwitch = useIsShowMultipleSwitch();
|
||||||
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
if (collectionField?.target) {
|
const variables = useVariables();
|
||||||
targetField = getCollectionJoinField(
|
const localVariables = useLocalVariables();
|
||||||
`${collectionField.target}.${fieldSchema['x-component-props']?.fieldNames?.label || 'id'}`,
|
const { isAllowToSetDefaultValue } = useIsAllowToSetDefaultValue();
|
||||||
);
|
const isAddNewForm = useIsAddNewForm();
|
||||||
}
|
|
||||||
|
|
||||||
const targetCollection = getCollection(collectionField?.target);
|
const targetCollection = getCollection(collectionField?.target);
|
||||||
const interfaceConfig = getInterface(collectionField?.interface);
|
const interfaceConfig = getInterface(collectionField?.interface);
|
||||||
@ -190,7 +127,7 @@ FormItem.Designer = function Designer() {
|
|||||||
const fieldModeOptions = useFieldModeOptions();
|
const fieldModeOptions = useFieldModeOptions();
|
||||||
const isAssociationField = ['obo', 'oho', 'o2o', 'o2m', 'm2m', 'm2o'].includes(collectionField?.interface);
|
const isAssociationField = ['obo', 'oho', 'o2o', 'o2m', 'm2m', 'm2o'].includes(collectionField?.interface);
|
||||||
const isTableField = fieldSchema['x-component'] === 'TableField';
|
const isTableField = fieldSchema['x-component'] === 'TableField';
|
||||||
const isFileField = isFileCollection(targetCollection);
|
const isFileField = isFileCollection(targetCollection as any);
|
||||||
const initialValue = {
|
const initialValue = {
|
||||||
title: field.title === originalTitle ? undefined : field.title,
|
title: field.title === originalTitle ? undefined : field.title,
|
||||||
};
|
};
|
||||||
@ -214,8 +151,6 @@ FormItem.Designer = function Designer() {
|
|||||||
if (fieldSchema['x-read-pretty'] === true) {
|
if (fieldSchema['x-read-pretty'] === true) {
|
||||||
readOnlyMode = 'read-pretty';
|
readOnlyMode = 'read-pretty';
|
||||||
}
|
}
|
||||||
const dataSource = useCollectionFilterOptions(collectionField?.target);
|
|
||||||
const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {};
|
|
||||||
const fieldMode = field?.componentProps?.['mode'] || (isFileField ? 'FileManager' : 'Select');
|
const fieldMode = field?.componentProps?.['mode'] || (isFileField ? 'FileManager' : 'Select');
|
||||||
const isSelectFieldMode = isAssociationField && fieldMode === 'Select';
|
const isSelectFieldMode = isAssociationField && fieldMode === 'Select';
|
||||||
const isSubFormMode = fieldSchema['x-component-props']?.mode === 'Nester';
|
const isSubFormMode = fieldSchema['x-component-props']?.mode === 'Nester';
|
||||||
@ -365,10 +300,6 @@ FormItem.Designer = function Designer() {
|
|||||||
const schema = {
|
const schema = {
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
};
|
};
|
||||||
// return;
|
|
||||||
// if (['number'].includes(collectionField?.interface) && collectionField?.uiSchema?.['x-component-props']?.['stringMode'] === true) {
|
|
||||||
// rules['numberStringMode'] = true;
|
|
||||||
// }
|
|
||||||
if (['percent'].includes(collectionField?.interface)) {
|
if (['percent'].includes(collectionField?.interface)) {
|
||||||
for (const rule of rules) {
|
for (const rule of rules) {
|
||||||
if (!!rule.maxValue || !!rule.minValue) {
|
if (!!rule.maxValue || !!rule.minValue) {
|
||||||
@ -391,41 +322,33 @@ FormItem.Designer = function Designer() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{form &&
|
{isAllowToSetDefaultValue() && <SchemaSettings.DefaultValue />}
|
||||||
!form?.readPretty &&
|
|
||||||
isShowDefaultValue(collectionField, getInterface) &&
|
|
||||||
!isPatternDisabled(fieldSchema) && <SchemaSettings.DefaultValue />}
|
|
||||||
{isSelectFieldMode && !field.readPretty && (
|
{isSelectFieldMode && !field.readPretty && (
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={collectionField?.target}
|
||||||
schema={
|
defaultFilter={fieldSchema?.['x-component-props']?.service?.params?.filter || {}}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
dynamicComponent={(props: DynamicComponentProps) => {
|
||||||
title: t('Set the data scope'),
|
return (
|
||||||
properties: {
|
<VariableInput
|
||||||
filter: {
|
{...props}
|
||||||
defaultValue: defaultFilter,
|
form={form}
|
||||||
enum: dataSource,
|
collectionField={props.collectionField}
|
||||||
'x-component': 'Filter',
|
record={record}
|
||||||
'x-component-props': {
|
shouldChange={getShouldChange({
|
||||||
collectionName: collectionField?.target,
|
collectionField: props.collectionField,
|
||||||
dynamicComponent: (props) =>
|
variables,
|
||||||
FilterDynamicComponent({
|
localVariables,
|
||||||
...props,
|
getAllCollectionsInheritChain,
|
||||||
form,
|
})}
|
||||||
collectionField,
|
/>
|
||||||
rootCollection: ctx.props.collection || ctx.props.resource,
|
);
|
||||||
}),
|
}}
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
filter = removeNullCondition(filter);
|
filter = removeNullCondition(filter);
|
||||||
_.set(field.componentProps, 'service.params.filter', filter);
|
_.set(field.componentProps, 'service.params.filter', filter);
|
||||||
fieldSchema['x-component-props'] = field.componentProps;
|
fieldSchema['x-component-props'] = field.componentProps;
|
||||||
dn.emit('patch', {
|
void dn.emit('patch', {
|
||||||
schema: {
|
schema: {
|
||||||
['x-uid']: fieldSchema['x-uid'],
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
'x-component-props': field.componentProps,
|
'x-component-props': field.componentProps,
|
||||||
@ -450,7 +373,17 @@ FormItem.Designer = function Designer() {
|
|||||||
schema['x-component-props'] = fieldSchema['x-component-props'];
|
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||||
field.componentProps = field.componentProps || {};
|
field.componentProps = field.componentProps || {};
|
||||||
field.componentProps.mode = mode;
|
field.componentProps.mode = mode;
|
||||||
dn.emit('patch', {
|
|
||||||
|
// 子表单状态不允许设置默认值
|
||||||
|
if (isSubMode(fieldSchema) && isAddNewForm) {
|
||||||
|
// @ts-ignore
|
||||||
|
schema.default = null;
|
||||||
|
fieldSchema.default = null;
|
||||||
|
field.setInitialValue(null);
|
||||||
|
field.setValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dn.emit('patch', {
|
||||||
schema,
|
schema,
|
||||||
});
|
});
|
||||||
dn.refresh();
|
dn.refresh();
|
||||||
@ -800,10 +733,9 @@ export function isFileCollection(collection: Collection) {
|
|||||||
|
|
||||||
FormItem.FilterFormDesigner = FilterFormDesigner;
|
FormItem.FilterFormDesigner = FilterFormDesigner;
|
||||||
|
|
||||||
export function getFieldDefaultValue(fieldSchema: ISchema, collectionField: CollectionFieldOptions) {
|
function useIsAddNewForm() {
|
||||||
const result = fieldSchema?.default ?? collectionField?.defaultValue;
|
const record = useRecord();
|
||||||
if (collectionField?.uiSchema?.['x-component'] === 'DatePicker' && result) {
|
const isAddNewForm = _.isEmpty(_.omit(record, '__parent'));
|
||||||
return dayjs(result);
|
|
||||||
}
|
return isAddNewForm;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
@ -382,7 +382,7 @@ export const EditComponent = () => {
|
|||||||
const fieldModeOptions = useFieldModeOptions();
|
const fieldModeOptions = useFieldModeOptions();
|
||||||
const isAssociationField = ['belongsTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(collectionField?.type);
|
const isAssociationField = ['belongsTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(collectionField?.type);
|
||||||
const targetCollection = getCollection(collectionField?.target);
|
const targetCollection = getCollection(collectionField?.target);
|
||||||
const isFileField = isFileCollection(targetCollection);
|
const isFileField = isFileCollection(targetCollection as any);
|
||||||
|
|
||||||
return isAssociationField && fieldModeOptions ? (
|
return isAssociationField && fieldModeOptions ? (
|
||||||
<SchemaSettings.SelectItem
|
<SchemaSettings.SelectItem
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
import { transformValue } from '../../hooks/useSpecialCase';
|
||||||
|
|
||||||
|
describe('transformValue', () => {
|
||||||
|
it('value is an array', () => {
|
||||||
|
const value = [{ a: 3 }, { a: 4 }];
|
||||||
|
const deps = {
|
||||||
|
field: {
|
||||||
|
value: [{ a: 1, b: 11 }],
|
||||||
|
},
|
||||||
|
subFieldSchema: {
|
||||||
|
name: 'a',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(transformValue(value, deps as any)).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"a": {
|
||||||
|
"a": 3,
|
||||||
|
},
|
||||||
|
"b": 11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__notFromDatabase": true,
|
||||||
|
"a": {
|
||||||
|
"a": 4,
|
||||||
|
},
|
||||||
|
"b": 11,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,50 @@
|
|||||||
|
import { Field } from '@formily/core';
|
||||||
|
import { useField, useFieldSchema, useForm } from '@formily/react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useCollection, useCollectionManager } from '../../../../collection-manager';
|
||||||
|
import { useVariables } from '../../../../variables';
|
||||||
|
import { transformVariableValue } from '../../../../variables/utils/transformVariableValue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于懒加载 Form 区块中只用于展示的关联字段的值
|
||||||
|
*/
|
||||||
|
const useLazyLoadAssociationFieldOfForm = () => {
|
||||||
|
const { name } = useCollection();
|
||||||
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
|
const form = useForm();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const variables = useVariables();
|
||||||
|
const field = useField<Field>();
|
||||||
|
|
||||||
|
const schemaName = fieldSchema.name.toString();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 在数据表管理页面,也存在 `a.b` 之类的 schema name,其 collectionName 为 fields,所以在这里排除一下 `name === 'fields'` 的情况
|
||||||
|
if (!schemaName.includes('.') || !variables || name === 'fields') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formVariable = {
|
||||||
|
name: '$nForm',
|
||||||
|
ctx: form.values,
|
||||||
|
collectionName: name,
|
||||||
|
};
|
||||||
|
const variableString = `{{ $nForm.${schemaName} }}`;
|
||||||
|
const collectionField = getCollectionJoinField(`${name}.${schemaName}`);
|
||||||
|
|
||||||
|
if (!collectionField) {
|
||||||
|
return console.error(new Error(`useLazyLoadAssociationField: ${schemaName} not found in collection ${name}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
variables
|
||||||
|
.parseVariable(variableString, formVariable)
|
||||||
|
.then((value) => {
|
||||||
|
field.value = transformVariableValue(value, { targetCollectionField: collectionField });
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [schemaName.includes('.') ? form.values[schemaName.split('.')[0]] : null]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useLazyLoadAssociationFieldOfForm;
|
@ -0,0 +1,62 @@
|
|||||||
|
import { Field } from '@formily/core';
|
||||||
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRecord } from '../../../..';
|
||||||
|
import { useCollection, useCollectionManager } from '../../../../collection-manager';
|
||||||
|
import { useFlag } from '../../../../flag-provider';
|
||||||
|
import { useVariables } from '../../../../variables';
|
||||||
|
import { transformVariableValue } from '../../../../variables/utils/transformVariableValue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于懒加载 subForm 区块中的关系字段
|
||||||
|
*/
|
||||||
|
const useLazyLoadAssociationFieldOfSubForm = () => {
|
||||||
|
const { name } = useCollection();
|
||||||
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
|
const record = useRecord();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const variables = useVariables();
|
||||||
|
const field = useField<Field>();
|
||||||
|
const { isInAssignFieldValues } = useFlag() || {};
|
||||||
|
|
||||||
|
const schemaName = fieldSchema.name.toString();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isInAssignFieldValues) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionField = getCollectionJoinField(`${name}.${schemaName}`);
|
||||||
|
|
||||||
|
if (!collectionField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloneRecord = { ...record };
|
||||||
|
delete cloneRecord['__parent'];
|
||||||
|
|
||||||
|
if (_.isEmpty(cloneRecord) || !variables || record[schemaName] != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过模拟一个 record 变量,用解析变量的方法去获取关系字段的值
|
||||||
|
const recordVariable = {
|
||||||
|
name: '$nRecord',
|
||||||
|
ctx: cloneRecord,
|
||||||
|
collectionName: name,
|
||||||
|
};
|
||||||
|
const variableString = `{{ $nRecord.${schemaName} }}`;
|
||||||
|
|
||||||
|
variables
|
||||||
|
.parseVariable(variableString, recordVariable)
|
||||||
|
.then((value) => {
|
||||||
|
field.value = transformVariableValue(value, { targetCollectionField: collectionField });
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [field, name, record, schemaName, isInAssignFieldValues]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useLazyLoadAssociationFieldOfSubForm;
|
@ -0,0 +1,112 @@
|
|||||||
|
import { Field } from '@formily/core';
|
||||||
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { reaction } from '@formily/reactive';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { useRecord, useRecordIndex } from '../../../../../src/record-provider';
|
||||||
|
import { useCollection } from '../../../../collection-manager';
|
||||||
|
import { useFlag } from '../../../../flag-provider';
|
||||||
|
import { DEBOUNCE_WAIT, useLocalVariables, useVariables } from '../../../../variables';
|
||||||
|
import { getPath } from '../../../../variables/utils/getPath';
|
||||||
|
import { getVariableName } from '../../../../variables/utils/getVariableName';
|
||||||
|
import { isVariable } from '../../../../variables/utils/isVariable';
|
||||||
|
import { transformVariableValue } from '../../../../variables/utils/transformVariableValue';
|
||||||
|
import { isSubMode } from '../../association-field/util';
|
||||||
|
import { isFromDatabase, useSpecialCase } from './useSpecialCase';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于解析并设置 FormItem 的默认值
|
||||||
|
*/
|
||||||
|
const useParseDefaultValue = () => {
|
||||||
|
const field = useField<Field>();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const variables = useVariables();
|
||||||
|
const localVariables = useLocalVariables();
|
||||||
|
const record = useRecord();
|
||||||
|
const { isInAssignFieldValues, isInSetDefaultValueDialog, isInFormDataTemplate } = useFlag() || {};
|
||||||
|
const { getField } = useCollection();
|
||||||
|
const { isSpecialCase, setDefaultValue } = useSpecialCase();
|
||||||
|
const index = useRecordIndex();
|
||||||
|
|
||||||
|
// 需要保存 record 的初始值,因为设置默认值的时候 record 会被修改,导致初始值丢失
|
||||||
|
const recordRef = useRef(_.omit(record, '__parent'));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
fieldSchema.default == null ||
|
||||||
|
isInSetDefaultValueDialog ||
|
||||||
|
isInFormDataTemplate ||
|
||||||
|
isSubMode(fieldSchema) ||
|
||||||
|
// 根据 record 是否为空,判断当前是否是新建状态,编辑状态下不需要设置默认值,否则会覆盖用户输入的值,只有新建状态下才需要设置默认值
|
||||||
|
(!_.isEmpty(recordRef.current) && isFromDatabase(record) && !isInAssignFieldValues)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _run = async () => {
|
||||||
|
// 如果默认值是一个变量,则需要解析之后再显示出来
|
||||||
|
if (isVariable(fieldSchema.default) && variables && field) {
|
||||||
|
// 一个变量字符串如果显示出来会比较奇怪
|
||||||
|
if (isVariable(field.value)) {
|
||||||
|
field.setValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
field.loading = true;
|
||||||
|
const collectionField = !fieldSchema.name.toString().includes('.') && getField(fieldSchema.name);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
if (!collectionField) {
|
||||||
|
throw new Error(`useParseDefaultValue: can not find field ${fieldSchema.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = transformVariableValue(await variables.parseVariable(fieldSchema.default, localVariables), {
|
||||||
|
targetCollectionField: collectionField,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (value == null || value === '') {
|
||||||
|
field.setValue(null);
|
||||||
|
} else if (isSpecialCase()) {
|
||||||
|
// 只需要设置一次就可以了
|
||||||
|
if (index === 0) {
|
||||||
|
setDefaultValue(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: 可能会因为异步时序问题导致设置默认值无效
|
||||||
|
// eslint-disable-next-line promise/catch-or-return
|
||||||
|
void Promise.resolve().then(() => {
|
||||||
|
field.setInitialValue(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
field.loading = false;
|
||||||
|
|
||||||
|
// 如果不是一个有效的变量字符串(如:`{{ $user.name }}`)却依然包含 `{{` 和 `}}`,
|
||||||
|
// 则可以断定是一个需要 compile(`useCompile` 返回的函数) 的字符串,这样的字符串会由 formily 自动解析,无需在这里赋值
|
||||||
|
} else if (!/\{\{.+\}\}/g.test(fieldSchema.default) && field.setInitialValue) {
|
||||||
|
field.setInitialValue(fieldSchema.default);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用防抖,提高性能和用户体验
|
||||||
|
const run = _.debounce(_run, DEBOUNCE_WAIT);
|
||||||
|
|
||||||
|
void _run();
|
||||||
|
|
||||||
|
if (isVariable(fieldSchema.default)) {
|
||||||
|
const variableName = getVariableName(fieldSchema.default);
|
||||||
|
const variableValue = localVariables.find((item) => item.name === variableName);
|
||||||
|
|
||||||
|
if (variableValue) {
|
||||||
|
// 实现联动的效果,当依赖的变量变化时(如 `$nForm` 变量),重新解析默认值
|
||||||
|
const dispose = reaction(() => {
|
||||||
|
return _.get({ [variableName]: variableValue?.ctx }, getPath(fieldSchema.default));
|
||||||
|
}, run);
|
||||||
|
|
||||||
|
return dispose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [fieldSchema.default]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useParseDefaultValue;
|
@ -0,0 +1,174 @@
|
|||||||
|
import { Field } from '@formily/core';
|
||||||
|
import { Schema, useFieldSchema, useForm } from '@formily/react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
|
import { CollectionFieldOptions, useCollection, useCollectionManager } from '../../../../collection-manager';
|
||||||
|
import { isSubMode } from '../../association-field/util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* #### 处理 `子表单` 和 `子表格` 中的特殊情况
|
||||||
|
*
|
||||||
|
* - 提供一个判断当前是否是特殊情况的方法
|
||||||
|
* - 提供一个设置默认值的方法
|
||||||
|
*/
|
||||||
|
export const useSpecialCase = () => {
|
||||||
|
const form = useForm();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { getField } = useCollection();
|
||||||
|
const { getCollectionField } = useCollectionManager();
|
||||||
|
|
||||||
|
const collectionField = useMemo(() => {
|
||||||
|
return getField(fieldSchema.name);
|
||||||
|
}, [fieldSchema.name, getField]);
|
||||||
|
|
||||||
|
const isSpecialCase = useCallback(() => {
|
||||||
|
return isSpecialCaseField({ collectionField, fieldSchema, getCollectionField });
|
||||||
|
}, [collectionField, fieldSchema, getCollectionField]);
|
||||||
|
|
||||||
|
const setDefaultValue = useCallback(
|
||||||
|
(value: any) => {
|
||||||
|
const parentFieldSchema = getParentFieldSchema(fieldSchema);
|
||||||
|
if (parentFieldSchema) {
|
||||||
|
const parentField: any = form.query(parentFieldSchema.name).take();
|
||||||
|
if (parentField) {
|
||||||
|
parentField.setInitialValue(transformValue(value, { field: parentField, subFieldSchema: fieldSchema }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[fieldSchema, form],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* 特殊情况指的是:当前字段是 `对一` 字段且存在于 `对多` 字段的 `子表格` 或 `子表单` 中
|
||||||
|
* 详细说明见:https://nocobase.feishu.cn/docx/EmNEdEBOnoQohUx2UmBcqIQ5nyh#CUdLdy6OpoPKjyx9DLPc3lqknVc
|
||||||
|
*/
|
||||||
|
isSpecialCase,
|
||||||
|
setDefaultValue,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过存在于 `子表格` 和 `子表单` 中的字段,获取父级字段的 fieldSchema
|
||||||
|
* @param fieldSchema
|
||||||
|
*/
|
||||||
|
export function getParentFieldSchema(fieldSchema: Schema) {
|
||||||
|
while (fieldSchema?.parent) {
|
||||||
|
fieldSchema = fieldSchema.parent;
|
||||||
|
|
||||||
|
if (isSubMode(fieldSchema)) {
|
||||||
|
return fieldSchema;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 特殊情况指的是:当前字段是 `对一` 字段且存在于 `对多` 字段的 `子表格` 或 `子表单` 中
|
||||||
|
* 详细说明见:https://nocobase.feishu.cn/docx/EmNEdEBOnoQohUx2UmBcqIQ5nyh#CUdLdy6OpoPKjyx9DLPc3lqknVc
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function isSpecialCaseField({
|
||||||
|
collectionField,
|
||||||
|
fieldSchema,
|
||||||
|
getCollectionField,
|
||||||
|
}: {
|
||||||
|
collectionField: CollectionFieldOptions;
|
||||||
|
fieldSchema: Schema;
|
||||||
|
getCollectionField: (name: string) => CollectionFieldOptions;
|
||||||
|
}) {
|
||||||
|
if (collectionField && ['hasOne', 'belongsTo'].includes(collectionField.type) && fieldSchema) {
|
||||||
|
const parentFieldSchema = getParentFieldSchema(fieldSchema);
|
||||||
|
if (parentFieldSchema && parentFieldSchema['x-collection-field']) {
|
||||||
|
const parentCollectionField = getCollectionField(parentFieldSchema['x-collection-field']);
|
||||||
|
if (['hasMany', 'belongsToMany'].includes(parentCollectionField?.type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 `对多` 字段的子表单或子表格中的 `对一` 字段的值转换成 `对多` 字段的值
|
||||||
|
* @param value `子表单` 和 `子表格` 中的 `对一` 字段的经过解析之后的变量的值
|
||||||
|
* @param deps
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function transformValue(
|
||||||
|
value: any,
|
||||||
|
deps: {
|
||||||
|
field: Field;
|
||||||
|
subFieldSchema: Schema;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const { field, subFieldSchema } = deps;
|
||||||
|
const oldValues = field.value;
|
||||||
|
let newValues = null;
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
newValues = value.map((item) => {
|
||||||
|
return {
|
||||||
|
[subFieldSchema.name]: item,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
newValues = [
|
||||||
|
{
|
||||||
|
[subFieldSchema.name]: value,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = _.assignWith([...oldValues], newValues, (oldValue, newValue) => {
|
||||||
|
if (!oldValue && newValue) {
|
||||||
|
// 用于标记这个值不是从数据库中获取的
|
||||||
|
newValue.__notFromDatabase = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 之所以取第一个值,是因为 `对一` 字段的默认值可能是异步的,导致会赋默认值值会晚于普通字段,导致普通字段的值会被覆盖
|
||||||
|
return _.assign({ ...oldValues[0] }, newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断一个 record 是否是从数据库中获取的,如果是则返回 true,否则返回 false
|
||||||
|
* @param value useRecord 返回的值
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
export function isFromDatabase(value: Record<string, any>) {
|
||||||
|
if (!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !value.__notFromDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解决 `子表格` 中的 “一对多/多对一” 字段的默认值问题。
|
||||||
|
*
|
||||||
|
* 问题:如果子表格是空的,会导致默认值不会被设置,这是因为子表格中没有 FormItem 渲染,而设置默认值的逻辑就在 FormItem 中。
|
||||||
|
*
|
||||||
|
* 解决方法:
|
||||||
|
* 1. 如果子表格是空的,就设置一个值,这样会渲染出 FormItem。
|
||||||
|
* 2. 在 FormItem 中会设置默认值。
|
||||||
|
* 3. 如果子表格中没有设置默认值,就会再把子表格重置为空。
|
||||||
|
* @param param0
|
||||||
|
*/
|
||||||
|
export const useSubTableSpecialCase = ({ field }) => {
|
||||||
|
useEffect(() => {
|
||||||
|
if (_.isEmpty(field.value)) {
|
||||||
|
field.value = [{}];
|
||||||
|
// 因为默认值的解析是异步的,所以下面的代码会优先于默认值的设置,这样就防止了设置完默认值后又被清空的问题
|
||||||
|
Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
field.value = [];
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
};
|
@ -5,12 +5,11 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useFormBlockContext } from '../../../block-provider';
|
import { useFormBlockContext } from '../../../block-provider';
|
||||||
import { useDetailsBlockContext } from '../../../block-provider/DetailsBlockProvider';
|
import { useDetailsBlockContext } from '../../../block-provider/DetailsBlockProvider';
|
||||||
import { useCollection } from '../../../collection-manager';
|
import { useCollection } from '../../../collection-manager';
|
||||||
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
|
import { useSortFields } from '../../../collection-manager/action-hooks';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
|
|
||||||
|
|
||||||
export const FormDesigner = () => {
|
export const FormDesigner = () => {
|
||||||
const { name, title } = useCollection();
|
const { name, title } = useCollection();
|
||||||
@ -74,12 +73,11 @@ export const DetailsDesigner = () => {
|
|||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const dataSource = useCollectionFilterOptions(name);
|
|
||||||
const { service } = useDetailsBlockContext();
|
const { service } = useDetailsBlockContext();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const sortFields = useSortFields(name);
|
const sortFields = useSortFields(name);
|
||||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
|
||||||
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;
|
||||||
const sort = defaultSort?.map((item: string) => {
|
const sort = defaultSort?.map((item: string) => {
|
||||||
@ -96,25 +94,10 @@ export const DetailsDesigner = () => {
|
|||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
<SchemaSettings.BlockTitleItem />
|
<SchemaSettings.BlockTitleItem />
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={name}
|
||||||
schema={
|
defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: defaultFilter,
|
|
||||||
// title: '数据范围',
|
|
||||||
enum: dataSource,
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {
|
|
||||||
dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
filter = removeNullCondition(filter);
|
filter = removeNullCondition(filter);
|
||||||
const params = field.decoratorProps.params || {};
|
const params = field.decoratorProps.params || {};
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { FormLayout } from '@formily/antd-v5';
|
import { FormLayout } from '@formily/antd-v5';
|
||||||
import { createForm, Field, onFieldChange, onFieldInit, onFieldReact, onFormInputChange } from '@formily/core';
|
import { createForm, Field, Form as FormilyForm, onFieldInit, onFormInputChange } from '@formily/core';
|
||||||
import { FieldContext, FormContext, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
import { FieldContext, FormContext, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { autorun } from '@formily/reactive';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { ConfigProvider, Spin } from 'antd';
|
import { ConfigProvider, Spin } from 'antd';
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { useActionContext } from '..';
|
import { useActionContext } from '..';
|
||||||
import { useAttach, useComponent } from '../..';
|
import { useAttach, useComponent } from '../..';
|
||||||
|
import { useLocalVariables, useVariables } from '../../../variables';
|
||||||
import { useProps } from '../../hooks/useProps';
|
import { useProps } from '../../hooks/useProps';
|
||||||
import { linkageMergeAction } from './utils';
|
import { linkageMergeAction } from './utils';
|
||||||
|
|
||||||
@ -66,81 +68,91 @@ const getLinkageRules = (fieldSchema) => {
|
|||||||
return linkageRules;
|
return linkageRules;
|
||||||
};
|
};
|
||||||
|
|
||||||
const WithForm = (props) => {
|
interface WithFormProps {
|
||||||
|
form: FormilyForm;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WithForm = (props: WithFormProps) => {
|
||||||
const { form } = props;
|
const { form } = props;
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { setFormValueChanged } = useActionContext();
|
const { setFormValueChanged } = useActionContext();
|
||||||
const linkageRules =
|
const variables = useVariables();
|
||||||
|
const localVariables = useLocalVariables({ currentForm: form });
|
||||||
|
const linkageRules: any[] =
|
||||||
(getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || [];
|
(getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || [];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = uid();
|
const id = uid();
|
||||||
|
|
||||||
form.addEffects(id, () => {
|
form.addEffects(id, () => {
|
||||||
onFormInputChange((form) => {
|
onFormInputChange(() => {
|
||||||
setFormValueChanged?.(true);
|
setFormValueChanged?.(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (props.disabled) {
|
if (props.disabled) {
|
||||||
form.disabled = props.disabled;
|
form.disabled = props.disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
form.removeEffects(id);
|
form.removeEffects(id);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [props.disabled]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = uid();
|
const id = uid();
|
||||||
|
const disposes = [];
|
||||||
|
|
||||||
form.addEffects(id, () => {
|
form.addEffects(id, () => {
|
||||||
return linkageRules.map((v) => {
|
linkageRules.forEach((v) => {
|
||||||
return v.actions?.map((h) => {
|
v.actions?.forEach((h) => {
|
||||||
if (h.targetFields) {
|
if (h.targetFields?.length) {
|
||||||
const fields = h.targetFields.join(',');
|
const fields = h.targetFields.join(',');
|
||||||
onFieldInit(`*(${fields})`, (field: any, form) => {
|
|
||||||
field['initProperty'] = {
|
// 当 `linkageRules` 变更时,需要把 `field` 上之前已设置的值还原成初始值,以防止下面所述的情况:
|
||||||
display: field.display,
|
//
|
||||||
required: field.required,
|
// 1. 更改 `linkageRules`,使 a 字段满足条件时被隐藏
|
||||||
pattern: field.pattern,
|
// 2. 设置表单,使其满足条件,进而隐藏 a 字段
|
||||||
value: field.value || field.initialValue,
|
// 3. 再次更改 `linkageRules`,使 a 字段满足条件时被禁用
|
||||||
};
|
// 4. 设置表单,使其满足条件,会发现 a 字段还是隐藏状态,这里期望的是只显示禁用状态
|
||||||
});
|
onFieldInit(`*(${fields})`, (field: any) => {
|
||||||
onFieldChange(`*(${fields})`, ['value', 'required', 'pattern', 'display'], (field: any) => {
|
Object.keys(field).forEach((key) => {
|
||||||
field.linkageProperty = {
|
if (key.startsWith('_') && field[key] !== undefined) {
|
||||||
display: field.linkageProperty?.display,
|
field[key.slice(1)] = field[key];
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// `onFieldReact` 有问题,没有办法被取消监听,所以这里用 `onFieldInit` 代替
|
||||||
|
onFieldInit(`*(${fields})`, (field: any, form) => {
|
||||||
|
disposes.push(
|
||||||
|
autorun(() => {
|
||||||
|
linkageMergeAction({
|
||||||
|
operator: h.operator,
|
||||||
|
value: h.value,
|
||||||
|
field,
|
||||||
|
condition: v.condition,
|
||||||
|
values: form?.values,
|
||||||
|
variables,
|
||||||
|
localVariables,
|
||||||
});
|
});
|
||||||
return () => {
|
|
||||||
form.removeEffects(id);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
useEffect(() => {
|
|
||||||
const id = uid();
|
|
||||||
const linkagefields = [];
|
|
||||||
form.addEffects(id, () => {
|
|
||||||
return linkageRules.map((v, index) => {
|
|
||||||
return v.actions?.map((h) => {
|
|
||||||
if (h.targetFields) {
|
|
||||||
const fields = h.targetFields.join(',');
|
|
||||||
return onFieldReact(`*(${fields})`, (field: any, form) => {
|
|
||||||
linkagefields.push(field);
|
|
||||||
linkageMergeAction(h, field, v.condition, form?.values);
|
|
||||||
if (index === linkageRules.length - 1) {
|
|
||||||
setTimeout(() =>
|
|
||||||
linkagefields.map((v) => {
|
|
||||||
v.linkageProperty = {};
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
form.removeEffects(id);
|
form.removeEffects(id);
|
||||||
|
disposes.forEach((dispose) => {
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}, [linkageRules]);
|
}, [JSON.stringify(linkageRules)]);
|
||||||
|
|
||||||
return fieldSchema['x-decorator'] === 'FormV2' ? <FormDecorator {...props} /> : <FormComponent {...props} />;
|
return fieldSchema['x-decorator'] === 'FormV2' ? <FormDecorator {...props} /> : <FormComponent {...props} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,83 +1,148 @@
|
|||||||
|
import { Field } from '@formily/core';
|
||||||
import { evaluators } from '@nocobase/evaluators/client';
|
import { evaluators } from '@nocobase/evaluators/client';
|
||||||
import { cloneDeep, last } from 'lodash';
|
import { uid } from '@nocobase/utils';
|
||||||
|
import _, { cloneDeep } from 'lodash';
|
||||||
import { ActionType } from '../../../schema-settings/LinkageRules/type';
|
import { ActionType } from '../../../schema-settings/LinkageRules/type';
|
||||||
import { conditionAnalyse } from '../../common/utils/uitls';
|
import { VariableOption, VariablesContextType } from '../../../variables/types';
|
||||||
|
import { REGEX_OF_VARIABLE } from '../../../variables/utils/isVariable';
|
||||||
|
import { conditionAnalyses } from '../../common/utils/uitls';
|
||||||
|
|
||||||
export const linkageMergeAction = ({ operator, value }, field, condition, values) => {
|
interface Props {
|
||||||
const requiredResult = field?.linkageProperty?.required || [field?.initProperty?.required || false];
|
operator;
|
||||||
const displayResult = field?.linkageProperty?.display || [field?.initProperty?.display];
|
value;
|
||||||
const patternResult = field?.linkageProperty?.pattern || [field?.initProperty?.pattern];
|
field: Field & {
|
||||||
const valueResult = field?.linkageProperty?.value || [field.value || field?.initProperty?.value];
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
condition;
|
||||||
|
values;
|
||||||
|
variables: VariablesContextType;
|
||||||
|
localVariables: VariableOption[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const linkageMergeAction = async ({
|
||||||
|
operator,
|
||||||
|
value,
|
||||||
|
field,
|
||||||
|
condition,
|
||||||
|
values,
|
||||||
|
variables,
|
||||||
|
localVariables,
|
||||||
|
}: Props) => {
|
||||||
const { evaluate } = evaluators.get('formula.js');
|
const { evaluate } = evaluators.get('formula.js');
|
||||||
|
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
case ActionType.Required:
|
case ActionType.Required:
|
||||||
if (conditionAnalyse(condition, values)) {
|
if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
|
||||||
requiredResult.push(true);
|
field.required = true;
|
||||||
|
} else {
|
||||||
|
field.required = false;
|
||||||
}
|
}
|
||||||
field.linkageProperty = {
|
|
||||||
...field.linkageProperty,
|
|
||||||
required: requiredResult,
|
|
||||||
};
|
|
||||||
field.required = last(field.linkageProperty?.required);
|
|
||||||
break;
|
break;
|
||||||
case ActionType.InRequired:
|
case ActionType.InRequired:
|
||||||
if (conditionAnalyse(condition, values)) {
|
if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
|
||||||
requiredResult.push(false);
|
field.required = false;
|
||||||
|
} else {
|
||||||
|
field.required = true;
|
||||||
}
|
}
|
||||||
field.linkageProperty = {
|
|
||||||
...field.linkageProperty,
|
|
||||||
required: requiredResult,
|
|
||||||
};
|
|
||||||
field.required = last(field.linkageProperty?.required);
|
|
||||||
break;
|
break;
|
||||||
case ActionType.Visible:
|
case ActionType.Visible:
|
||||||
case ActionType.None:
|
case ActionType.None:
|
||||||
case ActionType.Hidden:
|
case ActionType.Hidden:
|
||||||
if (conditionAnalyse(condition, values)) {
|
if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
|
||||||
displayResult.push(operator);
|
field._display = '_display' in field ? field._display : field.display;
|
||||||
|
field.display = operator;
|
||||||
|
} else {
|
||||||
|
field.display = field._display;
|
||||||
}
|
}
|
||||||
field.linkageProperty = {
|
|
||||||
...field.linkageProperty,
|
|
||||||
display: displayResult,
|
|
||||||
};
|
|
||||||
field.display = last(field.linkageProperty?.display);
|
|
||||||
break;
|
break;
|
||||||
case ActionType.Editable:
|
case ActionType.Editable:
|
||||||
case ActionType.ReadOnly:
|
case ActionType.ReadOnly:
|
||||||
case ActionType.ReadPretty:
|
case ActionType.ReadPretty:
|
||||||
if (conditionAnalyse(condition, values)) {
|
if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
|
||||||
patternResult.push(operator);
|
field._pattern = '_pattern' in field ? field._pattern : field.pattern;
|
||||||
|
field.pattern = operator;
|
||||||
|
} else {
|
||||||
|
field.pattern = field._pattern;
|
||||||
}
|
}
|
||||||
field.linkageProperty = {
|
|
||||||
...field.linkageProperty,
|
|
||||||
pattern: patternResult,
|
|
||||||
};
|
|
||||||
field.pattern = last(field.linkageProperty.pattern);
|
|
||||||
break;
|
break;
|
||||||
case ActionType.Value:
|
case ActionType.Value:
|
||||||
if (conditionAnalyse(condition, values)) {
|
if (
|
||||||
|
isConditionEmpty(condition) ||
|
||||||
|
(await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables }))
|
||||||
|
) {
|
||||||
|
let result = null;
|
||||||
if (value?.mode === 'express') {
|
if (value?.mode === 'express') {
|
||||||
const scope = cloneDeep(values);
|
const scope = cloneDeep(values);
|
||||||
|
|
||||||
|
// 1. 解析如 `{{$user.name}}` 之类的变量
|
||||||
|
const { exp, scope: expScope } = await replaceVariables(value.value || value.result, {
|
||||||
|
variables,
|
||||||
|
localVariables,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = evaluate(value.result || value.value, { ...scope, now: () => new Date().toString() });
|
// 2. TODO: 需要把里面解析变量的逻辑删除,因为在上一步已经解析过了
|
||||||
valueResult.push(result);
|
result = evaluate(exp, { ...scope, now: () => new Date().toString(), ...expScope });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (value?.mode === 'constant') {
|
} else if (value?.mode === 'constant') {
|
||||||
valueResult.push(value?.value || value);
|
result = value?.value || value;
|
||||||
} else {
|
} else {
|
||||||
valueResult.push(null);
|
result = null;
|
||||||
}
|
}
|
||||||
|
field._value = '_value' in field ? field._value : field.value;
|
||||||
|
field.value = result === undefined ? field.value : result;
|
||||||
|
} else {
|
||||||
|
field.value = field._value;
|
||||||
}
|
}
|
||||||
field.linkageProperty = {
|
|
||||||
...field.linkageProperty,
|
|
||||||
value: valueResult,
|
|
||||||
};
|
|
||||||
field.value = last(valueResult) === undefined ? field.value : last(valueResult);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function replaceVariables(
|
||||||
|
value: string,
|
||||||
|
{
|
||||||
|
variables,
|
||||||
|
localVariables,
|
||||||
|
}: {
|
||||||
|
variables: VariablesContextType;
|
||||||
|
localVariables: VariableOption[];
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const store = {};
|
||||||
|
const scope = {};
|
||||||
|
|
||||||
|
const waitForParsing = value.match(REGEX_OF_VARIABLE)?.map(async (item) => {
|
||||||
|
const result = await variables.parseVariable(item, localVariables);
|
||||||
|
|
||||||
|
// 在开头加 `_` 是为了保证 id 不能以数字开头,否则在解析表达式的时候(不是解析变量)会报错
|
||||||
|
const id = `_${uid()}`;
|
||||||
|
|
||||||
|
scope[id] = result;
|
||||||
|
store[item] = id;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (waitForParsing) {
|
||||||
|
await Promise.all(waitForParsing);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
exp: value.replace(REGEX_OF_VARIABLE, (match) => {
|
||||||
|
return store[match] || match;
|
||||||
|
}),
|
||||||
|
scope,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isConditionEmpty(rules: { $and?: any; $or?: any }) {
|
||||||
|
const type = Object.keys(rules)[0] || '$and';
|
||||||
|
const conditions = rules[type];
|
||||||
|
|
||||||
|
return _.isEmpty(conditions);
|
||||||
|
}
|
||||||
|
@ -13,18 +13,24 @@ describe('Form', () => {
|
|||||||
it('basic', async () => {
|
it('basic', async () => {
|
||||||
render(<App2 />);
|
render(<App2 />);
|
||||||
|
|
||||||
|
await sleep();
|
||||||
|
|
||||||
const submit = screen.getByText('Submit');
|
const submit = screen.getByText('Submit');
|
||||||
const input = document.querySelector('.ant-input') as HTMLInputElement;
|
const input = document.querySelector('.ant-input') as HTMLInputElement;
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
expect(submit).toBeInTheDocument();
|
expect(submit).toBeInTheDocument();
|
||||||
expect(input).toBeInTheDocument();
|
expect(input).toBeInTheDocument();
|
||||||
expect(screen.getByText(/form title/i)).toBeInTheDocument();
|
expect(screen.queryByText(/form title/i)).toBeInTheDocument();
|
||||||
expect(screen.getByText('T1')).toBeInTheDocument();
|
expect(screen.queryByText('T1')).toBeInTheDocument();
|
||||||
expect(screen.getByText(/\{\}/i)).toBeInTheDocument();
|
expect(screen.queryByText(/\{\}/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
// 使用 waitFor 防止报错,参见:https://github.com/testing-library/user-event/issues/662#issuecomment-904365493
|
// 使用 waitFor 防止报错,参见:https://github.com/testing-library/user-event/issues/662#issuecomment-904365493
|
||||||
await waitFor(() => userEvent.type(input, '123'));
|
await waitFor(() => userEvent.type(input, '123'));
|
||||||
expect(screen.getByText(/\{ "field1": "123" \}/i)).toBeInTheDocument();
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText(/\{ "field1": "123" \}/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('decorator', async () => {
|
it('decorator', async () => {
|
||||||
@ -46,7 +52,9 @@ describe('Form', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await userEvent.type(input, '123');
|
await userEvent.type(input, '123');
|
||||||
expect(screen.getByText(/\{ "field1": "aaa123" \}/i)).toBeInTheDocument();
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText(/\{ "field1": "aaa123" \}/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Form & Drawer', async () => {
|
it('Form & Drawer', async () => {
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCompile, useDesignable } from '../..';
|
import { useCompile, useDesignable } from '../..';
|
||||||
import { useGanttBlockContext } from '../../../block-provider';
|
import { useFormBlockContext, useGanttBlockContext } from '../../../block-provider';
|
||||||
import { useCollection } from '../../../collection-manager';
|
import { useCollection } from '../../../collection-manager';
|
||||||
import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks';
|
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
|
|
||||||
@ -25,14 +24,13 @@ const useOptions = (type = 'string') => {
|
|||||||
export const GanttDesigner = () => {
|
export const GanttDesigner = () => {
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { name, title, fields } = useCollection();
|
const { form } = useFormBlockContext();
|
||||||
const dataSource = useCollectionFilterOptions(name);
|
const { name, title } = useCollection();
|
||||||
const { service } = useGanttBlockContext();
|
const { service } = useGanttBlockContext();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
|
||||||
const fieldNames = fieldSchema?.['x-decorator-props']?.['fieldNames'] || {};
|
const fieldNames = fieldSchema?.['x-decorator-props']?.['fieldNames'] || {};
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||||
return (
|
return (
|
||||||
@ -146,28 +144,10 @@ export const GanttDesigner = () => {
|
|||||||
dn.refresh();
|
dn.refresh();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={name}
|
||||||
schema={
|
defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: defaultFilter,
|
|
||||||
enum: dataSource,
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
initialValues={
|
|
||||||
{
|
|
||||||
// title: field.title,
|
|
||||||
// icon: field.componentProps.icon,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
const params = field.decoratorProps.params || {};
|
const params = field.decoratorProps.params || {};
|
||||||
params.filter = filter;
|
params.filter = filter;
|
||||||
|
@ -4,13 +4,13 @@ import { Slider } from 'antd';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCollection, useCollectionFilterOptions, useSortFields } from '../../../collection-manager';
|
import { useFormBlockContext } from '../../../block-provider';
|
||||||
|
import { useCollection, useSortFields } from '../../../collection-manager';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { SchemaComponentOptions } from '../../core';
|
import { SchemaComponentOptions } from '../../core';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
|
|
||||||
import { defaultColumnCount, gridSizes, pageSizeOptions, screenSizeMaps, screenSizeTitleMaps } from './options';
|
import { defaultColumnCount, gridSizes, pageSizeOptions, screenSizeMaps, screenSizeTitleMaps } from './options';
|
||||||
|
|
||||||
const columnCountMarks = [1, 2, 3, 4, 6, 8, 12, 24].reduce((obj, cur) => {
|
const columnCountMarks = [1, 2, 3, 4, 6, 8, 12, 24].reduce((obj, cur) => {
|
||||||
@ -23,11 +23,10 @@ export const GridCardDesigner = () => {
|
|||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const dataSource = useCollectionFilterOptions(name);
|
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const sortFields = useSortFields(name);
|
const sortFields = useSortFields(name);
|
||||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
|
||||||
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;
|
||||||
const columnCount = field.decoratorProps.columnCount || defaultColumnCount;
|
const columnCount = field.decoratorProps.columnCount || defaultColumnCount;
|
||||||
@ -94,25 +93,10 @@ export const GridCardDesigner = () => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={name}
|
||||||
schema={
|
defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: defaultFilter,
|
|
||||||
// title: '数据范围',
|
|
||||||
enum: dataSource,
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {
|
|
||||||
dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
filter = removeNullCondition(filter);
|
filter = removeNullCondition(filter);
|
||||||
_.set(fieldSchema, 'x-decorator-props.params.filter', filter);
|
_.set(fieldSchema, 'x-decorator-props.params.filter', filter);
|
||||||
|
@ -1,49 +1,30 @@
|
|||||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useFormBlockContext, useKanbanBlockContext } from '../../../block-provider';
|
||||||
import { useKanbanBlockContext } from '../../../block-provider';
|
|
||||||
import { useCollection } from '../../../collection-manager';
|
import { useCollection } from '../../../collection-manager';
|
||||||
import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks';
|
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
import { FixedBlockDesignerItem } from '../page';
|
import { FixedBlockDesignerItem } from '../page';
|
||||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
|
|
||||||
|
|
||||||
export const KanbanDesigner = () => {
|
export const KanbanDesigner = () => {
|
||||||
const { name, title } = useCollection();
|
const { name, title } = useCollection();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const dataSource = useCollectionFilterOptions(name);
|
const { form } = useFormBlockContext();
|
||||||
const { service } = useKanbanBlockContext();
|
const { service } = useKanbanBlockContext();
|
||||||
const { t } = useTranslation();
|
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
|
||||||
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
|
||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
<SchemaSettings.BlockTitleItem />
|
<SchemaSettings.BlockTitleItem />
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={name}
|
||||||
schema={
|
defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: defaultFilter,
|
|
||||||
enum: dataSource,
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {
|
|
||||||
dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
filter = removeNullCondition(filter);
|
filter = removeNullCondition(filter);
|
||||||
const params = field.decoratorProps.params || {};
|
const params = field.decoratorProps.params || {};
|
||||||
|
@ -3,25 +3,22 @@ import { ISchema, useField, useFieldSchema } from '@formily/react';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCollection, useCollectionFilterOptions, useSortFields } from '../../../collection-manager';
|
import { useFormBlockContext } from '../../../block-provider';
|
||||||
|
import { useCollection, useSortFields } from '../../../collection-manager';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
|
|
||||||
import { useListBlockContext } from './List.Decorator';
|
|
||||||
|
|
||||||
export const ListDesigner = () => {
|
export const ListDesigner = () => {
|
||||||
const { name, title } = useCollection();
|
const { name, title } = useCollection();
|
||||||
const template = useSchemaTemplate();
|
const template = useSchemaTemplate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const dataSource = useCollectionFilterOptions(name);
|
|
||||||
const { service } = useListBlockContext();
|
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const sortFields = useSortFields(name);
|
const sortFields = useSortFields(name);
|
||||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
|
||||||
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;
|
||||||
const sort = defaultSort?.map((item: string) => {
|
const sort = defaultSort?.map((item: string) => {
|
||||||
@ -38,25 +35,10 @@ export const ListDesigner = () => {
|
|||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
<SchemaSettings.BlockTitleItem />
|
<SchemaSettings.BlockTitleItem />
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={name}
|
||||||
schema={
|
defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: defaultFilter,
|
|
||||||
// title: '数据范围',
|
|
||||||
enum: dataSource,
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {
|
|
||||||
dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
filter = removeNullCondition(filter);
|
filter = removeNullCondition(filter);
|
||||||
_.set(fieldSchema, 'x-decorator-props.params.filter', filter);
|
_.set(fieldSchema, 'x-decorator-props.params.filter', filter);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import { connect, mapProps, mapReadPretty, useFieldSchema } from '@formily/react';
|
import { connect, mapProps, mapReadPretty, useFieldSchema } from '@formily/react';
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { Divider, SelectProps, Tag } from 'antd';
|
import { Divider, SelectProps, Tag } from 'antd';
|
||||||
import _, { uniqBy } from 'lodash';
|
import dayjs from 'dayjs';
|
||||||
|
import { uniqBy } from 'lodash';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { ResourceActionOptions, useRequest } from '../../../api-client';
|
import { ResourceActionOptions, useRequest } from '../../../api-client';
|
||||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
||||||
@ -127,7 +127,7 @@ const InternalRemoteSelect = connect(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
manual: manual && Object.prototype.toString.call(value) === '[object Object]',
|
manual,
|
||||||
debounceWait: wait,
|
debounceWait: wait,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -194,6 +194,7 @@ const InternalRemoteSelect = connect(
|
|||||||
}
|
}
|
||||||
firstRun.current = true;
|
firstRun.current = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
open={open}
|
open={open}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { connect, mapReadPretty, mapProps } from '@formily/react';
|
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactQuill from 'react-quill';
|
import ReactQuill from 'react-quill';
|
||||||
|
import { isVariable } from '../../../variables/utils/isVariable';
|
||||||
import { ReadPretty as InputReadPretty } from '../input';
|
import { ReadPretty as InputReadPretty } from '../input';
|
||||||
import { useStyles } from './style';
|
import { useStyles } from './style';
|
||||||
|
|
||||||
@ -23,24 +24,22 @@ export const RichText = connect(
|
|||||||
'link',
|
'link',
|
||||||
'image',
|
'image',
|
||||||
];
|
];
|
||||||
const { value, onChange, disabled } = props;
|
const { value, defaultValue, onChange, disabled } = props;
|
||||||
|
const resultValue = isVariable(value || defaultValue) ? undefined : value || defaultValue || '';
|
||||||
|
|
||||||
return wrapSSR(
|
return wrapSSR(
|
||||||
<ReactQuill
|
<ReactQuill
|
||||||
className={`${componentCls} ${hashId}`}
|
className={`${componentCls} ${hashId}`}
|
||||||
modules={modules}
|
modules={modules}
|
||||||
formats={formats}
|
formats={formats}
|
||||||
value={typeof value === 'string' ? value : ''}
|
value={resultValue}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
readOnly={disabled}
|
readOnly={disabled}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
mapProps((props) => {
|
mapProps({
|
||||||
const { value } = props;
|
initialValue: 'defaultValue',
|
||||||
return {
|
|
||||||
...props,
|
|
||||||
value: typeof value === 'object' ? value?.default : value,
|
|
||||||
};
|
|
||||||
}),
|
}),
|
||||||
mapReadPretty((props) => {
|
mapReadPretty((props) => {
|
||||||
return <InputReadPretty.Html {...props} />;
|
return <InputReadPretty.Html {...props} />;
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useVariableOptions } from '../../../schema-settings/VariableInput/hooks/useVariableOptions';
|
|
||||||
import { Variable } from '../variable';
|
|
||||||
|
|
||||||
export function FilterDynamicComponent(props) {
|
|
||||||
const { value, onChange, renderSchemaComponent, form, collectionField, ...other } = props;
|
|
||||||
const options = useVariableOptions({ form, collectionField, ...other });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Variable.Input value={value} onChange={onChange} scope={options}>
|
|
||||||
{renderSchemaComponent()}
|
|
||||||
</Variable.Input>
|
|
||||||
);
|
|
||||||
}
|
|
@ -2,13 +2,13 @@ import { ISchema, useField, useFieldSchema } from '@formily/react';
|
|||||||
import { set } from 'lodash';
|
import { set } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ArrayItems } from '@formily/antd-v5';
|
import { useFormBlockContext } from '../../../block-provider';
|
||||||
import { useCollectionFilterOptions, useCollectionManager } from '../../../collection-manager';
|
import { useCollectionManager } from '../../../collection-manager';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled } from '../../../schema-settings';
|
||||||
|
import useIsAllowToSetDefaultValue from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||||
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
|
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
|
||||||
import { useAssociationFieldContext } from '../association-field/hooks';
|
import { useAssociationFieldContext } from '../association-field/hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
import { FilterDynamicComponent } from './FilterDynamicComponent';
|
|
||||||
|
|
||||||
const useLabelFields = (collectionName?: any) => {
|
const useLabelFields = (collectionName?: any) => {
|
||||||
// 需要在组件顶层调用
|
// 需要在组件顶层调用
|
||||||
@ -46,6 +46,7 @@ export const useColorFields = (collectionName?: any) => {
|
|||||||
};
|
};
|
||||||
export const TableColumnDesigner = (props) => {
|
export const TableColumnDesigner = (props) => {
|
||||||
const { uiSchema, fieldSchema, collectionField } = props;
|
const { uiSchema, fieldSchema, collectionField } = props;
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
const { getInterface, getCollection } = useCollectionManager();
|
const { getInterface, getCollection } = useCollectionManager();
|
||||||
const field: any = useField();
|
const field: any = useField();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -55,13 +56,13 @@ export const TableColumnDesigner = (props) => {
|
|||||||
fieldSchema?.['x-component-props']?.['fieldNames'] || uiSchema?.['x-component-props']?.['fieldNames'];
|
fieldSchema?.['x-component-props']?.['fieldNames'] || uiSchema?.['x-component-props']?.['fieldNames'];
|
||||||
const options = useLabelFields(collectionField?.target ?? collectionField?.targetCollection);
|
const options = useLabelFields(collectionField?.target ?? collectionField?.targetCollection);
|
||||||
const colorFieldOptions = useColorFields(collectionField?.target ?? collectionField?.targetCollection);
|
const colorFieldOptions = useColorFields(collectionField?.target ?? collectionField?.targetCollection);
|
||||||
const intefaceCfg = getInterface(collectionField?.interface);
|
const interfaceCfg = getInterface(collectionField?.interface);
|
||||||
const targetCollection = getCollection(collectionField?.target);
|
const targetCollection = getCollection(collectionField?.target);
|
||||||
const isFileField = isFileCollection(targetCollection);
|
const isFileField = isFileCollection(targetCollection);
|
||||||
const isSubTableColumn = ['QuickEdit', 'FormItem'].includes(fieldSchema['x-decorator']);
|
const isSubTableColumn = ['QuickEdit', 'FormItem'].includes(fieldSchema['x-decorator']);
|
||||||
const { currentMode } = useAssociationFieldContext();
|
const { currentMode } = useAssociationFieldContext();
|
||||||
const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {};
|
const { isAllowToSetDefaultValue } = useIsAllowToSetDefaultValue({ collectionField, fieldSchema });
|
||||||
const dataSource = useCollectionFilterOptions(collectionField?.target);
|
|
||||||
const isDateField = ['datetime', 'createdAt', 'updatedAt'].includes(collectionField?.interface);
|
const isDateField = ['datetime', 'createdAt', 'updatedAt'].includes(collectionField?.interface);
|
||||||
const isAssociationField = ['obo', 'oho', 'o2o', 'o2m', 'm2m', 'm2o', 'snapshot'].includes(
|
const isAssociationField = ['obo', 'oho', 'o2o', 'o2m', 'm2m', 'm2o', 'snapshot'].includes(
|
||||||
collectionField?.interface,
|
collectionField?.interface,
|
||||||
@ -76,6 +77,7 @@ export const TableColumnDesigner = (props) => {
|
|||||||
readOnlyMode = 'read-pretty';
|
readOnlyMode = 'read-pretty';
|
||||||
}
|
}
|
||||||
const isSelectFieldMode = isAssociationField && fieldMode === 'Select';
|
const isSelectFieldMode = isAssociationField && fieldMode === 'Select';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner disableInitializer>
|
<GeneralSchemaDesigner disableInitializer>
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.ModalItem
|
||||||
@ -111,24 +113,10 @@ export const TableColumnDesigner = (props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{isSelectFieldMode && !field.readPretty && !uiSchema?.['x-read-pretty'] && isSubTableColumn && (
|
{isSelectFieldMode && !field.readPretty && !uiSchema?.['x-read-pretty'] && isSubTableColumn && (
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={collectionField?.target}
|
||||||
schema={
|
defaultFilter={fieldSchema?.['x-component-props']?.service?.params?.filter || {}}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: defaultFilter,
|
|
||||||
enum: dataSource,
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {
|
|
||||||
dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
filter = removeNullCondition(filter);
|
filter = removeNullCondition(filter);
|
||||||
set(field.componentProps, 'service.params.filter', filter);
|
set(field.componentProps, 'service.params.filter', filter);
|
||||||
@ -178,7 +166,7 @@ export const TableColumnDesigner = (props) => {
|
|||||||
dn.refresh();
|
dn.refresh();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{intefaceCfg && intefaceCfg.sortable === true && !currentMode && (
|
{interfaceCfg && interfaceCfg.sortable === true && !currentMode && (
|
||||||
<SchemaSettings.SwitchItem
|
<SchemaSettings.SwitchItem
|
||||||
title={t('Sortable')}
|
title={t('Sortable')}
|
||||||
checked={field.componentProps.sorter}
|
checked={field.componentProps.sorter}
|
||||||
@ -407,9 +395,7 @@ export const TableColumnDesigner = (props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isSubTableColumn && !field?.readPretty && isShowDefaultValue(collectionField, getInterface) && (
|
{isAllowToSetDefaultValue(isSubTableColumn) && <SchemaSettings.DefaultValue fieldSchema={fieldSchema} />}
|
||||||
<SchemaSettings.DefaultValue fieldSchema={fieldSchema} />
|
|
||||||
)}
|
|
||||||
<SchemaSettings.Divider />
|
<SchemaSettings.Divider />
|
||||||
<SchemaSettings.Remove
|
<SchemaSettings.Remove
|
||||||
removeParentsIfNoChildren={!isSubTableColumn}
|
removeParentsIfNoChildren={!isSubTableColumn}
|
||||||
|
@ -2,10 +2,11 @@ import { DeleteOutlined, MenuOutlined } from '@ant-design/icons';
|
|||||||
import { TinyColor } from '@ctrl/tinycolor';
|
import { TinyColor } from '@ctrl/tinycolor';
|
||||||
import { SortableContext, SortableContextProps, useSortable } from '@dnd-kit/sortable';
|
import { SortableContext, SortableContextProps, useSortable } from '@dnd-kit/sortable';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { ArrayField, Field } from '@formily/core';
|
import { ArrayField } from '@formily/core';
|
||||||
import { spliceArrayState } from '@formily/core/esm/shared/internals';
|
import { spliceArrayState } from '@formily/core/esm/shared/internals';
|
||||||
import { RecursionField, Schema, observer, useField, useFieldSchema } from '@formily/react';
|
import { RecursionField, Schema, observer, useField, useFieldSchema } from '@formily/react';
|
||||||
import { action, reaction } from '@formily/reactive';
|
import { action } from '@formily/reactive';
|
||||||
|
import { isPortalInBody } from '@nocobase/utils/client';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Table as AntdTable, TableColumnProps } from 'antd';
|
import { Table as AntdTable, TableColumnProps } from 'antd';
|
||||||
import { default as classNames, default as cls } from 'classnames';
|
import { default as classNames, default as cls } from 'classnames';
|
||||||
@ -20,16 +21,17 @@ import {
|
|||||||
useTableSelectorContext,
|
useTableSelectorContext,
|
||||||
} from '../../../';
|
} from '../../../';
|
||||||
import { useACLFieldWhitelist } from '../../../acl/ACLProvider';
|
import { useACLFieldWhitelist } from '../../../acl/ACLProvider';
|
||||||
|
import { LocalVariablesProvider } from '../../../variables/hooks/useLocalVariables';
|
||||||
import { useToken } from '../__builtins__';
|
import { useToken } from '../__builtins__';
|
||||||
import { ColumnFieldProvider } from './components/ColumnFieldProvider';
|
import { ColumnFieldProvider } from './components/ColumnFieldProvider';
|
||||||
import { extractIndex, isCollectionFieldComponent, isColumnComponent, isPortalInBody } from './utils';
|
import { extractIndex, isCollectionFieldComponent, isColumnComponent } from './utils';
|
||||||
|
|
||||||
const useArrayField = (props) => {
|
const useArrayField = (props) => {
|
||||||
const field = useField<ArrayField>();
|
const field = useField<ArrayField>();
|
||||||
return (props.field || field) as ArrayField;
|
return (props.field || field) as ArrayField;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useTableColumns = (props) => {
|
const useTableColumns = (props: { showDel?: boolean; isSubTable?: boolean }) => {
|
||||||
const field = useArrayField(props);
|
const field = useArrayField(props);
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
const { schemaInWhitelist } = useACLFieldWhitelist();
|
const { schemaInWhitelist } = useACLFieldWhitelist();
|
||||||
@ -60,6 +62,7 @@ const useTableColumns = (props) => {
|
|||||||
const index = field.value?.indexOf(record);
|
const index = field.value?.indexOf(record);
|
||||||
return (
|
return (
|
||||||
<RecordIndexProvider index={record.__index || index}>
|
<RecordIndexProvider index={record.__index || index}>
|
||||||
|
<LocalVariablesProvider iterationCtx={record}>
|
||||||
<RecordProvider record={record}>
|
<RecordProvider record={record}>
|
||||||
<ColumnFieldProvider schema={s} basePath={field.address.concat(record.__index || index)}>
|
<ColumnFieldProvider schema={s} basePath={field.address.concat(record.__index || index)}>
|
||||||
<RecursionField
|
<RecursionField
|
||||||
@ -69,6 +72,7 @@ const useTableColumns = (props) => {
|
|||||||
/>
|
/>
|
||||||
</ColumnFieldProvider>
|
</ColumnFieldProvider>
|
||||||
</RecordProvider>
|
</RecordProvider>
|
||||||
|
</LocalVariablesProvider>
|
||||||
</RecordIndexProvider>
|
</RecordIndexProvider>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -97,7 +101,7 @@ const useTableColumns = (props) => {
|
|||||||
<DeleteOutlined
|
<DeleteOutlined
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
action(() => {
|
void action(() => {
|
||||||
spliceArrayState(field as any, {
|
spliceArrayState(field as any, {
|
||||||
startIndex: index,
|
startIndex: index,
|
||||||
deleteCount: 1,
|
deleteCount: 1,
|
||||||
@ -161,7 +165,11 @@ const SortHandle = (props) => {
|
|||||||
const TableIndex = (props) => {
|
const TableIndex = (props) => {
|
||||||
const { index } = props;
|
const { index } = props;
|
||||||
return (
|
return (
|
||||||
<div className={classNames('nb-table-index')} style={{ padding: '0 8px 0 16px' }}>
|
<div
|
||||||
|
data-testid={`table-index-${index}`}
|
||||||
|
className={classNames('nb-table-index')}
|
||||||
|
style={{ padding: '0 8px 0 16px' }}
|
||||||
|
>
|
||||||
{index}
|
{index}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -184,28 +192,22 @@ const usePaginationProps = (pagination1, pagination2) => {
|
|||||||
return result.total <= result.pageSize ? false : result;
|
return result.total <= result.pageSize ? false : result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useValidator = (validator: (value: any) => string) => {
|
|
||||||
const field = useField<Field>();
|
|
||||||
useEffect(() => {
|
|
||||||
const dispose = reaction(
|
|
||||||
() => field.value,
|
|
||||||
(value) => {
|
|
||||||
const message = validator(value);
|
|
||||||
field.setFeedback({
|
|
||||||
type: 'error',
|
|
||||||
code: 'ValidateError',
|
|
||||||
messages: message ? [message] : [],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return () => {
|
|
||||||
dispose();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Table: any = observer(
|
export const Table: any = observer(
|
||||||
(props: any) => {
|
(props: {
|
||||||
|
useProps?: () => any;
|
||||||
|
onChange?: (pagination, filters, sorter, extra) => void;
|
||||||
|
onRowSelectionChange?: (selectedRowKeys: any[], selectedRows: any[]) => void;
|
||||||
|
onRowDragEnd?: (e: { from: any; to: any }) => void;
|
||||||
|
onClickRow?: (record: any, setSelectedRow: (selectedRow: any[]) => void, selectedRow: any[]) => void;
|
||||||
|
pagination?: any;
|
||||||
|
showIndex?: boolean;
|
||||||
|
dragSort?: boolean;
|
||||||
|
rowKey?: string | ((record: any) => string);
|
||||||
|
rowSelection?: any;
|
||||||
|
required?: boolean;
|
||||||
|
onExpand?: (flag: boolean, record: any) => void;
|
||||||
|
isSubTable?: boolean;
|
||||||
|
}) => {
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
const { pagination: pagination1, useProps, onChange, ...others1 } = props;
|
const { pagination: pagination1, useProps, onChange, ...others1 } = props;
|
||||||
const { pagination: pagination2, onClickRow, ...others2 } = useProps?.() || {};
|
const { pagination: pagination2, onClickRow, ...others2 } = useProps?.() || {};
|
||||||
@ -306,7 +308,7 @@ export const Table: any = observer(
|
|||||||
const toIndex = e.over?.data.current?.sortable?.index;
|
const toIndex = e.over?.data.current?.sortable?.index;
|
||||||
const from = field.value[fromIndex] || e.active;
|
const from = field.value[fromIndex] || e.active;
|
||||||
const to = field.value[toIndex] || e.over;
|
const to = field.value[toIndex] || e.over;
|
||||||
field.move(fromIndex, toIndex);
|
void field.move(fromIndex, toIndex);
|
||||||
onRowDragEnd({ from, to });
|
onRowDragEnd({ from, to });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -1,31 +1,28 @@
|
|||||||
import { ArrayItems } from '@formily/antd-v5';
|
import { ArrayItems } from '@formily/antd-v5';
|
||||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useTableBlockContext } from '../../../block-provider';
|
import { useFormBlockContext, useTableBlockContext } from '../../../block-provider';
|
||||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
|
import { useSortFields } from '../../../collection-manager/action-hooks';
|
||||||
import { FilterBlockType } from '../../../filter-provider/utils';
|
import { FilterBlockType } from '../../../filter-provider/utils';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { useCompile, useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
import { FixedBlockDesignerItem } from '../page';
|
import { FixedBlockDesignerItem } from '../page';
|
||||||
import { FilterDynamicComponent } from './FilterDynamicComponent';
|
|
||||||
|
|
||||||
export const TableBlockDesigner = () => {
|
export const TableBlockDesigner = () => {
|
||||||
const { name, title, sortable } = useCollection();
|
const { name, title, sortable } = useCollection();
|
||||||
const { getCollectionField, getCollection } = useCollectionManager();
|
const { getCollectionField, getCollection } = useCollectionManager();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const dataSource = useCollectionFilterOptions(name);
|
const { form } = useFormBlockContext();
|
||||||
const sortFields = useSortFields(name);
|
const sortFields = useSortFields(name);
|
||||||
const { service } = useTableBlockContext();
|
const { service } = useTableBlockContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const compile = useCompile();
|
|
||||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
|
||||||
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;
|
||||||
const supportTemplate = !fieldSchema?.['x-decorator-props']?.disableTemplate;
|
const supportTemplate = !fieldSchema?.['x-decorator-props']?.disableTemplate;
|
||||||
@ -45,23 +42,6 @@ export const TableBlockDesigner = () => {
|
|||||||
const { dragSort, resource } = field.decoratorProps;
|
const { dragSort, resource } = field.decoratorProps;
|
||||||
const collectionField = resource && getCollectionField(resource);
|
const collectionField = resource && getCollectionField(resource);
|
||||||
const treeCollection = resource?.includes('.') ? getCollection(collectionField?.target)?.tree : !!collection?.tree;
|
const treeCollection = resource?.includes('.') ? getCollection(collectionField?.target)?.tree : !!collection?.tree;
|
||||||
const dataScopeSchema = useMemo(() => {
|
|
||||||
return {
|
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: defaultFilter,
|
|
||||||
// title: '数据范围',
|
|
||||||
enum: compile(dataSource),
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {
|
|
||||||
dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema;
|
|
||||||
}, [dataSource, defaultFilter]);
|
|
||||||
const onDataScopeSubmit = useCallback(
|
const onDataScopeSubmit = useCallback(
|
||||||
({ filter }) => {
|
({ filter }) => {
|
||||||
filter = removeNullCondition(filter);
|
filter = removeNullCondition(filter);
|
||||||
@ -81,7 +61,7 @@ export const TableBlockDesigner = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[field],
|
[dn, field.decoratorProps, fieldSchema, service],
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||||
@ -124,7 +104,12 @@ export const TableBlockDesigner = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<FixedBlockDesignerItem />
|
<FixedBlockDesignerItem />
|
||||||
<SchemaSettings.ModalItem title={t('Set the data scope')} schema={dataScopeSchema} onSubmit={onDataScopeSubmit} />
|
<SchemaSettings.DataScope
|
||||||
|
collectionName={name}
|
||||||
|
defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
|
||||||
|
form={form}
|
||||||
|
onSubmit={onDataScopeSubmit}
|
||||||
|
/>
|
||||||
{!dragSort && (
|
{!dragSort && (
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.ModalItem
|
||||||
title={t('Set default sorting rules')}
|
title={t('Set default sorting rules')}
|
||||||
|
@ -3,27 +3,25 @@ import { ISchema, useField, useFieldSchema } from '@formily/react';
|
|||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useTableSelectorContext } from '../../../block-provider';
|
import { useFormBlockContext, useTableSelectorContext } from '../../../block-provider';
|
||||||
|
import { recursiveParent } from '../../../block-provider/TableSelectorProvider';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
|
import { useSortFields } from '../../../collection-manager/action-hooks';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||||
import { useSchemaTemplate } from '../../../schema-templates';
|
import { useSchemaTemplate } from '../../../schema-templates';
|
||||||
import { useDesignable } from '../../hooks';
|
import { useDesignable } from '../../hooks';
|
||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
import { FilterDynamicComponent } from './FilterDynamicComponent';
|
|
||||||
import { recursiveParent } from '../../../block-provider/TableSelectorProvider';
|
|
||||||
|
|
||||||
export const TableSelectorDesigner = () => {
|
export const TableSelectorDesigner = () => {
|
||||||
const { name, title } = useCollection();
|
const { name, title } = useCollection();
|
||||||
const { getCollectionJoinField } = useCollectionManager();
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const dataSource = useCollectionFilterOptions(name);
|
const { form } = useFormBlockContext();
|
||||||
const sortFields = useSortFields(name);
|
const sortFields = useSortFields(name);
|
||||||
const { service, extraFilter } = useTableSelectorContext();
|
const { service, extraFilter } = useTableSelectorContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
|
||||||
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
||||||
const collectionFieldSchema = recursiveParent(fieldSchema, 'CollectionField');
|
const collectionFieldSchema = recursiveParent(fieldSchema, 'CollectionField');
|
||||||
const collectionField = getCollectionJoinField(collectionFieldSchema?.['x-collection-field']);
|
const collectionField = getCollectionJoinField(collectionFieldSchema?.['x-collection-field']);
|
||||||
@ -43,25 +41,10 @@ export const TableSelectorDesigner = () => {
|
|||||||
const { dragSort } = field.decoratorProps;
|
const { dragSort } = field.decoratorProps;
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner template={template} title={title || name} disableInitializer>
|
<GeneralSchemaDesigner template={template} title={title || name} disableInitializer>
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={name}
|
||||||
schema={
|
defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: defaultFilter,
|
|
||||||
// title: '数据范围',
|
|
||||||
enum: dataSource,
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {
|
|
||||||
dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
filter = removeNullCondition(filter);
|
filter = removeNullCondition(filter);
|
||||||
const params = field.decoratorProps.params || {};
|
const params = field.decoratorProps.params || {};
|
||||||
|
@ -10,7 +10,6 @@ import { TableSelector } from './TableSelector';
|
|||||||
export * from './TableBlockDesigner';
|
export * from './TableBlockDesigner';
|
||||||
export * from './TableField';
|
export * from './TableField';
|
||||||
export * from './TableSelectorDesigner';
|
export * from './TableSelectorDesigner';
|
||||||
export * from './FilterDynamicComponent';
|
|
||||||
|
|
||||||
export const TableV2 = Table;
|
export const TableV2 = Table;
|
||||||
|
|
||||||
|
@ -17,17 +17,3 @@ export function extractIndex(str) {
|
|||||||
});
|
});
|
||||||
return numbers.join('.');
|
return numbers.join('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断一个 DOM 对象是否是由 createPortal 挂在到了 body 上
|
|
||||||
* @param domNode DOM 对象
|
|
||||||
*/
|
|
||||||
export const isPortalInBody = (dom: HTMLElement) => {
|
|
||||||
while (dom) {
|
|
||||||
if (dom.id === 'root') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
dom = dom.parentNode as HTMLElement;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
@ -15,23 +15,6 @@ import { XButton } from './XButton';
|
|||||||
import { useStyles } from './style';
|
import { useStyles } from './style';
|
||||||
|
|
||||||
const JT_VALUE_RE = /^\s*{{\s*([^{}]+)\s*}}\s*$/;
|
const JT_VALUE_RE = /^\s*{{\s*([^{}]+)\s*}}\s*$/;
|
||||||
const groupClass = css`
|
|
||||||
width: auto;
|
|
||||||
display: flex;
|
|
||||||
&.ant-input-group-compact {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.ant-input-disabled {
|
|
||||||
.ant-tag {
|
|
||||||
color: #bfbfbf;
|
|
||||||
border-color: #d9d9d9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ant-input.null-value {
|
|
||||||
width: 4em;
|
|
||||||
min-width: 4em;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function parseValue(value: any): string | string[] {
|
function parseValue(value: any): string | string[] {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@ -217,20 +200,20 @@ export function Input(props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSwitch = useCallback(
|
const onSwitch = useCallback(
|
||||||
(next) => {
|
(next, optionPath: any[]) => {
|
||||||
if (next[0] === '') {
|
if (next[0] === '') {
|
||||||
if (next[1]) {
|
if (next[1]) {
|
||||||
if (next[1] !== type) {
|
if (next[1] !== type) {
|
||||||
onChange(ConstantTypes[next[1]]?.default ?? null);
|
onChange(ConstantTypes[next[1]]?.default ?? null, optionPath);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (variable) {
|
if (variable) {
|
||||||
onChange(null);
|
onChange(null, optionPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onChange(`{{${next.join('.')}}}`);
|
onChange(`{{${next.join('.')}}}`, optionPath);
|
||||||
},
|
},
|
||||||
[type, variable, onChange],
|
[type, variable, onChange],
|
||||||
);
|
);
|
||||||
@ -254,6 +237,13 @@ export function Input(props) {
|
|||||||
}
|
}
|
||||||
prevOption = prevOption.children.find((item) => item[names.value] === key);
|
prevOption = prevOption.children.find((item) => item[names.value] === key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果为空则说明相关字段已被删除
|
||||||
|
// fix T-1565
|
||||||
|
if (!prevOption) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
labels.push(prevOption[names.label]);
|
labels.push(prevOption[names.label]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error(err);
|
error(err);
|
||||||
@ -270,7 +260,7 @@ export function Input(props) {
|
|||||||
const disabled = props.disabled || form.disabled;
|
const disabled = props.disabled || form.disabled;
|
||||||
|
|
||||||
return wrapSSR(
|
return wrapSSR(
|
||||||
<Space.Compact style={style} className={classNames(groupClass, componentCls, hashId, className)}>
|
<Space.Compact style={style} className={classNames(componentCls, hashId, className)}>
|
||||||
{variable ? (
|
{variable ? (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
@ -332,7 +322,7 @@ export function Input(props) {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
children ?? <ConstantComponent value={value} onChange={onChange} />
|
<div style={{ flex: 1 }}>{children ?? <ConstantComponent value={value} onChange={onChange} />}</div>
|
||||||
)}
|
)}
|
||||||
{options.length > 1 && !disabled ? (
|
{options.length > 1 && !disabled ? (
|
||||||
<Cascader
|
<Cascader
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { createContext, useContext } from 'react';
|
|
||||||
import { connect, mapReadPretty } from '@formily/react';
|
import { connect, mapReadPretty } from '@formily/react';
|
||||||
|
import React, { createContext, useContext } from 'react';
|
||||||
|
|
||||||
import { IField } from '../../../collection-manager';
|
|
||||||
import { Input } from './Input';
|
import { Input } from './Input';
|
||||||
import { JSONInput } from './JSONInput';
|
import { JSONInput } from './JSONInput';
|
||||||
import { RawTextArea } from './RawTextArea';
|
import { RawTextArea } from './RawTextArea';
|
||||||
@ -10,11 +9,7 @@ import { TextArea } from './TextArea';
|
|||||||
const VariableScopeContext = createContext([]);
|
const VariableScopeContext = createContext([]);
|
||||||
|
|
||||||
export function VariableScopeProvider({ scope = [], children }) {
|
export function VariableScopeProvider({ scope = [], children }) {
|
||||||
return (
|
return <VariableScopeContext.Provider value={scope}>{children}</VariableScopeContext.Provider>;
|
||||||
<VariableScopeContext.Provider value={scope}>
|
|
||||||
{children}
|
|
||||||
</VariableScopeContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useVariableScope() {
|
export function useVariableScope() {
|
||||||
@ -34,7 +29,3 @@ Variable.RawTextArea = connect(RawTextArea);
|
|||||||
Variable.JSON = connect(JSONInput);
|
Variable.JSON = connect(JSONInput);
|
||||||
|
|
||||||
export default Variable;
|
export default Variable;
|
||||||
|
|
||||||
export function isInvariable(value: IField) {
|
|
||||||
return !!value?.invariable;
|
|
||||||
}
|
|
||||||
|
@ -13,7 +13,7 @@ export function VariableSelect({ options, setOptions, onInsert, changeOnSelect =
|
|||||||
|
|
||||||
async function loadData(selectedOptions) {
|
async function loadData(selectedOptions) {
|
||||||
const option = selectedOptions[selectedOptions.length - 1];
|
const option = selectedOptions[selectedOptions.length - 1];
|
||||||
if (!option.children && !option.isLeaf && option.loadChildren) {
|
if (!option.children?.length && !option.isLeaf && option.loadChildren) {
|
||||||
await option.loadChildren(option);
|
await option.loadChildren(option);
|
||||||
setOptions((prev) => [...prev]);
|
setOptions((prev) => [...prev]);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,14 @@ export const useStyles = genStyleHook('nb-variable', (token) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
[componentCls]: {
|
[componentCls]: {
|
||||||
|
width: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
'&.ant-input-group-compact': { display: 'flex' },
|
||||||
|
'.ant-input-disabled': {
|
||||||
|
'.ant-tag': { color: '#bfbfbf', borderColor: '#d9d9d9' },
|
||||||
|
},
|
||||||
|
'.ant-input.null-value': { width: '4em', minWidth: '4em' },
|
||||||
|
|
||||||
'.ant-formily-item .ant-formily-item-control .ant-formily-item-control-content .ant-formily-item-control-content-component':
|
'.ant-formily-item .ant-formily-item-control .ant-formily-item-control-content .ant-formily-item-control-content-component':
|
||||||
{
|
{
|
||||||
lineHeight: 'normal',
|
lineHeight: 'normal',
|
||||||
@ -82,6 +90,10 @@ export const useStyles = genStyleHook('nb-variable', (token) => {
|
|||||||
'.ant-btn': {
|
'.ant-btn': {
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'.variable': {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
/* globals define,module */
|
/* globals define,module */
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Using a Universal Module Loader that should be browser, require, and AMD friendly
|
Using a Universal Module Loader that should be browser, require, and AMD friendly
|
||||||
http://ricostacruz.com/cheatsheets/umdjs.html
|
http://ricostacruz.com/cheatsheets/umdjs.html
|
||||||
@ -146,50 +149,58 @@ export function getJsonLogic() {
|
|||||||
if (!a || !b) {
|
if (!a || !b) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const milliseconds1 = new Date(a).getTime();
|
if (!Array.isArray(a)) {
|
||||||
const milliseconds2 = new Date(b).getTime();
|
a = [a, a];
|
||||||
return milliseconds1 === milliseconds2;
|
}
|
||||||
|
if (!Array.isArray(b)) {
|
||||||
|
b = [b, b];
|
||||||
|
}
|
||||||
|
a = a.map((date) => dayjs(date));
|
||||||
|
b = b.map((date) => dayjs(date));
|
||||||
|
|
||||||
|
return a[0].isBetween(b[0], b[1], null, '[]') && a[1].isBetween(b[0], b[1], null, '[]');
|
||||||
},
|
},
|
||||||
$dateBefore: function (a, b) {
|
$dateBefore: function (a, b) {
|
||||||
if (!a || !b) {
|
if (!a || !b) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!Array.isArray(a)) {
|
||||||
|
a = [a, a];
|
||||||
|
}
|
||||||
|
if (!Array.isArray(b)) {
|
||||||
|
b = [b, b];
|
||||||
|
}
|
||||||
|
a = a.map((date) => dayjs(date));
|
||||||
|
b = b.map((date) => dayjs(date));
|
||||||
|
|
||||||
return new Date(a) < new Date(b);
|
return a[0].isBefore(b[0]) && a[1].isBefore(b[0]);
|
||||||
},
|
},
|
||||||
$dateNotBefore: function (a, b) {
|
$dateNotBefore: function (a, b) {
|
||||||
if (!a || !b) {
|
return !operations.$dateBefore(a, b);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Date(a) <= new Date(b);
|
|
||||||
},
|
},
|
||||||
$dateAfter: function (a, b) {
|
$dateAfter: function (a, b) {
|
||||||
if (!a || !b) {
|
if (!a || !b) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!Array.isArray(a)) {
|
||||||
|
a = [a, a];
|
||||||
|
}
|
||||||
|
if (!Array.isArray(b)) {
|
||||||
|
b = [b, b];
|
||||||
|
}
|
||||||
|
a = a.map((date) => dayjs(date));
|
||||||
|
b = b.map((date) => dayjs(date));
|
||||||
|
|
||||||
return new Date(a) > new Date(b);
|
return a[0].isAfter(b[1]) && a[1].isAfter(b[1]);
|
||||||
},
|
},
|
||||||
$dateNotAfter: function (a, b) {
|
$dateNotAfter: function (a, b) {
|
||||||
if (!a || !b) {
|
return !operations.$dateAfter(a, b);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return new Date(a) >= new Date(b);
|
|
||||||
},
|
},
|
||||||
$dateBetween: function (a, b) {
|
$dateBetween: function (a, b) {
|
||||||
if (!a || !b) {
|
return operations.$dateOn(a, b);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const startTime = new Date(b?.[0]);
|
|
||||||
const endTime = new Date(b?.[1]);
|
|
||||||
return new Date(a) > startTime && new Date(a) < endTime;
|
|
||||||
},
|
},
|
||||||
$dateNotOn: function (a, b) {
|
$dateNotOn: function (a, b) {
|
||||||
if (!a || !b) {
|
return !operations.$dateOn(a, b);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return new Date(a) != new Date(b);
|
|
||||||
},
|
},
|
||||||
$isTruly: function (a) {
|
$isTruly: function (a) {
|
||||||
if (Array.isArray(a)) return a.some((k) => k === true || k === 1);
|
if (Array.isArray(a)) return a.some((k) => k === true || k === 1);
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import dayjs from 'dayjs';
|
import _, { every, findIndex, some } from 'lodash';
|
||||||
import flat from 'flat';
|
import { VariableOption, VariablesContextType } from '../../../variables/types';
|
||||||
import _, { every, findIndex, isArray, some } from 'lodash';
|
import { isVariable } from '../../../variables/utils/isVariable';
|
||||||
import { useMemo } from 'react';
|
import { transformVariableValue } from '../../../variables/utils/transformVariableValue';
|
||||||
import { useTableBlockContext } from '../../../block-provider';
|
|
||||||
import { useCurrentUserContext } from '../../../user';
|
|
||||||
import { getJsonLogic } from '../../common/utils/logic';
|
import { getJsonLogic } from '../../common/utils/logic';
|
||||||
|
|
||||||
type VariablesCtx = {
|
type VariablesCtx = {
|
||||||
@ -13,42 +11,6 @@ type VariablesCtx = {
|
|||||||
$form?: Record<string, any>;
|
$form?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function flattenDeep(data, result = []) {
|
|
||||||
for (let i = 0; i < data?.length; i++) {
|
|
||||||
const { children, ...rest } = data[i];
|
|
||||||
result.push(rest);
|
|
||||||
if (children) {
|
|
||||||
flattenDeep(children, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useVariablesCtx = (): VariablesCtx => {
|
|
||||||
const currentUser = useCurrentUserContext();
|
|
||||||
const { field, service, rowKey } = useTableBlockContext();
|
|
||||||
const tableData = flattenDeep(service?.data?.data);
|
|
||||||
const contextData = tableData?.filter((v) => (field?.data?.selectedRowKeys || [])?.includes(v[rowKey]));
|
|
||||||
return useMemo(() => {
|
|
||||||
return {
|
|
||||||
$user: currentUser?.data?.data || {},
|
|
||||||
$date: {
|
|
||||||
now: () => dayjs().toISOString(),
|
|
||||||
},
|
|
||||||
$context: contextData,
|
|
||||||
};
|
|
||||||
}, [contextData, currentUser?.data?.data]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isVariable = (str: unknown) => {
|
|
||||||
if (typeof str !== 'string') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const regex = /{{(.*?)}}/;
|
|
||||||
const matches = str?.match?.(regex);
|
|
||||||
return matches ? true : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const parseVariables = (str: string, ctx: VariablesCtx | any) => {
|
export const parseVariables = (str: string, ctx: VariablesCtx | any) => {
|
||||||
const regex = /{{(.*?)}}/;
|
const regex = /{{(.*?)}}/;
|
||||||
const matches = str?.match?.(regex);
|
const matches = str?.match?.(regex);
|
||||||
@ -76,41 +38,6 @@ export function getInnermostKeyAndValue(obj) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFieldValue = (fieldPath, values) => {
|
|
||||||
const v = fieldPath[0];
|
|
||||||
const h = fieldPath[1];
|
|
||||||
const regex = new RegExp('^' + v + '\\..+\\.' + h + '$'); // 构建匹配的正则表达式
|
|
||||||
const matchedValues = [];
|
|
||||||
const data = flat.flatten(values, { maxDepth: 3 });
|
|
||||||
for (const key in data) {
|
|
||||||
if (regex.test(key)) {
|
|
||||||
matchedValues.push(data[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matchedValues;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getValue = (str: string, values) => {
|
|
||||||
const regex = /{{(.*?)}}/;
|
|
||||||
const matches = str?.match?.(regex);
|
|
||||||
if (matches) {
|
|
||||||
return getVariableValue(str, values);
|
|
||||||
} else {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const getVariableValue = (str, values) => {
|
|
||||||
const regex = /{{[^.]+\.([^}]+)}}/;
|
|
||||||
const match = regex.exec(str);
|
|
||||||
const targetField = match?.[1]?.split('.') || [];
|
|
||||||
const isArrayField = isArray(values[targetField[0]]);
|
|
||||||
if (isArrayField && targetField.length > 1) {
|
|
||||||
//对多关系字段
|
|
||||||
return getFieldValue(targetField, values);
|
|
||||||
} else {
|
|
||||||
return flat(values)[match?.[1]];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const getTargetField = (obj) => {
|
const getTargetField = (obj) => {
|
||||||
const keys = getAllKeys(obj);
|
const keys = getAllKeys(obj);
|
||||||
const index = findIndex(keys, (key, index, keys) => {
|
const index = findIndex(keys, (key, index, keys) => {
|
||||||
@ -138,39 +65,71 @@ function getAllKeys(obj) {
|
|||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const conditionAnalyse = (rules, scope) => {
|
export const conditionAnalyses = async ({
|
||||||
const values = { ...scope, now: new Date() };
|
rules,
|
||||||
|
formValues,
|
||||||
|
variables,
|
||||||
|
localVariables,
|
||||||
|
}: {
|
||||||
|
rules;
|
||||||
|
formValues;
|
||||||
|
variables: VariablesContextType;
|
||||||
|
localVariables: VariableOption[];
|
||||||
|
}) => {
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
if (!variables) {
|
||||||
|
throw new Error(`conditionAnalyses: variables cannot be ${variables}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const type = Object.keys(rules)[0] || '$and';
|
const type = Object.keys(rules)[0] || '$and';
|
||||||
const conditions = rules[type];
|
const conditions = rules[type];
|
||||||
const results = conditions.map((c) => {
|
|
||||||
|
let results = conditions.map(async (c) => {
|
||||||
const jsonlogic = getInnermostKeyAndValue(c);
|
const jsonlogic = getInnermostKeyAndValue(c);
|
||||||
const operator = jsonlogic?.key;
|
const operator = jsonlogic?.key;
|
||||||
const value = getValue(jsonlogic?.value, values);
|
|
||||||
const targetField = getTargetField(c);
|
|
||||||
if (!operator) {
|
if (!operator) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
const isArrayField = isArray(values[targetField[0]]);
|
|
||||||
const jsonLogic = getJsonLogic();
|
|
||||||
|
|
||||||
if (isArrayField && targetField.length > 1) {
|
const targetVariableName = targetFieldToVariableString(getTargetField(c));
|
||||||
//对多关系字段比较
|
const parsingResult = isVariable(jsonlogic?.value)
|
||||||
const currentValue = getFieldValue(targetField, values);
|
? [
|
||||||
const result = jsonLogic.apply({ [operator]: [currentValue, value] });
|
variables.parseVariable(jsonlogic?.value, localVariables),
|
||||||
return result;
|
variables.parseVariable(targetVariableName, localVariables),
|
||||||
} else {
|
]
|
||||||
const currentValue = targetField.length > 1 ? flat(values)?.[targetField.join('.')] : values?.[targetField[0]];
|
: [jsonlogic?.value, variables.parseVariable(targetVariableName, localVariables)];
|
||||||
const result = jsonLogic.apply({ [operator]: [currentValue, value] });
|
|
||||||
return result;
|
try {
|
||||||
}
|
const jsonLogic = getJsonLogic();
|
||||||
|
const [value, targetValue] = await Promise.all(parsingResult);
|
||||||
|
const targetCollectionField = await variables.getCollectionField(targetVariableName, localVariables);
|
||||||
|
return jsonLogic.apply({
|
||||||
|
[operator]: [
|
||||||
|
transformVariableValue(targetValue, { targetCollectionField }),
|
||||||
|
transformVariableValue(value, { targetCollectionField }),
|
||||||
|
],
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
results = await Promise.all(results);
|
||||||
|
|
||||||
if (type === '$and') {
|
if (type === '$and') {
|
||||||
return every(results, (v) => v);
|
return every(results, (v) => v);
|
||||||
} else {
|
} else {
|
||||||
return some(results, (v) => v);
|
return some(results, (v) => v);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转化成变量字符串,方便解析出值
|
||||||
|
* @param targetField
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function targetFieldToVariableString(targetField: string[]) {
|
||||||
|
// Action 中的联动规则虽然没有 form 上下文但是在这里也使用的是 `$nForm` 变量,这样实现更简单
|
||||||
|
return `{{ $nForm.${targetField.join('.')} }}`;
|
||||||
|
}
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import { Schema, SchemaExpressionScopeContext, SchemaOptionsContext } from '@formily/react';
|
import { Schema, SchemaExpressionScopeContext, SchemaOptionsContext } from '@formily/react';
|
||||||
import { useContext, isValidElement } from 'react';
|
import { isValidElement, useContext } from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/**
|
||||||
|
* 不使用缓存
|
||||||
|
*/
|
||||||
|
noCache?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const compileCache = {};
|
const compileCache = {};
|
||||||
|
|
||||||
export const useCompile = () => {
|
export const useCompile = ({ noCache }: Props = { noCache: false }) => {
|
||||||
const options = useContext(SchemaOptionsContext);
|
const options = useContext(SchemaOptionsContext);
|
||||||
const scope = useContext(SchemaExpressionScopeContext);
|
const scope = useContext(SchemaExpressionScopeContext);
|
||||||
return (source: any, ext?: any) => {
|
return (source: any, ext?: any) => {
|
||||||
@ -33,6 +40,9 @@ export const useCompile = () => {
|
|||||||
return Schema.compile(source, mergedScope);
|
return Schema.compile(source, mergedScope);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
if (noCache) {
|
||||||
|
return Schema.compile(source, mergedScope);
|
||||||
|
}
|
||||||
compileCache[cacheKey] = compileCache[cacheKey] || Schema.compile(source, mergedScope);
|
compileCache[cacheKey] = compileCache[cacheKey] || Schema.compile(source, mergedScope);
|
||||||
return compileCache[cacheKey];
|
return compileCache[cacheKey];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -39,7 +39,7 @@ export const TableColumnInitializers = (props: any) => {
|
|||||||
{
|
{
|
||||||
type: 'itemGroup',
|
type: 'itemGroup',
|
||||||
title: t(`Parent collection fields`) + '(' + compile(`${Object.keys(inherit)[0]}`) + ')',
|
title: t(`Parent collection fields`) + '(' + compile(`${Object.keys(inherit)[0]}`) + ')',
|
||||||
children: Object.values(inherit)[0].filter((v) => !v?.field?.isForeignKey),
|
children: Object.values(inherit)[0].filter((v: any) => !v?.field?.isForeignKey),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -10,6 +10,7 @@ import { useRecord } from '../../record-provider';
|
|||||||
import { ActionContextProvider, useActionContext, useCompile } from '../../schema-component';
|
import { ActionContextProvider, useActionContext, useCompile } from '../../schema-component';
|
||||||
import { linkageAction } from '../../schema-component/antd/action/utils';
|
import { linkageAction } from '../../schema-component/antd/action/utils';
|
||||||
import { parseVariables } from '../../schema-component/common/utils/uitls';
|
import { parseVariables } from '../../schema-component/common/utils/uitls';
|
||||||
|
import { useLocalVariables, useVariables } from '../../variables';
|
||||||
|
|
||||||
export const actionDesignerCss = css`
|
export const actionDesignerCss = css`
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -89,19 +90,28 @@ export const CreateRecordAction = observer(
|
|||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const field: any = useField();
|
const field: any = useField();
|
||||||
const [currentCollection, setCurrentCollection] = useState(collection.name);
|
const [currentCollection, setCurrentCollection] = useState(collection.name);
|
||||||
const linkageRules = fieldSchema?.['x-linkage-rules'] || [];
|
const linkageRules: any[] = fieldSchema?.['x-linkage-rules'] || [];
|
||||||
const values = useRecord();
|
const values = useRecord();
|
||||||
const ctx = useActionContext();
|
const ctx = useActionContext();
|
||||||
|
const variables = useVariables();
|
||||||
|
const localVariables = useLocalVariables({ currentForm: { values } as any });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
field.linkageProperty = {};
|
|
||||||
linkageRules
|
linkageRules
|
||||||
.filter((k) => !k.disabled)
|
.filter((k) => !k.disabled)
|
||||||
.map((v) => {
|
.forEach((v) => {
|
||||||
return v.actions?.map((h) => {
|
v.actions?.forEach((h) => {
|
||||||
linkageAction(h.operator, field, v.condition, values);
|
linkageAction({
|
||||||
|
operator: h.operator,
|
||||||
|
field,
|
||||||
|
condition: v.condition,
|
||||||
|
values,
|
||||||
|
variables,
|
||||||
|
localVariables,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [linkageRules, values]);
|
});
|
||||||
|
}, [JSON.stringify(linkageRules), values]);
|
||||||
return (
|
return (
|
||||||
<div className={actionDesignerCss}>
|
<div className={actionDesignerCss}>
|
||||||
<ActionContextProvider value={{ ...ctx, visible, setVisible }}>
|
<ActionContextProvider value={{ ...ctx, visible, setVisible }}>
|
||||||
@ -142,6 +152,8 @@ export const CreateAction = observer(
|
|||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const field: any = useField();
|
const field: any = useField();
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
|
const variables = useVariables();
|
||||||
|
|
||||||
const enableChildren = fieldSchema['x-enable-children'] || [];
|
const enableChildren = fieldSchema['x-enable-children'] || [];
|
||||||
const allowAddToCurrent = fieldSchema?.['x-allow-add-to-current'];
|
const allowAddToCurrent = fieldSchema?.['x-allow-add-to-current'];
|
||||||
const linkageFromForm = fieldSchema?.['x-component-props']?.['linkageFromForm'];
|
const linkageFromForm = fieldSchema?.['x-component-props']?.['linkageFromForm'];
|
||||||
@ -168,8 +180,9 @@ export const CreateAction = observer(
|
|||||||
return v && actionAclCheck(`${v.name}:create`);
|
return v && actionAclCheck(`${v.name}:create`);
|
||||||
});
|
});
|
||||||
}, [enableChildren, totalChildCollections]);
|
}, [enableChildren, totalChildCollections]);
|
||||||
const linkageRules = fieldSchema?.['x-linkage-rules'] || [];
|
const linkageRules: any[] = fieldSchema?.['x-linkage-rules'] || [];
|
||||||
const values = useRecord();
|
const values = useRecord();
|
||||||
|
const localVariables = useLocalVariables({ currentForm: { values } as any });
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const { designable } = useDesignable();
|
const { designable } = useDesignable();
|
||||||
const icon = props.icon || null;
|
const icon = props.icon || null;
|
||||||
@ -188,15 +201,21 @@ export const CreateAction = observer(
|
|||||||
}, [menuItems]);
|
}, [menuItems]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
field.linkageProperty = {};
|
|
||||||
linkageRules
|
linkageRules
|
||||||
.filter((k) => !k.disabled)
|
.filter((k) => !k.disabled)
|
||||||
.map((v) => {
|
.forEach((v) => {
|
||||||
return v.actions?.map((h) => {
|
v.actions?.forEach((h) => {
|
||||||
linkageAction(h.operator, field, v.condition, values);
|
linkageAction({
|
||||||
|
operator: h.operator,
|
||||||
|
field,
|
||||||
|
condition: v.condition,
|
||||||
|
values,
|
||||||
|
variables,
|
||||||
|
localVariables,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [linkageRules, values]);
|
});
|
||||||
|
}, [JSON.stringify(linkageRules), values]);
|
||||||
return (
|
return (
|
||||||
<div className={actionDesignerCss}>
|
<div className={actionDesignerCss}>
|
||||||
{FinallyButton({
|
{FinallyButton({
|
||||||
|
@ -1,19 +1,30 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { connect, useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import { merge } from '@formily/shared';
|
import { merge } from '@formily/shared';
|
||||||
import React, { useEffect, useState } from 'react';
|
import _ from 'lodash';
|
||||||
import { useTranslation } from 'react-i18next';
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useFormBlockContext } from '../../../block-provider';
|
import { useFormBlockContext } from '../../../block-provider';
|
||||||
import {
|
import {
|
||||||
CollectionFieldProvider,
|
CollectionFieldProvider,
|
||||||
useCollection,
|
useCollection,
|
||||||
useCollectionField,
|
useCollectionField,
|
||||||
useCollectionFilterOptions,
|
useCollectionFilterOptions,
|
||||||
|
useCollectionManager,
|
||||||
} from '../../../collection-manager';
|
} from '../../../collection-manager';
|
||||||
import { Variable, useCompile, useComponent, useVariableScope } from '../../../schema-component';
|
import { useRecord } from '../../../record-provider';
|
||||||
|
import { useCompile, useComponent } from '../../../schema-component';
|
||||||
|
import { VariableInput, getShouldChange } from '../../../schema-settings/VariableInput/VariableInput';
|
||||||
|
import { Option } from '../../../schema-settings/VariableInput/type';
|
||||||
|
import { formatVariableScop } from '../../../schema-settings/VariableInput/utils/formatVariableScop';
|
||||||
|
import { useLocalVariables, useVariables } from '../../../variables';
|
||||||
import { DeletedField } from '../DeletedField';
|
import { DeletedField } from '../DeletedField';
|
||||||
|
|
||||||
|
interface AssignedFieldProps {
|
||||||
|
value: any;
|
||||||
|
onChange: (value: any) => void;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
const InternalField: React.FC = (props) => {
|
const InternalField: React.FC = (props) => {
|
||||||
const field = useField<Field>();
|
const field = useField<Field>();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
@ -68,72 +79,73 @@ const InternalField: React.FC = (props) => {
|
|||||||
return React.createElement(component, props, props.children);
|
return React.createElement(component, props, props.children);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CollectionField = connect((props) => {
|
const CollectionField = (props) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
return (
|
return (
|
||||||
<CollectionFieldProvider name={fieldSchema.name} fallback={<DeletedField />}>
|
<CollectionFieldProvider name={fieldSchema.name} fallback={<DeletedField />}>
|
||||||
<InternalField {...props} />
|
<InternalField {...props} />
|
||||||
</CollectionFieldProvider>
|
</CollectionFieldProvider>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
export enum AssignedFieldValueType {
|
export enum AssignedFieldValueType {
|
||||||
ConstantValue = 'constantValue',
|
ConstantValue = 'constantValue',
|
||||||
DynamicValue = 'dynamicValue',
|
DynamicValue = 'dynamicValue',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AssignedField = (props: any) => {
|
export const AssignedField = (props: AssignedFieldProps) => {
|
||||||
const { value, onChange } = props;
|
const { value, onChange } = props;
|
||||||
const { t } = useTranslation();
|
const { getCollectionFields, getAllCollectionsInheritChain } = useCollectionManager();
|
||||||
const compile = useCompile();
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
const { getField } = useCollection();
|
|
||||||
const collectionField = getField(fieldSchema.name);
|
|
||||||
const [options, setOptions] = useState<any[]>([]);
|
|
||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
const fields = compile(useCollectionFilterOptions(collection));
|
const { form } = useFormBlockContext();
|
||||||
const userFields = compile(useCollectionFilterOptions('users'));
|
const fieldSchema = useFieldSchema();
|
||||||
const scope = useVariableScope();
|
const record = useRecord();
|
||||||
useEffect(() => {
|
const variables = useVariables();
|
||||||
const opt = [
|
const localVariables = useLocalVariables();
|
||||||
{
|
const currentFormFields = useCollectionFilterOptions(collection);
|
||||||
name: 'currentRecord',
|
|
||||||
title: t('Current record'),
|
const { name, getField } = collection;
|
||||||
children: fields,
|
const collectionField = getField(fieldSchema.name);
|
||||||
},
|
|
||||||
{
|
const shouldChange = useMemo(
|
||||||
name: 'currentUser',
|
() => getShouldChange({ collectionField, variables, localVariables, getAllCollectionsInheritChain }),
|
||||||
title: t('Current user'),
|
[collectionField, getAllCollectionsInheritChain, localVariables, variables],
|
||||||
children: userFields,
|
);
|
||||||
},
|
|
||||||
];
|
const returnScope = useCallback(
|
||||||
if (['createdAt', 'datetime', 'time', 'updatedAt'].includes(collectionField?.interface)) {
|
(scope: Option[]) => {
|
||||||
opt.unshift({
|
const currentForm = scope.find((item) => item.value === '$nForm');
|
||||||
name: 'currentTime',
|
const fields = getCollectionFields(name);
|
||||||
title: t('Current time'),
|
|
||||||
children: null,
|
// fix https://nocobase.height.app/T-1355
|
||||||
});
|
// 工作流人工节点的 `自定义表单` 区块,与其它表单区块不同,根据它的数据表名称,获取到的字段列表为空,所以需要在这里特殊处理一下
|
||||||
|
if (!fields?.length && currentForm) {
|
||||||
|
currentForm.children = formatVariableScop(currentFormFields);
|
||||||
}
|
}
|
||||||
const next = opt.concat(scope);
|
|
||||||
setOptions(next);
|
return scope;
|
||||||
}, [fields, userFields, scope]);
|
},
|
||||||
|
[currentFormFields, name],
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderSchemaComponent = useCallback(
|
||||||
|
({ value, onChange }): React.JSX.Element => {
|
||||||
|
return <CollectionField {...props} value={value} onChange={onChange} />;
|
||||||
|
},
|
||||||
|
[JSON.stringify(_.omit(props, 'value'))],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Variable.Input
|
<VariableInput
|
||||||
|
form={form}
|
||||||
|
record={record}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
scope={options}
|
renderSchemaComponent={renderSchemaComponent}
|
||||||
className={css`
|
collectionField={collectionField}
|
||||||
.variable {
|
shouldChange={shouldChange}
|
||||||
width: 100%;
|
returnScope={returnScope}
|
||||||
}
|
targetFieldSchema={fieldSchema}
|
||||||
`}
|
/>
|
||||||
fieldNames={{
|
|
||||||
label: 'title',
|
|
||||||
value: 'name',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CollectionField value={value} onChange={onChange} />
|
|
||||||
</Variable.Input>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import { Field, Form } from '@formily/core';
|
||||||
import { ISchema, Schema, useFieldSchema, useForm } from '@formily/react';
|
import { ISchema, Schema, useFieldSchema, useForm } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { useContext, useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { BlockRequestContext, SchemaInitializerItemOptions } from '..';
|
import { SchemaInitializerItemOptions, useFormActiveFields, useFormBlockContext } from '../';
|
||||||
import { CollectionFieldOptions, FieldOptions, useCollection, useCollectionManager } from '../collection-manager';
|
import { CollectionFieldOptions, FieldOptions, useCollection, useCollectionManager } from '../collection-manager';
|
||||||
import { isAssocField } from '../filter-provider/utils';
|
import { isAssocField } from '../filter-provider/utils';
|
||||||
import { useActionContext, useDesignable } from '../schema-component';
|
import { useActionContext, useDesignable } from '../schema-component';
|
||||||
@ -744,7 +745,10 @@ const recursiveParent = (schema: Schema) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useCurrentSchema = (action: string, key: string, find = findSchema, rm = removeSchema) => {
|
export const useCurrentSchema = (action: string, key: string, find = findSchema, rm = removeSchema) => {
|
||||||
|
const { removeActiveFieldName } = useFormActiveFields() || {};
|
||||||
|
const { form }: { form: Form } = useFormBlockContext();
|
||||||
let fieldSchema = useFieldSchema();
|
let fieldSchema = useFieldSchema();
|
||||||
|
|
||||||
if (!fieldSchema?.['x-initializer']) {
|
if (!fieldSchema?.['x-initializer']) {
|
||||||
const recursiveInitializerSchema = recursiveParent(fieldSchema);
|
const recursiveInitializerSchema = recursiveParent(fieldSchema);
|
||||||
if (recursiveInitializerSchema) {
|
if (recursiveInitializerSchema) {
|
||||||
@ -753,16 +757,15 @@ export const useCurrentSchema = (action: string, key: string, find = findSchema,
|
|||||||
}
|
}
|
||||||
const { remove } = useDesignable();
|
const { remove } = useDesignable();
|
||||||
const schema = find(fieldSchema, key, action);
|
const schema = find(fieldSchema, key, action);
|
||||||
const ctx = useContext(BlockRequestContext);
|
|
||||||
return {
|
return {
|
||||||
schema,
|
schema,
|
||||||
exists: !!schema,
|
exists: !!schema,
|
||||||
remove() {
|
remove() {
|
||||||
if (ctx.field) {
|
removeActiveFieldName?.(schema.name);
|
||||||
ctx.field.data = ctx.field.data || {};
|
form?.query(schema.name).forEach((field: Field) => {
|
||||||
ctx.field.data.activeFields = ctx.field.data.activeFields || new Set();
|
field.setInitialValue(null);
|
||||||
ctx.field.data.activeFields.delete(schema.name);
|
field.reset();
|
||||||
}
|
});
|
||||||
schema && rm(schema, remove);
|
schema && rm(schema, remove);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -6,11 +6,10 @@ import _ from 'lodash';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings } from '../..';
|
import { GeneralSchemaDesigner, SchemaSettings } from '../..';
|
||||||
import { mergeFilter } from '../../../block-provider';
|
import { mergeFilter, useFormBlockContext } from '../../../block-provider';
|
||||||
import { useCollectionFilterOptions, useCollectionManager } from '../../../collection-manager';
|
import { useCollectionManager } from '../../../collection-manager';
|
||||||
import { removeNullCondition, useCompile, useDesignable } from '../../../schema-component';
|
import { removeNullCondition, useCompile, useDesignable } from '../../../schema-component';
|
||||||
import { ITemplate } from '../../../schema-component/antd/form-v2/Templates';
|
import { ITemplate } from '../../../schema-component/antd/form-v2/Templates';
|
||||||
import { FilterDynamicComponent } from '../../../schema-component/antd/table-v2/FilterDynamicComponent';
|
|
||||||
|
|
||||||
export const Designer = observer(
|
export const Designer = observer(
|
||||||
() => {
|
() => {
|
||||||
@ -24,13 +23,13 @@ export const Designer = observer(
|
|||||||
formSchema: ISchema;
|
formSchema: ISchema;
|
||||||
data?: ITemplate;
|
data?: ITemplate;
|
||||||
};
|
};
|
||||||
|
const { form } = useFormBlockContext();
|
||||||
|
|
||||||
// 在这里读取 resource 的值,当 resource 变化时,会触发该组件的更新
|
// 在这里读取 resource 的值,当 resource 变化时,会触发该组件的更新
|
||||||
const collectionName = field.componentProps.service.resource;
|
const collectionName = field.componentProps.service.resource;
|
||||||
|
|
||||||
const collection = getCollection(collectionName);
|
const collection = getCollection(collectionName);
|
||||||
const collectionFields = getCollectionFields(collectionName);
|
const collectionFields = getCollectionFields(collectionName);
|
||||||
const dataSource = useCollectionFilterOptions(collectionName);
|
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
error('data is required');
|
error('data is required');
|
||||||
@ -63,25 +62,10 @@ export const Designer = observer(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner draggable={false}>
|
<GeneralSchemaDesigner draggable={false}>
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.DataScope
|
||||||
title={t('Set the data scope')}
|
collectionName={collectionName}
|
||||||
schema={
|
defaultFilter={getFilter()}
|
||||||
{
|
form={form}
|
||||||
type: 'object',
|
|
||||||
title: t('Set the data scope'),
|
|
||||||
properties: {
|
|
||||||
filter: {
|
|
||||||
default: getFilter(),
|
|
||||||
// title: '数据范围',
|
|
||||||
enum: dataSource,
|
|
||||||
'x-component': 'Filter',
|
|
||||||
'x-component-props': {
|
|
||||||
dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ISchema
|
|
||||||
}
|
|
||||||
onSubmit={({ filter }) => {
|
onSubmit={({ filter }) => {
|
||||||
setFilter(filter);
|
setFilter(filter);
|
||||||
|
|
||||||
|
@ -7,21 +7,18 @@ import { useFormVariable } from '../VariableInput/hooks/useFormVariable';
|
|||||||
import { useIterationVariable } from '../VariableInput/hooks/useIterationVariable';
|
import { useIterationVariable } from '../VariableInput/hooks/useIterationVariable';
|
||||||
|
|
||||||
export const ChildDynamicComponent = observer(
|
export const ChildDynamicComponent = observer(
|
||||||
(props: { rootCollection: string; form: any; onChange; value; default; collectionField }) => {
|
(props: { rootCollection: string; onChange; value; default; collectionField }) => {
|
||||||
const { form, rootCollection, onChange, value, collectionField } = props;
|
const { rootCollection, onChange, value, collectionField } = props;
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const formVariabele = useFormVariable({ blockForm: form, rootCollection });
|
const formVariable = useFormVariable({ collectionName: rootCollection, collectionField });
|
||||||
const iterationVariabele = useIterationVariable({
|
const iterationVariable = useIterationVariable({
|
||||||
blockForm: form,
|
currentCollection: collectionField?.collectionName,
|
||||||
currentCollection: collectionField.collectionName,
|
schema: collectionField?.uiSchema,
|
||||||
rootCollection,
|
collectionField,
|
||||||
});
|
});
|
||||||
|
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const result = useMemo(
|
const result = useMemo(() => [formVariable, iterationVariable].filter(Boolean), [formVariable, iterationVariable]);
|
||||||
() => [formVariabele, iterationVariabele].filter(Boolean),
|
|
||||||
[formVariabele, iterationVariabele],
|
|
||||||
);
|
|
||||||
const scope = compile(result);
|
const scope = compile(result);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChange(fieldSchema.default);
|
onChange(fieldSchema.default);
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Variable } from '../../schema-component';
|
|
||||||
import { useVariableOptions } from './Variables';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
value: any;
|
|
||||||
onChange: (value: any) => void;
|
|
||||||
renderSchemaComponent: () => React.ReactNode;
|
|
||||||
collectionName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function FilterDynamicComponent(props: Props) {
|
|
||||||
const { value, onChange, renderSchemaComponent, collectionName } = props;
|
|
||||||
const scope = useVariableOptions(collectionName, ['o2m', 'm2m']);
|
|
||||||
return (
|
|
||||||
<Variable.Input value={value} onChange={onChange} scope={scope}>
|
|
||||||
{renderSchemaComponent()}
|
|
||||||
</Variable.Input>
|
|
||||||
);
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ import { TreeSelect } from '@formily/antd-v5';
|
|||||||
import { observer } from '@formily/react';
|
import { observer } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { Select, Space } from 'antd';
|
import { Select, Space } from 'antd';
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useCallback, useContext, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCompile } from '../..';
|
import { useCompile } from '../..';
|
||||||
import { DynamicComponent } from './DynamicComponent';
|
import { DynamicComponent } from './DynamicComponent';
|
||||||
@ -110,40 +110,57 @@ export const FormButtonLinkageRuleAction = observer(
|
|||||||
const { value, options } = props;
|
const { value, options } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const [editFalg, setEditFlag] = useState(false);
|
const [editFlag, setEditFlag] = useState(false);
|
||||||
const remove = useContext(RemoveActionContext);
|
const remove = useContext(RemoveActionContext);
|
||||||
const { schema, operator, setOperator, setValue } = useValues(options);
|
const { schema, operator, setOperator, setValue } = useValues(options);
|
||||||
const operators = [
|
const operators = useMemo(
|
||||||
|
() =>
|
||||||
|
compile([
|
||||||
{ label: t('Visible'), value: ActionType.Visible, schema: {} },
|
{ label: t('Visible'), value: ActionType.Visible, schema: {} },
|
||||||
{ label: t('Hidden'), value: ActionType.Hidden, schema: {} },
|
{ label: t('Hidden'), value: ActionType.Hidden, schema: {} },
|
||||||
{ label: t('Disabled'), value: ActionType.Disabled, schema: {} },
|
{ label: t('Disabled'), value: ActionType.Disabled, schema: {} },
|
||||||
{ label: t('Enabled'), value: ActionType.Active, schema: {} },
|
{ label: t('Enabled'), value: ActionType.Active, schema: {} },
|
||||||
];
|
]),
|
||||||
|
[compile, t],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChange = useCallback(
|
||||||
|
(value) => {
|
||||||
|
const flag = [ActionType.Value].includes(value);
|
||||||
|
setEditFlag(flag);
|
||||||
|
setOperator(value);
|
||||||
|
},
|
||||||
|
[setOperator],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangeValue = useCallback(
|
||||||
|
(value) => {
|
||||||
|
setValue(value);
|
||||||
|
},
|
||||||
|
[setValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
const closeStyle = useMemo(() => ({ color: '#bfbfbf' }), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBottom: 8 }}>
|
<div style={{ marginBottom: 8 }}>
|
||||||
<Space>
|
<Space>
|
||||||
<Select
|
<Select
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
value={operator}
|
value={operator}
|
||||||
options={compile(operators)}
|
options={operators}
|
||||||
onChange={(value) => {
|
onChange={onChange}
|
||||||
const flag = [ActionType.Value].includes(value);
|
|
||||||
setEditFlag(flag);
|
|
||||||
setOperator(value);
|
|
||||||
}}
|
|
||||||
placeholder={t('action')}
|
placeholder={t('action')}
|
||||||
/>
|
/>
|
||||||
{editFalg &&
|
{editFlag &&
|
||||||
React.createElement(DynamicComponent, {
|
React.createElement(DynamicComponent, {
|
||||||
value,
|
value,
|
||||||
schema,
|
schema,
|
||||||
onChange(value) {
|
onChange: onChangeValue,
|
||||||
setValue(value);
|
|
||||||
},
|
|
||||||
})}
|
})}
|
||||||
{!props.disabled && (
|
{!props.disabled && (
|
||||||
<a data-testid="close-icon-button">
|
<a data-testid="close-icon-button">
|
||||||
<CloseCircleOutlined onClick={() => remove()} style={{ color: '#bfbfbf' }} />
|
<CloseCircleOutlined onClick={remove} style={closeStyle} />
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { ArrayField as ArrayFieldModel, VoidField } from '@formily/core';
|
import { ArrayField as ArrayFieldModel, VoidField } from '@formily/core';
|
||||||
import { ArrayField, ObjectField, observer, useField } from '@formily/react';
|
import { ArrayField, ObjectField, observer, useField } from '@formily/react';
|
||||||
import { Space } from 'antd';
|
import { Space } from 'antd';
|
||||||
import React from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RemoveActionContext } from './context';
|
|
||||||
import { FormButtonLinkageRuleAction, FormFieldLinkageRuleAction } from './LinkageRuleAction';
|
import { FormButtonLinkageRuleAction, FormFieldLinkageRuleAction } from './LinkageRuleAction';
|
||||||
|
import { RemoveActionContext } from './context';
|
||||||
export const LinkageRuleActions = observer(
|
export const LinkageRuleActions = observer(
|
||||||
(props: any): any => {
|
(props: any): any => {
|
||||||
const { type, linkageOptions } = props;
|
const { type, linkageOptions } = props;
|
||||||
@ -26,29 +26,38 @@ export const LinkageRuleActions = observer(
|
|||||||
{ displayName: 'LinkageRuleActions' },
|
{ displayName: 'LinkageRuleActions' },
|
||||||
);
|
);
|
||||||
|
|
||||||
export const LinkageRuleActionGroup = (props) => {
|
interface LinkageRuleActionGroupProps {
|
||||||
|
useProps: () => {
|
||||||
|
type: 'button' | 'field';
|
||||||
|
linkageOptions: any;
|
||||||
|
collectionName: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LinkageRuleActionGroup = (props: LinkageRuleActionGroupProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const field = useField<VoidField>();
|
const field = useField<VoidField>();
|
||||||
const logic = 'actions';
|
const logic = 'actions';
|
||||||
const { type, linkageOptions, collectionName } = props?.useProps();
|
const { type, linkageOptions, collectionName } = props.useProps();
|
||||||
return (
|
|
||||||
<div style={{ marginLeft: 10 }}>
|
const style = useMemo(() => ({ marginLeft: 10 }), []);
|
||||||
<ArrayField
|
const components = useMemo(
|
||||||
name={logic}
|
() => [LinkageRuleActions, { type, linkageOptions, collectionName }],
|
||||||
component={[LinkageRuleActions, { type, linkageOptions, collectionName }]}
|
[collectionName, linkageOptions, type],
|
||||||
disabled={false}
|
);
|
||||||
/>
|
const spaceStyle = useMemo(() => ({ marginTop: 8, marginBottom: 8 }), []);
|
||||||
<Space size={16} style={{ marginTop: 8, marginBottom: 8 }}>
|
const onClick = useCallback(() => {
|
||||||
<a
|
|
||||||
onClick={() => {
|
|
||||||
const f = field.query('.actions').take() as ArrayFieldModel;
|
const f = field.query('.actions').take() as ArrayFieldModel;
|
||||||
const items = f.value || [];
|
const items = f.value || [];
|
||||||
items.push({});
|
items.push({});
|
||||||
f.value = items;
|
f.value = items;
|
||||||
}}
|
}, [field]);
|
||||||
>
|
|
||||||
{t('Add property')}
|
return (
|
||||||
</a>
|
<div style={style}>
|
||||||
|
<ArrayField name={logic} component={components} disabled={false} />
|
||||||
|
<Space size={16} style={spaceStyle}>
|
||||||
|
<a onClick={onClick}>{t('Add property')}</a>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,17 +1,107 @@
|
|||||||
import { Input, Select } from 'antd';
|
import { Input, Select } from 'antd';
|
||||||
import React, { useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useFormBlockContext } from '../../block-provider';
|
||||||
|
import { useRecord } from '../../record-provider';
|
||||||
import { Variable } from '.././../schema-component';
|
import { Variable } from '.././../schema-component';
|
||||||
|
import { useCompatOldVariables } from '../VariableInput/VariableInput';
|
||||||
|
import { useVariableOptions } from '../VariableInput/hooks/useVariableOptions';
|
||||||
import { DynamicComponent } from './DynamicComponent';
|
import { DynamicComponent } from './DynamicComponent';
|
||||||
import { useVariableOptions } from './Variables';
|
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
export const ValueDynamicComponent = (props) => {
|
interface ValueDynamicComponentProps {
|
||||||
|
fieldValue: any;
|
||||||
|
schema: any;
|
||||||
|
setValue: (value: any) => void;
|
||||||
|
collectionName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ValueDynamicComponent = (props: ValueDynamicComponentProps) => {
|
||||||
const { fieldValue, schema, setValue, collectionName } = props;
|
const { fieldValue, schema, setValue, collectionName } = props;
|
||||||
const [mode, setMode] = useState(fieldValue?.mode || 'constant');
|
const [mode, setMode] = useState(fieldValue?.mode || 'constant');
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const scope = useVariableOptions(collectionName);
|
const { form } = useFormBlockContext();
|
||||||
|
const record = useRecord();
|
||||||
|
const scope = useVariableOptions({
|
||||||
|
collectionField: { uiSchema: schema },
|
||||||
|
form,
|
||||||
|
record,
|
||||||
|
uiSchema: schema,
|
||||||
|
});
|
||||||
|
const { compatOldVariables } = useCompatOldVariables({
|
||||||
|
collectionField: { uiSchema: schema, collectionName },
|
||||||
|
uiSchema: schema,
|
||||||
|
blockCollectionName: collectionName,
|
||||||
|
});
|
||||||
|
const constantStyle = useMemo(() => {
|
||||||
|
return { minWidth: 150, maxWidth: 430, marginLeft: 5 };
|
||||||
|
}, []);
|
||||||
|
const handleChangeOfConstant = useCallback(
|
||||||
|
(value) => {
|
||||||
|
setValue({
|
||||||
|
mode,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[mode, setValue],
|
||||||
|
);
|
||||||
|
const expressStyle = useMemo(() => {
|
||||||
|
return { minWidth: 150, maxWidth: 430, fontSize: 13 };
|
||||||
|
}, []);
|
||||||
|
const handleChangeOfExpress = useCallback(
|
||||||
|
(value) => {
|
||||||
|
const result = value.replaceAll(`${collectionName}.`, '').replaceAll('$system.', '').trim();
|
||||||
|
setValue({
|
||||||
|
mode,
|
||||||
|
value,
|
||||||
|
result,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[collectionName, mode, setValue],
|
||||||
|
);
|
||||||
|
const textAreaStyle = useMemo(() => {
|
||||||
|
return { minWidth: 460, marginRight: 15 };
|
||||||
|
}, []);
|
||||||
|
const compatScope = useMemo(() => {
|
||||||
|
return compatOldVariables(scope, {
|
||||||
|
value: fieldValue?.value,
|
||||||
|
});
|
||||||
|
}, [compatOldVariables, fieldValue?.value, scope]);
|
||||||
|
|
||||||
|
const modeMap = {
|
||||||
|
// 常量
|
||||||
|
constant: (
|
||||||
|
<div style={constantStyle}>
|
||||||
|
{React.createElement(DynamicComponent, {
|
||||||
|
value: fieldValue?.value || fieldValue,
|
||||||
|
schema,
|
||||||
|
onChange: handleChangeOfConstant,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
|
||||||
|
// 表达式
|
||||||
|
express: (
|
||||||
|
<div style={expressStyle}>
|
||||||
|
<Variable.TextArea
|
||||||
|
value={fieldValue?.value}
|
||||||
|
onChange={handleChangeOfExpress}
|
||||||
|
scope={compatScope}
|
||||||
|
style={textAreaStyle}
|
||||||
|
/>
|
||||||
|
<>
|
||||||
|
<span style={{ marginLeft: '.25em' }} className={'ant-formily-item-extra'}>
|
||||||
|
{t('Syntax references')}:
|
||||||
|
</span>
|
||||||
|
<a href="https://formulajs.info/functions/" target="_blank" rel="noreferrer">
|
||||||
|
Formula.js
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input.Group compact>
|
<Input.Group compact>
|
||||||
<Select
|
<Select
|
||||||
@ -29,44 +119,7 @@ export const ValueDynamicComponent = (props) => {
|
|||||||
<Option value="express">{t('Expression')}</Option>
|
<Option value="express">{t('Expression')}</Option>
|
||||||
<Option value="empty">{t('Empty')}</Option>
|
<Option value="empty">{t('Empty')}</Option>
|
||||||
</Select>
|
</Select>
|
||||||
{mode === 'constant' ? (
|
{modeMap[mode]}
|
||||||
<div style={{ minWidth: 150, maxWidth: 430, marginLeft: 5 }}>
|
|
||||||
{React.createElement(DynamicComponent, {
|
|
||||||
value: fieldValue?.value || fieldValue,
|
|
||||||
schema,
|
|
||||||
onChange(value) {
|
|
||||||
setValue({
|
|
||||||
mode,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : mode === 'express' ? (
|
|
||||||
<div style={{ minWidth: 150, maxWidth: 430, fontSize: 13 }}>
|
|
||||||
<Variable.TextArea
|
|
||||||
value={fieldValue?.value}
|
|
||||||
onChange={(value) => {
|
|
||||||
const result = value.replaceAll(`${collectionName}.`, '').replaceAll('$system.', '').trim();
|
|
||||||
setValue({
|
|
||||||
mode,
|
|
||||||
value,
|
|
||||||
result,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
scope={scope}
|
|
||||||
style={{ minWidth: 460, marginRight: 15 }}
|
|
||||||
/>
|
|
||||||
<>
|
|
||||||
<span style={{ marginLeft: '.25em' }} className={'ant-formily-item-extra'}>
|
|
||||||
{t('Syntax references')}:
|
|
||||||
</span>
|
|
||||||
<a href="https://formulajs.info/functions/" target="_blank" rel="noreferrer">
|
|
||||||
Formula.js
|
|
||||||
</a>
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</Input.Group>
|
</Input.Group>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user