fix: subdetail fields (#9008)

This commit is contained in:
Jiann
2026-04-02 10:32:15 +08:00
committed by GitHub
parent 06b67d44f6
commit dd5d6aa88e
2 changed files with 105 additions and 1 deletions
@@ -20,6 +20,7 @@ import React from 'react';
import { FieldModel } from '../../base';
import { DetailsGridModel } from './DetailsGridModel';
import { rebuildFieldSubModel } from '../../../internal/utils/rebuildFieldSubModel';
import { isToManyAssociationField } from '../../../internal/utils/modelUtils';
/**
* 从 record 中取值
@@ -58,6 +59,50 @@ export function getValueWithIndex(record: any, fieldPath: string, idx?: string[]
return get(record, path);
}
export function getDetailsItemValue(options: {
record: any;
currentObject?: any;
fieldPath: string;
prefixFieldPath?: string;
idx?: string[];
}) {
const { record, currentObject, fieldPath, prefixFieldPath, idx } = options;
const valueFromRecord = getValueWithIndex(record, fieldPath, idx);
if (valueFromRecord !== undefined) {
return valueFromRecord;
}
if (currentObject == null) {
return valueFromRecord;
}
if (prefixFieldPath && fieldPath === prefixFieldPath) {
return currentObject;
}
const relativeFieldPath =
prefixFieldPath && fieldPath.startsWith(`${prefixFieldPath}.`)
? fieldPath.slice(prefixFieldPath.length + 1)
: fieldPath;
if (!relativeFieldPath) {
return currentObject;
}
return get(currentObject, relativeFieldPath);
}
function normalizeDetailsAssociationValue(collectionField: any, value: any) {
if (!isToManyAssociationField(collectionField)) {
return value;
}
if (Array.isArray(value)) {
return value;
}
if (value && typeof value === 'object' && Array.isArray((value as { rows?: unknown[] }).rows)) {
return (value as { rows: unknown[] }).rows;
}
if (value == null) {
return [];
}
return [value];
}
export class DetailsItemModel extends DisplayItemModel<{
parent: DetailsGridModel;
subModels: { field: FieldModel };
@@ -122,6 +167,7 @@ export class DetailsItemModel extends DisplayItemModel<{
const currentObject = this.context.currentObject;
const item = this.context.item;
const itemOptions = this.context.getPropertyOptions('item');
const prefixFieldPath = this.context.prefixFieldPath;
// 嵌套场景下继续传透,为字段子模型创建 fork
const modelForRender =
@@ -161,7 +207,16 @@ export class DetailsItemModel extends DisplayItemModel<{
disabled: this.context.pattern === 'readPretty',
}
: { ...this.context.blockModel.props, ...this.props };
const value = getValueWithIndex(record, this.fieldPath, idx);
const value = normalizeDetailsAssociationValue(
this.collectionField,
getDetailsItemValue({
record,
currentObject,
fieldPath: this.fieldPath,
prefixFieldPath,
idx,
}),
);
return (
<FormItem {...mergedProps} value={value}>
<FieldModelRenderer model={modelForRender} />
@@ -0,0 +1,49 @@
/**
* 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 { describe, expect, it } from 'vitest';
import { getDetailsItemValue, getValueWithIndex } from '../DetailsItemModel';
describe('DetailsItemModel value resolving', () => {
it('reads nested list item from record with index chain', () => {
expect(
getValueWithIndex(
{
users: [{ profile: { nickname: 'A' } }],
},
'users.profile.nickname',
['users:0'],
),
).toBe('A');
});
it('falls back to currentObject with relative field path in sub-detail rows', () => {
expect(
getDetailsItemValue({
record: { unrelated: true },
currentObject: { nickname: 'B' },
fieldPath: 'users.nickname',
prefixFieldPath: 'users',
idx: ['users:0'],
}),
).toBe('B');
});
it('returns currentObject itself when field path equals prefix field path', () => {
const currentObject = { id: 1, nickname: 'C' };
expect(
getDetailsItemValue({
record: {},
currentObject,
fieldPath: 'users',
prefixFieldPath: 'users',
}),
).toBe(currentObject);
});
});