feat(collection-manager): tableOID field and collection field (#2161)
* feat: support tableOid interface * chore: child collection filter operator * chore: test * refactor: support tableOid and collection field * chore: test * fix: refactor: collectionSelect * refactor: support linkage from form in add child * refactor: add child support linkage form form * refactor: code improve * feat: support undefined value in childIn query * chore: test * refactor: locale improve * refactor: code improve * refactor: code improve * refactor: tableoid only support pg * refactor: tableoid only support pg * refactor: code improve * refactor: collection operator * refactor: code improve * refactor: code improve * refactor: code improve * refactor: code improve * refactor: collection field support options config * refactor: collection field support options config * feat: tableoid migration * fix: item.options?.inherits --------- Co-authored-by: ChengLei Shao <chareice@live.com> Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
637ccb0457
commit
046a0b4f4d
@ -9,10 +9,10 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useRequest } from '../../api-client';
|
import { useRequest } from '../../api-client';
|
||||||
import { RecordProvider, useRecord } from '../../record-provider';
|
import { RecordProvider, useRecord } from '../../record-provider';
|
||||||
import { ActionContextProvider, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
import { ActionContextProvider, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
|
||||||
import { useCancelAction } from '../action-hooks';
|
import { useCancelAction } from '../action-hooks';
|
||||||
import { useCollectionManager } from '../hooks';
|
import { useCollectionManager } from '../hooks';
|
||||||
import { IField } from '../interfaces/types';
|
import { IField } from '../interfaces/types';
|
||||||
|
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||||
import * as components from './components';
|
import * as components from './components';
|
||||||
import { getOptions } from './interfaces';
|
import { getOptions } from './interfaces';
|
||||||
|
|
||||||
@ -167,13 +167,21 @@ export const AddCollectionField = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AddFieldAction = (props) => {
|
export const AddFieldAction = (props) => {
|
||||||
const { scope, getContainer, item: record, children, trigger, align } = props;
|
const { scope, getContainer, item: record, children, trigger, align, database } = props;
|
||||||
const { getInterface, getTemplate } = useCollectionManager();
|
const { getInterface, getTemplate, collections } = useCollectionManager();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [targetScope, setTargetScope] = useState();
|
const [targetScope, setTargetScope] = useState();
|
||||||
const [schema, setSchema] = useState({});
|
const [schema, setSchema] = useState({});
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const currentCollections = useMemo(() => {
|
||||||
|
return collections.map((v) => {
|
||||||
|
return {
|
||||||
|
label: compile(v.title),
|
||||||
|
value: v.name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
const getFieldOptions = useCallback(() => {
|
const getFieldOptions = useCallback(() => {
|
||||||
const { availableFieldInterfaces } = getTemplate(record.template) || {};
|
const { availableFieldInterfaces } = getTemplate(record.template) || {};
|
||||||
const { exclude, include } = availableFieldInterfaces || {};
|
const { exclude, include } = availableFieldInterfaces || {};
|
||||||
@ -185,6 +193,8 @@ export const AddFieldAction = (props) => {
|
|||||||
children: v.children.filter((v) => {
|
children: v.children.filter((v) => {
|
||||||
if (v.value === 'id') {
|
if (v.value === 'id') {
|
||||||
return typeof record['autoGenId'] === 'boolean' ? record['autoGenId'] : true;
|
return typeof record['autoGenId'] === 'boolean' ? record['autoGenId'] : true;
|
||||||
|
} else if (v.value === 'tableoid') {
|
||||||
|
return database?.dialect === 'postgres';
|
||||||
} else {
|
} else {
|
||||||
return typeof record[v.value] === 'boolean' ? record[v.value] : true;
|
return typeof record[v.value] === 'boolean' ? record[v.value] : true;
|
||||||
}
|
}
|
||||||
@ -241,7 +251,6 @@ export const AddFieldAction = (props) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [getFieldOptions]);
|
}, [getFieldOptions]);
|
||||||
|
|
||||||
const menu = useMemo<MenuProps>(() => {
|
const menu = useMemo<MenuProps>(() => {
|
||||||
return {
|
return {
|
||||||
style: {
|
style: {
|
||||||
@ -286,6 +295,7 @@ export const AddFieldAction = (props) => {
|
|||||||
record,
|
record,
|
||||||
showReverseFieldConfig: true,
|
showReverseFieldConfig: true,
|
||||||
targetScope,
|
targetScope,
|
||||||
|
collections: currentCollections,
|
||||||
...scope,
|
...scope,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,29 +1,30 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Field, createForm } from '@formily/core';
|
import { createForm, Field } from '@formily/core';
|
||||||
import { FieldContext, FormContext, useField } from '@formily/react';
|
import { FieldContext, FormContext, useField } from '@formily/react';
|
||||||
import { Space, Switch, Table, TableColumnProps, Tag, Tooltip } from 'antd';
|
import { Space, Switch, Table, TableColumnProps, Tag, Tooltip } from 'antd';
|
||||||
import React, { useContext, useMemo } from 'react';
|
import React, { useContext, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useCurrentAppInfo } from '../../appInfo';
|
||||||
import { RecordProvider, useRecord } from '../../record-provider';
|
import { RecordProvider, useRecord } from '../../record-provider';
|
||||||
import { Action, useAttach, useCompile } from '../../schema-component';
|
import { Action, useAttach, useCompile } from '../../schema-component';
|
||||||
import {
|
|
||||||
ResourceActionContext,
|
|
||||||
ResourceActionProvider,
|
|
||||||
useResourceActionContext,
|
|
||||||
useResourceContext,
|
|
||||||
} from '../ResourceActionProvider';
|
|
||||||
import {
|
import {
|
||||||
isDeleteButtonDisabled,
|
isDeleteButtonDisabled,
|
||||||
useBulkDestroyActionAndRefreshCM,
|
useBulkDestroyActionAndRefreshCM,
|
||||||
useDestroyActionAndRefreshCM,
|
useDestroyActionAndRefreshCM,
|
||||||
} from '../action-hooks';
|
} from '../action-hooks';
|
||||||
import { useCollectionManager } from '../hooks/useCollectionManager';
|
import { useCollectionManager } from '../hooks/useCollectionManager';
|
||||||
|
import {
|
||||||
|
ResourceActionContext,
|
||||||
|
ResourceActionProvider,
|
||||||
|
useResourceActionContext,
|
||||||
|
useResourceContext,
|
||||||
|
} from '../ResourceActionProvider';
|
||||||
import { AddCollectionField } from './AddFieldAction';
|
import { AddCollectionField } from './AddFieldAction';
|
||||||
import { EditCollectionField } from './EditFieldAction';
|
import { EditCollectionField } from './EditFieldAction';
|
||||||
import { OverridingCollectionField } from './OverridingCollectionField';
|
import { OverridingCollectionField } from './OverridingCollectionField';
|
||||||
|
import { collection } from './schemas/collectionFields';
|
||||||
import { SyncFieldsAction } from './SyncFieldsAction';
|
import { SyncFieldsAction } from './SyncFieldsAction';
|
||||||
import { ViewCollectionField } from './ViewInheritedField';
|
import { ViewCollectionField } from './ViewInheritedField';
|
||||||
import { collection } from './schemas/collectionFields';
|
|
||||||
|
|
||||||
const indentStyle = css`
|
const indentStyle = css`
|
||||||
.ant-table {
|
.ant-table {
|
||||||
@ -269,6 +270,9 @@ export const CollectionFields = () => {
|
|||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const field = useField<Field>();
|
const field = useField<Field>();
|
||||||
const { name } = useRecord();
|
const { name } = useRecord();
|
||||||
|
const {
|
||||||
|
data: { database },
|
||||||
|
} = useCurrentAppInfo();
|
||||||
const { getInterface, getInheritCollections, getCollection, getCurrentCollectionFields } = useCollectionManager();
|
const { getInterface, getInheritCollections, getCollection, getCurrentCollectionFields } = useCollectionManager();
|
||||||
const form = useMemo(() => createForm(), []);
|
const form = useMemo(() => createForm(), []);
|
||||||
const f = useAttach(form.createArrayField({ ...field.props, basePath: '' }));
|
const f = useAttach(form.createArrayField({ ...field.props, basePath: '' }));
|
||||||
@ -403,7 +407,7 @@ export const CollectionFields = () => {
|
|||||||
}),
|
}),
|
||||||
[t],
|
[t],
|
||||||
);
|
);
|
||||||
const addProps = { type: 'primary' };
|
const addProps = { type: 'primary', database };
|
||||||
const syncProps = { type: 'primary' };
|
const syncProps = { type: 'primary' };
|
||||||
return (
|
return (
|
||||||
<ResourceActionProvider {...resourceActionProps}>
|
<ResourceActionProvider {...resourceActionProps}>
|
||||||
|
@ -3,15 +3,15 @@ import { ISchema, useForm } from '@formily/react';
|
|||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import set from 'lodash/set';
|
import set from 'lodash/set';
|
||||||
import React, { useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAPIClient, useRequest } from '../../api-client';
|
import { useAPIClient, useRequest } from '../../api-client';
|
||||||
import { RecordProvider, useRecord } from '../../record-provider';
|
import { RecordProvider, useRecord } from '../../record-provider';
|
||||||
import { ActionContextProvider, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
import { ActionContextProvider, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
|
||||||
import { useCancelAction, useUpdateAction } from '../action-hooks';
|
import { useCancelAction, useUpdateAction } from '../action-hooks';
|
||||||
import { useCollectionManager } from '../hooks';
|
import { useCollectionManager } from '../hooks';
|
||||||
import { IField } from '../interfaces/types';
|
import { IField } from '../interfaces/types';
|
||||||
|
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||||
import * as components from './components';
|
import * as components from './components';
|
||||||
|
|
||||||
const getSchema = (schema: IField, record: any, compile, getContainer): ISchema => {
|
const getSchema = (schema: IField, record: any, compile, getContainer): ISchema => {
|
||||||
@ -136,13 +136,21 @@ export const EditCollectionField = (props) => {
|
|||||||
|
|
||||||
export const EditFieldAction = (props) => {
|
export const EditFieldAction = (props) => {
|
||||||
const { scope, getContainer, item: record, children } = props;
|
const { scope, getContainer, item: record, children } = props;
|
||||||
const { getInterface } = useCollectionManager();
|
const { getInterface, collections } = useCollectionManager();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [schema, setSchema] = useState({});
|
const [schema, setSchema] = useState({});
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const [data, setData] = useState<any>({});
|
const [data, setData] = useState<any>({});
|
||||||
|
const currentCollections = useMemo(() => {
|
||||||
|
return collections.map((v) => {
|
||||||
|
return {
|
||||||
|
label: compile(v.title),
|
||||||
|
value: v.name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<RecordProvider record={record}>
|
<RecordProvider record={record}>
|
||||||
<ActionContextProvider value={{ visible, setVisible }}>
|
<ActionContextProvider value={{ visible, setVisible }}>
|
||||||
@ -184,6 +192,7 @@ export const EditFieldAction = (props) => {
|
|||||||
useUpdateCollectionField,
|
useUpdateCollectionField,
|
||||||
useCancelAction,
|
useCancelAction,
|
||||||
showReverseFieldConfig: !data?.reverseField,
|
showReverseFieldConfig: !data?.reverseField,
|
||||||
|
collections: currentCollections,
|
||||||
...scope,
|
...scope,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -4,7 +4,6 @@ import omit from 'lodash/omit';
|
|||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCollection, useCollectionManager } from '.';
|
import { useCollection, useCollectionManager } from '.';
|
||||||
import { useCompile } from '..';
|
|
||||||
import { useRequest } from '../api-client';
|
import { useRequest } from '../api-client';
|
||||||
import { useRecord } from '../record-provider';
|
import { useRecord } from '../record-provider';
|
||||||
import { useActionContext } from '../schema-component';
|
import { useActionContext } from '../schema-component';
|
||||||
@ -108,10 +107,28 @@ export const useChildrenCollections = (collectionName: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCollectionFilterOptions = (collectionName: string) => {
|
export const useSelfAndChildrenCollections = (collectionName: string) => {
|
||||||
const { getCollectionFields, getInterface, getChildrenCollections, getCollection } = useCollectionManager();
|
const { getChildrenCollections, getCollection } = useCollectionManager();
|
||||||
const compile = useCompile();
|
const childrenCollections = getChildrenCollections(collectionName);
|
||||||
|
const self = getCollection(collectionName);
|
||||||
|
if (!collectionName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const options = childrenCollections.map((collection: any) => {
|
||||||
|
return {
|
||||||
|
value: collection.name,
|
||||||
|
label: collection?.title || collection.name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
options.unshift({
|
||||||
|
value: self.name,
|
||||||
|
label: self?.title || self.name,
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCollectionFilterOptions = (collectionName: string) => {
|
||||||
|
const { getCollectionFields, getInterface } = useCollectionManager();
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const fields = getCollectionFields(collectionName);
|
const fields = getCollectionFields(collectionName);
|
||||||
const field2option = (field, depth) => {
|
const field2option = (field, depth) => {
|
||||||
@ -161,44 +178,6 @@ export const useCollectionFilterOptions = (collectionName: string) => {
|
|||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
const options = getOptions(fields, 1);
|
const options = getOptions(fields, 1);
|
||||||
const collection = getCollection(collectionName);
|
|
||||||
const childrenCollections = getChildrenCollections(collectionName);
|
|
||||||
if (childrenCollections.length > 0 && !options.find((v) => v.name == 'tableoid')) {
|
|
||||||
options.push({
|
|
||||||
name: 'tableoid',
|
|
||||||
type: 'string',
|
|
||||||
title: '{{t("Table OID(Inheritance)")}}',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Select',
|
|
||||||
enum: [{ value: collectionName, label: compile(collection.title) }].concat(
|
|
||||||
childrenCollections.map((v) => {
|
|
||||||
return {
|
|
||||||
value: v.name,
|
|
||||||
label: compile(v.title),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
operators: [
|
|
||||||
{
|
|
||||||
label: '{{t("contains")}}',
|
|
||||||
value: '$childIn',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Select',
|
|
||||||
'x-component-props': { mode: 'tags' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{t("does not contain")}}',
|
|
||||||
value: '$childNotIn',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Select',
|
|
||||||
'x-component-props': { mode: 'tags' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return options;
|
return options;
|
||||||
}, [collectionName]);
|
}, [collectionName]);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
import { collectionDataSource, defaultProps, operators } from './properties';
|
||||||
|
import { IField } from './types';
|
||||||
|
|
||||||
|
export const collection: IField = {
|
||||||
|
name: 'collection',
|
||||||
|
type: 'string',
|
||||||
|
group: 'advanced',
|
||||||
|
order: 5,
|
||||||
|
title: '{{t("Collection")}}',
|
||||||
|
sortable: true,
|
||||||
|
default: {
|
||||||
|
type: 'string',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'CollectionSelect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
availableTypes: ['string'],
|
||||||
|
hasDefaultValue: false,
|
||||||
|
properties: {
|
||||||
|
...defaultProps,
|
||||||
|
'uiSchema.enum': collectionDataSource,
|
||||||
|
},
|
||||||
|
filterable: { operators: operators.collection },
|
||||||
|
schemaInitialize(schema: ISchema, { block }) {
|
||||||
|
const props = (schema['x-component-props'] = schema['x-component-props'] || {});
|
||||||
|
props.style = {
|
||||||
|
...(props.style || {}),
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (['Table', 'Kanban'].includes(block)) {
|
||||||
|
props['ellipsis'] = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
@ -1,6 +1,7 @@
|
|||||||
export * from './checkbox';
|
export * from './checkbox';
|
||||||
export * from './checkboxGroup';
|
export * from './checkboxGroup';
|
||||||
export * from './chinaRegion';
|
export * from './chinaRegion';
|
||||||
|
export * from './collection';
|
||||||
export * from './createdAt';
|
export * from './createdAt';
|
||||||
export * from './createdBy';
|
export * from './createdBy';
|
||||||
export * from './datetime';
|
export * from './datetime';
|
||||||
@ -25,6 +26,7 @@ export * from './radioGroup';
|
|||||||
export * from './richText';
|
export * from './richText';
|
||||||
export * from './select';
|
export * from './select';
|
||||||
export * from './subTable';
|
export * from './subTable';
|
||||||
|
export * from './tableoid';
|
||||||
export * from './textarea';
|
export * from './textarea';
|
||||||
export * from './time';
|
export * from './time';
|
||||||
export * from './updatedAt';
|
export * from './updatedAt';
|
||||||
|
@ -368,3 +368,14 @@ export const recordPickerViewer = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const collectionDataSource: ISchema = {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Options")}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: true,
|
||||||
|
},
|
||||||
|
enum: '{{collections}}',
|
||||||
|
};
|
||||||
|
@ -118,3 +118,54 @@ export const boolean = [
|
|||||||
{ label: '{{t("Yes")}}', value: '$isTruly', selected: true, noValue: true },
|
{ label: '{{t("Yes")}}', value: '$isTruly', selected: true, noValue: true },
|
||||||
{ label: '{{t("No")}}', value: '$isFalsy', noValue: true },
|
{ label: '{{t("No")}}', value: '$isFalsy', noValue: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const tableoid = [
|
||||||
|
{
|
||||||
|
label: '{{t("contains")}}',
|
||||||
|
value: '$childIn',
|
||||||
|
schema: {
|
||||||
|
'x-component': 'CollectionSelect',
|
||||||
|
'x-component-props': { mode: 'tags' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("does not contain")}}',
|
||||||
|
value: '$childNotIn',
|
||||||
|
schema: {
|
||||||
|
'x-component': 'CollectionSelect',
|
||||||
|
'x-component-props': { mode: 'tags' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const collection = [
|
||||||
|
{
|
||||||
|
label: '{{t("is")}}',
|
||||||
|
value: '$eq',
|
||||||
|
selected: true,
|
||||||
|
schema: { 'x-component': 'CollectionSelect' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("is not")}}',
|
||||||
|
value: '$ne',
|
||||||
|
schema: { 'x-component': 'CollectionSelect' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("contains")}}',
|
||||||
|
value: '$in',
|
||||||
|
schema: {
|
||||||
|
'x-component': 'CollectionSelect',
|
||||||
|
'x-component-props': { mode: 'tags' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("does not contain")}}',
|
||||||
|
value: '$notIn',
|
||||||
|
schema: {
|
||||||
|
'x-component': 'CollectionSelect',
|
||||||
|
'x-component-props': { mode: 'tags' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ label: '{{t("is empty")}}', value: '$empty', noValue: true },
|
||||||
|
{ label: '{{t("is not empty")}}', value: '$notEmpty', noValue: true },
|
||||||
|
];
|
@ -0,0 +1,45 @@
|
|||||||
|
import { operators } from './properties';
|
||||||
|
import { IField } from './types';
|
||||||
|
|
||||||
|
export const tableoid: IField = {
|
||||||
|
name: 'tableoid',
|
||||||
|
type: 'object',
|
||||||
|
group: 'systemInfo',
|
||||||
|
order: 0,
|
||||||
|
title: '{{t("Table OID")}}',
|
||||||
|
sortable: true,
|
||||||
|
default: {
|
||||||
|
name: '__collection',
|
||||||
|
type: 'virtual',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Table OID")}}',
|
||||||
|
'x-component': 'CollectionSelect',
|
||||||
|
'x-component-props': {
|
||||||
|
isTableOid: true,
|
||||||
|
},
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
availableTypes: ['string'],
|
||||||
|
properties: {
|
||||||
|
'uiSchema.title': {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Field display name")}}',
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Field name")}}',
|
||||||
|
required: true,
|
||||||
|
'x-disabled': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filterable: {
|
||||||
|
operators: operators.tableoid,
|
||||||
|
},
|
||||||
|
};
|
@ -705,5 +705,6 @@ export default {
|
|||||||
"Find by the following fields":"Find by the following fields",
|
"Find by the following fields":"Find by the following fields",
|
||||||
"Create":"Create",
|
"Create":"Create",
|
||||||
"Current form": "Current form",
|
"Current form": "Current form",
|
||||||
"Current object":"Current object"
|
"Current object":"Current object",
|
||||||
|
"Linkage form form data":"Linkage form form data",
|
||||||
};
|
};
|
||||||
|
@ -616,5 +616,6 @@ export default {
|
|||||||
"Find by the following fields":"次のフィールドで検索",
|
"Find by the following fields":"次のフィールドで検索",
|
||||||
"Create":"新規のみ" ,
|
"Create":"新規のみ" ,
|
||||||
"Current form":"現在のフォーム",
|
"Current form":"現在のフォーム",
|
||||||
"Current object":"現在のオブジェクト"
|
"Current object":"現在のオブジェクト",
|
||||||
|
"Linkage form form data":"フォームデータから連動",
|
||||||
}
|
}
|
||||||
|
@ -790,4 +790,5 @@ export default {
|
|||||||
"File manager": "文件管理器",
|
"File manager": "文件管理器",
|
||||||
"Direct duplicate": "直接复制",
|
"Direct duplicate": "直接复制",
|
||||||
"Copy into the form and continue to fill in": "复制到表单并继续填写",
|
"Copy into the form and continue to fill in": "复制到表单并继续填写",
|
||||||
|
"Linkage form form data":"从表单数据里联动",
|
||||||
}
|
}
|
||||||
|
@ -63,9 +63,8 @@ const ToManyNester = observer(
|
|||||||
insertCount: 1,
|
insertCount: 1,
|
||||||
});
|
});
|
||||||
field.value.splice(index + 1, 0, {});
|
field.value.splice(index + 1, 0, {});
|
||||||
|
each(field.form.fields, (targetField, key) => {
|
||||||
each(field.form.fields, (field, key) => {
|
if (!targetField) {
|
||||||
if (!field) {
|
|
||||||
delete field.form.fields[key];
|
delete field.form.fields[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,23 +1,39 @@
|
|||||||
import { connect, mapReadPretty, observer } from '@formily/react';
|
import { connect, mapReadPretty, observer, useField } from '@formily/react';
|
||||||
import { Select, SelectProps, Tag } from 'antd';
|
import { Select, SelectProps, Tag } from 'antd';
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCollectionManager } from '../../../collection-manager/hooks';
|
import { useSelfAndChildrenCollections } from '../../../collection-manager/action-hooks';
|
||||||
|
import { useCollection, useCollectionManager } from '../../../collection-manager/hooks';
|
||||||
import { useCompile } from '../../hooks';
|
import { useCompile } from '../../hooks';
|
||||||
|
import { FilterContext } from '../filter/context';
|
||||||
|
|
||||||
export type CollectionSelectProps = SelectProps<any, any> & {
|
export type CollectionSelectProps = SelectProps<any, any> & {
|
||||||
filter?: (item: any, index: number, array: any[]) => boolean;
|
filter?: (item: any, index: number, array: any[]) => boolean;
|
||||||
|
isTableOid?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function useOptions({ filter }: CollectionSelectProps) {
|
function useOptions({ filter, isTableOid }: CollectionSelectProps) {
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
|
const field: any = useField();
|
||||||
|
const ctx = useContext(FilterContext);
|
||||||
|
const collection = useCollection();
|
||||||
|
const targetCollection = isTableOid && (ctx?.collectionName || collection.name);
|
||||||
|
const inheritCollections = useSelfAndChildrenCollections(targetCollection);
|
||||||
const { collections = [] } = useCollectionManager();
|
const { collections = [] } = useCollectionManager();
|
||||||
const filtered = typeof filter === 'function' ? collections.filter(filter) : collections;
|
const currentCollections = field?.dataSource
|
||||||
|
? collections.filter((v) => {
|
||||||
|
return field?.dataSource.find((i) => i.value === v.name) || field?.dataSource.includes(v.name);
|
||||||
|
})
|
||||||
|
: collections;
|
||||||
|
const filtered =
|
||||||
|
typeof filter === 'function'
|
||||||
|
? (inheritCollections || currentCollections).filter(filter)
|
||||||
|
: inheritCollections || currentCollections;
|
||||||
return filtered
|
return filtered
|
||||||
.filter((item) => !item.hidden)
|
.filter((item) => !item.hidden)
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
label: compile(item.title),
|
label: compile(item.title || item.label),
|
||||||
value: item.name,
|
value: item.name || item.value,
|
||||||
color: item.category?.color,
|
color: item.category?.color,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -27,7 +43,6 @@ export const CollectionSelect = connect(
|
|||||||
const { filter, ...others } = props;
|
const { filter, ...others } = props;
|
||||||
const options = useOptions(props);
|
const options = useOptions(props);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
placeholder={t('Select collection')}
|
placeholder={t('Select collection')}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { ObjectField as ObjectFieldModel } from '@formily/core';
|
import { ObjectField as ObjectFieldModel } from '@formily/core';
|
||||||
import { observer, useField, useFieldSchema } from '@formily/react';
|
import { observer, useField, useFieldSchema } from '@formily/react';
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useRequest } from '../../../api-client';
|
import { useRequest } from '../../../api-client';
|
||||||
import { useProps } from '../../hooks/useProps';
|
import { useProps } from '../../hooks/useProps';
|
||||||
import { DatePickerProvider } from '../date-picker';
|
import { DatePickerProvider } from '../date-picker';
|
||||||
|
import { FilterContext } from './context';
|
||||||
import { FilterActionDesigner } from './Filter.Action.Designer';
|
import { FilterActionDesigner } from './Filter.Action.Designer';
|
||||||
import { FilterAction } from './FilterAction';
|
import { FilterAction } from './FilterAction';
|
||||||
import { FilterGroup } from './FilterGroup';
|
import { FilterGroup } from './FilterGroup';
|
||||||
import { SaveDefaultValue } from './SaveDefaultValue';
|
import { SaveDefaultValue } from './SaveDefaultValue';
|
||||||
import { FilterContext } from './context';
|
|
||||||
|
|
||||||
const useDef = (options) => {
|
const useDef = (options) => {
|
||||||
const field = useField<ObjectFieldModel>();
|
const field = useField<ObjectFieldModel>();
|
||||||
@ -18,14 +18,17 @@ const useDef = (options) => {
|
|||||||
export const Filter: any = observer(
|
export const Filter: any = observer(
|
||||||
(props: any) => {
|
(props: any) => {
|
||||||
const { useDataSource = useDef } = props;
|
const { useDataSource = useDef } = props;
|
||||||
const { options, dynamicComponent, className } = useProps(props);
|
const { options, dynamicComponent, className, collectionName } = useProps(props);
|
||||||
const field = useField<ObjectFieldModel>();
|
const field = useField<ObjectFieldModel>();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema: any = useFieldSchema();
|
||||||
useDataSource({
|
useDataSource({
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
field.dataSource = data?.data || [];
|
field.dataSource = data?.data || [];
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
field.initialValue = fieldSchema.defaultValue;
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<DatePickerProvider value={{ utc: false }}>
|
<DatePickerProvider value={{ utc: false }}>
|
||||||
@ -36,6 +39,7 @@ export const Filter: any = observer(
|
|||||||
dynamicComponent,
|
dynamicComponent,
|
||||||
options: options || field.dataSource || [],
|
options: options || field.dataSource || [],
|
||||||
disabled: props.disabled,
|
disabled: props.disabled,
|
||||||
|
collectionName,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FilterGroup {...props} bordered={false} />
|
<FilterGroup {...props} bordered={false} />
|
||||||
|
@ -5,8 +5,8 @@ import { Cascader, Select, Space } from 'antd';
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCompile } from '../..';
|
import { useCompile } from '../..';
|
||||||
import { DynamicComponent } from './DynamicComponent';
|
|
||||||
import { RemoveConditionContext } from './context';
|
import { RemoveConditionContext } from './context';
|
||||||
|
import { DynamicComponent } from './DynamicComponent';
|
||||||
import { useValues } from './useValues';
|
import { useValues } from './useValues';
|
||||||
|
|
||||||
export const FilterItem = observer(
|
export const FilterItem = observer(
|
||||||
|
@ -8,6 +8,7 @@ export interface FilterContextProps {
|
|||||||
dynamicComponent?: any;
|
dynamicComponent?: any;
|
||||||
options?: any[];
|
options?: any[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
collectionName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RemoveConditionContext = createContext(null);
|
export const RemoveConditionContext = createContext(null);
|
||||||
|
@ -2,108 +2,25 @@ import { Field } from '@formily/core';
|
|||||||
import { useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import flat from 'flat';
|
import flat from 'flat';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCompile } from '../..';
|
|
||||||
import { useBlockRequestContext } from '../../../block-provider';
|
import { useBlockRequestContext } from '../../../block-provider';
|
||||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
|
|
||||||
export const useGetFilterOptions = () => {
|
export const useGetFilterOptions = () => {
|
||||||
const { getCollectionFields } = useCollectionManager();
|
const { getCollectionFields } = useCollectionManager();
|
||||||
const compile = useCompile();
|
|
||||||
const { getChildrenCollections } = useCollectionManager();
|
|
||||||
const collection = useCollection();
|
|
||||||
const getFilterFieldOptions = useGetFilterFieldOptions();
|
const getFilterFieldOptions = useGetFilterFieldOptions();
|
||||||
|
|
||||||
return (collectionName) => {
|
return (collectionName) => {
|
||||||
const fields = getCollectionFields(collectionName);
|
const fields = getCollectionFields(collectionName);
|
||||||
const options = getFilterFieldOptions(fields);
|
const options = getFilterFieldOptions(fields);
|
||||||
const childrenCollections = getChildrenCollections(collection.name);
|
|
||||||
|
|
||||||
if (childrenCollections.length > 0 && !options.find((v) => v.name == 'tableoid')) {
|
|
||||||
options.push({
|
|
||||||
name: 'tableoid',
|
|
||||||
type: 'string',
|
|
||||||
title: '{{t("Table OID(Inheritance)")}}',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Select',
|
|
||||||
enum: [{ value: collection.name, label: compile(collection.title) }].concat(
|
|
||||||
childrenCollections.map((v) => {
|
|
||||||
return {
|
|
||||||
value: v.name,
|
|
||||||
label: compile(v.title),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
operators: [
|
|
||||||
{
|
|
||||||
label: '{{t("contains")}}',
|
|
||||||
value: '$childIn',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Select',
|
|
||||||
'x-component-props': { mode: 'tags' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{t("does not contain")}}',
|
|
||||||
value: '$childNotIn',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Select',
|
|
||||||
'x-component-props': { mode: 'tags' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFilterOptions = (collectionName: string) => {
|
export const useFilterOptions = (collectionName: string) => {
|
||||||
const { getCollectionFields } = useCollectionManager();
|
const { getCollectionFields } = useCollectionManager();
|
||||||
const compile = useCompile();
|
|
||||||
const { getChildrenCollections } = useCollectionManager();
|
|
||||||
const collection = useCollection();
|
|
||||||
const fields = getCollectionFields(collectionName);
|
const fields = getCollectionFields(collectionName);
|
||||||
const options = useFilterFieldOptions(fields);
|
const options = useFilterFieldOptions(fields);
|
||||||
|
|
||||||
const childrenCollections = getChildrenCollections(collection.name);
|
|
||||||
if (childrenCollections.length > 0 && !options.find((v) => v.name == 'tableoid')) {
|
|
||||||
options.push({
|
|
||||||
name: 'tableoid',
|
|
||||||
type: 'string',
|
|
||||||
title: '{{t("Table OID(Inheritance)")}}',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Select',
|
|
||||||
enum: [{ value: collection.name, label: compile(collection.title) }].concat(
|
|
||||||
childrenCollections.map((v) => {
|
|
||||||
return {
|
|
||||||
value: v.name,
|
|
||||||
label: compile(v.title),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
operators: [
|
|
||||||
{
|
|
||||||
label: '{{t("contains")}}',
|
|
||||||
value: '$childIn',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Select',
|
|
||||||
'x-component-props': { mode: 'tags' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{t("does not contain")}}',
|
|
||||||
value: '$childNotIn',
|
|
||||||
schema: {
|
|
||||||
'x-component': 'Select',
|
|
||||||
'x-component-props': { mode: 'tags' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ export const useValues = () => {
|
|||||||
const s2 = cloneDeep(operator?.schema);
|
const s2 = cloneDeep(operator?.schema);
|
||||||
field.data.schema = merge(s1, s2);
|
field.data.schema = merge(s1, s2);
|
||||||
field.data.dataIndex = dataIndex;
|
field.data.dataIndex = dataIndex;
|
||||||
field.data.value = operator?.noValue ? operator.default || true : null;
|
field.data.value = operator?.noValue ? operator.default || true : undefined;
|
||||||
data2value();
|
data2value();
|
||||||
},
|
},
|
||||||
setOperator(operatorValue) {
|
setOperator(operatorValue) {
|
||||||
@ -71,7 +71,7 @@ export const useValues = () => {
|
|||||||
const s1 = cloneDeep(option?.schema);
|
const s1 = cloneDeep(option?.schema);
|
||||||
const s2 = cloneDeep(operator?.schema);
|
const s2 = cloneDeep(operator?.schema);
|
||||||
field.data.schema = merge(s1, s2);
|
field.data.schema = merge(s1, s2);
|
||||||
field.data.value = operator.noValue ? operator.default || true : null;
|
field.data.value = operator.noValue ? operator.default || true : undefined;
|
||||||
data2value();
|
data2value();
|
||||||
},
|
},
|
||||||
setValue(value) {
|
setValue(value) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { ArrayCollapse, ArrayItems, FormLayout, FormItem as Item } from '@formily/antd';
|
import { ArrayCollapse, ArrayItems, FormItem as Item, FormLayout } from '@formily/antd';
|
||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { ISchema, Schema, observer, useField, useFieldSchema } from '@formily/react';
|
import { ISchema, observer, Schema, useField, useFieldSchema } from '@formily/react';
|
||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@ -20,9 +20,9 @@ import {
|
|||||||
} from '../../../collection-manager';
|
} from '../../../collection-manager';
|
||||||
import { isTitleField } from '../../../collection-manager/Configuration/CollectionFields';
|
import { isTitleField } from '../../../collection-manager/Configuration/CollectionFields';
|
||||||
import { GeneralSchemaItems } from '../../../schema-items/GeneralSchemaItems';
|
import { GeneralSchemaItems } from '../../../schema-items/GeneralSchemaItems';
|
||||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings';
|
import { GeneralSchemaDesigner, isPatternDisabled, isShowDefaultValue, SchemaSettings } from '../../../schema-settings';
|
||||||
import { VariableInput } from '../../../schema-settings/VariableInput/VariableInput';
|
|
||||||
import { useIsShowMultipleSwitch } from '../../../schema-settings/hooks/useIsShowMultipleSwitch';
|
import { useIsShowMultipleSwitch } from '../../../schema-settings/hooks/useIsShowMultipleSwitch';
|
||||||
|
import { VariableInput } from '../../../schema-settings/VariableInput/VariableInput';
|
||||||
import { isVariable, parseVariables, useVariablesCtx } from '../../common/utils/uitls';
|
import { isVariable, parseVariables, useVariablesCtx } from '../../common/utils/uitls';
|
||||||
import { SchemaComponent } from '../../core';
|
import { SchemaComponent } from '../../core';
|
||||||
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
|
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
|
||||||
@ -153,7 +153,7 @@ FormItem.Designer = function Designer() {
|
|||||||
readOnlyMode = 'read-pretty';
|
readOnlyMode = 'read-pretty';
|
||||||
}
|
}
|
||||||
const dataSource = useCollectionFilterOptions(collectionField?.target);
|
const dataSource = useCollectionFilterOptions(collectionField?.target);
|
||||||
const defaultFilter = field.componentProps?.service?.params?.filter || {};
|
const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {};
|
||||||
const sortFields = useSortFields(collectionField?.target);
|
const sortFields = useSortFields(collectionField?.target);
|
||||||
const defaultSort = field.componentProps?.service?.params?.sort || [];
|
const defaultSort = field.componentProps?.service?.params?.sort || [];
|
||||||
const fieldMode = field?.componentProps?.['mode'] || (isFileField ? 'FileManager' : 'Select');
|
const fieldMode = field?.componentProps?.['mode'] || (isFileField ? 'FileManager' : 'Select');
|
||||||
@ -446,10 +446,11 @@ FormItem.Designer = function Designer() {
|
|||||||
title: t('Set the data scope'),
|
title: t('Set the data scope'),
|
||||||
properties: {
|
properties: {
|
||||||
filter: {
|
filter: {
|
||||||
default: defaultFilter,
|
defaultValue: defaultFilter,
|
||||||
enum: dataSource,
|
enum: dataSource,
|
||||||
'x-component': 'Filter',
|
'x-component': 'Filter',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
|
collectionName: collectionField?.target,
|
||||||
dynamicComponent: (props) =>
|
dynamicComponent: (props) =>
|
||||||
FilterDynamicComponent({
|
FilterDynamicComponent({
|
||||||
...props,
|
...props,
|
||||||
|
@ -11,7 +11,7 @@ import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
|
|||||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||||
import { getInnermostKeyAndValue } from '../../common/utils/uitls';
|
import { getInnermostKeyAndValue } from '../../common/utils/uitls';
|
||||||
import { useCompile } from '../../hooks';
|
import { useCompile } from '../../hooks';
|
||||||
import { Select, defaultFieldNames } from '../select';
|
import { defaultFieldNames, Select } from '../select';
|
||||||
import { ReadPretty } from './ReadPretty';
|
import { ReadPretty } from './ReadPretty';
|
||||||
import { extractFilterfield, extractValuesByPattern, generatePattern, parseVariables } from './utils';
|
import { extractFilterfield, extractValuesByPattern, generatePattern, parseVariables } from './utils';
|
||||||
const EMPTY = 'N/A';
|
const EMPTY = 'N/A';
|
||||||
@ -156,11 +156,18 @@ const InternalRemoteSelect = connect(
|
|||||||
str = str.replace('$iteration.', `$iteration.${path.join('.')}.`);
|
str = str.replace('$iteration.', `$iteration.${path.join('.')}.`);
|
||||||
}
|
}
|
||||||
const parseValue = parseVariables(str, variablesCtx);
|
const parseValue = parseVariables(str, variablesCtx);
|
||||||
|
if (Array.isArray(parseValue)) {
|
||||||
|
const filters = parseValue.map((v) => {
|
||||||
|
return JSON.parse(JSON.stringify(c).replace(jsonlogic.value, v));
|
||||||
|
});
|
||||||
|
results.push({ $or: filters });
|
||||||
|
} else {
|
||||||
const filterObj = JSON.parse(
|
const filterObj = JSON.parse(
|
||||||
JSON.stringify(c).replace(jsonlogic.value, str.endsWith('id') ? parseValue ?? 0 : parseValue),
|
JSON.stringify(c).replace(jsonlogic.value, str.endsWith('id') ? parseValue ?? 0 : parseValue),
|
||||||
);
|
);
|
||||||
results.push(filterObj);
|
results.push(filterObj);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return { [type]: results };
|
return { [type]: results };
|
||||||
};
|
};
|
||||||
@ -173,7 +180,9 @@ const InternalRemoteSelect = connect(
|
|||||||
pageSize: 200,
|
pageSize: 200,
|
||||||
...service?.params,
|
...service?.params,
|
||||||
// search needs
|
// search needs
|
||||||
filter: mergeFilter([parseFilter(field.componentProps?.service?.params?.filter) || service?.params?.filter]),
|
filter: mergeFilter([
|
||||||
|
parseFilter(fieldSchema?.['x-component-props']?.service?.params?.filter) || service?.params?.filter,
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -221,7 +230,7 @@ const InternalRemoteSelect = connect(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {},
|
: {},
|
||||||
field.componentProps?.service?.params?.filter || service?.params?.filter,
|
fieldSchema?.['x-component-props']?.service?.params?.filter || service?.params?.filter,
|
||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
searchData.current = search;
|
searchData.current = search;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import flat from 'flat';
|
import flat from 'flat';
|
||||||
import _, { every, findIndex, isArray, some } from 'lodash';
|
import _, { every, findIndex, isArray, some } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@ -8,8 +7,9 @@ import jsonLogic from '../../common/utils/logic';
|
|||||||
|
|
||||||
type VariablesCtx = {
|
type VariablesCtx = {
|
||||||
/** 当前登录的用户 */
|
/** 当前登录的用户 */
|
||||||
$user: Record<string, any>;
|
$user?: Record<string, any>;
|
||||||
$date: Record<string, any>;
|
$date?: Record<string, any>;
|
||||||
|
$form?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useVariablesCtx = (): VariablesCtx => {
|
export const useVariablesCtx = (): VariablesCtx => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { DownOutlined, PlusOutlined } from '@ant-design/icons';
|
import { DownOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { RecursionField, observer, useField, useFieldSchema } from '@formily/react';
|
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
|
||||||
import { Button, Dropdown, MenuProps } from 'antd';
|
import { Button, Dropdown, MenuProps } from 'antd';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { useDesignable } from '../../';
|
import { useDesignable } from '../../';
|
||||||
@ -9,6 +9,7 @@ import { CollectionProvider, useCollection, useCollectionManager } from '../../c
|
|||||||
import { useRecord } from '../../record-provider';
|
import { useRecord } from '../../record-provider';
|
||||||
import { ActionContextProvider, useActionContext, useCompile } from '../../schema-component';
|
import { ActionContextProvider, useActionContext, useCompile } from '../../schema-component';
|
||||||
import { linkageAction } from '../../schema-component/antd/action/utils';
|
import { linkageAction } from '../../schema-component/antd/action/utils';
|
||||||
|
import { parseVariables } from '../../schema-component/common/utils/uitls';
|
||||||
|
|
||||||
export const actionDesignerCss = css`
|
export const actionDesignerCss = css`
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -49,7 +50,7 @@ export const actionDesignerCss = css`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const actionAclCheck = (actionPath) => {
|
const actionAclCheck = function useAclCheck(actionPath) {
|
||||||
const { data, inResources, getResourceActionParams, getStrategyActionParams } = useACLRolesCheck();
|
const { data, inResources, getResourceActionParams, getStrategyActionParams } = useACLRolesCheck();
|
||||||
const recordPkValue = useRecordPkValue();
|
const recordPkValue = useRecordPkValue();
|
||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
@ -120,14 +121,20 @@ export const CreateRecordAction = observer(
|
|||||||
{ displayName: 'CreateRecordAction' },
|
{ displayName: 'CreateRecordAction' },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function getLinkageCollection(str, form) {
|
||||||
|
const data = parseVariables(str, { $form: form.values });
|
||||||
|
return data;
|
||||||
|
}
|
||||||
export const CreateAction = observer(
|
export const CreateAction = observer(
|
||||||
(props: any) => {
|
(props: any) => {
|
||||||
const { onClick } = props;
|
const { onClick } = props;
|
||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const field: any = useField();
|
||||||
|
const form = useForm();
|
||||||
const enableChildren = fieldSchema['x-enable-children'] || [];
|
const enableChildren = fieldSchema['x-enable-children'] || [];
|
||||||
const allowAddToCurrent = fieldSchema?.['x-allow-add-to-current'];
|
const allowAddToCurrent = fieldSchema?.['x-allow-add-to-current'];
|
||||||
const field: any = useField();
|
const linkageFromForm = fieldSchema?.['x-component-props']?.['linkageFromForm'];
|
||||||
const componentType = field.componentProps.type || 'primary';
|
const componentType = field.componentProps.type || 'primary';
|
||||||
const { getChildrenCollections } = useCollectionManager();
|
const { getChildrenCollections } = useCollectionManager();
|
||||||
const totalChildCollections = getChildrenCollections(collection.name);
|
const totalChildCollections = getChildrenCollections(collection.name);
|
||||||
@ -182,6 +189,7 @@ export const CreateAction = observer(
|
|||||||
return (
|
return (
|
||||||
<div className={actionDesignerCss}>
|
<div className={actionDesignerCss}>
|
||||||
{inheritsCollections?.length > 0 ? (
|
{inheritsCollections?.length > 0 ? (
|
||||||
|
!linkageFromForm ? (
|
||||||
allowAddToCurrent === undefined || allowAddToCurrent ? (
|
allowAddToCurrent === undefined || allowAddToCurrent ? (
|
||||||
<Dropdown.Button
|
<Dropdown.Button
|
||||||
type={componentType}
|
type={componentType}
|
||||||
@ -207,6 +215,27 @@ export const CreateAction = observer(
|
|||||||
}
|
}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)
|
)
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type={componentType}
|
||||||
|
disabled={field.disabled}
|
||||||
|
danger={componentType === 'danger'}
|
||||||
|
icon={icon}
|
||||||
|
onClick={(info) => {
|
||||||
|
const collectionName = getLinkageCollection(linkageFromForm, form);
|
||||||
|
const targetCollection = inheritsCollections.find((v) => v.name === collectionName)
|
||||||
|
? collectionName
|
||||||
|
: collection.name;
|
||||||
|
onClick?.(targetCollection);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
display: !designable && field?.data?.hidden && 'none',
|
||||||
|
opacity: designable && field?.data?.hidden && 0.1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
type={componentType}
|
type={componentType}
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { observer, useFieldSchema } from '@formily/react';
|
||||||
|
import React, { useEffect, useMemo } from 'react';
|
||||||
|
import { useCompile } from '../../schema-component';
|
||||||
|
import { Variable } from '.././../schema-component';
|
||||||
|
import { useFormVariable } from '../VariableInput/hooks/useFormVariable';
|
||||||
|
|
||||||
|
export const ChildDynamicComponent = observer(
|
||||||
|
(props: { collectionName: string; form: any; onChange; value; default }) => {
|
||||||
|
const { form, collectionName, onChange, value } = props;
|
||||||
|
const formVariabele = useFormVariable({ blockForm: form, rootCollection: collectionName });
|
||||||
|
const compile = useCompile();
|
||||||
|
const result = useMemo(() => [formVariabele].filter(Boolean), [formVariabele]);
|
||||||
|
const scope = compile(result);
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
useEffect(() => {
|
||||||
|
onChange(fieldSchema.default);
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<Variable.Input
|
||||||
|
value={value}
|
||||||
|
onChange={(v) => onChange(v)}
|
||||||
|
scope={scope}
|
||||||
|
style={{ minWidth: '400px', marginRight: 15 }}
|
||||||
|
className={css`
|
||||||
|
.ant-input {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ displayName: 'ChildDynamicComponent' },
|
||||||
|
);
|
@ -1,8 +1,8 @@
|
|||||||
import { observer, useForm } from '@formily/react';
|
import { observer, useForm } from '@formily/react';
|
||||||
import React from 'react';
|
|
||||||
import { action } from '@formily/reactive';
|
import { action } from '@formily/reactive';
|
||||||
import { SchemaComponent, useCompile } from '../../schema-component';
|
import React from 'react';
|
||||||
import { useCollectionManager } from '../../collection-manager';
|
import { useCollectionManager } from '../../collection-manager';
|
||||||
|
import { SchemaComponent, useCompile } from '../../schema-component';
|
||||||
|
|
||||||
export const EnableChildCollections = observer(
|
export const EnableChildCollections = observer(
|
||||||
(props: any) => {
|
(props: any) => {
|
||||||
@ -16,6 +16,7 @@ export const EnableChildCollections = observer(
|
|||||||
const useAsyncDataSource = (service: any) => {
|
const useAsyncDataSource = (service: any) => {
|
||||||
return (field: any, options?: any) => {
|
return (field: any, options?: any) => {
|
||||||
field.loading = true;
|
field.loading = true;
|
||||||
|
// eslint-disable-next-line promise/catch-or-return
|
||||||
service(field, options).then(
|
service(field, options).then(
|
||||||
action.bound((data: any) => {
|
action.bound((data: any) => {
|
||||||
field.dataSource = data;
|
field.dataSource = data;
|
||||||
@ -28,7 +29,7 @@ export const EnableChildCollections = observer(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
const loadData = async (field) => {
|
const loadData = async (field) => {
|
||||||
const { childrenCollections: childCollections } = form.values?.enableChildren;
|
const { childrenCollections: childCollections } = form.values?.enableChildren || {};
|
||||||
return childrenCollections
|
return childrenCollections
|
||||||
.filter((v) => {
|
.filter((v) => {
|
||||||
return !childCollections.find((k) => k.collection === v.name) || field.initialValue || v.name === field.value;
|
return !childCollections.find((k) => k.collection === v.name) || field.initialValue || v.name === field.value;
|
||||||
|
@ -3,10 +3,10 @@ import { observer, useFieldSchema } from '@formily/react';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SchemaComponent } from '../../schema-component';
|
import { SchemaComponent } from '../../schema-component';
|
||||||
import { FilterContext } from '../../schema-component/antd/filter/context';
|
import { FilterContext } from '../../schema-component/antd/filter/context';
|
||||||
|
import { EnableLinkage } from './components/EnableLinkage';
|
||||||
|
import { ArrayCollapse } from './components/LinkageHeader';
|
||||||
import { FilterDynamicComponent } from './FilterDynamicComponent';
|
import { FilterDynamicComponent } from './FilterDynamicComponent';
|
||||||
import { LinkageRuleActionGroup } from './LinkageRuleActionGroup';
|
import { LinkageRuleActionGroup } from './LinkageRuleActionGroup';
|
||||||
import { ArrayCollapse } from './components/LinkageHeader';
|
|
||||||
import { EnableLinkage } from './components/EnableLinkage';
|
|
||||||
|
|
||||||
export const FormLinkageRules = observer(
|
export const FormLinkageRules = observer(
|
||||||
(props: any) => {
|
(props: any) => {
|
||||||
@ -32,7 +32,7 @@ export const FormLinkageRules = observer(
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
'x-component': 'ArrayCollapse.CollapsePanel',
|
'x-component': 'ArrayCollapse.CollapsePanel',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
extra: [<EnableLinkage />],
|
extra: <EnableLinkage />,
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
layout: {
|
layout: {
|
||||||
@ -53,6 +53,7 @@ export const FormLinkageRules = observer(
|
|||||||
condition: {
|
condition: {
|
||||||
'x-component': 'Filter',
|
'x-component': 'Filter',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
|
collectionName,
|
||||||
useProps() {
|
useProps() {
|
||||||
return {
|
return {
|
||||||
options,
|
options,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { ArrayCollapse, ArrayItems, FormDialog, FormItem, FormLayout, Input } from '@formily/antd';
|
import { ArrayCollapse, ArrayItems, FormDialog, FormItem, FormLayout, Input } from '@formily/antd';
|
||||||
import { Field, GeneralField, createForm } from '@formily/core';
|
import { createForm, Field, GeneralField } from '@formily/core';
|
||||||
import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react';
|
import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { error } from '@nocobase/utils/client';
|
import { error } from '@nocobase/utils/client';
|
||||||
@ -21,30 +21,31 @@ import {
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import _, { cloneDeep } from 'lodash';
|
import _, { cloneDeep } from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
ReactNode,
|
|
||||||
createContext,
|
createContext,
|
||||||
|
ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
useState,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
useTransition as useReactTransition,
|
useTransition as useReactTransition,
|
||||||
useState,
|
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
APIClientProvider,
|
|
||||||
ActionContextProvider,
|
ActionContextProvider,
|
||||||
|
APIClientProvider,
|
||||||
CollectionFieldOptions,
|
CollectionFieldOptions,
|
||||||
CollectionManagerContext,
|
CollectionManagerContext,
|
||||||
|
CollectionProvider,
|
||||||
|
createDesignable,
|
||||||
Designable,
|
Designable,
|
||||||
|
findFormBlock,
|
||||||
FormProvider,
|
FormProvider,
|
||||||
RemoteSchemaComponent,
|
RemoteSchemaComponent,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
SchemaComponentContext,
|
SchemaComponentContext,
|
||||||
SchemaComponentOptions,
|
SchemaComponentOptions,
|
||||||
createDesignable,
|
|
||||||
findFormBlock,
|
|
||||||
useAPIClient,
|
useAPIClient,
|
||||||
useCollection,
|
useCollection,
|
||||||
useCollectionManager,
|
useCollectionManager,
|
||||||
@ -61,6 +62,7 @@ import { useSchemaTemplateManager } from '../schema-templates';
|
|||||||
import { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
|
import { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
|
||||||
import { FormDataTemplates } from './DataTemplates';
|
import { FormDataTemplates } from './DataTemplates';
|
||||||
import { EnableChildCollections } from './EnableChildCollections';
|
import { EnableChildCollections } from './EnableChildCollections';
|
||||||
|
import { ChildDynamicComponent } from './EnableChildCollections/DynamicComponent';
|
||||||
import { FormLinkageRules } from './LinkageRules';
|
import { FormLinkageRules } from './LinkageRules';
|
||||||
import { useLinkageCollectionFieldOptions } from './LinkageRules/action-hooks';
|
import { useLinkageCollectionFieldOptions } from './LinkageRules/action-hooks';
|
||||||
|
|
||||||
@ -870,6 +872,7 @@ SchemaSettings.ModalItem = function ModalItem(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
const options = useContext(SchemaOptionsContext);
|
const options = useContext(SchemaOptionsContext);
|
||||||
const cm = useContext(CollectionManagerContext);
|
const cm = useContext(CollectionManagerContext);
|
||||||
|
const collection = useCollection();
|
||||||
const apiClient = useAPIClient();
|
const apiClient = useAPIClient();
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
return null;
|
return null;
|
||||||
@ -882,6 +885,7 @@ SchemaSettings.ModalItem = function ModalItem(props) {
|
|||||||
FormDialog({ title: schema.title || title, width }, () => {
|
FormDialog({ title: schema.title || title, width }, () => {
|
||||||
return (
|
return (
|
||||||
<CollectionManagerContext.Provider value={cm}>
|
<CollectionManagerContext.Provider value={cm}>
|
||||||
|
<CollectionProvider collection={collection}>
|
||||||
<SchemaComponentOptions scope={options.scope} components={options.components}>
|
<SchemaComponentOptions scope={options.scope} components={options.components}>
|
||||||
<FormLayout layout={'vertical'} style={{ minWidth: 520 }}>
|
<FormLayout layout={'vertical'} style={{ minWidth: 520 }}>
|
||||||
<APIClientProvider apiClient={apiClient}>
|
<APIClientProvider apiClient={apiClient}>
|
||||||
@ -889,6 +893,7 @@ SchemaSettings.ModalItem = function ModalItem(props) {
|
|||||||
</APIClientProvider>
|
</APIClientProvider>
|
||||||
</FormLayout>
|
</FormLayout>
|
||||||
</SchemaComponentOptions>
|
</SchemaComponentOptions>
|
||||||
|
</CollectionProvider>
|
||||||
</CollectionManagerContext.Provider>
|
</CollectionManagerContext.Provider>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -1169,13 +1174,19 @@ SchemaSettings.DataTemplates = function DataTemplates(props) {
|
|||||||
SchemaSettings.EnableChildCollections = function EnableChildCollectionsItem(props) {
|
SchemaSettings.EnableChildCollections = function EnableChildCollectionsItem(props) {
|
||||||
const { collectionName } = props;
|
const { collectionName } = props;
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const field = useField();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const allowAddToCurrent = fieldSchema?.['x-allow-add-to-current'];
|
const allowAddToCurrent = fieldSchema?.['x-allow-add-to-current'];
|
||||||
|
const form = useForm();
|
||||||
|
const { getCollectionJoinField } = useCollectionManager();
|
||||||
|
const collectionField = getCollectionJoinField(fieldSchema?.parent?.['x-collection-field']) || {};
|
||||||
|
const isAssocationAdd = fieldSchema?.parent?.['x-component'] === 'CollectionField';
|
||||||
return (
|
return (
|
||||||
<SchemaSettings.ModalItem
|
<SchemaSettings.ModalItem
|
||||||
title={t('Enable child collections')}
|
title={t('Enable child collections')}
|
||||||
components={{ ArrayItems, FormLayout }}
|
components={{ ArrayItems, FormLayout }}
|
||||||
|
scope={{ isAssocationAdd }}
|
||||||
schema={
|
schema={
|
||||||
{
|
{
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -1199,6 +1210,18 @@ SchemaSettings.EnableChildCollections = function EnableChildCollectionsItem(prop
|
|||||||
'x-component': 'Checkbox',
|
'x-component': 'Checkbox',
|
||||||
default: allowAddToCurrent === undefined ? true : allowAddToCurrent,
|
default: allowAddToCurrent === undefined ? true : allowAddToCurrent,
|
||||||
},
|
},
|
||||||
|
linkageFromForm: {
|
||||||
|
type: 'string',
|
||||||
|
title: "{{t('Linkage form form')}}",
|
||||||
|
'x-visible': '{{isAssocationAdd}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': ChildDynamicComponent,
|
||||||
|
'x-component-props': {
|
||||||
|
collectionName: collectionField?.collectionName || collectionName,
|
||||||
|
form,
|
||||||
|
},
|
||||||
|
default: fieldSchema?.['x-component-props']?.['linkageFromForm'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as ISchema
|
} as ISchema
|
||||||
}
|
}
|
||||||
@ -1216,13 +1239,16 @@ SchemaSettings.EnableChildCollections = function EnableChildCollectionsItem(prop
|
|||||||
fieldSchema['x-component-props'] = {
|
fieldSchema['x-component-props'] = {
|
||||||
...fieldSchema['x-component-props'],
|
...fieldSchema['x-component-props'],
|
||||||
component: 'CreateRecordAction',
|
component: 'CreateRecordAction',
|
||||||
|
linkageFromForm: v?.linkageFromForm,
|
||||||
};
|
};
|
||||||
schema['x-enable-children'] = enableChildren;
|
schema['x-enable-children'] = enableChildren;
|
||||||
schema['x-allow-add-to-current'] = v.allowAddToCurrent;
|
schema['x-allow-add-to-current'] = v.allowAddToCurrent;
|
||||||
schema['x-component-props'] = {
|
schema['x-component-props'] = {
|
||||||
...fieldSchema['x-component-props'],
|
...fieldSchema['x-component-props'],
|
||||||
component: 'CreateRecordAction',
|
component: 'CreateRecordAction',
|
||||||
|
linkageFromForm: v?.linkageFromForm,
|
||||||
};
|
};
|
||||||
|
field.componentProps['linkageFromForm'] = v.linkageFromForm;
|
||||||
dn.emit('patch', {
|
dn.emit('patch', {
|
||||||
schema,
|
schema,
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useCompile, useGetFilterOptions } from '../../../schema-component';
|
import { useCompile, useGetFilterOptions } from '../../../schema-component';
|
||||||
import { Schema } from '@formily/react';
|
|
||||||
import { FieldOption, Option } from '../type';
|
import { FieldOption, Option } from '../type';
|
||||||
|
|
||||||
interface GetOptionsParams {
|
interface GetOptionsParams {
|
||||||
@ -50,13 +49,11 @@ export const useFormVariable = ({
|
|||||||
blockForm,
|
blockForm,
|
||||||
rootCollection,
|
rootCollection,
|
||||||
operator,
|
operator,
|
||||||
schema,
|
|
||||||
level,
|
level,
|
||||||
}: {
|
}: {
|
||||||
blockForm?: any;
|
blockForm?: any;
|
||||||
rootCollection: string;
|
rootCollection: string;
|
||||||
operator?: any;
|
operator?: any;
|
||||||
schema: Schema;
|
|
||||||
level?: number;
|
level?: number;
|
||||||
}) => {
|
}) => {
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
@ -97,7 +94,6 @@ export const useFormVariable = ({
|
|||||||
}, 5);
|
}, 5);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = useMemo(() => {
|
const result = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
blockForm && {
|
blockForm && {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useValues } from '../../../schema-component/antd/filter/useValues';
|
import { useValues } from '../../../schema-component/antd/filter/useValues';
|
||||||
import { useDateVariable } from './useDateVariable';
|
import { useDateVariable } from './useDateVariable';
|
||||||
import { useUserVariable } from './useUserVariable';
|
|
||||||
import { useFormVariable } from './useFormVariable';
|
import { useFormVariable } from './useFormVariable';
|
||||||
import { useIterationVariable } from './useIterationVariable';
|
import { useIterationVariable } from './useIterationVariable';
|
||||||
|
import { useUserVariable } from './useUserVariable';
|
||||||
|
|
||||||
export const useVariableOptions = ({ form, collectionField, rootCollection }) => {
|
export const useVariableOptions = ({ form, collectionField, rootCollection }: any) => {
|
||||||
const { operator, schema } = useValues();
|
const { operator, schema } = useValues();
|
||||||
const userVariable = useUserVariable({ maxDepth: 3, schema });
|
const userVariable = useUserVariable({ maxDepth: 3, schema });
|
||||||
const dateVariable = useDateVariable({ operator, schema });
|
const dateVariable = useDateVariable({ operator, schema });
|
||||||
const formVariabele = useFormVariable({ blockForm: form, rootCollection, schema });
|
const formVariabele = useFormVariable({ blockForm: form, rootCollection });
|
||||||
const iterationVariabele = useIterationVariable({ blockForm: form, collectionField, schema, rootCollection });
|
const iterationVariabele = useIterationVariable({ blockForm: form, collectionField, schema, rootCollection });
|
||||||
|
|
||||||
const result = useMemo(
|
const result = useMemo(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { BelongsToManyRepository } from '@nocobase/database';
|
||||||
import Database from '../../database';
|
import Database from '../../database';
|
||||||
import { InheritedCollection } from '../../inherited-collection';
|
import { InheritedCollection } from '../../inherited-collection';
|
||||||
import { mockDatabase } from '../index';
|
import { mockDatabase } from '../index';
|
||||||
import { BelongsToManyRepository } from '@nocobase/database';
|
|
||||||
import pgOnly from './helper';
|
import pgOnly from './helper';
|
||||||
|
|
||||||
pgOnly()('collection inherits', () => {
|
pgOnly()('collection inherits', () => {
|
||||||
@ -182,9 +182,17 @@ pgOnly()('collection inherits', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should list data filtered by child type', async () => {
|
it('should list data filtered by child type', async () => {
|
||||||
|
const assocs = db.collection({
|
||||||
|
name: 'assocs',
|
||||||
|
fields: [{ name: 'name', type: 'string' }],
|
||||||
|
});
|
||||||
|
|
||||||
const rootCollection = db.collection({
|
const rootCollection = db.collection({
|
||||||
name: 'root',
|
name: 'root',
|
||||||
fields: [{ name: 'name', type: 'string' }],
|
fields: [
|
||||||
|
{ name: 'name', type: 'string' },
|
||||||
|
{ name: 'assocs', type: 'hasMany', target: 'assocs' },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const child1Collection = db.collection({
|
const child1Collection = db.collection({
|
||||||
@ -202,11 +210,29 @@ pgOnly()('collection inherits', () => {
|
|||||||
await rootCollection.repository.create({
|
await rootCollection.repository.create({
|
||||||
values: {
|
values: {
|
||||||
name: 'root1',
|
name: 'root1',
|
||||||
|
assocs: [
|
||||||
|
{
|
||||||
|
name: 'assoc1',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await child1Collection.repository.create({
|
await child1Collection.repository.create({
|
||||||
values: [{ name: 'child1-1' }, { name: 'child1-2' }],
|
values: [
|
||||||
|
{
|
||||||
|
name: 'child1-1',
|
||||||
|
assocs: [
|
||||||
|
{
|
||||||
|
name: 'child-assoc1-1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'child1-2',
|
||||||
|
assocs: [{ name: 'child-assoc1-2' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
await child2Collection.repository.create({
|
await child2Collection.repository.create({
|
||||||
@ -215,18 +241,38 @@ pgOnly()('collection inherits', () => {
|
|||||||
|
|
||||||
const records = await rootCollection.repository.find({
|
const records = await rootCollection.repository.find({
|
||||||
filter: {
|
filter: {
|
||||||
'tableoid.$childIn': [child1Collection.name],
|
'__collection.$childIn': [child1Collection.name],
|
||||||
},
|
},
|
||||||
|
appends: ['assocs'],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(records.every((r) => r.get('__collection') === child1Collection.name)).toBe(true);
|
expect(records.every((r) => r.get('__collection') === child1Collection.name)).toBe(true);
|
||||||
|
|
||||||
const records2 = await rootCollection.repository.find({
|
const records2 = await rootCollection.repository.find({
|
||||||
filter: {
|
filter: {
|
||||||
'tableoid.$childNotIn': [child1Collection.name],
|
'__collection.$childNotIn': [child1Collection.name],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(records2.every((r) => r.get('__collection') !== child1Collection.name)).toBe(true);
|
expect(records2.every((r) => r.get('__collection') !== child1Collection.name)).toBe(true);
|
||||||
|
|
||||||
|
const recordsWithFilter = await rootCollection.repository.find({
|
||||||
|
filter: {
|
||||||
|
'__collection.$childIn': [child1Collection.name],
|
||||||
|
assocs: {
|
||||||
|
name: 'child-assoc1-1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(recordsWithFilter.every((r) => r.get('__collection') == child1Collection.name)).toBe(true);
|
||||||
|
|
||||||
|
const filterWithUndefined = await rootCollection.repository.find({
|
||||||
|
filter: {
|
||||||
|
'__collection.$childIn': 'undefined',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(filterWithUndefined).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should list collection name in relation repository', async () => {
|
it('should list collection name in relation repository', async () => {
|
||||||
|
@ -9,7 +9,7 @@ export class UidField extends Field {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
const { name, prefix = '', pattern } = this.options;
|
const { name, prefix = '', pattern } = this.options;
|
||||||
const re = new RegExp(pattern || '^[A-Za-z0-9][A-Za-z0-9_-]*$');
|
const re = new RegExp(pattern || '^[A-Za-z0-9_][A-Za-z0-9_-]*$');
|
||||||
this.listener = async (instance) => {
|
this.listener = async (instance) => {
|
||||||
const value = instance.get(name);
|
const value = instance.get(name);
|
||||||
if (!value) {
|
if (!value) {
|
||||||
|
@ -226,6 +226,7 @@ export default class FilterParser {
|
|||||||
if (values && typeof values === 'object' && value && typeof value === 'object') {
|
if (values && typeof values === 'object' && value && typeof value === 'object') {
|
||||||
value = { ...value, ...values };
|
value = { ...value, ...values };
|
||||||
}
|
}
|
||||||
|
|
||||||
_.set(where, paths, value);
|
_.set(where, paths, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Op, Sequelize } from 'sequelize';
|
import lodash from 'lodash';
|
||||||
|
import { Sequelize } from 'sequelize';
|
||||||
|
|
||||||
const mapVal = (values, db) =>
|
const mapVal = (values, db) =>
|
||||||
values.map((v) => {
|
values.map((v) => {
|
||||||
@ -6,19 +7,37 @@ const mapVal = (values, db) =>
|
|||||||
return Sequelize.literal(`'${collection.tableNameAsString()}'::regclass`);
|
return Sequelize.literal(`'${collection.tableNameAsString()}'::regclass`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const filterItems = (values, db) => {
|
||||||
|
return lodash
|
||||||
|
.castArray(values)
|
||||||
|
.map((v) => {
|
||||||
|
const collection = db.getCollection(v);
|
||||||
|
if (!collection) return null;
|
||||||
|
return `'${collection.tableNameAsString()}'::regclass`;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
};
|
||||||
|
const joinValues = (items) => items.join(', ');
|
||||||
export default {
|
export default {
|
||||||
$childIn(values, ctx: any) {
|
$childIn(values, ctx: any) {
|
||||||
const db = ctx.db;
|
const db = ctx.db;
|
||||||
|
const items = filterItems(values, db);
|
||||||
|
|
||||||
return {
|
if (items.length) {
|
||||||
[Op.in]: mapVal(values, db),
|
return Sequelize.literal(`"${ctx.model.name}"."tableoid" IN (${joinValues(items)})`);
|
||||||
};
|
} else {
|
||||||
|
return Sequelize.literal(`1 = 2`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
$childNotIn(values, ctx: any) {
|
$childNotIn(values, ctx: any) {
|
||||||
const db = ctx.db;
|
const db = ctx.db;
|
||||||
|
const items = filterItems(values, db);
|
||||||
|
|
||||||
return {
|
if (items.length) {
|
||||||
[Op.notIn]: mapVal(values, db),
|
return Sequelize.literal(`"${ctx.model.name}"."tableoid" NOT IN (${joinValues(items)})`);
|
||||||
};
|
} else {
|
||||||
|
return Sequelize.literal(`1 = 1`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
} as Record<string, any>;
|
} as Record<string, any>;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { flatten } from 'flat';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
import {
|
import {
|
||||||
Association,
|
Association,
|
||||||
@ -18,6 +19,7 @@ import { Collection } from './collection';
|
|||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
import mustHaveFilter from './decorators/must-have-filter-decorator';
|
import mustHaveFilter from './decorators/must-have-filter-decorator';
|
||||||
import { transactionWrapperBuilder } from './decorators/transaction-decorator';
|
import { transactionWrapperBuilder } from './decorators/transaction-decorator';
|
||||||
|
import { EagerLoadingTree } from './eager-loading/eager-loading-tree';
|
||||||
import { ArrayFieldRepository } from './field-repository/array-field-repository';
|
import { ArrayFieldRepository } from './field-repository/array-field-repository';
|
||||||
import { ArrayField, RelationField } from './fields';
|
import { ArrayField, RelationField } from './fields';
|
||||||
import FilterParser from './filter-parser';
|
import FilterParser from './filter-parser';
|
||||||
@ -31,8 +33,6 @@ import { HasOneRepository } from './relation-repository/hasone-repository';
|
|||||||
import { RelationRepository } from './relation-repository/relation-repository';
|
import { RelationRepository } from './relation-repository/relation-repository';
|
||||||
import { updateAssociations, updateModelByValues } from './update-associations';
|
import { updateAssociations, updateModelByValues } from './update-associations';
|
||||||
import { UpdateGuard } from './update-guard';
|
import { UpdateGuard } from './update-guard';
|
||||||
import { EagerLoadingTree } from './eager-loading/eager-loading-tree';
|
|
||||||
import { flatten } from 'flat';
|
|
||||||
|
|
||||||
const debug = require('debug')('noco-database');
|
const debug = require('debug')('noco-database');
|
||||||
|
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
import { Migration } from '@nocobase/server';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export default class extends Migration {
|
||||||
|
async up() {
|
||||||
|
if (!this.db.inDialect('postgres')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const repository = this.db.getRepository('collections');
|
||||||
|
let names = [];
|
||||||
|
const items = await repository.find();
|
||||||
|
for (const item of items) {
|
||||||
|
if (Array.isArray(item.options?.inherits) && item.options.inherits.length) {
|
||||||
|
names.push(item.name);
|
||||||
|
names.push(...item.options.inherits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
names = _.uniq(names);
|
||||||
|
console.log('collection names:', names);
|
||||||
|
for (const name of names) {
|
||||||
|
const fieldRepository = this.db.getRepository('fields');
|
||||||
|
await fieldRepository.firstOrCreate({
|
||||||
|
values: {
|
||||||
|
collectionName: name,
|
||||||
|
name: '__collection',
|
||||||
|
type: 'virtual',
|
||||||
|
interface: 'tableoid',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Table OID")}}',
|
||||||
|
'x-component': 'CollectionSelect',
|
||||||
|
'x-component-props': { isTableOid: true },
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filterKeys: ['name', 'collectionName'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ const useCreateCollectionField = (record) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddFieldAction = ({ item: record }) => {
|
export const AddFieldAction = ({ item: record, database }) => {
|
||||||
return (
|
return (
|
||||||
<AddCollectionFieldAction
|
<AddCollectionFieldAction
|
||||||
trigger={['click']}
|
trigger={['click']}
|
||||||
@ -24,6 +24,7 @@ export const AddFieldAction = ({ item: record }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
item={record}
|
item={record}
|
||||||
|
database={database}
|
||||||
scope={{
|
scope={{
|
||||||
useCancelAction,
|
useCancelAction,
|
||||||
useCreateCollectionField: () => useCreateCollectionField(record),
|
useCreateCollectionField: () => useCreateCollectionField(record),
|
||||||
|
@ -5,6 +5,7 @@ import { uid } from '@formily/shared';
|
|||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
collection,
|
||||||
CollectionCategroriesContext,
|
CollectionCategroriesContext,
|
||||||
CollectionField,
|
CollectionField,
|
||||||
CollectionProvider,
|
CollectionProvider,
|
||||||
@ -19,7 +20,6 @@ import {
|
|||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
SchemaComponentProvider,
|
SchemaComponentProvider,
|
||||||
Select,
|
Select,
|
||||||
collection,
|
|
||||||
useCollectionManager,
|
useCollectionManager,
|
||||||
useCompile,
|
useCompile,
|
||||||
useCurrentAppInfo,
|
useCurrentAppInfo,
|
||||||
@ -190,6 +190,7 @@ const PortsCom = React.memo<any>(({ targetGraph, collectionData, setTargetNode,
|
|||||||
const [collapse, setCollapse] = useState(false);
|
const [collapse, setCollapse] = useState(false);
|
||||||
const { t } = useGCMTranslation();
|
const { t } = useGCMTranslation();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
|
const database = useCurrentAppInfo();
|
||||||
const portsData = groupBy(ports.items, (v) => {
|
const portsData = groupBy(ports.items, (v) => {
|
||||||
if (
|
if (
|
||||||
v.isForeignKey ||
|
v.isForeignKey ||
|
||||||
@ -292,6 +293,7 @@ const PortsCom = React.memo<any>(({ targetGraph, collectionData, setTargetNode,
|
|||||||
...property,
|
...property,
|
||||||
title,
|
title,
|
||||||
},
|
},
|
||||||
|
database,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
|
Loading…
Reference in New Issue
Block a user