feat(client): improve the collection manager module

This commit is contained in:
chenos 2022-02-16 22:48:24 +08:00
parent 451f706b46
commit fc1a65a2fb
15 changed files with 658 additions and 159 deletions

View File

@ -5,6 +5,7 @@ import {
AntdSchemaComponentProvider,
APIClientProvider,
AuthLayout,
CollectionManagerProvider,
CollectionManagerShortcut,
compose,
DesignableSwitch,
@ -40,6 +41,7 @@ const providers = [
{ components: { ACLShortcut, DesignableSwitch, CollectionManagerShortcut, SystemSettingsShortcut } },
],
[SchemaComponentProvider, { components: { Link, NavLink } }],
CollectionManagerProvider,
AntdSchemaComponentProvider,
[DocumentTitleProvider, { addonAfter: 'NocoBase' }],
[RouteSwitchProvider, { components: { AuthLayout, AdminLayout, RouteSchemaComponent, SigninPage, SignupPage } }],

View File

@ -3,6 +3,7 @@ import { Result } from 'ahooks/lib/useRequest/src/types';
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
export interface ActionParams {
filterByTk?: any;
[key: string]: any;
}

View File

@ -5,6 +5,7 @@ import {
AntdSchemaComponentProvider,
APIClientProvider,
AuthLayout,
CollectionManagerProvider,
CollectionManagerShortcut,
compose,
DesignableSwitch,
@ -39,6 +40,7 @@ const providers = [
{ components: { ACLShortcut, DesignableSwitch, CollectionManagerShortcut, SystemSettingsShortcut } },
],
[SchemaComponentProvider, { components: { Link, NavLink } }],
CollectionManagerProvider,
AntdSchemaComponentProvider,
[DocumentTitleProvider, { addonAfter: 'NocoBase' }],
[RouteSwitchProvider, { components: { AuthLayout, AdminLayout, RouteSchemaComponent, SigninPage, SignupPage } }],

View File

@ -26,6 +26,8 @@ const InternalField: React.FC = (props) => {
setFieldProps('description', uiSchema.description);
setFieldProps('initialValue', uiSchema.default);
setRequired();
// @ts-ignore
field.dataSource = uiSchema.enum;
field.component = [component, uiSchema['x-component-props']];
}, [uiSchema.title, uiSchema.description, uiSchema.required]);
return React.createElement(component, props);

View File

@ -1,7 +1,7 @@
import React from 'react';
import { CollectionContext } from './context';
import { useCollectionManager } from './hooks';
import { CollectionOptions } from './types';
import { CollectionContext } from './context';
export const CollectionProvider: React.FC<{ name?: string; collection: CollectionOptions }> = (props) => {
const { name, collection, children } = props;

View File

@ -1,146 +0,0 @@
import { Button, Divider, Drawer, Space, Table, Typography } from 'antd';
import React, { useState } from 'react';
export const ConfigurationTable = () => {
return (
<div>
<Space style={{ justifyContent: 'flex-end', width: '100%', marginBottom: 16 }}>
<Button key="destroy"></Button>
<Button type={'primary'} key="create">
</Button>
</Space>
<Table
rowSelection={{
type: 'checkbox',
}}
columns={[
{
title: '数据表名称',
dataIndex: 'title',
key: 'title',
},
{
title: '数据表标识',
dataIndex: 'name',
key: 'name',
},
{
title: '操作',
dataIndex: 'actions',
key: 'actions',
render: () => (
<Space split={<Divider type="vertical" />}>
<ConfigureFields />
<Typography.Link>Edit</Typography.Link>
<Typography.Link>Delete</Typography.Link>
</Space>
),
},
]}
dataSource={[
{
name: 'users',
title: '用户',
},
]}
/>
</div>
);
};
export const ConfigureFields = () => {
const [visible, setVisible] = useState(false);
return (
<>
<Typography.Link onClick={() => setVisible(true)}>Configure</Typography.Link>
<Drawer width={800} title={'字段配置'} visible={visible} destroyOnClose onClose={() => setVisible(false)}>
<CollectionFieldList />
</Drawer>
</>
);
};
export const CollectionFieldList = () => {
return (
<div>
<Space style={{ justifyContent: 'flex-end', width: '100%', marginBottom: 16 }}>
<Button key="destroy"></Button>
<Button type={'primary'} key="create">
</Button>
</Space>
<Table
rowSelection={{
type: 'checkbox',
}}
columns={[
{
title: '字段名称',
dataIndex: 'title',
key: 'title',
},
{
title: '字段标识',
dataIndex: 'name',
key: 'name',
},
{
title: '操作',
dataIndex: 'actions',
key: 'actions',
render: () => (
<Space split={<Divider type="vertical" />}>
<CollectionFieldEdit />
<Typography.Link>Delete</Typography.Link>
</Space>
),
},
]}
dataSource={[
{
name: 'title',
title: '标题',
},
]}
/>
</div>
);
};
export const CollectionFieldEdit = () => {
const [visible, setVisible] = useState(false);
return (
<>
<Typography.Link onClick={() => setVisible(true)}>Edit</Typography.Link>
<Drawer
width={800}
title={'字段配置'}
visible={visible}
destroyOnClose
onClose={() => setVisible(false)}
footer={
<Space style={{ justifyContent: 'flex-end', width: '100%' }}>
<Button
onClick={() => {
setVisible(false);
}}
>
Cancel
</Button>
<Button
type={'primary'}
onClick={() => {
setVisible(false);
}}
>
Submit
</Button>
</Space>
}
>
CollectionFieldEdit
</Drawer>
</>
);
};

View File

@ -0,0 +1,82 @@
import { useForm } from '@formily/react';
import React from 'react';
import { useResourceActionContext, useResourceContext } from '..';
import { useRequest } from '../../api-client';
import { useRecord } from '../../record-provider';
import { SchemaComponent, useActionContext } from '../../schema-component';
import { collectionSchema } from './schemas/collections';
const useCancelAction = () => {
const form = useForm();
const ctx = useActionContext();
return {
async run() {
ctx.setVisible(false);
form.reset();
},
};
};
const useCreateAction = () => {
const form = useForm();
const ctx = useActionContext();
const { refresh } = useResourceActionContext();
const { resource } = useResourceContext();
return {
async run() {
await form.submit();
await resource.create({ values: form.values });
ctx.setVisible(false);
await form.reset();
refresh();
},
};
};
const useUpdateAction = () => {
const form = useForm();
const ctx = useActionContext();
const { refresh } = useResourceActionContext();
const { resource, targetKey } = useResourceContext();
const { [targetKey]: filterByTk } = useRecord();
return {
async run() {
await form.submit();
await resource.update({ filterByTk, values: form.values });
ctx.setVisible(false);
await form.reset();
refresh();
},
};
};
const useDestroyAction = () => {
const { refresh } = useResourceActionContext();
const { resource, targetKey } = useResourceContext();
const { [targetKey]: filterByTk } = useRecord();
return {
async run() {
await resource.destroy({ filterByTk });
refresh();
},
};
};
const useValues = (options) => {
const record = useRecord();
return useRequest(() => Promise.resolve({ data: record }), {
...options,
refreshDeps: [record],
});
};
export const ConfigurationTable = () => {
return (
<div>
<SchemaComponent
schema={collectionSchema}
scope={{ useCancelAction, useCreateAction, useUpdateAction, useDestroyAction, useValues }}
/>
</div>
);
};

View File

@ -0,0 +1 @@
export * from './ConfigurationTable';

View File

@ -0,0 +1,249 @@
import { ISchema } from '@formily/react';
const collection = {
name: 'fields',
fields: [
{
type: 'string',
name: 'type',
interface: 'input',
uiSchema: {
title: '存储类型',
type: 'string',
'x-component': 'Select',
enum: [
{
label: 'String',
value: 'string',
},
],
required: true,
} as ISchema,
},
{
type: 'string',
name: 'title',
interface: 'input',
uiSchema: {
title: '字段名称',
type: 'string',
'x-component': 'Input',
required: true,
} as ISchema,
},
{
type: 'string',
name: 'name',
interface: 'input',
uiSchema: {
title: '字段标识',
type: 'string',
'x-component': 'Input',
description: '使用英文',
} as ISchema,
},
],
};
export const collectionFieldSchema: ISchema = {
type: 'void',
'x-collection-field': 'collections.fields',
'x-decorator': 'ResourceActionProvider',
'x-decorator-props': {
association: {
sourceKey: 'name',
targetKey: 'name',
},
request: {
resource: 'collections.fields',
action: 'list',
params: {
pageSize: 5,
filter: {},
sort: ['sort'],
appends: [],
},
},
},
'x-component': 'CollectionProvider',
'x-component-props': {
collection,
},
properties: {
actions: {
type: 'void',
'x-component': 'ActionBar',
properties: {
delete: {
type: 'void',
title: '删除',
'x-component': 'Action',
},
create: {
type: 'void',
title: '创建',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
title: 'Drawer Title',
properties: {
type: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
name: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: 'Cancel',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useCreateAction }}',
},
},
},
},
},
},
},
},
},
},
table1: {
type: 'void',
'x-uid': 'input',
'x-component': 'VoidTable',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useDataSource: '{{ useDataSourceFromRAC }}',
},
properties: {
column1: {
type: 'void',
'x-decorator': 'TableColumnDecorator',
'x-component': 'VoidTable.Column',
properties: {
title: {
type: 'number',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
column2: {
type: 'void',
'x-decorator': 'TableColumnDecorator',
'x-component': 'VoidTable.Column',
properties: {
name: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
column3: {
type: 'void',
title: 'Actions',
'x-component': 'VoidTable.Column',
properties: {
actions: {
type: 'void',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {
update: {
type: 'void',
title: '编辑',
'x-component': 'Action.Link',
'x-component-props': {
type: 'primary',
},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
useValues: '{{ useValues }}',
},
title: 'Drawer Title',
properties: {
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
name: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-disabled': true,
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: 'Cancel',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useUpdateAction }}',
},
},
},
},
},
},
},
},
delete: {
type: 'void',
title: '删除',
'x-component': 'Action.Link',
'x-component-props': {
useAction: '{{ useDestroyAction }}',
},
},
},
},
},
},
},
},
},
};

View File

@ -0,0 +1,259 @@
import { ISchema } from '@formily/react';
import { collectionFieldSchema } from './collectionFields';
const collection = {
name: 'collections',
filterTargetKey: 'name',
fields: [
{
type: 'integer',
name: 'title',
interface: 'input',
uiSchema: {
title: '数据表名称',
type: 'number',
'x-component': 'Input',
required: true,
} as ISchema,
},
{
type: 'string',
name: 'name',
interface: 'input',
uiSchema: {
title: '数据表标识',
type: 'string',
'x-component': 'Input',
description: '使用英文',
} as ISchema,
},
{
type: 'hasMany',
name: 'fields',
target: 'fields',
collectionName: 'collections',
sourceKey: 'name',
targetKey: 'name',
uiSchema: {},
},
],
};
export const collectionSchema: ISchema = {
type: 'object',
properties: {
block1: {
type: 'void',
'x-collection': 'collections',
'x-decorator': 'ResourceActionProvider',
'x-decorator-props': {
collection: {
targetKey: 'name',
},
request: {
resource: 'collections',
action: 'list',
params: {
pageSize: 5,
filter: {},
sort: ['sort'],
appends: [],
},
},
},
'x-component': 'CollectionProvider',
'x-component-props': {
collection,
},
properties: {
actions: {
type: 'void',
'x-component': 'ActionBar',
properties: {
delete: {
type: 'void',
title: '删除',
'x-component': 'Action',
},
create: {
type: 'void',
title: '创建',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
title: 'Drawer Title',
properties: {
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
name: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: 'Cancel',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useCreateAction }}',
},
},
},
},
},
},
},
},
},
},
table1: {
type: 'void',
'x-uid': 'input',
'x-component': 'VoidTable',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useDataSource: '{{ useDataSourceFromRAC }}',
},
properties: {
column1: {
type: 'void',
'x-decorator': 'TableColumnDecorator',
'x-component': 'VoidTable.Column',
properties: {
title: {
type: 'number',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
column2: {
type: 'void',
'x-decorator': 'TableColumnDecorator',
'x-component': 'VoidTable.Column',
properties: {
name: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
column3: {
type: 'void',
title: 'Actions',
'x-component': 'VoidTable.Column',
properties: {
actions: {
type: 'void',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {
view: {
type: 'void',
title: '配置字段',
'x-component': 'Action.Link',
'x-component-props': {},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
title: 'Drawer Title',
properties: {
collectionFieldSchema,
},
},
},
},
update: {
type: 'void',
title: '编辑',
'x-component': 'Action.Link',
'x-component-props': {
type: 'primary',
},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
useValues: '{{ useValues }}',
},
title: 'Drawer Title',
properties: {
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
name: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-disabled': true,
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
action1: {
title: 'Cancel',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
action2: {
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useUpdateAction }}',
},
},
},
},
},
},
},
},
delete: {
type: 'void',
title: '删除',
'x-component': 'Action.Link',
'x-component-props': {
useAction: '{{ useDestroyAction }}',
},
},
},
},
},
},
},
},
},
},
},
};

View File

@ -1,22 +1,59 @@
import { Result } from 'ahooks/lib/useRequest/src/types';
import React, { createContext, useContext, useEffect } from 'react';
import { useRequest } from '../api-client';
import { useRecord } from '..';
import { useAPIClient, useRequest } from '../api-client';
export const ResourceActionContext = createContext<Result<any, any>>(null);
interface ResourceActionProviderProps {
type?: 'association' | 'collection';
request?: any;
uid?: string;
}
export const ResourceActionProvider: React.FC<ResourceActionProviderProps> = (props) => {
const { request, uid } = props;
console.log(request);
const ResourceContext = createContext<any>(null);
const CollectionResourceActionProvider = (props) => {
const { collection, request, uid } = props;
const api = useAPIClient();
const service = useRequest(request, {
uid,
refreshDeps: [request],
});
return <ResourceActionContext.Provider value={service}>{props.children}</ResourceActionContext.Provider>;
const resource = api.resource(request.resource);
return (
<ResourceContext.Provider value={{ type: 'collection', resource, collection }}>
<ResourceActionContext.Provider value={service}>{props.children}</ResourceActionContext.Provider>
</ResourceContext.Provider>
);
};
const AssociationResourceActionProvider = (props) => {
const { association, request, uid } = props;
const api = useAPIClient();
const record = useRecord();
const resourceOf = record[association.sourceKey];
const service = useRequest(
{ resourceOf, ...request },
{
uid,
refreshDeps: [request, resourceOf],
},
);
const resource = api.resource(request.resource, resourceOf);
return (
<ResourceContext.Provider value={{ type: 'association', resource, association }}>
<ResourceActionContext.Provider value={service}>{props.children}</ResourceActionContext.Provider>
</ResourceContext.Provider>
);
};
export const ResourceActionProvider: React.FC<ResourceActionProviderProps> = (props) => {
const { request } = props;
if (request?.resource?.includes('.')) {
return <AssociationResourceActionProvider {...props} />;
}
return <CollectionResourceActionProvider {...props} />;
};
export const useResourceActionContext = () => {
@ -32,3 +69,8 @@ export const useDataSourceFromRAC = (options: any) => {
}, [service.loading]);
return service;
};
export const useResourceContext = () => {
const { type, resource, collection, association } = useContext(ResourceContext);
return { type, resource, collection, association, targetKey: association?.targetKey || collection?.targetKey };
};

View File

@ -1,3 +1,4 @@
export * from './useCollection';
export * from './useCollectionField';
export * from './useCollectionManager';

View File

@ -1,8 +1,8 @@
import { useContext } from 'react';
import { SchemaKey } from '@formily/react';
import { CollectionFieldOptions } from '../types';
import { CollectionContext } from '../context';
import { useContext } from 'react';
import { useAPIClient } from '../../api-client';
import { CollectionContext } from '../context';
import { CollectionFieldOptions } from '../types';
export const useCollection = () => {
const collection = useContext(CollectionContext);

View File

@ -1,15 +1,17 @@
import { useContext } from 'react';
import { CollectionFieldContext } from '../context';
import { useRecord } from '../../record-provider';
import { useCollection } from './useCollection';
import { useAPIClient } from '../../api-client';
import { useRecord } from '../../record-provider';
import { CollectionFieldContext } from '../context';
import { useCollection } from './useCollection';
export const useCollectionField = () => {
const collection = useCollection();
const record = useRecord();
const api = useAPIClient();
const ctx = useContext(CollectionFieldContext);
const resource = api?.resource(`${collection?.name || ctx?.collectinName}.${ctx.name}`, record[ctx.sourceKey]);
const resourceName = `${ctx?.collectinName || collection?.name}.${ctx.name}`;
const resource = api?.resource(resourceName, record[ctx.sourceKey]);
console.log({ resourceName });
return {
...ctx,
resource,

View File

@ -7,6 +7,7 @@ export interface CollectionManagerOptions {
export interface CollectionOptions {
name?: string;
filterTargetKey?: string;
fields?: any[];
}
@ -18,6 +19,7 @@ export interface ICollectionProviderProps {
export interface CollectionFieldOptions {
name?: any;
collectinName?: string;
targetKey?: string;
sourceKey?: string; // association field
uiSchema?: ISchema;
}