mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 01:48:14 +00:00
feat(variable): add a new variable named Parent object (#5449)
* feat(variable): add a new variable named Parent object * test: add tests and fix bug * fix(linkageRules): fix issue with variable options in subtables * test: add e2e test
This commit is contained in:
parent
361558a0d0
commit
dd71cdaaa4
@ -841,5 +841,6 @@
|
||||
"is any of": "is any of",
|
||||
"Plugin dependency version mismatch": "Plugin dependency version mismatch",
|
||||
"The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?",
|
||||
"Allow multiple selection": "Allow multiple selection"
|
||||
"Allow multiple selection": "Allow multiple selection",
|
||||
"Parent object": "Parent object"
|
||||
}
|
||||
|
@ -766,5 +766,6 @@
|
||||
"Clear default value": "Borrar valor por defecto",
|
||||
"Open in new window": "Abrir en una nueva ventana",
|
||||
"Sorry, the page you visited does not exist.": "Lo siento, la página que visitaste no existe.",
|
||||
"Allow multiple selection": "Permitir selección múltiple"
|
||||
"Allow multiple selection": "Permitir selección múltiple",
|
||||
"Parent object": "Objeto padre"
|
||||
}
|
||||
|
@ -786,5 +786,6 @@
|
||||
"Clear default value": "Effacer la valeur par défaut",
|
||||
"Open in new window": "Ouvrir dans une nouvelle fenêtre",
|
||||
"Sorry, the page you visited does not exist.": "Désolé, la page que vous avez visitée n'existe pas.",
|
||||
"Allow multiple selection": "Permettre la sélection multiple"
|
||||
"Allow multiple selection": "Permettre la sélection multiple",
|
||||
"Parent object": "Objet parent"
|
||||
}
|
||||
|
@ -1007,5 +1007,6 @@
|
||||
"NaN": "なし",
|
||||
"true": "真",
|
||||
"false": "偽",
|
||||
"Allow multiple selection": "複数選択を許可"
|
||||
"Allow multiple selection": "複数選択を許可",
|
||||
"Parent object": "親オブジェクト"
|
||||
}
|
||||
|
@ -877,5 +877,6 @@
|
||||
"Clear default value": "기본값 지우기",
|
||||
"Open in new window": "새 창에서 열기",
|
||||
"Sorry, the page you visited does not exist.": "죄송합니다. 방문한 페이지가 존재하지 않습니다.",
|
||||
"Allow multiple selection": "다중 선택 허용"
|
||||
"Allow multiple selection": "다중 선택 허용",
|
||||
"Parent object": "부모 객체"
|
||||
}
|
||||
|
@ -743,5 +743,6 @@
|
||||
"Clear default value": "Limpar valor padrão",
|
||||
"Open in new window": "Abrir em nova janela",
|
||||
"Sorry, the page you visited does not exist.": "Desculpe, a página que você visitou não existe.",
|
||||
"Allow multiple selection": "Permitir seleção múltipla"
|
||||
"Allow multiple selection": "Permitir seleção múltipla",
|
||||
"Parent object": "Objeto pai"
|
||||
}
|
||||
|
@ -580,5 +580,6 @@
|
||||
"Clear default value": "Очистить значение по умолчанию",
|
||||
"Open in new window": "Открыть в новом окне",
|
||||
"Sorry, the page you visited does not exist.": "Извините, посещенной вами страницы не существует.",
|
||||
"Allow multiple selection": "Разрешить множественный выбор"
|
||||
"Allow multiple selection": "Разрешить множественный выбор",
|
||||
"Parent object": "Родительский объект"
|
||||
}
|
||||
|
@ -578,5 +578,6 @@
|
||||
"Clear default value": "Varsayılan değeri temizle",
|
||||
"Open in new window": "Yeni pencerede aç",
|
||||
"Sorry, the page you visited does not exist.": "Üzgünüz, ziyaret ettiğiniz sayfa mevcut değil.",
|
||||
"Allow multiple selection": "Çoklu seçim izni"
|
||||
"Allow multiple selection": "Çoklu seçim izni",
|
||||
"Parent object": "Üst nesne"
|
||||
}
|
||||
|
@ -786,5 +786,6 @@
|
||||
"Clear default value": "Очистити значення за замовчуванням",
|
||||
"Open in new window": "Відкрити в новому вікні",
|
||||
"Sorry, the page you visited does not exist.": "Вибачте, сторінка, яку ви відвідали, не існує.",
|
||||
"Allow multiple selection": "Дозволити множинний вибір"
|
||||
"Allow multiple selection": "Дозволити множинний вибір",
|
||||
"Parent object": "Батьківський об'єкт"
|
||||
}
|
||||
|
@ -975,5 +975,6 @@
|
||||
"The current user only has the UI configuration permission, but don't have view permission for collection \"{{name}}\"": "当前用户只有 UI 配置权限,但没有数据表 \"{{name}}\" 查看权限。",
|
||||
"Plugin dependency version mismatch": "插件依赖版本不一致",
|
||||
"The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "当前插件的依赖版本与应用的版本不一致,可能无法正常工作。您确定要继续激活插件吗?",
|
||||
"Allow multiple selection": "允许多选"
|
||||
"Allow multiple selection": "允许多选",
|
||||
"Parent object": "上级对象"
|
||||
}
|
||||
|
@ -875,5 +875,6 @@
|
||||
"Clear default value": "清除預設值",
|
||||
"Open in new window": "新窗口打開",
|
||||
"Sorry, the page you visited does not exist.": "抱歉,你訪問的頁面不存在。",
|
||||
"Allow multiple selection": "允許多選"
|
||||
"Allow multiple selection": "允許多選",
|
||||
"Parent object": "上級物件"
|
||||
}
|
||||
|
@ -0,0 +1,135 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { expect, test } from '@nocobase/test/e2e';
|
||||
import { inDefaultValue } from './templates';
|
||||
|
||||
test.describe('variable: parent object', () => {
|
||||
test('in default value', async ({ page, mockPage }) => {
|
||||
await mockPage(inDefaultValue).goto();
|
||||
|
||||
// 1. 在当前表单中的子表单中,使用 “当前表单” 变量为 text2 字段设置默认值
|
||||
await page.getByLabel('block-item-CollectionField-collection2-form-collection2.text2-text2').hover();
|
||||
await page
|
||||
.getByLabel('designer-schema-settings-CollectionField-fieldSettings:FormItem-collection2-collection2.text2', {
|
||||
exact: true,
|
||||
})
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Set default value' }).click();
|
||||
await page.getByLabel('variable-button').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Current form right' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'text1' }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
// 2. 在子表单中的子表格中,使用 “上级对象” 变量为 text3 字段设置默认值
|
||||
await page.getByRole('button', { name: 'text3' }).click();
|
||||
await page.getByLabel('designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-collection3').click();
|
||||
await page.getByRole('menuitem', { name: 'Set default value' }).click();
|
||||
await page.getByLabel('variable-button').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Parent object right' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'text2' }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
// 3. 当更改当前表单中的 text1 字段后,text2 和 text3 字段应该也会被自动更改
|
||||
await page.getByRole('button', { name: 'Add new' }).click();
|
||||
await page
|
||||
.getByLabel('block-item-CollectionField-collection1-form-collection1.text1-text1')
|
||||
.getByRole('textbox')
|
||||
.fill('123456abcdefg');
|
||||
await expect(
|
||||
page.getByLabel('block-item-CollectionField-collection2-form-collection2.text2-text2').getByRole('textbox'),
|
||||
).toHaveValue('123456abcdefg');
|
||||
await expect(
|
||||
page.getByLabel('block-item-CollectionField-collection2-form-collection2.m2m2-m2m2').getByRole('textbox'),
|
||||
).toHaveValue('123456abcdefg');
|
||||
});
|
||||
|
||||
test('in linkage rules', async ({ page, mockPage }) => {
|
||||
await mockPage(inDefaultValue).goto();
|
||||
|
||||
// 1. Use "Current form" and "Parent object" variables in nested subforms and subtables
|
||||
await page.getByLabel('block-item-CollectionField-collection1-form-collection1.m2m1-m2m1').hover();
|
||||
await page
|
||||
.getByLabel('designer-schema-settings-CollectionField-fieldSettings:FormItem-collection1-collection1.m2m1', {
|
||||
exact: true,
|
||||
})
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add property').click();
|
||||
await page.getByTestId('select-linkage-property-field').click();
|
||||
await page.getByTitle('text2').click();
|
||||
await page.getByTestId('select-linkage-action-field').click();
|
||||
await page.getByRole('option', { name: 'Value', exact: true }).click();
|
||||
await page.getByTestId('select-linkage-value-type').click();
|
||||
await page.getByTitle('Expression').click();
|
||||
await page.getByLabel('variable-button').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Current form right' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'text1' }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-collection2-form-collection2.m2m2-m2m2').hover();
|
||||
await page
|
||||
.getByLabel('designer-schema-settings-CollectionField-fieldSettings:FormItem-collection2-collection2.m2m2', {
|
||||
exact: true,
|
||||
})
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add property').click();
|
||||
await page.getByTestId('select-linkage-property-field').click();
|
||||
await page.getByTitle('text3').click();
|
||||
await page.getByTestId('select-linkage-action-field').click();
|
||||
await page.getByRole('option', { name: 'Value', exact: true }).click();
|
||||
await page.getByTestId('select-linkage-value-type').click();
|
||||
await page.getByTitle('Expression').click();
|
||||
await page.getByLabel('variable-button').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Parent object right' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'text2' }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
// 2. Assert: When the text1 field in the current form is changed, the text2 and text3 fields should also be automatically changed
|
||||
await page.getByRole('button', { name: 'Add new' }).click();
|
||||
await page
|
||||
.getByLabel('block-item-CollectionField-collection1-form-collection1.text1-text1')
|
||||
.getByRole('textbox')
|
||||
.fill('123456abcdefg');
|
||||
|
||||
await expect(
|
||||
page.getByLabel('block-item-CollectionField-collection2-form-collection2.text2-text2').getByRole('textbox'),
|
||||
).toHaveValue('123456abcdefg');
|
||||
await expect(
|
||||
page.getByLabel('block-item-CollectionField-collection2-form-collection2.m2m2-m2m2').getByRole('textbox'),
|
||||
).toHaveValue('123456abcdefg');
|
||||
|
||||
// 3. Test if the "Current object" variable can be used normally in the subform
|
||||
await page.getByLabel('schema-initializer-Grid-form:configureFields-collection2').hover();
|
||||
await page.getByRole('menuitem', { name: 'form Add text' }).click();
|
||||
await page.getByLabel('block-item-Markdown.Void-').hover();
|
||||
await page.getByLabel('designer-schema-settings-Markdown.Void-blockSettings:markdown-collection2').hover();
|
||||
await page.getByRole('menuitem', { name: 'Edit markdown' }).click();
|
||||
await page.getByText('This is a demo text, **').click();
|
||||
await page.getByText('This is a demo text, **').clear();
|
||||
await page.getByLabel('variable-button').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Current object right' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'text2' }).click();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
await page
|
||||
.getByLabel('block-item-CollectionField-collection2-form-collection2.text2-text2')
|
||||
.getByRole('textbox')
|
||||
.fill('987654321');
|
||||
|
||||
// 4. Assert: The subtable and Markdown should be updated in real-time
|
||||
await expect(
|
||||
page.getByLabel('block-item-CollectionField-collection2-form-collection2.m2m2-m2m2').getByRole('textbox'),
|
||||
).toHaveValue('987654321');
|
||||
// await expect(page.getByLabel('block-item-Markdown.Void-')).toHaveText('987654321');
|
||||
});
|
||||
});
|
@ -1297,3 +1297,406 @@ export const tableSelectedRecords = {
|
||||
'x-index': 1,
|
||||
},
|
||||
};
|
||||
export const inDefaultValue = {
|
||||
collections: [
|
||||
{
|
||||
name: 'collection1',
|
||||
fields: [
|
||||
{
|
||||
name: 'text1',
|
||||
interface: 'input',
|
||||
},
|
||||
{
|
||||
name: 'm2m1',
|
||||
interface: 'm2m',
|
||||
target: 'collection2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'collection2',
|
||||
fields: [
|
||||
{
|
||||
name: 'text2',
|
||||
interface: 'input',
|
||||
},
|
||||
{
|
||||
name: 'm2m2',
|
||||
interface: 'm2m',
|
||||
target: 'collection3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'collection3',
|
||||
fields: [
|
||||
{
|
||||
name: 'text3',
|
||||
interface: 'input',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
pageSchema: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Page',
|
||||
properties: {
|
||||
qiis77b2b96: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'page:addBlock',
|
||||
properties: {
|
||||
xn73tu52o4l: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
ovrxf0qi4oh: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
clq66owv5vt: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-acl-action-props': {
|
||||
skipScopeCheck: true,
|
||||
},
|
||||
'x-acl-action': 'collection1:create',
|
||||
'x-decorator': 'FormBlockProvider',
|
||||
'x-use-decorator-props': 'useCreateFormBlockDecoratorProps',
|
||||
'x-decorator-props': {
|
||||
dataSource: 'main',
|
||||
collection: 'collection1',
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': 'blockSettings:createForm',
|
||||
'x-component': 'CardItem',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
jgyr5k5rhl5: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'FormV2',
|
||||
'x-use-component-props': 'useCreateFormBlockProps',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
grid: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'form:configureFields',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
'704zd4gqwia': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
bng2scwwp21: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
text1: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'string',
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'collection1.text1',
|
||||
'x-component-props': {},
|
||||
'x-app-version': '1.3.33-beta',
|
||||
'x-uid': '8yrtgs4fij4',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '5mpw6xv5t53',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'umhyk321or1',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
'7jmmz0am2mp': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
kuwqh6jsb0z: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
m2m1: {
|
||||
'x-uid': '4cojuep3jug',
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'string',
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'collection1.m2m1',
|
||||
'x-component-props': {
|
||||
fieldNames: {
|
||||
label: 'id',
|
||||
value: 'id',
|
||||
},
|
||||
mode: 'Nester',
|
||||
},
|
||||
'x-app-version': '1.3.33-beta',
|
||||
default: null,
|
||||
properties: {
|
||||
sqommd77rxp: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'AssociationField.Nester',
|
||||
'x-index': 1,
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
grid: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'form:configureFields',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
'7pnogkc6aso': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
xsbs3warqhf: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
text2: {
|
||||
'x-uid': 's0lsw2l9gxo',
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'string',
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'collection2.text2',
|
||||
'x-component-props': {},
|
||||
'x-app-version': '1.3.33-beta',
|
||||
default: null,
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'fh40b1ec8xe',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'twc2iso6ij8',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
'758esk132v5': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
t9ijtjbpryx: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
m2m2: {
|
||||
'x-uid': 'dgul9qn182o',
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'string',
|
||||
'x-toolbar': 'FormItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:FormItem',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': 'collection2.m2m2',
|
||||
'x-component-props': {
|
||||
fieldNames: {
|
||||
label: 'id',
|
||||
value: 'id',
|
||||
},
|
||||
mode: 'SubTable',
|
||||
},
|
||||
'x-app-version': '1.3.33-beta',
|
||||
default: null,
|
||||
properties: {
|
||||
wzyleesvy5a: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'AssociationField.SubTable',
|
||||
'x-initializer': 'table:configureColumns',
|
||||
'x-initializer-props': {
|
||||
action: false,
|
||||
},
|
||||
'x-index': 1,
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
'1rhnqhhrxtl': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-toolbar': 'TableColumnSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:TableColumn',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-app-version': '1.3.33-beta',
|
||||
properties: {
|
||||
text3: {
|
||||
'x-uid': 'qr2z1604tdt',
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
'x-collection-field': 'collection3.text3',
|
||||
'x-component': 'CollectionField',
|
||||
'x-component-props': {
|
||||
ellipsis: true,
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelStyle: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
'x-app-version': '1.3.33-beta',
|
||||
default: null,
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'veibwzrxmwt',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '12y4qwbwh9v',
|
||||
'x-async': false,
|
||||
},
|
||||
},
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'fjqt5n7vnp1',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'p5xwk5asiyx',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
'x-uid': 'rv2r7oq9i5v',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'mk2emm6340d',
|
||||
'x-async': false,
|
||||
},
|
||||
},
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'iibkaselueb',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'cgkroe30mh2',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
'x-uid': 'ccpvbj4s1fn',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
u9ryrklw5oj: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-initializer': 'createForm:configureActions',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
layout: 'one-column',
|
||||
},
|
||||
'x-app-version': '1.3.33-beta',
|
||||
'x-uid': '0ib597ro9p7',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
'x-uid': 'gx13vgubf5i',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '01nwdjwsedu',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'cxse6wcqnm3',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'pp209qnmn8v',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'i11l6wkz6b2',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'a9u1cjps5th',
|
||||
'x-async': true,
|
||||
'x-index': 1,
|
||||
},
|
||||
};
|
||||
|
@ -168,8 +168,8 @@ export const SubTable: any = observer(
|
||||
<FlagProvider isInSubTable>
|
||||
<CollectionRecordProvider record={null} parentRecord={recordV2}>
|
||||
<FormActiveFieldsProvider name="nester">
|
||||
{/* 在这里加,是为了让 “当前对象” 的配置显示正确 */}
|
||||
<SubFormProvider value={{ value: null, collection, fieldSchema: fieldSchema.parent }}>
|
||||
{/* 在这里加,是为了让子表格中默认值的 “当前对象” 的配置显示正确 */}
|
||||
<SubFormProvider value={{ value: null, collection, fieldSchema: fieldSchema.parent, skip: true }}>
|
||||
<Table
|
||||
className={tableClassName}
|
||||
bordered
|
||||
|
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import React from 'react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { SubFormProvider, useSubFormValue } from '../hooks';
|
||||
|
||||
describe('useSubFormValue', () => {
|
||||
it('should return the correct values from SubFormContext', () => {
|
||||
const mockValue = { id: 1, name: 'Test' };
|
||||
const mockCollection = { name: 'users' };
|
||||
const mockFieldSchema = { type: 'object', properties: {} };
|
||||
const mockParent = { value: { parentId: 2 }, collection: { name: 'parents' } };
|
||||
|
||||
const wrapper = ({ children }) => (
|
||||
<SubFormProvider
|
||||
value={{
|
||||
value: mockValue,
|
||||
collection: mockCollection as any,
|
||||
fieldSchema: mockFieldSchema as any,
|
||||
parent: mockParent as any,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SubFormProvider>
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useSubFormValue(), { wrapper });
|
||||
|
||||
expect(result.current).toEqual({
|
||||
formValue: mockValue,
|
||||
collection: mockCollection,
|
||||
fieldSchema: mockFieldSchema,
|
||||
parent: mockParent,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined values when SubFormContext is not provided', () => {
|
||||
const { result } = renderHook(() => useSubFormValue());
|
||||
|
||||
expect(result.current).toEqual({
|
||||
formValue: undefined,
|
||||
collection: undefined,
|
||||
fieldSchema: undefined,
|
||||
parent: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should update values when SubFormContext changes', () => {
|
||||
const initialValue = { id: 1, name: 'Initial' };
|
||||
const updatedValue = { id: 1, name: 'Updated' };
|
||||
const mockCollection = { name: 'users' };
|
||||
const mockFieldSchema = { type: 'object', properties: {} };
|
||||
|
||||
const wrapper = ({ children }) => (
|
||||
<SubFormProvider
|
||||
value={{
|
||||
value: initialValue,
|
||||
collection: mockCollection as any,
|
||||
fieldSchema: mockFieldSchema as any,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SubFormProvider>
|
||||
);
|
||||
|
||||
const { result, rerender } = renderHook(() => useSubFormValue(), { wrapper });
|
||||
|
||||
expect(result.current.formValue).toEqual(initialValue);
|
||||
|
||||
// Update the context value
|
||||
Object.assign(initialValue, updatedValue);
|
||||
|
||||
rerender();
|
||||
|
||||
expect(result.current.formValue).toEqual(updatedValue);
|
||||
});
|
||||
|
||||
it('should use provided parent when available', () => {
|
||||
const mockParent = { value: { id: 1 } };
|
||||
const wrapper = ({ children }) => (
|
||||
<SubFormProvider value={{ parent: mockParent as any } as any}>{children}</SubFormProvider>
|
||||
);
|
||||
const { result } = renderHook(() => useSubFormValue(), { wrapper });
|
||||
expect(result.current.parent).toBe(mockParent);
|
||||
});
|
||||
|
||||
it('should use _parent when no parent is provided and skip is false', () => {
|
||||
const mockParent = { value: { id: 1 }, skip: false, parent: null };
|
||||
const wrapper = ({ children }) => (
|
||||
<SubFormProvider value={mockParent as any}>
|
||||
<SubFormProvider value={{ skip: false } as any}>{children}</SubFormProvider>
|
||||
</SubFormProvider>
|
||||
);
|
||||
const { result } = renderHook(() => useSubFormValue(), { wrapper });
|
||||
expect(result.current.parent).toEqual(mockParent);
|
||||
});
|
||||
|
||||
it('should use _parent.parent when _parent.skip is true', () => {
|
||||
const mockGrandParent = { value: { id: 1 }, parent: null };
|
||||
const wrapper = ({ children }) => (
|
||||
<SubFormProvider value={mockGrandParent as any}>
|
||||
<SubFormProvider value={{ skip: true } as any}>
|
||||
<SubFormProvider value={{} as any}>{children}</SubFormProvider>
|
||||
</SubFormProvider>
|
||||
</SubFormProvider>
|
||||
);
|
||||
const { result } = renderHook(() => useSubFormValue(), { wrapper });
|
||||
expect(result.current.parent).toEqual(mockGrandParent);
|
||||
});
|
||||
});
|
@ -9,7 +9,7 @@
|
||||
|
||||
import { GeneralField } from '@formily/core';
|
||||
import { Schema, useField, useFieldSchema } from '@formily/react';
|
||||
import { isString } from 'lodash';
|
||||
import _, { isString } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import React, { createContext, FC, useCallback, useContext, useMemo } from 'react';
|
||||
import { useParsedFilter } from '../../../block-provider/hooks/useParsedFilter';
|
||||
@ -149,14 +149,27 @@ interface SubFormProviderProps {
|
||||
* the schema of the current sub-table or sub-form
|
||||
*/
|
||||
fieldSchema?: Schema;
|
||||
parent?: SubFormProviderProps;
|
||||
/**
|
||||
* Ignore the current value in the upper and lower levels
|
||||
*/
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
const SubFormContext = createContext<SubFormProviderProps>(null);
|
||||
SubFormContext.displayName = 'SubFormContext';
|
||||
|
||||
export const SubFormProvider: FC<{ value: SubFormProviderProps }> = (props) => {
|
||||
const { value, collection, fieldSchema } = props.value;
|
||||
const memoValue = useMemo(() => ({ value, collection, fieldSchema }), [value, collection, fieldSchema]);
|
||||
const _parent = useContext(SubFormContext);
|
||||
const { value, collection, fieldSchema, parent, skip } = props.value;
|
||||
const memoValue = useMemo(
|
||||
() =>
|
||||
_.omitBy(
|
||||
{ value, collection, fieldSchema, skip, parent: parent || (_parent?.skip ? _parent.parent : _parent) },
|
||||
_.isUndefined,
|
||||
),
|
||||
[value, collection, fieldSchema, skip, parent, _parent],
|
||||
) as SubFormProviderProps;
|
||||
return <SubFormContext.Provider value={memoValue}>{props.children}</SubFormContext.Provider>;
|
||||
};
|
||||
|
||||
@ -170,10 +183,11 @@ export const SubFormProvider: FC<{ value: SubFormProviderProps }> = (props) => {
|
||||
* @returns
|
||||
*/
|
||||
export const useSubFormValue = () => {
|
||||
const { value, collection, fieldSchema } = useContext(SubFormContext) || {};
|
||||
const { value, collection, fieldSchema, parent } = useContext(SubFormContext) || {};
|
||||
return {
|
||||
formValue: value,
|
||||
collection,
|
||||
fieldSchema,
|
||||
parent,
|
||||
};
|
||||
};
|
||||
|
@ -10,13 +10,13 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { observer, useFieldSchema } from '@formily/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { FormBlockContext } from '../../block-provider/FormBlockProvider';
|
||||
import { useCollectionManager_deprecated } from '../../collection-manager';
|
||||
import { useCollectionParentRecordData } from '../../data-source/collection-record/CollectionRecordProvider';
|
||||
import { CollectionProvider } from '../../data-source/collection/CollectionProvider';
|
||||
import { withDynamicSchemaProps } from '../../hoc/withDynamicSchemaProps';
|
||||
import { RecordProvider } from '../../record-provider';
|
||||
import { SchemaComponent, useProps } from '../../schema-component';
|
||||
import { SubFormProvider } from '../../schema-component/antd/association-field/hooks';
|
||||
import { DynamicComponentProps } from '../../schema-component/antd/filter/DynamicComponent';
|
||||
import { FilterContext } from '../../schema-component/antd/filter/context';
|
||||
import { VariableInput, getShouldChange } from '../VariableInput/VariableInput';
|
||||
@ -31,17 +31,8 @@ export interface Props {
|
||||
export const FormLinkageRules = withDynamicSchemaProps(
|
||||
observer((props: Props) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const {
|
||||
options,
|
||||
defaultValues,
|
||||
collectionName,
|
||||
form,
|
||||
formBlockType,
|
||||
variables,
|
||||
localVariables,
|
||||
record,
|
||||
dynamicComponent,
|
||||
} = useProps(props); // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||
const { options, defaultValues, collectionName, form, variables, localVariables, record, dynamicComponent } =
|
||||
useProps(props); // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||
const { getAllCollectionsInheritChain } = useCollectionManager_deprecated();
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
|
||||
@ -176,7 +167,8 @@ export const FormLinkageRules = withDynamicSchemaProps(
|
||||
);
|
||||
|
||||
return (
|
||||
<FormBlockContext.Provider value={{ form, type: formBlockType, collectionName }}>
|
||||
// 这里使用 SubFormProvider 包裹,是为了让子表格的联动规则中 “当前对象” 的配置显示正确
|
||||
<SubFormProvider value={{ value: null, collection: { name: collectionName } as any }}>
|
||||
<RecordProvider record={record} parent={parentRecordData}>
|
||||
<FilterContext.Provider value={value}>
|
||||
<CollectionProvider name={collectionName}>
|
||||
@ -184,7 +176,7 @@ export const FormLinkageRules = withDynamicSchemaProps(
|
||||
</CollectionProvider>
|
||||
</FilterContext.Provider>
|
||||
</RecordProvider>
|
||||
</FormBlockContext.Provider>
|
||||
</SubFormProvider>
|
||||
);
|
||||
}),
|
||||
{ displayName: 'FormLinkageRules' },
|
||||
|
@ -780,7 +780,7 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
|
||||
const locationSearch = useLocationSearch();
|
||||
|
||||
// 解决变量`当前对象`值在弹窗中丢失的问题
|
||||
const { formValue: subFormValue, collection: subFormCollection } = useSubFormValue();
|
||||
const { formValue: subFormValue, collection: subFormCollection, parent } = useSubFormValue();
|
||||
|
||||
// 解决弹窗变量丢失的问题
|
||||
const popupRecordVariable = useCurrentPopupRecord();
|
||||
@ -812,7 +812,7 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
|
||||
>
|
||||
<CollectionRecordProvider record={noRecord ? null : record}>
|
||||
<FormBlockContext.Provider value={formCtx}>
|
||||
<SubFormProvider value={{ value: subFormValue, collection: subFormCollection }}>
|
||||
<SubFormProvider value={{ value: subFormValue, collection: subFormCollection, parent }}>
|
||||
<FormActiveFieldsProvider
|
||||
name="form"
|
||||
getActiveFieldsName={upLevelActiveFields?.getActiveFieldsName}
|
||||
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { Schema } from '@formily/json-schema';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CollectionFieldOptions } from '../../../data-source/collection/Collection';
|
||||
import { useFlag } from '../../../flag-provider';
|
||||
import { useSubFormValue } from '../../../schema-component/antd/association-field/hooks';
|
||||
import { useBaseVariable } from './useBaseVariable';
|
||||
|
||||
/**
|
||||
* 变量:`上级对象`
|
||||
* @param param0
|
||||
* @returns
|
||||
*/
|
||||
export const useParentObjectVariable = ({
|
||||
collectionField,
|
||||
schema,
|
||||
noDisabled,
|
||||
targetFieldSchema,
|
||||
}: {
|
||||
collectionField?: CollectionFieldOptions;
|
||||
schema?: any;
|
||||
noDisabled?: boolean;
|
||||
/** 消费变量值的字段 */
|
||||
targetFieldSchema?: Schema;
|
||||
} = {}) => {
|
||||
// const { getActiveFieldsName } = useFormActiveFields() || {};
|
||||
const { parent } = useSubFormValue();
|
||||
const { value: parentObjectCtx, collection: collectionOfParentObject } = parent || {};
|
||||
const { isInSubForm, isInSubTable } = useFlag() || {};
|
||||
const { t } = useTranslation();
|
||||
const parentObjectSettings = useBaseVariable({
|
||||
collectionField,
|
||||
uiSchema: schema,
|
||||
targetFieldSchema,
|
||||
maxDepth: 4,
|
||||
name: '$nParentIteration',
|
||||
title: t('Parent object'),
|
||||
collectionName: collectionOfParentObject?.name,
|
||||
noDisabled,
|
||||
returnFields: (fields, option) => {
|
||||
return fields;
|
||||
// const activeFieldsName = getActiveFieldsName?.('nester') || [];
|
||||
|
||||
// return option.depth === 0
|
||||
// ? fields.filter((field) => {
|
||||
// return activeFieldsName?.includes(field.name);
|
||||
// })
|
||||
// : fields;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
/** 是否显示变量 */
|
||||
shouldDisplayParentObject: (isInSubForm || isInSubTable) && !!collectionOfParentObject,
|
||||
/** 变量的值 */
|
||||
parentObjectCtx,
|
||||
/** 变量的配置项 */
|
||||
parentObjectSettings,
|
||||
collectionName: collectionOfParentObject?.name,
|
||||
};
|
||||
};
|
@ -15,6 +15,7 @@ import { useAPITokenVariable } from './useAPITokenVariable';
|
||||
import { useDatetimeVariable } from './useDateVariable';
|
||||
import { useCurrentFormVariable } from './useFormVariable';
|
||||
import { useCurrentObjectVariable } from './useIterationVariable';
|
||||
import { useParentObjectVariable } from './useParentIterationVariable';
|
||||
import { useParentPopupVariable } from './useParentPopupVariable';
|
||||
import { useCurrentParentRecordVariable } from './useParentRecordVariable';
|
||||
import { usePopupVariable } from './usePopupVariable';
|
||||
@ -87,6 +88,12 @@ export const useVariableOptions = ({
|
||||
noDisabled,
|
||||
targetFieldSchema,
|
||||
});
|
||||
const { parentObjectSettings, shouldDisplayParentObject } = useParentObjectVariable({
|
||||
collectionField,
|
||||
schema: uiSchema,
|
||||
noDisabled,
|
||||
targetFieldSchema,
|
||||
});
|
||||
const { currentRecordSettings, shouldDisplayCurrentRecord } = useCurrentRecordVariable({
|
||||
schema: uiSchema,
|
||||
collectionField,
|
||||
@ -122,6 +129,7 @@ export const useVariableOptions = ({
|
||||
datetimeSettings,
|
||||
shouldDisplayCurrentForm && currentFormSettings,
|
||||
shouldDisplayCurrentObject && currentObjectSettings,
|
||||
shouldDisplayParentObject && parentObjectSettings,
|
||||
shouldDisplayCurrentRecord && currentRecordSettings,
|
||||
shouldDisplayCurrentParentRecord && currentParentRecordSettings,
|
||||
shouldDisplayPopupRecord && popupRecordSettings,
|
||||
@ -137,6 +145,8 @@ export const useVariableOptions = ({
|
||||
currentFormSettings,
|
||||
shouldDisplayCurrentObject,
|
||||
currentObjectSettings,
|
||||
shouldDisplayParentObject,
|
||||
parentObjectSettings,
|
||||
shouldDisplayCurrentRecord,
|
||||
currentRecordSettings,
|
||||
shouldDisplayCurrentParentRecord,
|
||||
|
@ -14,6 +14,7 @@ import { useBlockCollection } from '../../schema-settings/VariableInput/hooks/us
|
||||
import { useDatetimeVariable } from '../../schema-settings/VariableInput/hooks/useDateVariable';
|
||||
import { useCurrentFormVariable } from '../../schema-settings/VariableInput/hooks/useFormVariable';
|
||||
import { useCurrentObjectVariable } from '../../schema-settings/VariableInput/hooks/useIterationVariable';
|
||||
import { useParentObjectVariable } from '../../schema-settings/VariableInput/hooks/useParentIterationVariable';
|
||||
import { useParentPopupVariable } from '../../schema-settings/VariableInput/hooks/useParentPopupVariable';
|
||||
import { useCurrentParentRecordVariable } from '../../schema-settings/VariableInput/hooks/useParentRecordVariable';
|
||||
import { usePopupVariable } from '../../schema-settings/VariableInput/hooks/usePopupVariable';
|
||||
@ -26,6 +27,11 @@ interface Props {
|
||||
}
|
||||
|
||||
const useLocalVariables = (props?: Props) => {
|
||||
const {
|
||||
parentObjectCtx,
|
||||
shouldDisplayParentObject,
|
||||
collectionName: collectionNameOfParentObject,
|
||||
} = useParentObjectVariable();
|
||||
const { currentObjectCtx, shouldDisplayCurrentObject } = useCurrentObjectVariable();
|
||||
const { currentRecordCtx, collectionName: collectionNameOfRecord } = useCurrentRecordVariable();
|
||||
const {
|
||||
@ -131,6 +137,11 @@ const useLocalVariables = (props?: Props) => {
|
||||
ctx: currentObjectCtx,
|
||||
collectionName: currentCollectionName,
|
||||
},
|
||||
shouldDisplayParentObject && {
|
||||
name: '$nParentIteration',
|
||||
ctx: parentObjectCtx,
|
||||
collectionName: collectionNameOfParentObject,
|
||||
},
|
||||
] as VariableOption[]
|
||||
).filter(Boolean);
|
||||
}, [
|
||||
@ -151,6 +162,9 @@ const useLocalVariables = (props?: Props) => {
|
||||
currentCollectionName,
|
||||
defaultValueOfPopupRecord,
|
||||
defaultValueOfParentPopupRecord,
|
||||
shouldDisplayParentObject,
|
||||
parentObjectCtx,
|
||||
collectionNameOfParentObject,
|
||||
]); // 尽量保持返回的值不变,这样可以减少接口的请求次数,因为关系字段会缓存到变量的 ctx 中
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user