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:
parent
95d0b524d0
commit
32e744319e
@ -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,
|
||||
|
@ -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}
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
}
|
@ -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>
|
||||
);
|
||||
|
@ -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 />;
|
||||
// }
|
||||
|
@ -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} />
|
||||
|
@ -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)) {
|
||||
|
@ -7,4 +7,5 @@ export * from './KanbanBlockProvider';
|
||||
export * from './TableBlockProvider';
|
||||
export * from './TableFieldProvider';
|
||||
export * from './TableSelectorProvider';
|
||||
export * from './FormFieldProvider';
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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) => {
|
||||
|
@ -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'] || {};
|
||||
|
@ -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'] || {};
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
}
|
||||
|
@ -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": "子表格模式",
|
||||
|
@ -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,15 +291,16 @@ FormItem.Designer = () => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
|
||||
dn.refresh();
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{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,
|
||||
});
|
||||
|
@ -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>;
|
||||
});
|
@ -5,3 +5,4 @@ FormV2.Designer = FormDesigner;
|
||||
FormV2.ReadPrettyDesigner = ReadPrettyFormDesigner;
|
||||
|
||||
export { FormV2, DetailsDesigner };
|
||||
export * from './FormField';
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 };
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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')}
|
||||
|
@ -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}>
|
||||
|
@ -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>;
|
||||
});
|
||||
|
@ -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,
|
||||
)}
|
||||
>
|
||||
|
@ -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',
|
||||
|
@ -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');
|
||||
|
Loading…
Reference in New Issue
Block a user