feat: support for displaying deeper level association fields in data blocks (#5243)

* feat(table): add support for selecting child fields of association fields

* style: fix style

* feat(details): add support for selecting child fields of association fields

* fix: correct sourceId retrieval

* test: add tests

* chore: fix build error

* chore: remove ConfigProvider

* test: update e2e tests
This commit is contained in:
Zeke Zhang 2024-09-10 11:13:37 +08:00 committed by GitHub
parent 5eee3dba91
commit 97d2ad6f52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 834 additions and 116 deletions

View File

@ -48,7 +48,11 @@ export const SchemaInitializerMenu: FC<MenuProps> = (props) => {
const { items, ...others } = props;
const { token } = theme.useToken();
const itemsWithPopupClass = useMemo(
() => items.map((item) => ({ ...item, popupClassName: `${hashId} ${componentCls}-menu-sub` })),
() =>
items.map((item) => ({
...item,
popupClassName: `${hashId} ${componentCls}-menu-sub`,
})),
[componentCls, hashId, items],
);
// selectedKeys 为了不让有选中效果
@ -62,9 +66,23 @@ export const SchemaInitializerMenu: FC<MenuProps> = (props) => {
border-inline-end: 0 !important;
.ant-menu-sub {
max-height: 50vh !important;
padding: ${token.paddingXXS}px !important;
}
.ant-menu-item {
margin-block: 0;
margin-inline: ${token.marginXXS}px !important;
margin-block: 0 !important;
width: auto !important;
padding: 0 ${token.paddingSM}px 0 ${token.padding}px !important;
}
.ant-menu-item-group-title {
padding: 0 ${token.padding}px;
margin-inline: 0;
line-height: 32px;
}
.ant-menu-submenu-title {
margin: 0 ${token.marginXXS}px !important;
padding-left: ${token.padding}px !important;
width: auto !important;
}
.ant-menu-root {
margin: 0 -${token.margin}px;

View File

@ -51,11 +51,6 @@ export const useSchemaInitializerStyles = genStyleHook('nb-schema-initializer',
},
},
},
[`${componentCls}-menu-sub`]: {
ul: {
maxHeight: '50vh !important',
},
},
[`${componentCls}-item-content`]: {
marginLeft: token.marginXS,
},

View File

@ -76,9 +76,7 @@ test.describe('configure fields', () => {
await expect(
page.getByLabel('block-item-CollectionField-general-details-general.singleSelect-Single select'),
).toBeVisible();
await expect(
page.getByLabel('block-item-CollectionField-general-details-general.manyToOne.nickname'),
).toBeVisible();
await expect(page.getByLabel('block-item-CollectionField-general-details-users.nickname-Nickname')).toBeVisible();
// delete fields
await formItemInitializer.hover();

View File

@ -68,7 +68,7 @@ test.describe('configure fields', () => {
await page.mouse.move(300, 0);
await expect(page.getByLabel('block-item-CollectionField-general-form-general.id-ID')).toBeVisible();
await expect(page.getByLabel('block-item-CollectionField-general-form-general.manyToOne.nickname')).toBeVisible();
await expect(page.getByLabel('block-item-CollectionField-general-form-users.nickname-Nickname')).toBeVisible();
// delete fields
await page.getByLabel('schema-initializer-Grid-form:configureFields-general').hover();

View File

@ -9,6 +9,7 @@
import { expect, oneEmptyTableBlockWithActions, test } from '@nocobase/test/e2e';
import { T3848 } from '../../../details-single/__e2e__/templatesOfBug';
import { addAssociationFields } from './templatesOfBug';
test.describe('where edit form block can be added', () => {
test('popup', async ({ page, mockPage, mockRecord }) => {
@ -69,4 +70,30 @@ test.describe('where edit form block can be added', () => {
test.describe('configure actions', () => {});
test.describe('configure fields', () => {});
test.describe('configure fields', () => {
test('add association fields', async ({ page, mockPage, mockRecord }) => {
const nocoPage = await mockPage(addAssociationFields).waitForInit();
const record = await mockRecord('general', 3);
await nocoPage.goto();
// Create association fields for the first, second, and third levels respectively, and assert whether the values are correct
await page.getByLabel('action-Action.Link-Edit-').first().click();
await page.getByLabel('schema-initializer-Grid-form:').hover();
await page.getByRole('menuitem', { name: 'manyToOne1', exact: true }).click();
await page.getByRole('menuitem', { name: 'manyToOne1 right' }).hover();
await page.getByRole('menuitem', { name: 'manyToOne2', exact: true }).click();
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
await page.mouse.move(600, 0);
await expect(page.getByLabel('block-item-CollectionField-general-form-general.manyToOne1-manyToOne1')).toHaveText(
`manyToOne1:${record.manyToOne1.id}`,
);
await expect(
page.getByLabel('block-item-CollectionField-general-form-targetCollection1.manyToOne2-manyToOne2'),
).toHaveText(`manyToOne2:${record.manyToOne1.manyToOne2.id}`);
await expect(
page.getByLabel('block-item-CollectionField-general-form-targetCollection2.manyToOne3-manyToOne3'),
).toHaveText(`manyToOne3:${record.manyToOne1.manyToOne2.manyToOne3.id}`);
});
});

View File

@ -1111,3 +1111,370 @@ export const T3924: PageConfig = {
'x-index': 1,
},
};
export const addAssociationFields = {
collections: [
{
name: 'general',
fields: [
{
interface: 'm2o',
name: 'manyToOne1',
target: 'targetCollection1',
},
{
interface: 'input',
name: 'generalText',
},
],
},
{
name: 'targetCollection1',
fields: [
{
interface: 'm2o',
name: 'manyToOne2',
target: 'targetCollection2',
},
{
interface: 'input',
name: 'targetCollection1Text',
},
],
},
{
name: 'targetCollection2',
fields: [
{
interface: 'm2o',
name: 'manyToOne3',
target: 'emptyCollection',
},
{
interface: 'input',
name: 'targetCollection2Text',
},
],
},
{
name: 'emptyCollection',
},
],
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
properties: {
'6d3i9i6e7vg': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
properties: {
krwfqgbq4fy: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.3.19-beta',
properties: {
js8p7716v8y: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.3.19-beta',
properties: {
nwk3otm0wbu: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'general:list',
'x-use-decorator-props': 'useTableBlockDecoratorProps',
'x-decorator-props': {
collection: 'general',
dataSource: 'main',
action: 'list',
params: {
pageSize: 20,
},
rowKey: 'id',
showIndex: true,
dragSort: false,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-filter-targets': [],
'x-app-version': '1.3.19-beta',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
'x-app-version': '1.3.19-beta',
'x-uid': 'wkkuk2ca9oz',
'x-async': false,
'x-index': 1,
},
s00slxnrdog: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-use-component-props': 'useTableBlockProps',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
},
'x-app-version': '1.3.19-beta',
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-toolbar': 'TableColumnSchemaToolbar',
'x-initializer': 'table:configureItemActions',
'x-settings': 'fieldSettings:TableColumn',
'x-toolbar-props': {
initializer: 'table:configureItemActions',
},
'x-app-version': '1.3.19-beta',
properties: {
mpthybxwhi6: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
'x-app-version': '1.3.19-beta',
properties: {
yp1u36754xm: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Edit") }}',
'x-action': 'update',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:edit',
'x-component': 'Action.Link',
'x-component-props': {
openMode: 'drawer',
icon: 'EditOutlined',
},
'x-action-context': {
dataSource: 'main',
collection: 'general',
},
'x-decorator': 'ACLActionProvider',
'x-designer-props': {
linkageAction: true,
},
properties: {
drawer: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Edit 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': 'popup:addTab',
properties: {
tab1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Edit")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:common:addBlock',
properties: {
dhu7la398bc: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
'x-app-version': '1.3.19-beta',
properties: {
ex9i4u2sm9o: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
'x-app-version': '1.3.19-beta',
properties: {
gdna46r5948: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-acl-action-props': {
skipScopeCheck: false,
},
'x-acl-action': 'general:update',
'x-decorator': 'FormBlockProvider',
'x-use-decorator-props':
'useEditFormBlockDecoratorProps',
'x-decorator-props': {
action: 'get',
dataSource: 'main',
collection: 'general',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:editForm',
'x-component': 'CardItem',
'x-app-version': '1.3.19-beta',
properties: {
q7vswewc9bk: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'FormV2',
'x-use-component-props': 'useEditFormBlockProps',
'x-app-version': '1.3.19-beta',
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'form:configureFields',
'x-app-version': '1.3.19-beta',
'x-uid': 'fu2wc0gvxg7',
'x-async': false,
'x-index': 1,
},
jvi33jag3zs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'editForm:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
},
'x-app-version': '1.3.19-beta',
'x-uid': 'zox6jjontst',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'e91gqbinrdp',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'd2lh71p96uu',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'e5pfc9sit0l',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '50qpb5g4zcw',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '3u9vcm0gftv',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '59ihnv5o5u5',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '7i9nnjy10pf',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'kov4btsbs2c',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '4fixo409z6d',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'kacm537dm0m',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'agw5b9adl6m',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '85dhumz9qp7',
'x-async': false,
'x-index': 2,
},
},
'x-uid': '300ltjt2u3c',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'ogjd21lszel',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'fn1knfs54ow',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'im83gpz4p8l',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'tir06tp0oyx',
'x-async': true,
'x-index': 1,
},
};

View File

@ -159,7 +159,7 @@ test.describe('configure fields', () => {
await page.mouse.move(300, 0);
await expect(page.getByLabel('block-item-CollectionField-general-grid-card-general.id-ID').first()).toBeVisible();
await expect(
page.getByLabel('block-item-CollectionField-general-grid-card-general.manyToOne.nickname').first(),
page.getByLabel('block-item-CollectionField-general-grid-card-users.nickname-Nickname').first(),
).toBeVisible();
// delete fields

View File

@ -157,7 +157,7 @@ test.describe('configure fields', () => {
await page.mouse.move(300, 0);
await expect(page.getByLabel('block-item-CollectionField-general-list-general.id-ID').first()).toBeVisible();
await expect(
page.getByLabel('block-item-CollectionField-general-list-general.manyToOne.nickname').first(),
page.getByLabel('block-item-CollectionField-general-list-users.nickname-Nickname').first(),
).toBeVisible();
// delete fields

View File

@ -8,6 +8,7 @@
*/
import { Page, expect, oneEmptyTable, test } from '@nocobase/test/e2e';
import { addAssociationFields } from '../../form/__e2e__/form-edit/templatesOfBug';
import { oneTableWithInheritFields } from './templatesOfBug';
const deleteButton = async (page: Page, name: string) => {
@ -113,6 +114,141 @@ test.describe('configure columns', () => {
await expect(page.getByRole('button', { name: 'Nickname', exact: true })).not.toBeVisible();
});
test('multiple depths of association fields', async ({ page, mockPage, mockRecord, mockRecords }) => {
const nocoPage = await mockPage(addAssociationFields).waitForInit();
// The purpose here is to make the IDs in the same row different
await mockRecords('targetCollection1', 2, 0);
await mockRecords('targetCollection2', 1, 0);
const record = await mockRecord('general', 3);
await nocoPage.goto();
// 1. Create association fields for the first, second, and third levels
await page.getByLabel('schema-initializer-TableV2-').hover();
await page.getByRole('menuitem', { name: 'manyToOne1', exact: true }).click();
await page.getByRole('menuitem', { name: 'manyToOne1 right' }).hover();
await page.getByRole('menuitem', { name: 'manyToOne2', exact: true }).click();
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
await page.mouse.move(600, 0);
// 2. Click on the association field, create a details block in the popup, display the ID field, and assert if it's correct
await page
.getByRole('button', { name: `${record.manyToOne1.id}`, exact: true })
.getByText(record.manyToOne1.id)
.click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Details right' }).hover();
await page.getByRole('menuitem', { name: 'Current record' }).click();
await page.getByLabel('schema-initializer-Grid-details:configureFields-targetCollection1').hover();
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
await page.mouse.move(600, 0);
await expect(page.getByLabel('block-item-CollectionField-')).toHaveText(`ID:${record.manyToOne1.id}`);
await page.getByLabel('drawer-AssociationField.Viewer-targetCollection1-View record-mask').click();
await page
.getByRole('button', { name: `${record.manyToOne1.manyToOne2.id}`, exact: true })
.getByText(record.manyToOne1.manyToOne2.id)
.click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Details right' }).hover();
await page.getByRole('menuitem', { name: 'Current record' }).click();
await page.getByLabel('schema-initializer-Grid-details:configureFields-targetCollection2').hover();
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
await page.mouse.move(600, 0);
await expect(page.getByLabel('block-item-CollectionField-')).toHaveText(`ID:${record.manyToOne1.manyToOne2.id}`);
await page.getByLabel('drawer-AssociationField.Viewer-targetCollection2-View record-mask').click();
await page
.getByRole('button', { name: `${record.manyToOne1.manyToOne2.manyToOne3.id}`, exact: true })
.getByText(record.manyToOne1.manyToOne2.manyToOne3.id)
.click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Details right' }).hover();
await page.getByRole('menuitem', { name: 'Current record' }).click();
await page.getByLabel('schema-initializer-Grid-details:configureFields-emptyCollection').hover();
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
await page.mouse.move(600, 0);
await expect(page.getByLabel('block-item-CollectionField-')).toHaveText(
`ID:${record.manyToOne1.manyToOne2.manyToOne3.id}`,
);
await page.getByLabel('drawer-AssociationField.Viewer-emptyCollection-View record-mask').click();
// 测试详情区块的关系字段
// 1. 点击行操作按钮打开弹窗,创建一个详情区块,并配置第一、二、三级关系字段
await page.getByLabel('action-Action.Link-Edit-').first().click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Details right' }).hover();
await page.getByRole('menuitem', { name: 'Current record' }).click();
await page.getByLabel('schema-initializer-Grid-details:configureFields-general').hover();
await page.getByRole('menuitem', { name: 'manyToOne1', exact: true }).click();
await page.getByRole('menuitem', { name: 'manyToOne1 right' }).hover();
await page.getByRole('menuitem', { name: 'manyToOne2', exact: true }).click();
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
await page.mouse.move(600, 0);
// 2. 点击每一个关系字段,创建一个详情区块,显示 ID 字段,断言 ID 是否正确
await page
.getByLabel('block-item-CollectionField-general-details-general.manyToOne1-manyToOne1')
.getByText(String(record.manyToOne1.id), { exact: true })
.click();
await page
.getByTestId('drawer-AssociationField.Viewer-targetCollection1-View record')
.getByLabel('schema-initializer-Grid-popup')
.hover();
await page.getByRole('menuitem', { name: 'table Details right' }).hover();
await page.getByRole('menuitem', { name: 'Current record' }).click();
await page.getByLabel('schema-initializer-Grid-details:configureFields-targetCollection1').hover();
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
await expect(
page
.getByTestId('drawer-AssociationField.Viewer-targetCollection1-View record')
.getByLabel('block-item-CollectionField-'),
).toHaveText(`ID:${record.manyToOne1.id}`);
await page.getByLabel('drawer-AssociationField.Viewer-targetCollection1-View record-mask').click();
// another field
await page
.getByLabel('block-item-CollectionField-general-details-targetCollection1.manyToOne2-')
.getByText(String(record.manyToOne1.manyToOne2.id), { exact: true })
.click();
await page
.getByTestId('drawer-AssociationField.Viewer-targetCollection2-View record')
.getByLabel('schema-initializer-Grid-popup')
.hover();
await page.getByRole('menuitem', { name: 'table Details right' }).hover();
await page.getByRole('menuitem', { name: 'Current record' }).click();
await page.getByLabel('schema-initializer-Grid-details:configureFields-targetCollection2').hover();
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
await expect(
page
.getByTestId('drawer-AssociationField.Viewer-targetCollection2-View record')
.getByLabel('block-item-CollectionField-'),
).toHaveText(`ID:${record.manyToOne1.manyToOne2.id}`);
await page.getByLabel('drawer-AssociationField.Viewer-targetCollection2-View record-mask').click();
// another field
await page
.getByLabel('block-item-CollectionField-general-details-targetCollection2.manyToOne3-')
.getByText(String(record.manyToOne1.manyToOne2.manyToOne3.id), { exact: true })
.click();
await page
.getByTestId('drawer-AssociationField.Viewer-emptyCollection-View record')
.getByLabel('schema-initializer-Grid-popup')
.hover();
await page.getByRole('menuitem', { name: 'table Details right' }).hover();
await page.getByRole('menuitem', { name: 'Current record' }).click();
await page.getByLabel('schema-initializer-Grid-details:configureFields-emptyCollection').hover();
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
await expect(
page
.getByTestId('drawer-AssociationField.Viewer-emptyCollection-View record')
.getByLabel('block-item-CollectionField-'),
).toHaveText(`ID:${record.manyToOne1.manyToOne2.manyToOne3.id}`);
});
test.pgOnly('display inherit fields', async ({ page, mockPage, mockRecord }) => {
const nocoPage = await mockPage(oneTableWithInheritFields).waitForInit();
const record = await mockRecord('child');

View File

@ -9,10 +9,11 @@
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { toArr } from '@formily/shared';
import _ from 'lodash';
import React, { FC, Fragment, useRef, useState } from 'react';
import { useDesignable } from '../../';
import { WithoutTableFieldResource } from '../../../block-provider';
import { useCollectionManager, useCollectionRecordData } from '../../../data-source';
import { CollectionRecordProvider, useCollectionManager, useCollectionRecordData } from '../../../data-source';
import { useOpenModeContext } from '../../../modules/popup/OpenModeProvider';
import { VariablePopupRecordProvider } from '../../../modules/variable/variablesProvider/VariablePopupRecordProvider';
import { useCompile } from '../../hooks';
@ -133,6 +134,25 @@ interface ReadPrettyInternalViewerProps {
};
}
/**
* the sourceData is used to get the sourceId
* @param recordData
* @param fieldSchema
* @returns
*/
const getSourceData = (recordData, fieldSchema) => {
const sourceRecordKey = (fieldSchema.name as string)
.split('.')
.filter((o, i, arr) => i < arr.length - 1)
.join('.');
if (!sourceRecordKey) {
return recordData;
}
return _.get(recordData, sourceRecordKey);
};
export const ReadPrettyInternalViewer: React.FC = observer(
(props: ReadPrettyInternalViewerProps) => {
const { value, ButtonList = ButtonLinkList } = props;
@ -146,10 +166,13 @@ export const ReadPrettyInternalViewer: React.FC = observer(
const { visibleWithURL, setVisibleWithURL } = usePopupUtils();
const [btnHover, setBtnHover] = useState(!!visibleWithURL);
const { defaultOpenMode } = useOpenModeContext();
const recordData = useCollectionRecordData();
const btnElement = (
<EllipsisWithTooltip ellipsis={true} ref={ellipsisWithTooltipRef}>
<ButtonList setBtnHover={setBtnHover} value={value} fieldNames={props.fieldNames} />
<CollectionRecordProvider isNew={false} record={getSourceData(recordData, fieldSchema)}>
<ButtonList setBtnHover={setBtnHover} value={value} fieldNames={props.fieldNames} />
</CollectionRecordProvider>
</EllipsisWithTooltip>
);

View File

@ -24,8 +24,13 @@ import {
} from '../';
import { useFormBlockContext } from '../block-provider/FormBlockProvider';
import { useFormActiveFields } from '../block-provider/hooks/useFormActiveFields';
import { FieldOptions, useCollectionManager_deprecated, useCollection_deprecated } from '../collection-manager';
import { Collection, CollectionFieldOptions } from '../data-source/collection/Collection';
import {
CollectionFieldOptions_deprecated,
FieldOptions,
useCollectionManager_deprecated,
useCollection_deprecated,
} from '../collection-manager';
import { Collection, CollectionFieldOptions, CollectionOptions } from '../data-source/collection/Collection';
import { useDataSourceManager } from '../data-source/data-source/DataSourceManagerProvider';
import { isAssocField } from '../filter-provider/utils';
import { useActionContext, useCompile, useDesignable } from '../schema-component';
@ -172,60 +177,132 @@ export function useTableColumnInitializerFields() {
export function useAssociatedTableColumnInitializerFields() {
const { name, fields } = useCollection_deprecated();
const { t } = useTranslation();
const { getInterface, getCollectionFields, getCollection } = useCollectionManager_deprecated();
const groups = fields
?.filter((field) => {
return ['o2o', 'oho', 'obo', 'm2o'].includes(field.interface);
})
?.map((field) => {
const subFields = getCollectionFields(field.target);
const items = subFields
// ?.filter((subField) => subField?.interface && !['o2o', 'oho', 'obo', 'o2m', 'm2o', 'subTable', 'linkTo'].includes(subField?.interface))
?.filter(
(subField) => subField?.interface && !['subTable'].includes(subField?.interface) && !subField?.treeChildren,
)
?.map((subField) => {
const interfaceConfig = getInterface(subField.interface);
const schema = {
// type: 'string',
name: `${field.name}.${subField.name}`,
// title: subField?.uiSchema?.title || subField.name,
'x-component': 'CollectionField',
'x-read-pretty': true,
'x-collection-field': `${name}.${field.name}.${subField.name}`,
'x-component-props': {},
};
return {
type: 'item',
name: subField.name,
title: subField?.uiSchema?.title || subField.name,
Component: 'TableCollectionFieldInitializer',
find: findTableColumn,
remove: removeTableColumn,
schemaInitialize: (s) => {
interfaceConfig?.schemaInitialize?.(s, {
field: subField,
readPretty: true,
block: 'Table',
targetCollection: getCollection(field.target),
});
},
field: subField,
schema,
} as SchemaInitializerItemType;
});
return {
type: 'subMenu',
name: field.uiSchema?.title,
title: field.uiSchema?.title,
children: items,
} as SchemaInitializerItemType;
return getGroupItemForTable({
getCollectionFields,
field,
getInterface,
getCollection,
schemaName: field.name,
maxDepth: 2,
depth: 1,
t,
});
});
return groups;
}
function getGroupItemForTable({
getCollectionFields,
field,
getInterface,
getCollection,
schemaName,
maxDepth,
depth,
t,
}: {
getCollectionFields: (name: any, customDataSource?: string) => CollectionFieldOptions_deprecated[];
field: CollectionFieldOptions;
getInterface: (name: string) => any;
getCollection: (name: any, customDataSource?: string) => CollectionOptions;
schemaName: string;
maxDepth: number;
depth: number;
t: any;
}) {
const subFields = getCollectionFields(field.target);
const items = subFields
?.filter(
(subField) => subField?.interface && !['subTable'].includes(subField?.interface) && !subField?.treeChildren,
)
?.map((subField) => {
const interfaceConfig = getInterface(subField.interface);
const newSchemaName = `${schemaName}.${subField.name}`;
const schema = {
// type: 'string',
name: newSchemaName,
// title: subField?.uiSchema?.title || subField.name,
'x-component': 'CollectionField',
'x-read-pretty': true,
'x-collection-field': `${field.target}.${subField.name}`,
'x-component-props': {},
};
return {
type: 'item',
name: newSchemaName,
title: subField?.uiSchema?.title || subField.name,
Component: 'TableCollectionFieldInitializer',
find: findTableColumn,
remove: removeTableColumn,
schemaInitialize: (s) => {
interfaceConfig?.schemaInitialize?.(s, {
field: subField,
readPretty: true,
block: 'Table',
targetCollection: getCollection(field.target),
});
},
field: subField,
schema,
} as SchemaInitializerItemType;
});
const displayCollectionFields = {
type: 'itemGroup',
name: `${schemaName}-displayCollectionFields`,
title: t('Display fields'),
children: items,
};
const children = [displayCollectionFields];
if (depth < maxDepth) {
const subChildren = subFields
?.filter((subField) => {
return ['o2o', 'oho', 'obo', 'm2o'].includes(subField.interface);
})
.map((subField) => {
return getGroupItemForTable({
getCollectionFields,
field: subField,
getInterface,
getCollection,
schemaName: `${schemaName}.${subField.name}`,
maxDepth,
depth: depth + 1,
t,
});
});
if (subChildren.length) {
const group: any = {
type: 'itemGroup',
name: `${schemaName}-associationFields`,
title: t('Display association fields'),
children: subChildren,
};
children.push(group);
}
}
return {
type: 'subMenu',
name: `${schemaName}`,
title: field.uiSchema?.title,
children,
} as SchemaInitializerItemType;
}
export function useInheritsTableColumnInitializerFields() {
const { name } = useCollection_deprecated();
const { getInterface, getInheritCollections, getCollection, getParentCollectionFields } =
@ -429,6 +506,7 @@ export const useAssociatedFormItemInitializerFields = (options?: any) => {
const { name, fields } = useCollection_deprecated();
const { getInterface, getCollectionFields, getCollection } = useCollectionManager_deprecated();
const form = useForm();
const { t } = useTranslation();
const { readPretty = form.readPretty, block = 'Form' } = options || {};
const interfaces = block === 'Form' ? ['m2o'] : ['o2o', 'oho', 'obo', 'm2o'];
const groups = fields
@ -436,58 +514,18 @@ export const useAssociatedFormItemInitializerFields = (options?: any) => {
return interfaces.includes(field.interface);
})
?.map((field) => {
const subFields = getCollectionFields(field.target);
const items = subFields
?.filter(
(subField) => subField?.interface && !['subTable'].includes(subField?.interface) && !subField.treeChildren,
)
?.map((subField) => {
const interfaceConfig = getInterface(subField.interface);
const isFileCollection = field?.target && getCollection(field?.target)?.template === 'file';
const schema = {
type: 'string',
name: `${field.name}.${subField.name}`,
// 'x-designer': 'FormItem.Designer',
'x-toolbar': 'FormItemSchemaToolbar',
'x-settings': 'fieldSettings:FormItem',
'x-component': 'CollectionField',
'x-read-pretty': readPretty,
'x-component-props': {
'pattern-disable': block === 'Form' && readPretty,
fieldNames: isFileCollection
? {
label: 'preview',
value: 'id',
}
: undefined,
},
'x-decorator': 'FormItem',
'x-collection-field': `${name}.${field.name}.${subField.name}`,
};
return {
name: subField?.uiSchema?.title || subField.name,
type: 'item',
title: subField?.uiSchema?.title || subField.name,
Component: 'CollectionFieldInitializer',
remove: removeGridFormItem,
schemaInitialize: (s) => {
interfaceConfig?.schemaInitialize?.(s, {
field: subField,
block,
readPretty,
targetCollection: getCollection(field.target),
});
},
schema,
} as SchemaInitializerItemType;
});
return {
type: 'subMenu',
name: field.uiSchema?.title,
title: field.uiSchema?.title,
children: items,
} as SchemaInitializerItemType;
return getGroupItemForForm({
getCollectionFields,
field,
getInterface,
getCollection,
readPretty,
block,
schemaName: field.name,
maxDepth: 2,
depth: 1,
t,
});
});
return groups;
};
@ -1540,6 +1578,123 @@ const getChildren = ({
});
};
function getGroupItemForForm({
getCollectionFields,
field,
getInterface,
getCollection,
readPretty,
block,
maxDepth,
depth,
schemaName,
t,
}: {
getCollectionFields: (name: any, customDataSource?: string) => CollectionFieldOptions_deprecated[];
field: CollectionFieldOptions;
getInterface: (name: string) => any;
getCollection: (name: any, customDataSource?: string) => CollectionOptions;
readPretty: any;
block: any;
maxDepth: number;
depth: number;
schemaName: string;
t: any;
}) {
const subFields = getCollectionFields(field.target);
const items = subFields
?.filter((subField) => subField?.interface && !['subTable'].includes(subField?.interface) && !subField.treeChildren)
?.map((subField) => {
const interfaceConfig = getInterface(subField.interface);
const isFileCollection = field?.target && getCollection(field?.target)?.template === 'file';
const newSchemaName = `${schemaName}.${subField.name}`;
const schema = {
type: 'string',
name: newSchemaName,
// 'x-designer': 'FormItem.Designer',
'x-toolbar': 'FormItemSchemaToolbar',
'x-settings': 'fieldSettings:FormItem',
'x-component': 'CollectionField',
'x-read-pretty': readPretty,
'x-component-props': {
'pattern-disable': block === 'Form' && readPretty,
fieldNames: isFileCollection
? {
label: 'preview',
value: 'id',
}
: undefined,
},
'x-decorator': 'FormItem',
'x-collection-field': `${field.target}.${subField.name}`,
};
return {
name: newSchemaName,
type: 'item',
title: subField?.uiSchema?.title || subField.name,
Component: 'CollectionFieldInitializer',
remove: removeGridFormItem,
schemaInitialize: (s) => {
interfaceConfig?.schemaInitialize?.(s, {
field: subField,
block,
readPretty,
targetCollection: getCollection(field.target),
});
},
schema,
} as SchemaInitializerItemType;
});
const displayCollectionFields = {
type: 'itemGroup',
name: `${schemaName}-displayCollectionFields`,
title: t('Display fields'),
children: items,
};
const children = [displayCollectionFields];
if (depth < maxDepth) {
const subChildren = subFields
?.filter((subField) => {
return ['o2o', 'oho', 'obo', 'm2o'].includes(subField.interface);
})
.map((subField) => {
return getGroupItemForForm({
getCollectionFields,
field: subField,
getInterface,
getCollection,
schemaName: `${schemaName}.${subField.name}`,
readPretty,
block,
maxDepth,
depth: depth + 1,
t,
});
});
if (subChildren.length) {
const group: any = {
type: 'itemGroup',
name: `${schemaName}-associationFields`,
title: t('Display association fields'),
children: subChildren,
};
children.push(group);
}
}
return {
type: 'subMenu',
name: `${schemaName}.${field.name}`,
title: field.uiSchema?.title,
children,
} as SchemaInitializerItemType;
}
function useAssociationFields({
componentName,
filterCollections,

View File

@ -7,11 +7,10 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { css } from '@emotion/css';
import { ISchema, Schema, useField, useForm } from '@formily/react';
import { Select } from 'antd';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Select } from 'antd';
import { useCollectionManager_deprecated, useDesignable } from '..';
import { SchemaSettingsModalItem } from './SchemaSettings';
@ -41,7 +40,7 @@ export const SchemaSettingsNumberFormat = function NumberFormatConfig(props: { f
const collectionField = getCollectionJoinField(fieldSchema?.['x-collection-field']) || {};
const { formatStyle, unitConversion, unitConversionType, separator, step, addonBefore, addonAfter } =
fieldSchema['x-component-props'] || {};
const { step: prescition } = collectionField?.uiSchema['x-component-props'] || {};
const { step: prescition } = collectionField?.uiSchema?.['x-component-props'] || {};
return (
<SchemaSettingsModalItem