feat(client): improve the collection manager module

This commit is contained in:
chenos 2022-02-23 18:22:37 +08:00
parent 57c9524f34
commit 37be46aacb
16 changed files with 231 additions and 40 deletions

View File

@ -5,6 +5,7 @@ import {
AntdSchemaComponentProvider,
APIClientProvider,
AuthLayout,
ChinaRegionProvider,
CollectionManagerShortcut,
compose,
DesignableSwitch,
@ -53,6 +54,7 @@ const providers = [
RemoteCollectionManagerProvider,
[SchemaInitializerProvider, { initializers: { MenuItemInitializers } }],
AntdSchemaComponentProvider,
ChinaRegionProvider,
[DocumentTitleProvider, { addonAfter: 'NocoBase' }],
[
RouteSwitchProvider,

View File

@ -0,0 +1,76 @@
import { ArrayField } from '@formily/core';
import { useField } from '@formily/react';
import React from 'react';
import { SchemaComponentOptions } from '..';
import { useAPIClient, useRequest } from '../api-client';
const useChinaRegionDataSource = (options) => {
const field = useField<ArrayField>();
const maxLevel = field.componentProps.maxLevel;
return useRequest(
{
resource: 'chinaRegions',
action: 'list',
params: {
sort: 'code',
filter: {
level: 1,
},
},
},
{
...options,
onSuccess(data) {
options?.onSuccess({
data:
data?.data?.map((item) => {
if (maxLevel !== 1) {
item.isLeaf = false;
}
return item;
}) || [],
});
},
},
);
};
const useChinaRegionLoadData = () => {
const api = useAPIClient();
const field = useField<ArrayField>();
const maxLevel = field.componentProps.maxLevel;
return (selectedOptions) => {
const targetOption = selectedOptions[selectedOptions.length - 1];
if (targetOption?.children?.length > 0) {
return;
}
targetOption.loading = true;
api
.resource('chinaRegions')
.list({
sort: 'code',
filter: {
parentCode: targetOption.code,
},
})
.then(({ data }) => {
targetOption.loading = false;
targetOption.children =
data?.data?.map((item) => {
if (maxLevel > item.level) {
item.isLeaf = false;
}
return item;
}) || [];
field.dataSource = [...field.dataSource];
});
};
};
export const ChinaRegionProvider = (props) => {
return (
<SchemaComponentOptions scope={{ useChinaRegionDataSource, useChinaRegionLoadData }}>
{props.children}
</SchemaComponentOptions>
);
};

View File

@ -1,7 +1,7 @@
import { Field } from '@formily/core';
import { connect, useField, useFieldSchema } from '@formily/react';
import React, { useEffect } from 'react';
import { useComponent } from '..';
import { useCompile, useComponent } from '..';
import { CollectionFieldProvider } from './CollectionFieldProvider';
import { useCollectionField } from './hooks';
@ -11,6 +11,7 @@ const InternalField: React.FC = (props) => {
const fieldSchema = useFieldSchema();
const { uiSchema } = useCollectionField();
const component = useComponent(uiSchema?.['x-component']);
const compile = useCompile();
const setFieldProps = (key, value) => {
field[key] = typeof field[key] === 'undefined' ? value : field[key];
};
@ -24,6 +25,7 @@ const InternalField: React.FC = (props) => {
if (!uiSchema) {
return;
}
console.log('uiSchema', uiSchema);
setFieldProps('title', uiSchema.title);
setFieldProps('description', uiSchema.description);
setFieldProps('initialValue', uiSchema.default);

View File

@ -32,7 +32,7 @@ export const RemoteCollectionManagerProvider = (props: any) => {
resource: 'collections',
action: 'list',
params: {
pageSize: 999,
paginate: false,
appends: ['fields', 'fields.uiSchema'],
},
};

View File

@ -1,18 +1,29 @@
import { PlusOutlined } from '@ant-design/icons';
import { ArrayTable } from '@formily/antd';
import { ISchema } from '@formily/react';
import { ISchema, useForm } from '@formily/react';
import { uid } from '@formily/shared';
import { Button, Dropdown, Menu } from 'antd';
import { cloneDeep } from 'lodash';
import React, { useState } from 'react';
import { useRequest } from '../../api-client';
import { ActionContext, SchemaComponent, useCompile } from '../../schema-component';
import { useCreateAction } from '../action-hooks';
import { useCollectionManager } from '../hooks';
import { IField } from '../interfaces/types';
import { ArrayTable } from './ArrayTable';
import { options } from './interfaces';
const getSchema = (schema: IField): ISchema => {
if (!schema) {
return;
}
const properties = cloneDeep(schema.properties) as any;
const initialValue = {
...cloneDeep(schema.default),
interface: schema.name,
name: `f_${uid()}`,
};
initialValue.uiSchema.title = schema.title;
console.log('initialValue', initialValue);
return {
type: 'object',
properties: {
@ -21,16 +32,20 @@ const getSchema = (schema: IField): ISchema => {
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
initialValue: {
interface: schema.name,
...schema.default,
name: `f_${uid()}`,
useValues(options) {
return useRequest(
() =>
Promise.resolve({
data: initialValue,
}),
options,
);
},
},
title: '{{ t("Add field") }}',
properties: {
// @ts-ignore
...schema.properties,
...properties,
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
@ -47,7 +62,7 @@ const getSchema = (schema: IField): ISchema => {
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ cm.useCreateActionAndRefreshCM }}',
useAction: '{{ useCreateCollectionField }}',
},
},
},
@ -58,6 +73,30 @@ const getSchema = (schema: IField): ISchema => {
};
};
const useCreateCollectionField = () => {
const form = useForm();
const { run } = useCreateAction();
const { refreshCM } = useCollectionManager();
return {
async run() {
await form.submit();
const options = form?.values?.uiSchema?.enum?.slice() || [];
form.setValuesIn(
'uiSchema.enum',
options.map((option) => {
return {
value: uid(),
...option,
};
}),
);
console.log('form.values', form.values);
await run();
await refreshCM();
},
};
};
export const AddFieldAction = () => {
const { getInterface } = useCollectionManager();
const [visible, setVisible] = useState(false);
@ -78,7 +117,6 @@ export const AddFieldAction = () => {
return (
<Menu.SubMenu title={compile(option.label)}>
{option.children.map((child) => {
console.log(child);
return <Menu.Item key={child.name}>{compile(child.title)}</Menu.Item>;
})}
</Menu.SubMenu>
@ -91,7 +129,7 @@ export const AddFieldAction = () => {
</Button>
</Dropdown>
<SchemaComponent schema={schema} components={{ ArrayTable }} />
<SchemaComponent schema={schema} components={{ ArrayTable }} scope={{ useCreateCollectionField }} />
</ActionContext.Provider>
);
};

View File

@ -0,0 +1,25 @@
import { ArrayBase, ArrayTable as FormilyArrayTable } from '@formily/antd';
import { connect } from '@formily/react';
import React, { Fragment } from 'react';
export const ArrayTable: any = connect((props) => {
const { onChange } = props;
return (
<FormilyArrayTable
{...props}
onChange={(value) => {
console.log('onChange', value);
onChange(value);
}}
/>
);
});
ArrayTable.displayName = 'ArrayTable'
ArrayTable.Column = () => {
return <Fragment />
}
ArrayBase.mixin(ArrayTable)

View File

@ -23,6 +23,8 @@ const useCollectionValues = (options) => {
Promise.resolve({
data: {
name: `t_${uid()}`,
createdBy: true,
updatedBy: true,
},
}),
{
@ -38,7 +40,7 @@ const useCollectionValues = (options) => {
}, [visible]);
return result;
}
};
export const ConfigurationTable = () => {
const { collections = [] } = useCollectionManager();
@ -50,7 +52,7 @@ export const ConfigurationTable = () => {
};
return (
<div>
<SchemaComponent schema={collectionSchema} scope={{ useCollectionValues, useAsyncDataSource, loadCollections }}/>
<SchemaComponent schema={collectionSchema} scope={{ useCollectionValues, useAsyncDataSource, loadCollections }} />
</div>
);
};

View File

@ -1,18 +1,22 @@
import { ISchema } from '@formily/react';
import { ISchema, useForm } from '@formily/react';
import { uid } from '@formily/shared';
import cloneDeep from 'lodash/cloneDeep';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useAPIClient } from '../../api-client';
import { useAPIClient, useRequest } from '../../api-client';
import { useRecord } from '../../record-provider';
import { ActionContext, SchemaComponent } from '../../schema-component';
import { useUpdateAction } from '../action-hooks';
import { useCollectionManager } from '../hooks';
import { IField } from '../interfaces/types';
import { ArrayTable } from './ArrayTable';
const getSchema = (schema: IField): ISchema => {
if (!schema) {
return;
}
const properties = cloneDeep(schema.properties) as any;
properties.name['x-disabled'] = true;
return {
type: 'object',
properties: {
@ -21,12 +25,20 @@ const getSchema = (schema: IField): ISchema => {
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
initialValue: cloneDeep(schema.default),
useValues(options) {
return useRequest(
() =>
Promise.resolve({
data: cloneDeep(schema.default),
}),
options,
);
},
},
title: '{{ t("Edit field") }}',
properties: {
// @ts-ignore
...schema.properties,
...properties,
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
@ -43,7 +55,7 @@ const getSchema = (schema: IField): ISchema => {
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ cm.useUpdateActionAndRefreshCM }}',
useAction: '{{ useUpdateCollectionField }}',
},
},
},
@ -54,6 +66,29 @@ const getSchema = (schema: IField): ISchema => {
};
};
const useUpdateCollectionField = () => {
const form = useForm();
const { run } = useUpdateAction();
const { refreshCM } = useCollectionManager();
return {
async run() {
await form.submit();
const options = form?.values?.uiSchema?.enum?.slice() || [];
form.setValuesIn(
'uiSchema.enum',
options.map((option) => {
return {
value: uid(),
...option,
};
}),
);
await run();
await refreshCM();
},
};
};
export const EditFieldAction = (props) => {
const record = useRecord();
const { getInterface } = useCollectionManager();
@ -79,7 +114,7 @@ export const EditFieldAction = (props) => {
>
{t('Edit')}
</a>
<SchemaComponent schema={schema} />
<SchemaComponent schema={schema} components={{ ArrayTable }} scope={{ useUpdateCollectionField }} />
</ActionContext.Provider>
);
};

View File

@ -1,6 +1,7 @@
import { useContext } from 'react';
import { useAPIClient } from '../../api-client';
import { useRecord } from '../../record-provider';
import { useCompile } from '../../schema-component';
import { CollectionFieldContext } from '../context';
import { useCollection } from './useCollection';
@ -8,6 +9,7 @@ export const useCollectionField = () => {
const collection = useCollection();
const record = useRecord();
const api = useAPIClient();
const compile = useCompile();
const ctx = useContext(CollectionFieldContext);
if (!ctx) {
return {} as any;
@ -16,6 +18,7 @@ export const useCollectionField = () => {
const resource = api?.resource(resourceName, record[ctx.sourceKey]);
return {
...ctx,
uiSchema: compile(ctx.uiSchema),
resource,
};
};

View File

@ -16,8 +16,9 @@ export const attachment: IField = {
type: 'array',
// title,
'x-component': 'Upload.Attachment',
'x-decorator': 'FormItem',
'x-designable-bar': 'Upload.DesignableBar',
'x-component-props': {
action: 'attachments:upload',
},
},
},
initialize: (values: any) => {

View File

@ -20,6 +20,8 @@ export const chinaRegion: IField = {
// title,
'x-component': 'Cascader',
'x-component-props': {
useDataSource: '{{ useChinaRegionDataSource }}',
useLoadData: '{{ useChinaRegionLoadData }}',
changeOnSelectLast: false,
labelInValue: true,
maxLevel: 3,

View File

@ -15,12 +15,12 @@ export const linkTo: IField = {
uiSchema: {
type: 'array',
// title,
'x-component': 'Select.Drawer',
'x-component': 'RecordPicker',
'x-component-props': {},
},
},
initialize: (values: any) => {
if (values.dataType === 'belongsToMany') {
if (values.type === 'belongsToMany') {
if (!values.through) {
values.through = `t_${uid()}`;
}
@ -34,11 +34,7 @@ export const linkTo: IField = {
values.sourceKey = 'id';
}
if (!values.targetKey) {
if (values.target === 'roles') {
values.targetKey = 'name';
} else {
values.targetKey = 'id';
}
values.targetKey = 'id';
}
}
},

View File

@ -15,7 +15,7 @@ export const updatedBy: IField = {
uiSchema: {
type: 'object',
title: '{{t("Last updated by")}}',
'x-component': 'Select.Drawer',
'x-component': 'RecordPicker',
'x-component-props': {
fieldNames: {
value: 'id',

View File

@ -7,6 +7,7 @@ export * from './api-client';
export * from './application';
export * from './async-data-provider';
export * from './board';
export * from './china-region';
export * from './collection-manager';
export * from './document-title';
export * from './i18n';

View File

@ -131,7 +131,7 @@
"Created at": "创建日期",
"Last updated at": "最后修改日期",
"Created by": "创建人",
"Last updated by": "最后更新日期",
"Last updated by": "最后修改人",
"Add field": "添加字段",
"Field display name": "字段名称",
"Field type": "字段类型",

View File

@ -5,10 +5,18 @@ import { toArr } from '@formily/shared';
import { Cascader as AntdCascader } from 'antd';
import { isBoolean, omit } from 'lodash';
import React from 'react';
import { useRequest } from '../../../api-client';
import { defaultFieldNames } from './defaultFieldNames';
import { ReadPretty } from './ReadPretty';
const useDefLoadData = (props: any) => props.loadData;
const useDefDataSource = (options) => {
const field = useField<ArrayField>();
return useRequest(() => Promise.resolve({ data: field.dataSource || [] }), options);
};
const useDefLoadData = (props: any) => {
return props?.loadData;
};
export const Cascader = connect(
(props: any) => {
@ -18,6 +26,7 @@ export const Cascader = connect(
onChange,
labelInValue,
fieldNames = defaultFieldNames,
useDataSource = useDefDataSource,
useLoadData = useDefLoadData,
changeOnSelectLast,
changeOnSelect,
@ -25,6 +34,11 @@ export const Cascader = connect(
...others
} = props;
const loadData = useLoadData(props);
const { loading } = useDataSource({
onSuccess(data) {
field.dataSource = data?.data || [];
},
});
// 兼容值为 object[] 的情况
const toValue = () => {
return toArr(value).map((item) => {
@ -54,17 +68,11 @@ export const Cascader = connect(
return <span key={option[fieldNames.value]}>{option[fieldNames.label]} / </span>;
});
};
// if (loadData) {
// Object.assign(others, {
// loadData: (selectedOptions: any[]) => {
// // 将 field 传给 loadData
// loadData(selectedOptions, field);
// },
// });
// }
return (
<AntdCascader
loading={loading}
{...others}
options={field.dataSource}
loadData={loadData}
changeOnSelect={isBoolean(changeOnSelectLast) ? !changeOnSelectLast : changeOnSelect}
value={toValue()}