feat(calendar): startDate and endDate support the use of association fields (#1397)

* feat(calendar): startDate and endDate support the use of association fields

* feat: improve association type
This commit is contained in:
Dunqing 2023-02-01 12:55:46 +08:00 committed by GitHub
parent c239a5ad63
commit 65a579384a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 63 deletions

View File

@ -1,11 +1,14 @@
import { clone } from '@formily/shared'; import { clone } from '@formily/shared';
import { CascaderProps } from 'antd';
import { reduce, unionBy, uniq } from 'lodash'; import { reduce, unionBy, uniq } from 'lodash';
import { useContext } from 'react'; import { useContext } from 'react';
import { useCompile } from '../../schema-component';
import { CollectionManagerContext } from '../context'; import { CollectionManagerContext } from '../context';
import { CollectionFieldOptions } from '../types'; import { CollectionFieldOptions } from '../types';
export const useCollectionManager = () => { export const useCollectionManager = () => {
const { refreshCM, service, interfaces, collections, templates } = useContext(CollectionManagerContext); const { refreshCM, service, interfaces, collections, templates } = useContext(CollectionManagerContext);
const compile = useCompile()
const getInheritedFields = (name) => { const getInheritedFields = (name) => {
const inheritKeys = getInheritCollections(name); const inheritKeys = getInheritCollections(name);
const inheritedFields = reduce( const inheritedFields = reduce(
@ -59,25 +62,65 @@ export const useCollectionManager = () => {
}; };
const getChildrenCollections = (name) => { const getChildrenCollections = (name) => {
const childrens = []; const children = [];
const getChildrens = (name) => { const getChildren = (name) => {
const inheritCollections = collections.filter((v) => { const inheritCollections = collections.filter((v) => {
return v.inherits?.includes(name); return v.inherits?.includes(name);
}); });
inheritCollections.forEach((v) => { inheritCollections.forEach((v) => {
const collectionKey = v.name; const collectionKey = v.name;
childrens.push(v); children.push(v);
return getChildrens(collectionKey); return getChildren(collectionKey);
}); });
return childrens; return children;
}; };
return getChildrens(name); return getChildren(name);
}; };
const getCurrentCollectionFields = (name: string) => { const getCurrentCollectionFields = (name: string) => {
const collection = collections?.find((collection) => collection.name === name); const collection = collections?.find((collection) => collection.name === name);
return collection?.fields || []; return collection?.fields || [];
}; };
const getCollectionFieldsOptions = (collectionName: string, type: string | string[] = 'string', opts?: {
/**
* true
* Array<string>
*/
association?: boolean | string[];
}) => {
const { association = false } = opts || {};
if (typeof type === 'string') {
type = [type];
}
const fields = getCollectionFields(collectionName);
const options = fields
?.filter(
(field) =>
field.interface &&
(type.includes(field.type) || (association && field.target && field.target !== collectionName &&
Array.isArray(association) ? association.includes(field.interface) : false
)),
)
?.map((field) => {
const result: CascaderProps<any>['options'][0] = {
value: field.name,
label: compile(field?.uiSchema?.title) || field.name,
};
if (association && field.target) {
result.children = getCollectionFieldsOptions(field.target, type, opts);
if (!result.children?.length) {
return null
}
}
return result;
})
// 过滤 map 产生为 null 的数据
.filter(Boolean)
return options;
};
return { return {
service, service,
interfaces, interfaces,
@ -91,6 +134,7 @@ export const useCollectionManager = () => {
getInheritedFields, getInheritedFields,
getCollectionField, getCollectionField,
getCollectionFields, getCollectionFields,
getCollectionFieldsOptions,
getCurrentCollectionFields, getCurrentCollectionFields,
getCollection(name: any) { getCollection(name: any) {
if (typeof name !== 'string') { if (typeof name !== 'string') {

View File

@ -3,33 +3,19 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useCompile, useDesignable } from '../..'; import { useCompile, useDesignable } from '../..';
import { useCalendarBlockContext } from '../../../block-provider'; import { useCalendarBlockContext } from '../../../block-provider';
import { useCollection } from '../../../collection-manager'; import { useCollection, useCollectionManager } from '../../../collection-manager';
import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks'; import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
import { useSchemaTemplate } from '../../../schema-templates'; import { useSchemaTemplate } from '../../../schema-templates';
const useOptions = (type = 'string') => {
const compile = useCompile();
const { fields } = useCollection();
const options = fields
?.filter((field) => field.type === type)
?.map((field) => {
return {
value: field.name,
label: compile(field?.uiSchema?.title),
};
});
return options;
};
export const CalendarDesigner = () => { export const CalendarDesigner = () => {
const field = useField(); const field = useField();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { name, title, fields } = useCollection(); const { name, title } = useCollection();
const { getCollectionFieldsOptions } = useCollectionManager();
const dataSource = useCollectionFilterOptions(name); const dataSource = useCollectionFilterOptions(name);
const { service } = useCalendarBlockContext(); const { service } = useCalendarBlockContext();
const { dn } = useDesignable(); const { dn } = useDesignable();
const compile = useCompile();
const { t } = useTranslation(); const { t } = useTranslation();
const template = useSchemaTemplate(); const template = useSchemaTemplate();
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {}; const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
@ -41,7 +27,7 @@ export const CalendarDesigner = () => {
<SchemaSettings.SelectItem <SchemaSettings.SelectItem
title={t('Title field')} title={t('Title field')}
value={fieldNames.title} value={fieldNames.title}
options={useOptions('string')} options={getCollectionFieldsOptions(name, 'string')}
onChange={(title) => { onChange={(title) => {
const fieldNames = field.decoratorProps.fieldNames || {}; const fieldNames = field.decoratorProps.fieldNames || {};
fieldNames['title'] = title; fieldNames['title'] = title;
@ -74,10 +60,12 @@ export const CalendarDesigner = () => {
dn.refresh(); dn.refresh();
}} }}
/> />
<SchemaSettings.SelectItem <SchemaSettings.CascaderItem
title={t('Start date field')} title={t('Start date field')}
value={fieldNames.start} value={fieldNames.start}
options={useOptions('date')} options={getCollectionFieldsOptions(name, 'date', {
association: ['o2o', 'obo', 'oho', 'm2o'],
})}
onChange={(start) => { onChange={(start) => {
const fieldNames = field.decoratorProps.fieldNames || {}; const fieldNames = field.decoratorProps.fieldNames || {};
fieldNames['start'] = start; fieldNames['start'] = start;
@ -93,10 +81,12 @@ export const CalendarDesigner = () => {
dn.refresh(); dn.refresh();
}} }}
/> />
<SchemaSettings.SelectItem <SchemaSettings.CascaderItem
title={t('End date field')} title={t('End date field')}
value={fieldNames.end} value={fieldNames.end}
options={useOptions('date')} options={getCollectionFieldsOptions(name, 'date', {
association: ['o2o', 'obo', 'oho', 'm2o'],
})}
onChange={(end) => { onChange={(end) => {
const fieldNames = field.decoratorProps.fieldNames || {}; const fieldNames = field.decoratorProps.fieldNames || {};
fieldNames['end'] = end; fieldNames['end'] = end;

View File

@ -1,18 +1,20 @@
import React, { useContext } from "react"; import React, { useContext } from 'react';
import { FormDialog, FormLayout } from "@formily/antd"; import { FormDialog, FormLayout } from '@formily/antd';
import { FormOutlined } from '@ant-design/icons'; import { FormOutlined } from '@ant-design/icons';
import { SchemaOptionsContext } from "@formily/react"; import { SchemaOptionsContext } from '@formily/react';
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next';
import { useCollectionManager } from "../../collection-manager"; import { useCollection, useCollectionManager } from '../../collection-manager';
import { SchemaComponent, SchemaComponentOptions } from "../../schema-component"; import { SchemaComponent, SchemaComponentOptions, useCompile } from '../../schema-component';
import { createCalendarBlockSchema } from "../utils"; import { createCalendarBlockSchema } from '../utils';
import { DataBlockInitializer } from "./DataBlockInitializer"; import { DataBlockInitializer } from './DataBlockInitializer';
import { CascaderProps } from 'antd';
export const CalendarBlockInitializer = (props) => { export const CalendarBlockInitializer = (props) => {
const { insert } = props; const { insert } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const { getCollectionFields } = useCollectionManager(); const { getCollectionField, getCollectionFieldsOptions } = useCollectionManager();
useCollectionManager;
const options = useContext(SchemaOptionsContext); const options = useContext(SchemaOptionsContext);
return ( return (
<DataBlockInitializer <DataBlockInitializer
@ -20,23 +22,11 @@ export const CalendarBlockInitializer = (props) => {
componentType={'Calendar'} componentType={'Calendar'}
icon={<FormOutlined />} icon={<FormOutlined />}
onCreateBlockSchema={async ({ item }) => { onCreateBlockSchema={async ({ item }) => {
const collectionFields = getCollectionFields(item.name); const stringFieldsOptions = getCollectionFieldsOptions(item.name, 'string');
const stringFields = collectionFields const dateFieldsOptions = getCollectionFieldsOptions(item.name, 'date', {
?.filter((field) => field.type === 'string') association: ['o2o', 'obo', 'oho', 'm2o'],
?.map((field) => { });
return {
label: field?.uiSchema?.title,
value: field.name,
};
});
const dateFields = collectionFields
?.filter((field) => field.type === 'date')
?.map((field) => {
return {
label: field?.uiSchema?.title,
value: field.name,
};
});
const values = await FormDialog(t('Create calendar block'), () => { const values = await FormDialog(t('Create calendar block'), () => {
return ( return (
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}> <SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
@ -46,23 +36,23 @@ export const CalendarBlockInitializer = (props) => {
properties: { properties: {
title: { title: {
title: t('Title field'), title: t('Title field'),
enum: stringFields, enum: stringFieldsOptions,
required: true, required: true,
'x-component': 'Select', 'x-component': 'Select',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
}, },
start: { start: {
title: t('Start date field'), title: t('Start date field'),
enum: dateFields, enum: dateFieldsOptions,
required: true, required: true,
default: 'createdAt', default: getCollectionField(`${item.name}.createdAt`) ? 'createdAt' : null,
'x-component': 'Select', 'x-component': 'Cascader',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
}, },
end: { end: {
title: t('End date field'), title: t('End date field'),
enum: dateFields, enum: dateFieldsOptions,
'x-component': 'Select', 'x-component': 'Cascader',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
}, },
}, },

View File

@ -3,7 +3,19 @@ import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd';
import { createForm, Field, GeneralField } 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 { Alert, Button, Dropdown, Menu, MenuItemProps, Modal, Select, Space, Switch } from 'antd'; import {
Alert,
Button,
Cascader,
CascaderProps,
Dropdown,
Menu,
MenuItemProps,
Modal,
Select,
Space,
Switch,
} from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import React, { createContext, useContext, useMemo, useState } from 'react'; import React, { createContext, useContext, useMemo, useState } from 'react';
@ -22,7 +34,7 @@ import {
useAPIClient, useAPIClient,
useCollection, useCollection,
useCompile, useCompile,
useDesignable useDesignable,
} from '..'; } from '..';
import { useSchemaTemplateManager } from '../schema-templates'; import { useSchemaTemplateManager } from '../schema-templates';
import { useBlockTemplateContext } from '../schema-templates/BlockTemplate'; import { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
@ -61,6 +73,7 @@ type SchemaSettingsNested = {
Divider?: React.FC; Divider?: React.FC;
Popup?: React.FC<MenuItemProps & { schema?: ISchema }>; Popup?: React.FC<MenuItemProps & { schema?: ISchema }>;
SwitchItem?: React.FC<SwitchItemProps>; SwitchItem?: React.FC<SwitchItemProps>;
CascaderItem?: React.FC<CascaderProps<any> & Omit<MenuItemProps, 'title'> & { title: any }>;
[key: string]: any; [key: string]: any;
}; };
@ -350,7 +363,7 @@ SchemaSettings.Item = (props) => {
return ( return (
<Menu.Item <Menu.Item
key={key} key={key}
eventKey={eventKey as any || key} eventKey={(eventKey as any) || key}
{...props} {...props}
onClick={(info) => { onClick={(info) => {
info.domEvent.preventDefault(); info.domEvent.preventDefault();
@ -434,6 +447,24 @@ SchemaSettings.SelectItem = (props) => {
); );
}; };
SchemaSettings.CascaderItem = (props: CascaderProps<any> & { title: any }) => {
const { title, options, value, onChange, ...others } = props;
return (
<SchemaSettings.Item {...(others as any)}>
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
{title}
<Cascader
bordered={false}
defaultValue={value}
onChange={onChange as any}
options={options}
style={{ textAlign: 'right', minWidth: 100 }}
/>
</div>
</SchemaSettings.Item>
);
};
interface SwitchItemProps extends Omit<MenuItemProps, 'onChange'> { interface SwitchItemProps extends Omit<MenuItemProps, 'onChange'> {
title: string; title: string;
checked?: boolean; checked?: boolean;