feat: display association fields (#512)

* feat: association field features

* fix: remove comments

* fix: association field in creation form will trigger error

* fix: column decorator title

* fix: column designer title

* fix: association field in table

* feat: adjust documents

* fix: remove m2o subfield mode

* fix: adjust title field display condition

* fix: relation field title bug

* fix: o2m multiple is true

* feat: association fields are loaded on demand

* fix: support sub field

* feat: remove FormField require config

* fix: two lines in Columns config menu of table block

* fix: could not find schema node

* fix: add form context to internal table block

* fix(client): non-empty judgment

* feat: translations

* fix: add / edit field title compile

* fix: unique

* fix: association feature bugs

* feat: add oho & o2m selector filter

* fix: add field added logic in FormField and TableField

* fix: remove updateAssociationValues middleware

* feat: recordprovider in association fields

* feat: add kanban association appends

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
金昶 2022-06-29 12:57:43 +08:00 committed by GitHub
parent 95d0b524d0
commit 32e744319e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 829 additions and 225 deletions

View File

@ -9,6 +9,7 @@ import { KanbanBlockProvider, useKanbanBlockProps } from './KanbanBlockProvider'
import { TableBlockProvider, useTableBlockProps } from './TableBlockProvider';
import { TableFieldProvider, useTableFieldProps } from './TableFieldProvider';
import { TableSelectorProvider, useTableSelectorProps } from './TableSelectorProvider';
import { FormFieldProvider, useFormFieldProps } from './FormFieldProvider';
export const BlockSchemaComponentProvider: React.FC = (props) => {
return (
@ -19,6 +20,7 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
TableBlockProvider,
TableSelectorProvider,
FormBlockProvider,
FormFieldProvider,
DetailsBlockProvider,
KanbanBlockProvider,
RecordLink,
@ -30,6 +32,7 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
useParamsFromRecord,
useCalendarBlockProps,
useFormBlockProps,
useFormFieldProps,
useDetailsBlockProps,
useTableFieldProps,
useTableBlockProps,

View File

@ -1,5 +1,5 @@
import { createForm } from '@formily/core';
import { useField } from '@formily/react';
import { useField, useFieldSchema } from '@formily/react';
import { Spin } from 'antd';
import React, { createContext, useContext, useEffect, useMemo } from 'react';
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
@ -29,6 +29,7 @@ const InternalFormBlockProvider = (props) => {
field,
service,
resource,
updateAssociationValues: [],
}}
>
{props.children}

View File

@ -0,0 +1,87 @@
import { createForm, onFieldReact, onFormInputChange, onFormValuesChange } from '@formily/core';
import { ArrayField, Field, ObjectField } from '@formily/core';
import { useField, useFieldSchema } from '@formily/react';
import { Spin } from 'antd';
import React, { createContext, useContext, useEffect, useMemo } from 'react';
import { RecordProvider } from '../record-provider';
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
import { useFormBlockContext } from './FormBlockProvider';
export const FormFieldContext = createContext<any>({});
const InternalFormFieldProvider = (props) => {
const { action, readPretty, fieldName } = props;
const formBlockCtx = useFormBlockContext();
if (!formBlockCtx?.updateAssociationValues?.includes(fieldName)) {
formBlockCtx?.updateAssociationValues?.push(fieldName);
}
const field = useField();
const form = useMemo(
() =>
createForm({
effects() {
onFormValuesChange((form) => {
formBlockCtx?.form?.setValuesIn(fieldName, form.values);
});
},
readPretty,
}),
[],
);
const { resource, service } = useBlockRequestContext();
if (service.loading) {
return <Spin />;
}
console.log('InternalFormFieldProvider', fieldName);
return (
<RecordProvider record={service?.data?.data}>
<FormFieldContext.Provider
value={{
action,
form,
field,
service,
resource,
fieldName,
}}
>
{props.children}
</FormFieldContext.Provider>
</RecordProvider>
);
}
export const WithoutFormFieldResource = createContext(null);
export const FormFieldProvider = (props) => {
console.log('FormFieldProvider', props);
return (
<WithoutFormFieldResource.Provider value={false}>
<BlockProvider block={'FormField'} {...props}>
<InternalFormFieldProvider {...props} />
</BlockProvider>
</WithoutFormFieldResource.Provider>
)
}
export const useFormFieldContext = () => {
return useContext(FormFieldContext);
};
export const useFormFieldProps = () => {
const ctx = useFormFieldContext();
useEffect(() => {
ctx?.form?.setInitialValues(ctx.service?.data?.data);
}, []);
return {
form: ctx.form,
};
}

View File

@ -1,9 +1,10 @@
import { ArrayField } from '@formily/core';
import { useField } from '@formily/react';
import { Schema, useField, useFieldSchema } from '@formily/react';
import { Spin } from 'antd';
import uniq from 'lodash/uniq';
import React, { createContext, useContext, useEffect } from 'react';
import { useACLRoleContext } from '../acl';
import { useCollection } from '../collection-manager';
import { useCollection, useCollectionManager } from '../collection-manager';
import { toColumns } from '../schema-component/antd/kanban/Kanban';
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
@ -48,9 +49,61 @@ const InternalKanbanBlockProvider = (props) => {
);
};
const recursiveProperties = (schema: Schema, component = 'CollectionField', associationFields, appends = []) => {
schema.mapProperties((s) => {
const name = s.name.toString();
if (s['x-component'] === component && !appends.includes(name)) {
// 关联字段和关联的关联字段
const [firstName] = name.split('.');
if (associationFields.has(name)) {
appends.push(name);
} else if (associationFields.has(firstName) && !appends.includes(firstName)) {
appends.push(firstName);
}
} else {
recursiveProperties(s, component, associationFields, appends);
}
})
}
export const useAssociationNames = (collection) => {
const { getCollectionFields } = useCollectionManager();
const collectionFields = getCollectionFields(collection);
const associationFields = new Set();
for (const collectionField of collectionFields) {
if (collectionField.target) {
associationFields.add(collectionField.name);
const fields = getCollectionFields(collectionField.target);
for (const field of fields) {
if (field.target) {
associationFields.add(`${collectionField.name}.${field.name}`);
}
}
}
}
const fieldSchema = useFieldSchema();
const kanbanSchema = fieldSchema.reduceProperties((buf, schema) => {
if (schema['x-component'] === 'KanbanV2') {
return schema;
}
return buf;
}, new Schema({}));
const gridSchema = kanbanSchema?.properties?.card?.properties?.grid;
const appends = [];
recursiveProperties(gridSchema, 'CollectionField', associationFields, appends);
console.log('useAssociationNames', appends);
return uniq(appends);
};
export const KanbanBlockProvider = (props) => {
const params = { ...props.params };
const appends = useAssociationNames(props.collection);
console.log('KanbanBlockProvider', appends);
if (!Object.keys(params).includes('appends')) {
params['appends'] = appends;
}
return (
<BlockProvider {...props}>
<BlockProvider {...props} params={params}>
<InternalKanbanBlockProvider {...props} />
</BlockProvider>
);

View File

@ -1,15 +1,26 @@
import { ArrayField, Field } from '@formily/core';
import { useField } from '@formily/react';
import { useField, useFieldSchema } from '@formily/react';
import React, { createContext, useContext, useEffect } from 'react';
import { APIClient } from '../api-client';
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
import { useFormBlockContext } from './FormBlockProvider';
import { useFormFieldContext } from './FormFieldProvider';
export const TableFieldContext = createContext<any>({});
const InternalTableFieldProvider = (props) => {
const { params = {}, showIndex, dragSort } = props;
const { params = {}, showIndex, dragSort, fieldName } = props;
const field = useField();
const { resource, service } = useBlockRequestContext();
const formBlockCtx = useFormBlockContext();
const formFieldCtx = useFormFieldContext();
const fullFieldName = formFieldCtx && formFieldCtx.fieldName ? `${formFieldCtx.fieldName}.${fieldName}` : fieldName;
if (!formBlockCtx?.updateAssociationValues?.includes(fullFieldName)) {
formBlockCtx?.updateAssociationValues?.push(fullFieldName);
}
// if (service.loading) {
// return <Spin />;
// }

View File

@ -1,8 +1,10 @@
import { ArrayField } from '@formily/core';
import { useField } from '@formily/react';
import { Schema, useField, useFieldSchema } from '@formily/react';
import React, { createContext, useContext, useEffect } from 'react';
import { useCollectionManager } from '../collection-manager';
import { useCollection, useCollectionManager } from '../collection-manager';
import { useRecord } from '../record-provider';
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
import { useFormBlockContext } from './FormBlockProvider';
export const TableSelectorContext = createContext<any>({});
@ -36,7 +38,23 @@ const useAssociationNames = (collection) => {
return names;
};
const recursiveParent = (schema: Schema, component) => {
return schema['x-component'] === component
? schema
: (schema.parent ? recursiveParent(schema.parent, component) : null);
}
export const TableSelectorProvider = (props) => {
const fieldSchema = useFieldSchema();
const ctx = useFormBlockContext()
const { getCollectionJoinField } = useCollectionManager();
const record = useRecord();
const collectionFieldSchema = recursiveParent(fieldSchema, 'CollectionField');
// const value = ctx.form.query(collectionFieldSchema?.name).value();
const collectionField = getCollectionJoinField(collectionFieldSchema?.['x-collection-field']);
console.log('TableSelectorProvider', collectionFieldSchema, collectionField, record);
const params = { ...props.params };
const appends = useAssociationNames(props.collection);
if (props.dragSort) {
@ -45,6 +63,23 @@ export const TableSelectorProvider = (props) => {
if (appends?.length) {
params['appends'] = appends;
}
if (collectionField) {
if (['oho', 'o2m'].includes(collectionField.interface)) {
params['filter'] = {
$or: [{
[collectionField.foreignKey]: {
$is: null,
}
}, {
[collectionField.foreignKey]: {
$eq: record?.[collectionField.sourceKey],
}
}]
}
}
if (['obo'].includes(collectionField.interface)) {
}
}
return (
<BlockProvider {...props} params={params}>
<InternalTableSelectorProvider {...props} params={params} />

View File

@ -1,5 +1,6 @@
import { Schema as SchemaCompiler } from '@formily/json-schema';
import { useField, useFieldSchema, useForm } from '@formily/react';
import { useFormBlockContext } from '@nocobase/client';
import { message, Modal } from 'antd';
import get from 'lodash/get';
import { useTranslation } from 'react-i18next';
@ -73,7 +74,7 @@ function getFormValues(filterByTk, field, form, fieldNames, getField, resource)
if (field.added && !field.added.has(key)) {
continue;
}
if (['subTable', 'o2m'].includes(collectionField.interface)) {
if (['subTable', 'o2m', 'o2o', 'oho', 'obo', 'm2o'].includes(collectionField.interface)) {
values[key] = form.values[key];
continue;
}
@ -304,9 +305,11 @@ export const useUpdateActionProps = () => {
const { setVisible } = useActionContext();
const actionSchema = useFieldSchema();
const history = useHistory();
const record = useRecord();
const { fields, getField } = useCollection();
const compile = useCompile();
const actionField = useField();
const { updateAssociationValues } = useFormBlockContext();
return {
async onClick() {
const { assignedValues, onSuccess, overwriteValues, skipValidator } = actionSchema?.['x-action-settings'] ?? {};
@ -315,7 +318,7 @@ export const useUpdateActionProps = () => {
await form.submit();
}
const fieldNames = fields.map((field) => field.name);
const values = getFormValues(filterByTk, field, form, fieldNames, getField, resource);
let values = getFormValues(filterByTk, field, form, fieldNames, getField, resource);
actionField.data = field.data || {};
actionField.data.loading = true;
try {
@ -326,6 +329,7 @@ export const useUpdateActionProps = () => {
...overwriteValues,
...assignedValues,
},
updateAssociationValues
});
actionField.data.loading = false;
if (!(resource instanceof TableFieldResource)) {

View File

@ -7,4 +7,5 @@ export * from './KanbanBlockProvider';
export * from './TableBlockProvider';
export * from './TableFieldProvider';
export * from './TableSelectorProvider';
export * from './FormFieldProvider';

View File

@ -9,7 +9,6 @@ import { useCollectionField } from './hooks';
// TODO: 初步适配
const InternalField: React.FC = (props) => {
const field = useField<Field>();
const fieldSchema = useFieldSchema();
const { name, interface: interfaceType, uiSchema } = useCollectionField();
const component = useComponent(uiSchema?.['x-component']);
@ -68,13 +67,15 @@ const InternalField: React.FC = (props) => {
if (!uiSchema) {
return null;
}
return React.createElement(component, props, props.children);
};
export const CollectionField = connect((props) => {
const fieldSchema = useFieldSchema();
const field = fieldSchema?.['x-component-props']?.['field'];
return (
<CollectionFieldProvider name={fieldSchema.name}>
<CollectionFieldProvider name={fieldSchema.name} field={field}>
<InternalField {...props} />
</CollectionFieldProvider>
);

View File

@ -1,13 +1,15 @@
import { SchemaKey } from '@formily/react';
import { SchemaKey, useFieldSchema } from '@formily/react';
import React from 'react';
import { CollectionFieldContext } from './context';
import { useCollection } from './hooks';
import { useCollection, useCollectionManager } from './hooks';
import { CollectionFieldOptions } from './types';
export const CollectionFieldProvider: React.FC<{ name?: SchemaKey; field?: CollectionFieldOptions }> = (props) => {
const { name, field, children } = props;
const fieldSchema = useFieldSchema();
const { getField } = useCollection();
const value = field || getField(field?.name || name);
const { getCollectionJoinField } = useCollectionManager();
const value = field || getField(field?.name || name) || getCollectionJoinField(fieldSchema?.['x-collection-field']);
if (!value) {
return null;
}

View File

@ -44,7 +44,7 @@ const getSchema = (schema: IField, record: any, compile): ISchema => {
);
},
},
title: `${record.title} - ${compile('{{ t("Add field") }}')}`,
title: `${compile(record.title)} - ${compile('{{ t("Add field") }}')}`,
properties: {
summary: {
type: 'void',

View File

@ -37,7 +37,7 @@ const getSchema = (schema: IField, record: any, compile): ISchema => {
);
},
},
title: `${record.__parent?.title} - ${compile('{{ t("Edit field") }}')}`,
title: `${compile(record.__parent?.title)} - ${compile('{{ t("Edit field") }}')}`,
properties: {
summary: {
type: 'void',

View File

@ -45,15 +45,22 @@ export const linkTo: IField = {
},
},
},
schemaInitialize(schema: ISchema, { readPretty }) {
if (readPretty) {
schemaInitialize(schema: ISchema, { readPretty, block }) {
if (block === 'Form') {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
};
} else {
schema['properties'] = {
selector: cloneDeep(recordPickerSelector),
};
} else {
if (readPretty) {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
};
} else {
schema['properties'] = {
selector: cloneDeep(recordPickerSelector),
}
}
}
},
initialize: (values: any) => {

View File

@ -46,14 +46,21 @@ export const m2m: IField = {
},
},
schemaInitialize(schema: ISchema, { readPretty, block }) {
if (readPretty) {
if (block === 'Form') {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
};
} else {
schema['properties'] = {
selector: cloneDeep(recordPickerSelector),
};
} else {
if (readPretty) {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
};
} else {
schema['properties'] = {
selector: cloneDeep(recordPickerSelector),
}
}
}
if (['Table', 'Kanban'].includes(block)) {
schema['x-component-props'] = schema['x-component-props'] || {};

View File

@ -44,15 +44,63 @@ export const m2o: IField = {
},
},
},
schemaInitialize(schema: ISchema, { readPretty, block }) {
if (readPretty) {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
};
schemaInitialize(schema: ISchema, { field, block, readPretty, action }) {
if (block === 'Form' && schema['x-component'] === 'FormField') {
const association = `${field.collectionName}.${field.name}`;
schema.type = 'void';
schema.properties = {
block: {
type: 'void',
'x-decorator': 'FormFieldProvider',
'x-decorator-props': {
collection: field.target,
association: association,
resource: association,
action,
fieldName: field.name,
readPretty
},
'x-component': 'CardItem',
'x-component-props': {
bordered: true,
},
properties: {
[field.name]: {
type: 'object',
'x-component': 'FormV2',
'x-component-props': {
useProps: '{{ useFormFieldProps }}',
},
properties: {
__form_grid: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'FormItemInitializers',
properties: {},
},
}
},
},
},
}
} else {
schema['properties'] = {
selector: cloneDeep(recordPickerSelector),
};
schema.type = 'string';
if (block === 'Form') {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
selector: cloneDeep(recordPickerSelector),
};
} else {
if (readPretty) {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
};
} else {
schema['properties'] = {
selector: cloneDeep(recordPickerSelector),
}
}
}
}
if (['Table', 'Kanban'].includes(block)) {
schema['x-component-props'] = schema['x-component-props'] || {};

View File

@ -1,5 +1,6 @@
import { ISchema } from '@formily/react';
import { relationshipType } from './properties';
import { cloneDeep } from 'lodash';
import { recordPickerSelector, recordPickerViewer, relationshipType } from './properties';
import { IField } from './types';
export const o2m: IField = {
@ -15,14 +16,14 @@ export const o2m: IField = {
// name,
uiSchema: {
// title,
'x-component': 'TableField',
'x-component': 'RecordPicker',
'x-component-props': {
// mode: 'tags',
// multiple: true,
// fieldNames: {
// label: 'id',
// value: 'id',
// },
multiple: true,
fieldNames: {
label: 'id',
value: 'id',
},
},
},
reverseField: {
@ -43,47 +44,68 @@ export const o2m: IField = {
},
},
},
schemaInitialize(schema: ISchema, { field, readPretty, block }) {
const association = `${field.collectionName}.${field.name}`;
schema['type'] = 'void';
schema['x-component'] = 'TableField';
schema['properties'] = {
block: {
type: 'void',
'x-decorator': 'TableFieldProvider',
'x-decorator-props': {
collection: field.target,
association: association,
resource: association,
action: 'list',
params: {
paginate: false,
schemaInitialize(schema: ISchema, { field, block, readPretty }) {
if (block === 'Form' && schema['x-component'] === 'TableField') {
const association = `${field.collectionName}.${field.name}`;
schema['type'] = 'void';
schema['properties'] = {
block: {
type: 'void',
'x-decorator': 'TableFieldProvider',
'x-decorator-props': {
collection: field.target,
association: association,
resource: association,
action: 'list',
params: {
paginate: false,
},
showIndex: true,
dragSort: false,
fieldName: field.name,
},
showIndex: true,
dragSort: false,
},
properties: {
actions: {
type: 'void',
'x-initializer': 'SubTableActionInitializers',
'x-component': 'TableField.ActionBar',
'x-component-props': {},
},
[field.name]: {
type: 'array',
'x-initializer': 'TableColumnInitializers',
'x-component': 'TableV2',
'x-component-props': {
rowSelection: {
type: 'checkbox',
properties: {
actions: {
type: 'void',
'x-initializer': 'SubTableActionInitializers',
'x-component': 'TableField.ActionBar',
'x-component-props': {},
},
[field.name]: {
type: 'array',
'x-initializer': 'TableColumnInitializers',
'x-component': 'TableV2',
'x-component-props': {
rowSelection: {
type: 'checkbox',
},
useProps: '{{ useTableFieldProps }}',
},
useProps: '{{ useTableFieldProps }}',
},
},
},
},
};
};
} else {
schema['x-component'] = 'CollectionField';
schema.type = 'object';
if (block === 'Form') {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
selector: cloneDeep(recordPickerSelector),
};
} else {
if (readPretty) {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
};
} else {
schema['properties'] = {
selector: cloneDeep(recordPickerSelector),
}
}
}
}
// if (readPretty) {
// schema['properties'] = {
// viewer: cloneDeep(recordPickerViewer),

View File

@ -3,6 +3,66 @@ import { cloneDeep } from 'lodash';
import { recordPickerSelector, recordPickerViewer, relationshipType } from './properties';
import { IField } from './types';
const internalSchameInitialize = (schema: ISchema, { field, block, readPretty, action }) => {
if (block === 'Form' && schema['x-component'] === 'FormField') {
const association = `${field.collectionName}.${field.name}`;
schema.type = 'void';
schema.properties = {
block: {
type: 'void',
'x-decorator': 'FormFieldProvider',
'x-decorator-props': {
collection: field.target,
association: association,
resource: association,
action: action,
fieldName: field.name,
readPretty,
},
'x-component': 'CardItem',
'x-component-props': {
bordered: true,
},
properties: {
[field.name]: {
type: 'object',
'x-component': 'FormV2',
'x-component-props': {
useProps: '{{ useFormFieldProps }}',
},
properties: {
__form_grid: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'FormItemInitializers',
properties: {},
},
}
},
},
},
}
} else {
schema.type = 'string';
if (block === 'Form') {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
selector: cloneDeep(recordPickerSelector),
};
} else {
if (readPretty) {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
};
} else {
schema['properties'] = {
selector: cloneDeep(recordPickerSelector),
}
}
}
}
}
export const o2o: IField = {
name: 'o2o',
type: 'object',
@ -45,16 +105,8 @@ export const o2o: IField = {
},
},
},
schemaInitialize(schema: ISchema, { readPretty, block }) {
if (readPretty) {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
};
} else {
schema['properties'] = {
selector: cloneDeep(recordPickerSelector),
};
}
schemaInitialize(schema: ISchema, { field, block, readPretty, action }) {
internalSchameInitialize(schema, { field, block, readPretty, action });
if (['Table', 'Kanban'].includes(block)) {
schema['x-component-props'] = schema['x-component-props'] || {};
schema['x-component-props']['ellipsis'] = true;
@ -217,16 +269,8 @@ export const oho: IField = {
},
},
},
schemaInitialize(schema: ISchema, { readPretty, block }) {
if (readPretty) {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
};
} else {
schema['properties'] = {
selector: cloneDeep(recordPickerSelector),
};
}
schemaInitialize(schema: ISchema, { field, block, readPretty, action }) {
internalSchameInitialize(schema, { field, block, readPretty, action });
if (['Table', 'Kanban'].includes(block)) {
schema['x-component-props'] = schema['x-component-props'] || {};
schema['x-component-props']['ellipsis'] = true;
@ -388,16 +432,8 @@ export const obo: IField = {
},
},
},
schemaInitialize(schema: ISchema, { readPretty, block }) {
if (readPretty) {
schema['properties'] = {
viewer: cloneDeep(recordPickerViewer),
};
} else {
schema['properties'] = {
selector: cloneDeep(recordPickerSelector),
};
}
schemaInitialize(schema: ISchema, { field, block, readPretty, action }) {
internalSchameInitialize(schema, { field, block, readPretty, action });
if (['Table', 'Kanban'].includes(block)) {
schema['x-component-props'] = schema['x-component-props'] || {};
schema['x-component-props']['ellipsis'] = true;

View File

@ -79,7 +79,7 @@ export default {
"Add new": "Add new",
"Add record": "Add record",
"Custom field display name": "Custom field display name",
"Display fields": "Display fields",
"Display fields": "Display collection fields",
"Edit record": "Edit record",
"Delete menu item": "Delete menu item",
"Add page": "Add page",
@ -541,5 +541,10 @@ export default {
"One to one (has one)": "One to one (has one)",
"One to one (belongs to)": "One to one (belongs to)",
"Use the same time zone (GMT) for all users": "Use the same time zone (GMT) for all users",
"Block title": "Block title",
"Edit block title": "Edit block title",
"Province/city/area name": "Province/city/area name",
"Field component": "Field component",
"Subtable": "Subtable",
"Subform": "Subform",
}

View File

@ -176,7 +176,11 @@ export default {
"Many to one description": "用于创建多对一关系,比如一个城市只能属于一个国家,一个国家可以有多个城市。作为字段存在时,它是一个下拉选择用于选择目标数据表的数据。创建后,会在目标数据表里自动生成一个多对一字段。",
"Many to many description": "用于创建多对多关系,比如一个学生会有多个老师,一个老师也会有多个学生。作为字段存在时,它是一个下拉选择用于选择目标数据表的数据。",
"Generated automatically if left blank": "留空时,自动生成中间表",
"References fields of associated table": "引用关联表的字段",
"Display association fields": "显示关联表的字段",
"Field component": "字段组件",
"Subtable": "子表格",
"Subform": "子表单",
"Record picker": "数据选择器",
"Toggles the subfield mode": "切换子字段模式",
"Selector mode": "选择器模式",
"Subtable mode": "子表格模式",

View File

@ -2,15 +2,26 @@ import { css } from '@emotion/css';
import { FormItem as Item } from '@formily/antd';
import { Field } from '@formily/core';
import { ISchema, useField, useFieldSchema } from '@formily/react';
import { uid } from '@formily/shared';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useCompile, useDesignable } from '../..';
import { useFormBlockContext } from '../../../block-provider';
import { useFilterByTk, useFormBlockContext } from '../../../block-provider';
import { useCollection, useCollectionManager } from '../../../collection-manager';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
import { BlockItem } from '../block-item';
import { HTMLEncode } from '../input/shared';
const divWrap = (schema: ISchema) => {
return {
type: 'void',
'x-component': 'div',
properties: {
[schema.name || uid()]: schema,
},
};
};
export const FormItem: any = (props) => {
const field = useField();
return (
@ -36,18 +47,21 @@ export const FormItem: any = (props) => {
);
};
FormItem.Designer = () => {
const { getCollectionFields } = useCollectionManager();
FormItem.Designer = (props) => {
const { getCollectionFields, getCollection, getInterface, getCollectionJoinField } = useCollectionManager();
const { getField } = useCollection();
const tk = useFilterByTk();
const { form } = useFormBlockContext();
const field = useField<Field>();
const fieldSchema = useFieldSchema();
const { t } = useTranslation();
const { dn, refresh } = useDesignable();
const { dn, refresh, insertAdjacent, insertBeforeBegin } = useDesignable();
const compile = useCompile();
const collectionField = getField(fieldSchema['name']);
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
const interfaceConfig = getInterface(collectionField?.interface);
const originalTitle = collectionField?.uiSchema?.title;
const targetFields = collectionField?.target ? getCollectionFields(collectionField.target) : [];
const isSubFormAssocitionField = field.address.segments.includes('__form_grid');
const initialValue = {
title: field.title === originalTitle ? undefined : field.title,
};
@ -168,7 +182,7 @@ FormItem.Designer = () => {
}}
/>
)}
{!field.readPretty && (
{!field.readPretty && fieldSchema['x-component'] !== 'FormField' && (
<SchemaSettings.SwitchItem
key="required"
title={t('Required')}
@ -187,6 +201,53 @@ FormItem.Designer = () => {
}}
/>
)}
{form && !isSubFormAssocitionField && ['o2o', 'oho', 'obo', 'o2m'].includes(collectionField?.interface) && (
<SchemaSettings.SelectItem
title={t('Field component')}
options={
collectionField?.interface === 'o2m'
? [
{ label: t('Record picker'), value: 'CollectionField' },
{ label: t('Subtable'), value: 'TableField' },
]
: [
{ label: t('Record picker'), value: 'CollectionField' },
{ label: t('Subform'), value: 'FormField' },
]
}
value={fieldSchema['x-component']}
onChange={(v) => {
const schema: ISchema = {
name: collectionField.name,
type: 'void',
// title: compile(collectionField.uiSchema?.title),
'x-decorator': 'FormItem',
'x-designer': 'FormItem.Designer',
'x-component': v,
'x-component-props': {},
'x-collection-field': fieldSchema['x-collection-field'],
};
interfaceConfig?.schemaInitialize?.(schema, {
field: collectionField,
block: 'Form',
readPretty: field.readPretty,
action: tk ? 'get' : null,
});
insertAdjacent('beforeBegin', divWrap(schema), {
onSuccess: () => {
dn.remove(null, {
removeParentsIfNoChildren: true,
breakRemoveOn: {
'x-component': 'Grid',
},
})
}
});
}}
/>
)}
{form && !form?.readPretty && collectionField?.interface !== 'o2m' && (
<SchemaSettings.SelectItem
key="pattern"
@ -198,7 +259,6 @@ FormItem.Designer = () => {
]}
value={readOnlyMode}
onChange={(v) => {
console.log('v', v);
const schema: ISchema = {
['x-uid']: fieldSchema['x-uid'],
};
@ -219,7 +279,6 @@ FormItem.Designer = () => {
schema['x-read-pretty'] = true;
schema['x-disabled'] = false;
field.readPretty = true;
// field.disabled = true;
break;
}
default: {
@ -232,6 +291,7 @@ FormItem.Designer = () => {
break;
}
}
dn.emit('patch', {
schema,
});
@ -240,7 +300,7 @@ FormItem.Designer = () => {
}}
/>
)}
{collectionField?.target && (
{collectionField?.target && fieldSchema['x-component'] === 'CollectionField' && (
<SchemaSettings.SelectItem
key="title-field"
title={t('Title field')}
@ -254,12 +314,15 @@ FormItem.Designer = () => {
...field.componentProps.fieldNames,
label,
};
// if (fieldSchema['x-component-props']?.['field']?.['uiSchema']?.['x-component-props']) {
// fieldSchema['x-component-props']['field']['uiSchema']['x-component-props']['fieldNames'] = fieldNames;
// } else {
// }
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
fieldSchema['x-component-props']['fieldNames'] = fieldNames;
field.componentProps.fieldNames = fieldNames;
schema['x-component-props'] = {
fieldNames,
};
schema['x-component-props'] = fieldSchema['x-component-props'];
dn.emit('patch', {
schema,
});

View File

@ -0,0 +1,24 @@
import { observer, useField, useFieldSchema, useForm } from '@formily/react';
import React, { useEffect } from 'react';
import { useFormBlockContext } from '../../../block-provider';
import { useCollection } from '../../../collection-manager';
import { useCompile } from '../../hooks';
export const FormField: any = observer((props) => {
const fieldSchema = useFieldSchema();
const { getField } = useCollection();
const field = useField();
const collectionField = getField(fieldSchema.name);
const compile = useCompile();
const ctx = useFormBlockContext();
useEffect(() => {
if (!field.title) {
field.title = compile(collectionField?.uiSchema?.title);
}
if (ctx?.field) {
ctx.field.added = ctx.field.added || new Set();
ctx.field.added.add(fieldSchema.name);
}
}, []);
return <div>{props.children}</div>;
});

View File

@ -5,3 +5,4 @@ FormV2.Designer = FormDesigner;
FormV2.ReadPrettyDesigner = ReadPrettyFormDesigner;
export { FormV2, DetailsDesigner };
export * from './FormField';

View File

@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
import { useAPIClient } from '../../../api-client';
import { createDesignable, useDesignable } from '../../../schema-component';
import { SchemaInitializer } from '../../../schema-initializer';
import { useFormItemInitializerFields } from '../../../schema-initializer/utils';
import { useAssociatedFormItemInitializerFields, useFormItemInitializerFields } from '../../../schema-initializer/utils';
const titleCss = css`
pointer-events: none;
@ -93,6 +93,14 @@ export const KanbanCardDesigner = (props: any) => {
title: t('Display fields'),
children: fields,
},
{
type: 'divider',
},
{
type: 'itemGroup',
title: t('Display association fields'),
children: useAssociatedFormItemInitializerFields({readPretty: true, block: 'KanbanV2'}),
},
]}
component={<MenuOutlined style={{ cursor: 'pointer', fontSize: 12 }} />}
/>
@ -101,3 +109,4 @@ export const KanbanCardDesigner = (props: any) => {
</div>
);
};

View File

@ -1,6 +1,6 @@
import { RecursionField, useFieldSchema } from '@formily/react';
import { Select } from 'antd';
import React, { createContext, useContext, useState } from 'react';
import React, { createContext, useContext, useEffect, useState } from 'react';
import { useTableSelectorProps as useTsp } from '../../../block-provider/TableSelectorProvider';
import { CollectionProvider, useCollection } from '../../../collection-manager';
import { FormProvider, SchemaComponentOptions } from '../../core';
@ -60,15 +60,31 @@ export const InputRecordPicker: React.FC<any> = (props) => {
const fieldSchema = useFieldSchema();
const collectionField = useAssociation(props);
const compile = useCompile();
const options = (Array.isArray(value) ? value : value ? [value] : []).map(option => {
const label = option[fieldNames.label];
return {
...option,
[fieldNames.label]: compile(label),
};
});
const [selectedRows, setSelectedRows] = useState(options);
const values = options?.map((option) => option[fieldNames.value]);
const [selectedRows, setSelectedRows] = useState([]);
const [options, setOptions] = useState([]);
useEffect(() => {
if (value) {
const opts = (Array.isArray(value) ? value : value ? [value] : []).map(option => {
const label = option[fieldNames.label];
return {
...option,
[fieldNames.label]: compile(label),
};
});
setOptions(opts);
setSelectedRows(opts);
}
}, [value])
useEffect(() => {})
const getValue = () => {
if (multiple == null) return null;
return multiple ? (Array.isArray(value) ? value?.map(v => v[fieldNames.value]) : []) : value?.[fieldNames.value];
}
return (
<div>
<Select
@ -93,7 +109,7 @@ export const InputRecordPicker: React.FC<any> = (props) => {
}
}}
options={options}
value={multiple ? values : values?.[0]}
value={getValue()}
open={false}
/>
<RecordPickerContext.Provider value={{ multiple, onChange, selectedRows, setSelectedRows }}>
@ -101,7 +117,13 @@ export const InputRecordPicker: React.FC<any> = (props) => {
<ActionContext.Provider value={{ openMode: 'drawer', visible, setVisible }}>
<FormProvider>
<SchemaComponentOptions scope={{ useTableSelectorProps, usePickActionProps }}>
<RecursionField schema={fieldSchema} onlyRenderProperties />
<RecursionField
schema={fieldSchema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] === 'RecordPicker.Selector';
}}
/>
</SchemaComponentOptions>
</FormProvider>
</ActionContext.Provider>

View File

@ -3,8 +3,8 @@ import { observer, RecursionField, useField, useFieldSchema } from '@formily/rea
import { toArr } from '@formily/shared';
import React, { useRef, useState } from 'react';
import { BlockAssociationContext, WithoutTableFieldResource } from '../../../block-provider';
import { CollectionProvider, useCollection } from '../../../collection-manager';
import { RecordProvider } from '../../../record-provider';
import { CollectionProvider, useCollection, useCollectionManager } from '../../../collection-manager';
import { RecordProvider, useRecord } from '../../../record-provider';
import { FormProvider } from '../../core';
import { useCompile } from '../../hooks';
import { ActionContext } from '../action';
@ -17,14 +17,17 @@ interface IEllipsisWithTooltipRef {
export const ReadPrettyRecordPicker: React.FC = observer((props: any) => {
const { ellipsis } = props;
const fieldSchema = useFieldSchema();
const recordCtx = useRecord();
const { getCollectionJoinField } = useCollectionManager();
const field = useField<Field>();
const fieldNames = useFieldNames(props);
const [visible, setVisible] = useState(false);
const [popoverVisible, setPopoverVisible] = useState<boolean>();
const { getField } = useCollection();
const collectionField = getField(fieldSchema.name);
const collectionField = getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']);
const [record, setRecord] = useState({});
const compile = useCompile();
const ellipsisWithTooltipRef = useRef<IEllipsisWithTooltipRef>();
const renderRecords = () =>
toArr(field.value).map((record, index, arr) => {
@ -47,6 +50,37 @@ export const ReadPrettyRecordPicker: React.FC = observer((props: any) => {
</>
);
});
const renderWithoutTableFieldResourceProvider = () => (
<WithoutTableFieldResource.Provider value={true}>
<FormProvider>
<RecursionField
schema={fieldSchema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] === 'RecordPicker.Viewer';
}}
/>
</FormProvider>
</WithoutTableFieldResource.Provider>
);
const renderRecordProvider = () => {
const collectionFieldNames = fieldSchema?.['x-collection-field']?.split('.');
return collectionFieldNames && collectionFieldNames.length > 2 ? (
<RecordProvider record={recordCtx[collectionFieldNames[1]]}>
<RecordProvider record={record}>
{renderWithoutTableFieldResourceProvider()}
</RecordProvider>
</RecordProvider>
) : (
<RecordProvider record={record}>
{renderWithoutTableFieldResourceProvider()}
</RecordProvider>
);
}
return collectionField ? (
<div>
<BlockAssociationContext.Provider value={`${collectionField.collectionName}.${collectionField.name}`}>
@ -55,13 +89,7 @@ export const ReadPrettyRecordPicker: React.FC = observer((props: any) => {
{renderRecords()}
</EllipsisWithTooltip>
<ActionContext.Provider value={{ visible, setVisible, openMode: 'drawer' }}>
<RecordProvider record={record}>
<WithoutTableFieldResource.Provider value={true}>
<FormProvider>
<RecursionField schema={fieldSchema} onlyRenderProperties />
</FormProvider>
</WithoutTableFieldResource.Provider>
</RecordProvider>
{renderRecordProvider()}
</ActionContext.Provider>
</CollectionProvider>
</BlockAssociationContext.Provider>

View File

@ -2,6 +2,8 @@ import { useFieldSchema } from "@formily/react";
export const useFieldNames = (props) => {
const fieldSchema = useFieldSchema();
const fieldNames = fieldSchema?.['x-component-props']?.['fieldNames'] || props.fieldNames;
const fieldNames = fieldSchema['x-component-props']?.['field']?.['uiSchema']?.['x-component-props']?.['fieldNames']
|| fieldSchema?.['x-component-props']?.['fieldNames']
|| props.fieldNames;
return { label: 'label', value: 'value', ...fieldNames };
};

View File

@ -1,12 +1,13 @@
import { useField, useFieldSchema } from '@formily/react';
import React, { useLayoutEffect } from 'react';
import { SortableItem, useCollection, useCompile, useDesignable, useDesigner } from '../../../';
import { SortableItem, useCollection, useCollectionManager, useCompile, useDesignable, useDesigner } from '../../../';
import { designerCss } from './Table.Column.ActionBar';
export const useColumnSchema = () => {
const { getField } = useCollection();
const compile = useCompile();
const columnSchema = useFieldSchema();
const { getCollectionJoinField } = useCollectionManager();
const fieldSchema = columnSchema.reduceProperties((buf, s) => {
if (s['x-component'] === 'CollectionField') {
return s;
@ -16,7 +17,8 @@ export const useColumnSchema = () => {
if (!fieldSchema) {
return {};
}
const collectionField = getField(fieldSchema.name);
const collectionField = getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']);
return { columnSchema, fieldSchema, collectionField, uiSchema: compile(collectionField?.uiSchema) };
};
@ -41,7 +43,7 @@ export const TableColumnDecorator = (props) => {
<SortableItem className={designerCss}>
<Designer fieldSchema={fieldSchema} uiSchema={uiSchema} collectionField={collectionField}/>
{/* <RecursionField name={columnSchema.name} schema={columnSchema}/> */}
{field.title || compile(uiSchema?.title)}
{field?.title || compile(uiSchema?.title)}
{/* <div
onClick={() => {
field.title = uid();

View File

@ -24,6 +24,7 @@ const useLabelFields = (collectionName?: any) => {
export const TableColumnDesigner = (props) => {
const { uiSchema, fieldSchema, collectionField } = props;
const { getInterface } = useCollectionManager();
const field = useField();
const { t } = useTranslation();
const columnSchema = useFieldSchema();
@ -31,6 +32,8 @@ export const TableColumnDesigner = (props) => {
const fieldNames =
fieldSchema?.['x-component-props']?.['fieldNames'] || uiSchema?.['x-component-props']?.['fieldNames'];
const options = useLabelFields(collectionField?.target);
const intefaceCfg = getInterface(collectionField?.interface);
return (
<GeneralSchemaDesigner disableInitializer>
<SchemaSettings.ModalItem
@ -43,7 +46,7 @@ export const TableColumnDesigner = (props) => {
title: {
// title: t('Column title'),
default: columnSchema?.title,
description: `${t('Original title: ')}${collectionField?.uiSchema?.title}`,
description: `${t('Original title: ')}${collectionField?.uiSchema?.title || fieldSchema?.title}`,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {},
@ -65,25 +68,29 @@ export const TableColumnDesigner = (props) => {
dn.refresh();
}}
/>
<SchemaSettings.SwitchItem
title={t('Sortable')}
checked={field.componentProps.sorter}
onChange={(v) => {
const schema: ISchema = {
['x-uid']: columnSchema['x-uid'],
};
columnSchema['x-component-props'] = {
...columnSchema['x-component-props'],
sorter: v,
};
schema['x-component-props'] = columnSchema['x-component-props'];
field.componentProps.sorter = v;
dn.emit('patch', {
schema,
});
dn.refresh();
}}
/>
{
intefaceCfg && intefaceCfg.sortable === true && (
<SchemaSettings.SwitchItem
title={t('Sortable')}
checked={field.componentProps.sorter}
onChange={(v) => {
const schema: ISchema = {
['x-uid']: columnSchema['x-uid'],
};
columnSchema['x-component-props'] = {
...columnSchema['x-component-props'],
sorter: v
}
schema['x-component-props'] = columnSchema['x-component-props'];
field.componentProps.sorter = v;
dn.emit('patch', {
schema
});
dn.refresh();
}}
/>
)
}
{['linkTo', 'm2m', 'm2o', 'o2m', 'obo', 'oho'].includes(collectionField?.interface) && (
<SchemaSettings.SelectItem
title={t('Title field')}

View File

@ -38,6 +38,7 @@ const useTableColumns = () => {
}
}, []);
const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : s.name;
console.log('useTableColumns', s.name, s, field.value);
return {
title: <RecursionField name={s.name} schema={s} onlyRenderSelf />,
dataIndex,
@ -46,7 +47,8 @@ const useTableColumns = () => {
// width: 300,
render: (v, record) => {
const index = field.value?.indexOf(record);
console.log((Date.now() - start) / 1000);
// console.log((Date.now() - start) / 1000);
console.log('useTableColumns.index', index, record);
return (
<RecordIndexProvider index={index}>
<RecordProvider record={record}>

View File

@ -1,5 +1,6 @@
import { observer, useField, useFieldSchema, useForm } from '@formily/react';
import React, { useEffect } from 'react';
import { useFormBlockContext } from '../../../block-provider';
import { useCollection } from '../../../collection-manager';
import { useCompile } from '../../hooks';
import { ActionBar } from '../action';
@ -10,10 +11,15 @@ export const TableField: any = observer((props) => {
const field = useField();
const collectionField = getField(fieldSchema.name);
const compile = useCompile();
const ctx = useFormBlockContext();
useEffect(() => {
if (!field.title) {
field.title = compile(collectionField?.uiSchema?.title);
}
if (ctx?.field) {
ctx.field.added = ctx.field.added || new Set();
ctx.field.added.add(fieldSchema.name);
}
}, []);
return <div>{props.children}</div>;
});

View File

@ -1,12 +1,34 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaInitializer } from '../SchemaInitializer';
import { itemsMerge, useTableColumnInitializerFields } from '../utils';
import { itemsMerge, useAssociatedTableColumnInitializerFields, useTableColumnInitializerFields } from '../utils';
// 表格列配置
export const TableColumnInitializers = (props: any) => {
const { items = [] } = props;
const { t } = useTranslation();
const associatedFields = useAssociatedTableColumnInitializerFields();
const fieldItems: any[] = [{
type: 'itemGroup',
title: t('Display fields'),
children: useTableColumnInitializerFields(),
}];
if (associatedFields?.length > 0) {
fieldItems.push({
type: 'divider',
}, {
type: 'itemGroup',
title: t('Display association fields'),
children: associatedFields,
})
}
fieldItems.push({
type: 'divider',
}, {
type: 'item',
title: t('Action column'),
component: 'TableActionColumnInitializer',
})
return (
<SchemaInitializer.Button
insertPosition={'beforeEnd'}
@ -28,21 +50,7 @@ export const TableColumnInitializers = (props: any) => {
};
}}
items={itemsMerge(
[
{
type: 'itemGroup',
title: t('Display fields'),
children: useTableColumnInitializerFields(),
},
{
type: 'divider',
},
{
type: 'item',
title: t('Action column'),
component: 'TableActionColumnInitializer',
},
],
fieldItems,
items,
)}
>

View File

@ -82,7 +82,8 @@ export const useTableColumnInitializerFields = () => {
'x-collection-field': `${name}.${field.name}`,
'x-component': 'CollectionField',
'x-read-pretty': true,
'x-component-props': {},
'x-component-props': {
},
};
// interfaceConfig?.schemaInitialize?.(schema, { field, readPretty: true, block: 'Table' });
return {
@ -100,6 +101,57 @@ export const useTableColumnInitializerFields = () => {
});
};
export const useAssociatedTableColumnInitializerFields = () => {
const { name, fields } = useCollection();
const { getInterface, getCollectionFields } = useCollectionManager();
const groups = fields
?.filter((field) => {
return ['o2o', 'oho', 'obo', 'm2o'].includes(field.interface);
})
?.map((field) => {
const subFields = getCollectionFields(field.target);
const items = subFields
// ?.filter((subField) => subField?.interface && !['o2o', 'oho', 'obo', 'o2m', 'm2o', 'subTable', 'linkTo'].includes(subField?.interface))
?.filter((subField) => subField?.interface && !['subTable'].includes(subField?.interface))
?.map((subField) => {
const interfaceConfig = getInterface(subField.interface);
const schema = {
// type: 'string',
name: `${field.name}.${subField.name}`,
// title: subField?.uiSchema?.title || subField.name,
'x-component': 'CollectionField',
'x-read-pretty': true,
'x-collection-field': `${name}.${field.name}.${subField.name}`,
'x-component-props': {
},
};
return {
type: 'item',
title: subField?.uiSchema?.title || subField.name,
component: 'TableCollectionFieldInitializer',
find: findTableColumn,
remove: removeTableColumn,
schemaInitialize: (s) => {
interfaceConfig?.schemaInitialize?.(s, { field: subField, readPretty: true, block: 'Table' });
},
field: subField,
schema,
} as SchemaInitializerItemOptions;
});
return {
type: 'subMenu',
title: field.uiSchema?.title,
children: items,
} as SchemaInitializerItemOptions;
});
return groups;
}
export const useFormItemInitializerFields = (options?: any) => {
const { name, fields } = useCollection();
const { getInterface } = useCollectionManager();
@ -110,14 +162,17 @@ export const useFormItemInitializerFields = (options?: any) => {
?.filter((field) => field?.interface)
?.map((field) => {
const interfaceConfig = getInterface(field.interface);
const schema = {
type: 'string',
name: field.name,
// title: field?.uiSchema?.title || field.name,
'x-designer': 'FormItem.Designer',
'x-component': 'CollectionField',
'x-component': field.interface === 'o2m' ? 'TableField' : 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': `${name}.${field.name}`,
'x-component-props': {
},
};
// interfaceConfig?.schemaInitialize?.(schema, { field, block: 'Form', readPretty: form.readPretty });
return {
@ -133,6 +188,55 @@ export const useFormItemInitializerFields = (options?: any) => {
});
};
export const useAssociatedFormItemInitializerFields = (options?: any) => {
const { name, fields } = useCollection();
const { getInterface, getCollectionFields } = useCollectionManager();
const form = useForm();
const { readPretty = form.readPretty, block = 'Form' } = options || {};
const groups = fields
?.filter((field) => {
return ['o2o', 'oho', 'obo', 'm2o'].includes(field.interface);
})
?.map((field) => {
const subFields = getCollectionFields(field.target);
const items = subFields
?.filter((subField) => subField?.interface && !['subTable'].includes(subField?.interface))
?.map((subField) => {
const interfaceConfig = getInterface(subField.interface);
const schema = {
type: 'string',
name: `${field.name}.${subField.name}`,
// title: subField?.uiSchema?.title || subField.name,
'x-designer': 'FormItem.Designer',
'x-component': 'CollectionField',
'x-component-props': {
},
'x-decorator': 'FormItem',
'x-collection-field': `${name}.${field.name}.${subField.name}`,
};
// interfaceConfig?.schemaInitialize?.(schema, { field, block: 'Form', readPretty: form.readPretty });
return {
type: 'item',
title: subField?.uiSchema?.title || subField.name,
component: 'CollectionFieldInitializer',
remove: removeGridFormItem,
schemaInitialize: (s) => {
interfaceConfig?.schemaInitialize?.(s, { field: subField, block, readPretty });
},
schema,
} as SchemaInitializerItemOptions;
});
return {
type: 'subMenu',
title: field.uiSchema?.title,
children: items,
} as SchemaInitializerItemOptions;
});
return groups;
}
export const useCustomFormItemInitializerFields = (options?: any) => {
const { name, fields } = useCollection();
const { getInterface } = useCollectionManager();
@ -168,6 +272,7 @@ export const useCustomFormItemInitializerFields = (options?: any) => {
};
const findSchema = (schema: Schema, key: string, action: string) => {
if (!Schema.isSchemaInstance(schema)) return null;
return schema.reduceProperties((buf, s) => {
if (s[key] === action) {
return s;
@ -420,7 +525,6 @@ export const createFormBlockSchema = (options) => {
template,
...others
} = options;
console.log('createFormBlockSchema', options);
const resourceName = resource || association || collection;
const schema: ISchema = {
type: 'void',

View File

@ -1,4 +1,3 @@
import { Collection } from '@nocobase/database';
import { Plugin } from '@nocobase/server';
import lodash from 'lodash';
import path from 'path';
@ -101,41 +100,41 @@ export class CollectionManagerPlugin extends Plugin {
await next();
});
this.app.resourcer.use(async (ctx, next) => {
const { resourceName, actionName } = ctx.action;
if (actionName === 'update') {
const { updateAssociationValues = [] } = ctx.action.params;
const [collectionName, associationName] = resourceName.split('.');
if (!associationName) {
const collection: Collection = ctx.db.getCollection(collectionName);
if (collection) {
for (const [, field] of collection.fields) {
if (['subTable', 'o2m'].includes(field.options.interface)) {
updateAssociationValues.push(field.name);
}
}
}
} else {
const association = ctx.db.getCollection(collectionName)?.getField?.(associationName);
if (association?.target) {
const collection: Collection = ctx.db.getCollection(association?.target);
if (collection) {
for (const [, field] of collection.fields) {
if (['subTable', 'o2m'].includes(field.options.interface)) {
updateAssociationValues.push(field.name);
}
}
}
}
}
if (updateAssociationValues.length) {
ctx.action.mergeParams({
updateAssociationValues,
});
}
}
await next();
});
// this.app.resourcer.use(async (ctx, next) => {
// const { resourceName, actionName } = ctx.action;
// if (actionName === 'update') {
// const { updateAssociationValues = [] } = ctx.action.params;
// const [collectionName, associationName] = resourceName.split('.');
// if (!associationName) {
// const collection: Collection = ctx.db.getCollection(collectionName);
// if (collection) {
// for (const [, field] of collection.fields) {
// if (['subTable', 'o2m'].includes(field.options.interface)) {
// updateAssociationValues.push(field.name);
// }
// }
// }
// } else {
// const association = ctx.db.getCollection(collectionName)?.getField?.(associationName);
// if (association?.target) {
// const collection: Collection = ctx.db.getCollection(association?.target);
// if (collection) {
// for (const [, field] of collection.fields) {
// if (['subTable', 'o2m'].includes(field.options.interface)) {
// updateAssociationValues.push(field.name);
// }
// }
// }
// }
// }
// if (updateAssociationValues.length) {
// ctx.action.mergeParams({
// updateAssociationValues,
// });
// }
// }
// await next();
// });
this.app.acl.allow('collections', 'list', 'loggedIn');
this.app.acl.allow('collections', ['create', 'update', 'destroy'], 'allowConfigure');