mirror of
https://github.com/nocobase/nocobase.git
synced 2026-09-19 02:23:00 +08:00
Merge branch 'main' into next
This commit is contained in:
@@ -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} />
|
||||
|
||||
+49
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user