diff --git a/packages/core/client/src/block-provider/BlockSchemaComponentProvider.tsx b/packages/core/client/src/block-provider/BlockSchemaComponentProvider.tsx index 942409534..fe853d948 100644 --- a/packages/core/client/src/block-provider/BlockSchemaComponentProvider.tsx +++ b/packages/core/client/src/block-provider/BlockSchemaComponentProvider.tsx @@ -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, diff --git a/packages/core/client/src/block-provider/FormBlockProvider.tsx b/packages/core/client/src/block-provider/FormBlockProvider.tsx index 818c7c217..cd9e795cd 100644 --- a/packages/core/client/src/block-provider/FormBlockProvider.tsx +++ b/packages/core/client/src/block-provider/FormBlockProvider.tsx @@ -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} diff --git a/packages/core/client/src/block-provider/FormFieldProvider.tsx b/packages/core/client/src/block-provider/FormFieldProvider.tsx new file mode 100644 index 000000000..0215a14f1 --- /dev/null +++ b/packages/core/client/src/block-provider/FormFieldProvider.tsx @@ -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({}); + +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 ; + } + + console.log('InternalFormFieldProvider', fieldName); + + return ( + + + {props.children} + + + + ); +} + +export const WithoutFormFieldResource = createContext(null); + +export const FormFieldProvider = (props) => { + console.log('FormFieldProvider', props); + return ( + + + + + + ) +} + +export const useFormFieldContext = () => { + return useContext(FormFieldContext); +}; + +export const useFormFieldProps = () => { + const ctx = useFormFieldContext(); + useEffect(() => { + ctx?.form?.setInitialValues(ctx.service?.data?.data); + }, []); + return { + form: ctx.form, + }; + +} \ No newline at end of file diff --git a/packages/core/client/src/block-provider/KanbanBlockProvider.tsx b/packages/core/client/src/block-provider/KanbanBlockProvider.tsx index 0ed4a567f..2cd863d96 100644 --- a/packages/core/client/src/block-provider/KanbanBlockProvider.tsx +++ b/packages/core/client/src/block-provider/KanbanBlockProvider.tsx @@ -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 ( - + ); diff --git a/packages/core/client/src/block-provider/TableFieldProvider.tsx b/packages/core/client/src/block-provider/TableFieldProvider.tsx index 9ad713fb5..af8726783 100644 --- a/packages/core/client/src/block-provider/TableFieldProvider.tsx +++ b/packages/core/client/src/block-provider/TableFieldProvider.tsx @@ -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({}); 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 ; // } diff --git a/packages/core/client/src/block-provider/TableSelectorProvider.tsx b/packages/core/client/src/block-provider/TableSelectorProvider.tsx index 86a5fc601..9281d86cf 100644 --- a/packages/core/client/src/block-provider/TableSelectorProvider.tsx +++ b/packages/core/client/src/block-provider/TableSelectorProvider.tsx @@ -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({}); @@ -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 ( diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index 9ee1686c8..837c0a9e3 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -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)) { diff --git a/packages/core/client/src/block-provider/index.tsx b/packages/core/client/src/block-provider/index.tsx index 1066e3f54..6d7e3aff1 100644 --- a/packages/core/client/src/block-provider/index.tsx +++ b/packages/core/client/src/block-provider/index.tsx @@ -7,4 +7,5 @@ export * from './KanbanBlockProvider'; export * from './TableBlockProvider'; export * from './TableFieldProvider'; export * from './TableSelectorProvider'; +export * from './FormFieldProvider'; diff --git a/packages/core/client/src/collection-manager/CollectionField.tsx b/packages/core/client/src/collection-manager/CollectionField.tsx index 16b41b329..044ffc4e6 100644 --- a/packages/core/client/src/collection-manager/CollectionField.tsx +++ b/packages/core/client/src/collection-manager/CollectionField.tsx @@ -9,7 +9,6 @@ import { useCollectionField } from './hooks'; // TODO: 初步适配 const InternalField: React.FC = (props) => { const field = useField(); - 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 ( - + ); diff --git a/packages/core/client/src/collection-manager/CollectionFieldProvider.tsx b/packages/core/client/src/collection-manager/CollectionFieldProvider.tsx index b9f551ee7..cbcca66b9 100644 --- a/packages/core/client/src/collection-manager/CollectionFieldProvider.tsx +++ b/packages/core/client/src/collection-manager/CollectionFieldProvider.tsx @@ -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; } diff --git a/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx b/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx index ec7163add..10ddbb300 100644 --- a/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx +++ b/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx @@ -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', diff --git a/packages/core/client/src/collection-manager/Configuration/EditFieldAction.tsx b/packages/core/client/src/collection-manager/Configuration/EditFieldAction.tsx index efc029776..7410e54e1 100644 --- a/packages/core/client/src/collection-manager/Configuration/EditFieldAction.tsx +++ b/packages/core/client/src/collection-manager/Configuration/EditFieldAction.tsx @@ -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', diff --git a/packages/core/client/src/collection-manager/interfaces/linkTo.ts b/packages/core/client/src/collection-manager/interfaces/linkTo.ts index 730be6c33..e75e52772 100644 --- a/packages/core/client/src/collection-manager/interfaces/linkTo.ts +++ b/packages/core/client/src/collection-manager/interfaces/linkTo.ts @@ -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) => { diff --git a/packages/core/client/src/collection-manager/interfaces/m2m.tsx b/packages/core/client/src/collection-manager/interfaces/m2m.tsx index 51b4bcbec..6f9827725 100644 --- a/packages/core/client/src/collection-manager/interfaces/m2m.tsx +++ b/packages/core/client/src/collection-manager/interfaces/m2m.tsx @@ -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'] || {}; diff --git a/packages/core/client/src/collection-manager/interfaces/m2o.tsx b/packages/core/client/src/collection-manager/interfaces/m2o.tsx index bc24736de..bead8740b 100644 --- a/packages/core/client/src/collection-manager/interfaces/m2o.tsx +++ b/packages/core/client/src/collection-manager/interfaces/m2o.tsx @@ -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'] || {}; diff --git a/packages/core/client/src/collection-manager/interfaces/o2m.tsx b/packages/core/client/src/collection-manager/interfaces/o2m.tsx index 5cc39b722..68b17c125 100644 --- a/packages/core/client/src/collection-manager/interfaces/o2m.tsx +++ b/packages/core/client/src/collection-manager/interfaces/o2m.tsx @@ -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), diff --git a/packages/core/client/src/collection-manager/interfaces/o2o.tsx b/packages/core/client/src/collection-manager/interfaces/o2o.tsx index 925165911..ced80ebe4 100644 --- a/packages/core/client/src/collection-manager/interfaces/o2o.tsx +++ b/packages/core/client/src/collection-manager/interfaces/o2o.tsx @@ -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; diff --git a/packages/core/client/src/locale/en_US.ts b/packages/core/client/src/locale/en_US.ts index 2fa54eec3..d4a7e6474 100644 --- a/packages/core/client/src/locale/en_US.ts +++ b/packages/core/client/src/locale/en_US.ts @@ -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", } diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts index 4c93e6dbf..ccf4103c5 100644 --- a/packages/core/client/src/locale/zh_CN.ts +++ b/packages/core/client/src/locale/zh_CN.ts @@ -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": "子表格模式", diff --git a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx index 5ec0814dd..0365a7d52 100644 --- a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx @@ -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(); 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' && ( { }} /> )} + {form && !isSubFormAssocitionField && ['o2o', 'oho', 'obo', 'o2m'].includes(collectionField?.interface) && ( + { + 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' && ( { ]} 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,15 +291,16 @@ FormItem.Designer = () => { break; } } + dn.emit('patch', { schema, }); - dn.refresh(); + dn.refresh(); }} /> )} - {collectionField?.target && ( + {collectionField?.target && fieldSchema['x-component'] === 'CollectionField' && ( { ...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, }); diff --git a/packages/core/client/src/schema-component/antd/form-v2/FormField.tsx b/packages/core/client/src/schema-component/antd/form-v2/FormField.tsx new file mode 100644 index 000000000..11f913bb0 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/form-v2/FormField.tsx @@ -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
{props.children}
; +}); \ No newline at end of file diff --git a/packages/core/client/src/schema-component/antd/form-v2/index.ts b/packages/core/client/src/schema-component/antd/form-v2/index.ts index 66195a9be..d2a9a8a48 100644 --- a/packages/core/client/src/schema-component/antd/form-v2/index.ts +++ b/packages/core/client/src/schema-component/antd/form-v2/index.ts @@ -5,3 +5,4 @@ FormV2.Designer = FormDesigner; FormV2.ReadPrettyDesigner = ReadPrettyFormDesigner; export { FormV2, DetailsDesigner }; +export * from './FormField'; diff --git a/packages/core/client/src/schema-component/antd/kanban/Kanban.Card.Designer.tsx b/packages/core/client/src/schema-component/antd/kanban/Kanban.Card.Designer.tsx index d8e0c09e9..5a0b0fb74 100644 --- a/packages/core/client/src/schema-component/antd/kanban/Kanban.Card.Designer.tsx +++ b/packages/core/client/src/schema-component/antd/kanban/Kanban.Card.Designer.tsx @@ -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={} /> @@ -101,3 +109,4 @@ export const KanbanCardDesigner = (props: any) => { ); }; + diff --git a/packages/core/client/src/schema-component/antd/record-picker/InputRecordPicker.tsx b/packages/core/client/src/schema-component/antd/record-picker/InputRecordPicker.tsx index 0824af193..2317b76b5 100644 --- a/packages/core/client/src/schema-component/antd/record-picker/InputRecordPicker.tsx +++ b/packages/core/client/src/schema-component/antd/record-picker/InputRecordPicker.tsx @@ -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 = (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 (