mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
Refactor/block model (#7137)
* refactor: block model flow * fix: bug * refactor: block title * fix: bug * refactor: code improve * fix: bug
This commit is contained in:
parent
bb3d9a78ec
commit
a06623ba9d
@ -6,13 +6,16 @@
|
||||
* 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 { tval } from '@nocobase/utils/client';
|
||||
import { APIResource, BaseRecordResource, Collection, DefaultStructure, FlowModel } from '@nocobase/flow-engine';
|
||||
import { Card } from 'antd';
|
||||
import React from 'react';
|
||||
import { BlockItemCard } from '../common/BlockItemCard';
|
||||
|
||||
export class BlockModel<T = DefaultStructure> extends FlowModel<T> {
|
||||
decoratorProps: Record<string, any> = {};
|
||||
setDecoratorProps(props) {
|
||||
this.decoratorProps = { ...this.decoratorProps, ...props };
|
||||
}
|
||||
|
||||
renderComponent() {
|
||||
throw new Error('renderComponent method must be implemented in subclasses of BlockModel');
|
||||
@ -20,10 +23,92 @@ export class BlockModel<T = DefaultStructure> extends FlowModel<T> {
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Card {...this.decoratorProps}>{this.renderComponent()}</Card>;
|
||||
return <BlockItemCard {...this.decoratorProps}>{this.renderComponent()}</BlockItemCard>;
|
||||
}
|
||||
}
|
||||
|
||||
export const HeightMode = {
|
||||
DEFAULT: 'defaultHeight',
|
||||
SPECIFY_VALUE: 'specifyValue',
|
||||
FULL_HEIGHT: 'fullHeight',
|
||||
};
|
||||
|
||||
BlockModel.registerFlow({
|
||||
key: 'blockProps',
|
||||
title: tval('Basic configuration'),
|
||||
auto: true,
|
||||
steps: {
|
||||
editBlockTitleAndDescription: {
|
||||
title: tval('Edit block title & description'),
|
||||
uiSchema: {
|
||||
title: {
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
title: tval('Title'),
|
||||
},
|
||||
description: {
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-decorator': 'FormItem',
|
||||
title: tval('Description'),
|
||||
},
|
||||
},
|
||||
handler(ctx, params) {
|
||||
const title = ctx.globals.flowEngine.translate(params.title);
|
||||
const description = ctx.globals.flowEngine.translate(params.description);
|
||||
ctx.model.setDecoratorProps({ title: title, description: description });
|
||||
},
|
||||
},
|
||||
setBlockHeight: {
|
||||
title: tval('Set block height'),
|
||||
uiSchema: {
|
||||
heightMode: {
|
||||
type: 'string',
|
||||
enum: [
|
||||
{ label: tval('Default'), value: HeightMode.DEFAULT },
|
||||
{ label: tval('Specify height'), value: HeightMode.SPECIFY_VALUE },
|
||||
{ label: tval('Full height'), value: HeightMode.FULL_HEIGHT },
|
||||
],
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
},
|
||||
height: {
|
||||
title: tval('Height'),
|
||||
type: 'string',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'NumberPicker',
|
||||
'x-component-props': {
|
||||
addonAfter: 'px',
|
||||
},
|
||||
'x-validator': [
|
||||
{
|
||||
minimum: 40,
|
||||
},
|
||||
],
|
||||
'x-reactions': {
|
||||
dependencies: ['heightMode'],
|
||||
fulfill: {
|
||||
state: {
|
||||
hidden: '{{ $deps[0]==="fullHeight"||$deps[0]==="defaultHeight"}}',
|
||||
value: '{{$deps[0]!=="specifyValue"?null:$self.value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultParams: () => {
|
||||
return {
|
||||
heightMode: HeightMode.DEFAULT,
|
||||
};
|
||||
},
|
||||
handler(ctx, params) {
|
||||
ctx.model.setProps('heightMode', params.heightMode);
|
||||
ctx.model.setProps('height', params.height);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
export class DataBlockModel<T = DefaultStructure> extends BlockModel<T> {
|
||||
resource: APIResource;
|
||||
collection: Collection;
|
||||
|
@ -8,20 +8,12 @@
|
||||
*/
|
||||
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Input } from '@formily/antd-v5';
|
||||
import { uid } from '@formily/shared';
|
||||
import {
|
||||
AddBlockButton,
|
||||
FlowModel,
|
||||
FlowModelRenderer,
|
||||
FlowSettingsButton,
|
||||
FlowsFloatContextMenu,
|
||||
useStepSettingContext,
|
||||
} from '@nocobase/flow-engine';
|
||||
import { AddBlockButton, FlowModel, FlowModelRenderer, FlowSettingsButton } from '@nocobase/flow-engine';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { Alert, Space } from 'antd';
|
||||
import { Space } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { Grid } from '../../components/Grid';
|
||||
import JsonEditor from '../../components/JsonEditor';
|
||||
import { SkeletonFallback } from '../../components/SkeletonFallback';
|
||||
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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 { theme, Card } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||
import { MarkdownReadPretty } from '../fields/EditableField/MarkdownEditableFieldModel';
|
||||
|
||||
export const BlockItemCard = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const { token } = theme.useToken();
|
||||
const { title: blockTitle, description, children } = props;
|
||||
const title = (blockTitle || description) && (
|
||||
<div style={{ padding: '8px 0px 8px' }}>
|
||||
<span> {t(blockTitle, { ns: NAMESPACE_UI_SCHEMA })}</span>
|
||||
{description && (
|
||||
<MarkdownReadPretty
|
||||
value={t(description, { ns: NAMESPACE_UI_SCHEMA })}
|
||||
style={{
|
||||
overflowWrap: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
fontWeight: 400,
|
||||
color: token.colorTextDescription,
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<Card
|
||||
title={title}
|
||||
style={{ display: 'flex', flexDirection: 'column', height: '100%' }}
|
||||
bodyStyle={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
|
||||
>
|
||||
{children}
|
||||
</Card>
|
||||
);
|
||||
};
|
@ -12,7 +12,6 @@ import { createForm, Form } from '@formily/core';
|
||||
import { FormProvider } from '@formily/react';
|
||||
import { AddActionButton, AddFieldButton, FlowModelRenderer, SingleRecordResource } from '@nocobase/flow-engine';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { Card } from 'antd';
|
||||
import React from 'react';
|
||||
import { DataBlockModel } from '../../base/BlockModel';
|
||||
import { EditableFieldModel } from '../../fields/EditableField/EditableFieldModel';
|
||||
@ -21,54 +20,51 @@ export class FormModel extends DataBlockModel {
|
||||
form: Form;
|
||||
declare resource: SingleRecordResource;
|
||||
|
||||
render() {
|
||||
console.log(this);
|
||||
renderComponent() {
|
||||
return (
|
||||
<Card>
|
||||
<FormProvider form={this.form}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
{this.mapSubModels('fields', (field) => (
|
||||
<FlowModelRenderer
|
||||
model={field}
|
||||
showFlowSettings={{ showBorder: false }}
|
||||
sharedContext={{ currentRecord: this.resource.getData() }}
|
||||
/>
|
||||
))}
|
||||
</FormLayout>
|
||||
<AddFieldButton
|
||||
buildCreateModelOptions={({ defaultOptions, fieldPath }) => ({
|
||||
use: defaultOptions.use,
|
||||
stepParams: {
|
||||
default: {
|
||||
step1: {
|
||||
dataSourceKey: this.collection.dataSourceKey,
|
||||
collectionName: this.collection.name,
|
||||
fieldPath,
|
||||
},
|
||||
<FormProvider form={this.form}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
{this.mapSubModels('fields', (field) => (
|
||||
<FlowModelRenderer
|
||||
model={field}
|
||||
showFlowSettings={{ showBorder: false }}
|
||||
sharedContext={{ currentRecord: this.resource.getData() }}
|
||||
/>
|
||||
))}
|
||||
</FormLayout>
|
||||
<AddFieldButton
|
||||
buildCreateModelOptions={({ defaultOptions, fieldPath }) => ({
|
||||
use: defaultOptions.use,
|
||||
stepParams: {
|
||||
default: {
|
||||
step1: {
|
||||
dataSourceKey: this.collection.dataSourceKey,
|
||||
collectionName: this.collection.name,
|
||||
fieldPath,
|
||||
},
|
||||
},
|
||||
})}
|
||||
subModelKey="fields"
|
||||
model={this}
|
||||
collection={this.collection}
|
||||
subModelBaseClass="EditableFieldModel"
|
||||
onSubModelAdded={async (model: EditableFieldModel) => {
|
||||
const params = model.getStepParams('default', 'step1');
|
||||
this.addAppends(params?.fieldPath, !!this.ctx.shared?.currentFlow?.extra?.filterByTk);
|
||||
}}
|
||||
/>
|
||||
<FormButtonGroup style={{ marginTop: 16 }}>
|
||||
{this.mapSubModels('actions', (action) => (
|
||||
<FlowModelRenderer
|
||||
model={action}
|
||||
showFlowSettings={{ showBackground: false, showBorder: false }}
|
||||
sharedContext={{ currentRecord: this.resource.getData() }}
|
||||
/>
|
||||
))}
|
||||
<AddActionButton model={this} subModelBaseClass="FormActionModel" />
|
||||
</FormButtonGroup>
|
||||
</FormProvider>
|
||||
</Card>
|
||||
},
|
||||
})}
|
||||
subModelKey="fields"
|
||||
model={this}
|
||||
collection={this.collection}
|
||||
subModelBaseClass="EditableFieldModel"
|
||||
onSubModelAdded={async (model: EditableFieldModel) => {
|
||||
const params = model.getStepParams('default', 'step1');
|
||||
this.addAppends(params?.fieldPath, !!this.ctx.shared?.currentFlow?.extra?.filterByTk);
|
||||
}}
|
||||
/>
|
||||
<FormButtonGroup style={{ marginTop: 16 }}>
|
||||
{this.mapSubModels('actions', (action) => (
|
||||
<FlowModelRenderer
|
||||
model={action}
|
||||
showFlowSettings={{ showBackground: false, showBorder: false }}
|
||||
sharedContext={{ currentRecord: this.resource.getData() }}
|
||||
/>
|
||||
))}
|
||||
<AddActionButton model={this} subModelBaseClass="FormActionModel" />
|
||||
</FormButtonGroup>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -20,11 +20,11 @@ import {
|
||||
useFlowEngine,
|
||||
} from '@nocobase/flow-engine';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { Card, Space, Spin, Table } from 'antd';
|
||||
import { Space, Spin, Table } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { t } from 'i18next';
|
||||
import _ from 'lodash';
|
||||
import React, { useRef } from 'react';
|
||||
import { BlockItemCard } from '../../common/BlockItemCard';
|
||||
import { ActionModel } from '../../base/ActionModel';
|
||||
import { DataBlockModel } from '../../base/BlockModel';
|
||||
import { QuickEditForm } from '../form/QuickEditForm';
|
||||
|
@ -9,6 +9,7 @@
|
||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||
import { Select } from 'antd';
|
||||
import React from 'react';
|
||||
import { castArray } from 'lodash';
|
||||
import { useFlowModel, FlowModel } from '@nocobase/flow-engine';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { AssociationFieldEditableFieldModel } from './AssociationFieldEditableFieldModel';
|
||||
@ -82,7 +83,42 @@ const AssociationSelect = connect(
|
||||
},
|
||||
),
|
||||
mapReadPretty((props) => {
|
||||
return props.value;
|
||||
const currentModel: any = useFlowModel();
|
||||
|
||||
const { fieldNames, value } = props;
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
const field = currentModel.subModels.field as FlowModel;
|
||||
const key = value?.[fieldNames.value];
|
||||
const fieldModel = field.createFork({}, key);
|
||||
fieldModel.setSharedContext({
|
||||
value: value?.[fieldNames.label],
|
||||
currentRecord: value,
|
||||
});
|
||||
const arrayValue = castArray(value);
|
||||
|
||||
return (
|
||||
<>
|
||||
{arrayValue.map((v, index) => {
|
||||
const key = `${index}`;
|
||||
const fieldModel = field.createFork({}, key);
|
||||
fieldModel.setSharedContext({
|
||||
index,
|
||||
value: v?.[fieldNames.label],
|
||||
currentRecord: v,
|
||||
});
|
||||
|
||||
const content = v?.[fieldNames.label] ? fieldModel.render() : tval('N/A');
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{index > 0 && ', '}
|
||||
{content}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@ -160,7 +196,7 @@ AssociationSelectEditableFieldModel.registerFlow({
|
||||
steps: {
|
||||
step1: {
|
||||
async handler(ctx, params) {
|
||||
const { target } = ctx.model.collectionField.options;
|
||||
const { target } = ctx.model.collectionField;
|
||||
const apiClient = ctx.app.apiClient;
|
||||
const response = await apiClient.request({
|
||||
url: `/${target}:list`,
|
||||
@ -244,9 +280,7 @@ AssociationSelectEditableFieldModel.registerFlow({
|
||||
async handler(ctx, params) {
|
||||
try {
|
||||
const collectionField = ctx.model.collectionField;
|
||||
const collectionManager = collectionField.collection.collectionManager;
|
||||
const targetCollection = collectionManager.getCollection(collectionField.options.target);
|
||||
|
||||
const targetCollection = ctx.model.collectionField.targetCollection;
|
||||
const labelFieldName = ctx.model.field.componentProps.fieldNames.label;
|
||||
const targetLabelField = targetCollection.getField(labelFieldName);
|
||||
|
||||
|
@ -119,7 +119,7 @@ EditableFieldModel.registerFlow({
|
||||
if (collectionField.enum.length) {
|
||||
ctx.model.setDataSource(collectionField.enum);
|
||||
}
|
||||
const validator = collectionField.options.uiSchema?.['x-validator'];
|
||||
const validator = collectionField.uiSchema?.['x-validator'];
|
||||
if (validator) {
|
||||
ctx.model.setValidator(validator);
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ export const MarkdownReadPretty = (props) => {
|
||||
<div
|
||||
className={` ${markdownClass} nb-markdown nb-markdown-default nb-markdown-table`}
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
style={props.style}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -12,7 +12,7 @@ import { customAlphabet as Alphabet } from 'nanoid';
|
||||
import { EditableFieldModel } from './EditableFieldModel';
|
||||
|
||||
export class NanoIDEditableFieldModel extends EditableFieldModel {
|
||||
static supportedFieldInterfaces = ['nanoID'];
|
||||
static supportedFieldInterfaces = ['nanoid'];
|
||||
|
||||
get component() {
|
||||
return [Input, {}];
|
||||
@ -22,7 +22,7 @@ export class NanoIDEditableFieldModel extends EditableFieldModel {
|
||||
NanoIDEditableFieldModel.registerFlow({
|
||||
key: 'initialValue',
|
||||
auto: true,
|
||||
sort: 5,
|
||||
sort: 1000,
|
||||
steps: {
|
||||
initialValue: {
|
||||
handler(ctx, params) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user