diff --git a/packages/app/src/pages/index.tsx b/packages/app/src/pages/index.tsx index bc214801d4..53ef500337 100644 --- a/packages/app/src/pages/index.tsx +++ b/packages/app/src/pages/index.tsx @@ -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 } }], diff --git a/packages/client/src/api-client/APIClient.ts b/packages/client/src/api-client/APIClient.ts index 8fb91424b2..d36234987d 100644 --- a/packages/client/src/api-client/APIClient.ts +++ b/packages/client/src/api-client/APIClient.ts @@ -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; } diff --git a/packages/client/src/application/demos/demo2/index.tsx b/packages/client/src/application/demos/demo2/index.tsx index 2ecb924371..4769026c2b 100644 --- a/packages/client/src/application/demos/demo2/index.tsx +++ b/packages/client/src/application/demos/demo2/index.tsx @@ -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 } }], diff --git a/packages/client/src/collection-manager/CollectionField.tsx b/packages/client/src/collection-manager/CollectionField.tsx index b8619f272a..fc186e40cf 100644 --- a/packages/client/src/collection-manager/CollectionField.tsx +++ b/packages/client/src/collection-manager/CollectionField.tsx @@ -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); diff --git a/packages/client/src/collection-manager/CollectionProvider.tsx b/packages/client/src/collection-manager/CollectionProvider.tsx index 4e00776885..3dabfd4b36 100644 --- a/packages/client/src/collection-manager/CollectionProvider.tsx +++ b/packages/client/src/collection-manager/CollectionProvider.tsx @@ -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; diff --git a/packages/client/src/collection-manager/Configuration.tsx b/packages/client/src/collection-manager/Configuration.tsx deleted file mode 100644 index edce1060ee..0000000000 --- a/packages/client/src/collection-manager/Configuration.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { Button, Divider, Drawer, Space, Table, Typography } from 'antd'; -import React, { useState } from 'react'; - -export const ConfigurationTable = () => { - return ( -
- - - - - ( - }> - - Edit - Delete - - ), - }, - ]} - dataSource={[ - { - name: 'users', - title: '用户', - }, - ]} - /> - - ); -}; - -export const ConfigureFields = () => { - const [visible, setVisible] = useState(false); - return ( - <> - setVisible(true)}>Configure - setVisible(false)}> - - - - ); -}; - -export const CollectionFieldList = () => { - return ( -
- - - - -
( - }> - - Delete - - ), - }, - ]} - dataSource={[ - { - name: 'title', - title: '标题', - }, - ]} - /> - - ); -}; - -export const CollectionFieldEdit = () => { - const [visible, setVisible] = useState(false); - return ( - <> - setVisible(true)}>Edit - setVisible(false)} - footer={ - - - - - } - > - CollectionFieldEdit - - - ); -}; diff --git a/packages/client/src/collection-manager/Configuration/ConfigurationTable.tsx b/packages/client/src/collection-manager/Configuration/ConfigurationTable.tsx new file mode 100644 index 0000000000..a170cd871c --- /dev/null +++ b/packages/client/src/collection-manager/Configuration/ConfigurationTable.tsx @@ -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 ( +
+ +
+ ); +}; diff --git a/packages/client/src/collection-manager/Configuration/index.tsx b/packages/client/src/collection-manager/Configuration/index.tsx new file mode 100644 index 0000000000..5cc8569915 --- /dev/null +++ b/packages/client/src/collection-manager/Configuration/index.tsx @@ -0,0 +1 @@ +export * from './ConfigurationTable'; diff --git a/packages/client/src/collection-manager/Configuration/schemas/collectionFields.ts b/packages/client/src/collection-manager/Configuration/schemas/collectionFields.ts new file mode 100644 index 0000000000..90a3ed02d7 --- /dev/null +++ b/packages/client/src/collection-manager/Configuration/schemas/collectionFields.ts @@ -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 }}', + }, + }, + }, + }, + }, + }, + }, + }, + }, +}; diff --git a/packages/client/src/collection-manager/Configuration/schemas/collections.ts b/packages/client/src/collection-manager/Configuration/schemas/collections.ts new file mode 100644 index 0000000000..8e1c48156a --- /dev/null +++ b/packages/client/src/collection-manager/Configuration/schemas/collections.ts @@ -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 }}', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}; diff --git a/packages/client/src/collection-manager/ResourceActionProvider.tsx b/packages/client/src/collection-manager/ResourceActionProvider.tsx index 4cc5b9c695..e9cec25ab6 100644 --- a/packages/client/src/collection-manager/ResourceActionProvider.tsx +++ b/packages/client/src/collection-manager/ResourceActionProvider.tsx @@ -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>(null); interface ResourceActionProviderProps { + type?: 'association' | 'collection'; request?: any; uid?: string; } -export const ResourceActionProvider: React.FC = (props) => { - const { request, uid } = props; - console.log(request); +const ResourceContext = createContext(null); + +const CollectionResourceActionProvider = (props) => { + const { collection, request, uid } = props; + const api = useAPIClient(); const service = useRequest(request, { uid, refreshDeps: [request], }); - return {props.children}; + const resource = api.resource(request.resource); + return ( + + {props.children} + + ); +}; + +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 ( + + {props.children} + + ); +}; + +export const ResourceActionProvider: React.FC = (props) => { + const { request } = props; + if (request?.resource?.includes('.')) { + return ; + } + return ; }; 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 }; +}; diff --git a/packages/client/src/collection-manager/hooks/index.ts b/packages/client/src/collection-manager/hooks/index.ts index 4317f65d27..a02c5a9555 100644 --- a/packages/client/src/collection-manager/hooks/index.ts +++ b/packages/client/src/collection-manager/hooks/index.ts @@ -1,3 +1,4 @@ export * from './useCollection'; export * from './useCollectionField'; export * from './useCollectionManager'; + diff --git a/packages/client/src/collection-manager/hooks/useCollection.ts b/packages/client/src/collection-manager/hooks/useCollection.ts index 20a18c3ab9..84e731ba11 100644 --- a/packages/client/src/collection-manager/hooks/useCollection.ts +++ b/packages/client/src/collection-manager/hooks/useCollection.ts @@ -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); diff --git a/packages/client/src/collection-manager/hooks/useCollectionField.ts b/packages/client/src/collection-manager/hooks/useCollectionField.ts index c2fda1e47e..58b3286488 100644 --- a/packages/client/src/collection-manager/hooks/useCollectionField.ts +++ b/packages/client/src/collection-manager/hooks/useCollectionField.ts @@ -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, diff --git a/packages/client/src/collection-manager/types.ts b/packages/client/src/collection-manager/types.ts index b4f25a2d4e..a9a4cd569e 100644 --- a/packages/client/src/collection-manager/types.ts +++ b/packages/client/src/collection-manager/types.ts @@ -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; }