mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
refactor: table column field model
This commit is contained in:
parent
f52b24eacb
commit
5f74104862
@ -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: {
|
||||
|
36
packages/core/client/src/flow/models/common/utils.ts
Normal file
36
packages/core/client/src/flow/models/common/utils.ts
Normal 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;
|
||||
};
|
||||
};
|
@ -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 });
|
||||
},
|
||||
},
|
||||
|
@ -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 });
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
@ -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',
|
@ -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) => {
|
@ -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,
|
@ -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';
|
||||
|
Loading…
x
Reference in New Issue
Block a user