refactor: table column field model

This commit is contained in:
katherinehhh 2025-06-19 19:57:18 +08:00
parent f52b24eacb
commit 5f74104862
8 changed files with 247 additions and 44 deletions

View File

@ -76,6 +76,9 @@ export class TableColumnModel extends FieldFlowModel {
setComponentProps(props) {
this.setProps('componentProps', { ...(this.props.componentProps || {}), ...props });
}
getComponentProps() {
return this.props.componentProps;
}
renderQuickEditButton(record) {
return (
@ -204,6 +207,7 @@ TableColumnModel.registerFlow({
ctx.model.field = field;
ctx.model.fieldPath = params.fieldPath;
ctx.model.setProps('dataIndex', field.name);
ctx.model.setComponentProps(field.getComponentProps());
},
},
editColumTitle: {

View File

@ -0,0 +1,36 @@
/**
* 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 { isTitleField } from '../../../data-source';
export const loadTitleFieldOptions = (collectionField, dataSourceManager) => {
return async (field) => {
const form = field.form;
const compile = form?.designable?.compile || ((v) => v);
const collectionManager = collectionField?.collection?.collectionManager;
const target = collectionField?.options?.target;
if (!collectionManager || !target) return;
const targetCollection = collectionManager.getCollection(target);
const targetFields = targetCollection?.getFields?.() ?? [];
field.loading = true;
const options = targetFields
.filter((field) => isTitleField(dataSourceManager, field.options))
.map((field) => ({
value: field.name,
label: compile(field.options.uiSchema?.title) || field.name,
}));
field.dataSource = options;
field.loading = false;
};
};

View File

@ -9,8 +9,9 @@
import React from 'react';
import { Select } from 'antd';
import { connect, mapReadPretty, mapProps } from '@formily/react';
import { isTitleField } from '../../../../../data-source';
import { FormFieldModel } from '../../../FormFieldModel';
import { loadTitleFieldOptions } from '../../../common/utils';
import { getUniqueKeyFromCollection } from '../../../../../collection-manager/interfaces/utils';
function toValue(record: any | any[], fieldNames, multiple = false) {
if (!record) return multiple ? [] : undefined;
@ -90,8 +91,7 @@ export const DEFAULT_ASSOCIATION_PAGE_SIZE = 40;
async function fetchAssociationItems({ ctx, page = 1, searchText = '' }) {
const { target } = ctx.model.collectionField.options;
const fieldNames = ctx.model.field.componentProps.fieldNames;
const labelField = fieldNames?.label;
const labelField = fieldNames?.label || 'id';
const apiClient = ctx.app.apiClient;
const collectionManager = ctx.model.collectionField.collection.collectionManager;
const targetCollection = collectionManager.getCollection(target);
@ -108,7 +108,6 @@ async function fetchAssociationItems({ ctx, page = 1, searchText = '' }) {
},
}
: undefined;
const response = await apiClient.request({
url: `/${target}:list`,
params: {
@ -237,32 +236,6 @@ AssociationSelectFieldModel.registerFlow({
},
});
const loadTitleFieldOptions = (collectionField, dataSourceManager) => {
return async (field) => {
const form = field.form;
const compile = form?.designable?.compile || ((v) => v);
const collectionManager = collectionField?.collection?.collectionManager;
const target = collectionField?.options?.target;
if (!collectionManager || !target) return;
const targetCollection = collectionManager.getCollection(target);
const targetFields = targetCollection?.getFields?.() ?? [];
field.loading = true;
const options = targetFields
.filter((field) => isTitleField(dataSourceManager, field.options))
.map((field) => ({
value: field.name,
label: compile(field.uiSchema?.title) || field.name,
}));
field.dataSource = options;
field.loading = false;
};
};
// 标题字段设置
AssociationSelectFieldModel.registerFlow({
key: 'fieldNames',
@ -278,21 +251,28 @@ AssociationSelectFieldModel.registerFlow({
'x-reactions': ['{{loadTitleFieldOptions(collectionField, dataSourceManager)}}'],
},
},
defaultParams: (ctx) => ({
label: ctx.model.field.componentProps.fieldNames?.label,
}),
defaultParams: (ctx) => {
const { target } = ctx.model.collectionField.options;
const collectionManager = ctx.model.collectionField.collection.collectionManager;
const targetCollection = collectionManager.getCollection(target);
return {
label: ctx.model.field.componentProps.fieldNames?.label || targetCollection.options.titleField,
};
},
handler(ctx, params) {
ctx.model.flowEngine.flowSettings.registerScopes({
loadTitleFieldOptions,
collectionField: ctx.model.collectionField,
dataSourceManager: ctx.app.dataSourceManager,
});
const { target } = ctx.model.collectionField.options;
const collectionManager = ctx.model.collectionField.collection.collectionManager;
const targetCollection = collectionManager.getCollection(target);
const newFieldNames = {
...ctx.model.field.componentProps.fieldNames,
label: params.label,
value: getUniqueKeyFromCollection(targetCollection.options as any),
label: params.label || targetCollection.options.titleField,
};
ctx.model.field.setComponentProps({ fieldNames: newFieldNames });
},
},

View File

@ -0,0 +1,182 @@
/**
* 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 React from 'react';
import { toArr, isArr } from '@formily/shared';
import { getDefaultFormat, str2moment } from '@nocobase/utils/client';
import { Tag } from 'antd';
import { useCompile } from '../../../../../schema-component/hooks';
import { TableColumnModel } from '../../../TableColumnModel';
import { loadTitleFieldOptions } from '../../../common/utils';
import { getUniqueKeyFromCollection } from '../../../../../collection-manager/interfaces/utils';
export function transformNestedData(inputData) {
const resultArray = [];
function recursiveTransform(data) {
if (data?.parent) {
const { parent } = data;
recursiveTransform(parent);
}
const { parent, ...other } = data;
resultArray.push(other);
}
if (inputData) {
recursiveTransform(inputData);
}
return resultArray;
}
const toValue = (value, placeholder) => {
if (value === null || value === undefined) {
return placeholder;
}
return value;
};
const getDatePickerLabels = (props): string => {
const format = getDefaultFormat(props) as string;
const m = str2moment(props.value, props);
const labels = m && m.isValid() ? m.format(format) : props.value;
return isArr(labels) ? labels.join('~') : labels;
};
const getLabelFormatValue = (labelUiSchema, value: any, isTag = false): any => {
const options = labelUiSchema?.enum;
if (Array.isArray(options) && value) {
const values = toArr(value).map((val) => {
const opt: any = options.find((option: any) => option.value === val);
if (isTag) {
return React.createElement(Tag, { color: opt?.color }, opt?.label);
}
return opt?.label;
});
return isTag ? values : values.join(', ');
}
switch (labelUiSchema?.['x-component']) {
case 'DatePicker':
return getDatePickerLabels({ ...labelUiSchema?.['x-component-props'], value });
default:
return value;
}
};
const getLabelUiSchema = (collectionManager, target, label) => {
const targetCollection = collectionManager.getCollection(target);
const targetLabelField = targetCollection.getField(label);
return targetLabelField?.uiSchema;
};
const AssociationSelectReadPretty = (props) => {
const { value, fieldNames, collectionField, ellipsis, cm } = props;
const compile = useCompile();
const targetCollection = cm.getCollection(collectionField?.target);
const isTreeCollection = targetCollection?.template === 'tree';
const labelUiSchema = getLabelUiSchema(cm, collectionField?.target, fieldNames?.label);
const items = toArr(value)
.map((record, index) => {
if (!record) return null;
let rawLabel = record?.[fieldNames?.label || 'label'];
// 树形结构下转换为路径形式
if (isTreeCollection) {
const pathLabels = transformNestedData(record).map((o) => o?.[fieldNames?.label || 'label']);
rawLabel = pathLabels.join(' / ');
} else if (typeof rawLabel === 'object' && rawLabel !== null) {
rawLabel = JSON.stringify(rawLabel);
}
const compiledLabel = toValue(compile(rawLabel), 'N/A');
const text = getLabelFormatValue(compile(labelUiSchema), compiledLabel, true);
return (
<React.Fragment key={record?.[fieldNames.value] ?? index}>
{index > 0 && ','}
{text}
</React.Fragment>
);
})
.filter(Boolean);
return <span style={ellipsis ? null : { whiteSpace: 'normal' }}>{items}</span>;
};
export class AssociationSelectReadPrettyFieldModel extends TableColumnModel {
public static readonly supportedFieldInterfaces = [
'm2m',
'm2o',
'o2o',
'o2m',
'oho',
'obo',
'updatedBy',
'createdBy',
];
render() {
console.log(this);
return (value, record, index) => {
return (
<>
{
<AssociationSelectReadPretty
{...this.getComponentProps()}
collectionField={this.field.options}
value={value}
cm={this.field.collection.dataSource.collectionManager}
/>
}
{this.renderQuickEditButton(record)}
</>
);
};
}
}
//标题字段设置 todo 复用
AssociationSelectReadPrettyFieldModel.registerFlow({
key: 'fieldNames',
auto: true,
sort: 200,
steps: {
fieldNames: {
title: 'Title field',
uiSchema: {
label: {
'x-component': 'Select',
'x-decorator': 'FormItem',
'x-reactions': ['{{loadTitleFieldOptions(collectionField, dataSourceManager)}}'],
},
},
defaultParams: (ctx) => {
const { target } = ctx.model.field.options;
const collectionManager = ctx.model.field.collection.collectionManager;
const targetCollection = collectionManager.getCollection(target);
return {
label: ctx.model.getComponentProps().fieldNames?.label || targetCollection.options.titleField,
};
},
handler(ctx, params) {
const { target } = ctx.model.field.options;
const collectionManager = ctx.model.field.collection.collectionManager;
const targetCollection = collectionManager.getCollection(target);
ctx.model.flowEngine.flowSettings.registerScopes({
loadTitleFieldOptions,
collectionField: ctx.model.field,
dataSourceManager: ctx.app.dataSourceManager,
});
const filterKey = getUniqueKeyFromCollection(targetCollection.options as any);
const newFieldNames = {
value: filterKey,
label: params.label || targetCollection.options.titleField || filterKey,
};
ctx.model.setComponentProps({ fieldNames: newFieldNames });
},
},
},
});

View File

@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next';
import { TableColumnModel } from '../../TableColumnModel';
import { InputNumberReadPretty } from '../components/InputNumberReadPretty';
export class NumberColumnModel extends TableColumnModel {
export class NumberReadPrettyFieldModel extends TableColumnModel {
public static readonly supportedFieldInterfaces = ['number'];
render() {
return (value, record, index) => {
@ -45,7 +45,7 @@ const UnitConversion = () => {
);
};
NumberColumnModel.registerFlow({
NumberReadPrettyFieldModel.registerFlow({
key: 'format',
sort: 100,
title: 'Specific properties',

View File

@ -21,7 +21,7 @@ const toValue = (value: any, callback: (v: number) => number) => {
}
return null;
};
export class PercentColumnModel extends TableColumnModel {
export class PercentReadPrettyFieldModel extends TableColumnModel {
public static readonly supportedFieldInterfaces = ['percent'];
render() {
return (value, record, index) => {

View File

@ -12,7 +12,7 @@ import { Tag } from 'antd';
import { TableColumnModel } from '../../TableColumnModel';
import { getCurrentOptions } from '../utils/utils';
export class SelectTableColumnModel extends TableColumnModel {
export class SelectReadPrettyFieldModel extends TableColumnModel {
public static readonly supportedFieldInterfaces = ['select', 'multipleSelect'];
dataSource;
fieldNames: { label: string; value: string; color?: string; icon?: any };
@ -49,7 +49,7 @@ export class SelectTableColumnModel extends TableColumnModel {
}
}
SelectTableColumnModel.registerFlow({
SelectReadPrettyFieldModel.registerFlow({
key: 'options',
auto: true,
sort: 100,

View File

@ -7,6 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
export * from './SelectColumnModel';
export * from './NumberColumnModel';
export * from './PercentColumnModel';
export * from './SelectFieldModel';
export * from './NumberFieldModel';
export * from './PercentFieldModel';
export * from './AssociationSelectFieldModel';