diff --git a/packages/client/src/components/admin-layout/Collections/index.tsx b/packages/client/src/components/admin-layout/Collections/index.tsx new file mode 100644 index 000000000..64081293d --- /dev/null +++ b/packages/client/src/components/admin-layout/Collections/index.tsx @@ -0,0 +1,756 @@ +import { SchemaRenderer } from '../../../'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; +import { FormItem } from '@formily/antd'; +import { action } from '@formily/reactive'; +import { + useCollectionContext, + useCollectionsContext, +} from '../../../constate/Collections'; +import { Button, Drawer, Menu, Dropdown, Space } from 'antd'; +import { PlusOutlined, DownOutlined } from '@ant-design/icons'; +import cls from 'classnames'; +import { uid, isValid, clone } from '@formily/shared'; +import { Resource } from '../../../resource'; +import { TableRowContext, useTable } from '../../../schemas/table'; +import { useRequest } from 'ahooks'; +import { VisibleContext } from '../../../context'; +import { connect, FormProvider, observer, useForm } from '@formily/react'; +import { DescriptionsContext } from '../../../schemas/form'; +import { createContext } from 'react'; +import { ISchema } from '../../../'; +import { createForm, Field } from '@formily/core'; +import { SchemaField } from '../../../schemas'; +import { + interfaces, + options, +} from '../../../schemas/database-field/interfaces'; + +export const RoleContext = createContext(null); + +function VisibleProvider(props) { + const ctx = useContext(TableRowContext); + const [visible, setVisible] = useState(false); + return ( + + {props.children} + + ); +} + +const useResource = () => { + const resource = Resource.make('collections'); + return { + resource, + }; +}; + +const useCollectionsResource = () => { + const descriptionsContext = useContext(DescriptionsContext); + console.log('descriptionsContext.service', descriptionsContext.service); + const resource = Resource.make('collections'); + return { + resource, + }; +}; + +class ActionPermissionResource extends Resource { + save(options?: any) { + console.log('ActionPermissionResource.save'); + return Promise.resolve({}); + } +} + +const useActionPermissionSubmit = () => { + const form = useForm(); + const role = useContext(RoleContext); + const resource = Resource.make({ + resourceName: 'roles', + resourceKey: role.name, + }); + return { + async run() { + await resource.save(form.values); + console.log('useActionPermissionSubmit', form.values?.actionPermissions); + }, + }; +}; + +const useActionPermissionResource = ({ onSuccess }) => { + const role = useContext(RoleContext); + console.log('RoleContext', role); + // const { props } = useTable(); + const ctx = useContext(TableRowContext); + const resource = ActionPermissionResource.make({ + resourceName: 'action_permissions', + }); + const service = useRequest( + (params?: any) => { + return resource.list({ + ...params, + filter: { + role_name: role.name, + collection_name: ctx.record.name, + }, + appends: ['fields'], + }); + }, + { + formatResult: (result) => result?.data, + onSuccess(data) { + console.log('actionPermissions', data); + onSuccess({ + actionPermissions: data.map((permission) => { + const item: any = {}; + Object.keys(permission).forEach((key) => { + if (isValid(permission[key])) { + item[key] = permission[key]; + } + }); + item.fields = permission?.fields.map((field) => field.key) || []; + return item; + }), + }); + }, + manual: true, + }, + ); + const [visible] = useContext(VisibleContext); + + useEffect(() => { + if (visible) { + service.run({}); + } + }, [visible]); + + return { resource, service, initialValues: service.data, ...service }; +}; + +const useDetailsResource = ({ onSuccess }) => { + const { props } = useTable(); + const ctx = useContext(TableRowContext); + const resource = Resource.make({ + resourceName: 'collections', + resourceKey: ctx.record[props.rowKey], + }); + const service = useRequest( + (params?: any) => { + return resource.get({ ...params }); + }, + { + formatResult: (result) => result?.data, + onSuccess, + manual: true, + }, + ); + const [visible] = useContext(VisibleContext); + + useEffect(() => { + if (visible) { + service.run({}); + } + }, [visible]); + + return { resource, service, initialValues: service.data, ...service }; +}; + +const useFieldsResource = () => { + const { props } = useTable(); + const ctx = useContext(TableRowContext); + class FieldResource extends Resource { + list(options) { + return super.list({ + ...options, + filter: { state: 1, collection_name: ctx.record[props.rowKey] }, + }); + } + } + const resource = FieldResource.make('fields'); + return { + resource, + }; +}; + +const fieldInterfaces = []; +for (const [key, schema] of interfaces) { + fieldInterfaces.push({ + value: key, + label: schema.title, + }); +} + +const collectionSchema: ISchema = { + type: 'array', + 'x-decorator': 'VisibleProvider', + 'x-component': 'Table', + default: [], + 'x-component-props': { + rowKey: 'key', + dragSort: true, + showIndex: true, + refreshRequestOnChange: true, + pagination: { + pageSize: 10, + }, + defaultAppends: ['uiSchema'], + useResource: useFieldsResource, + collectionName: 'fields', + }, + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Table.ActionBar', + properties: { + [uid()]: { + type: 'void', + name: 'action1', + title: '删除', + 'x-align': 'right', + 'x-decorator': 'AddNew.Displayed', + 'x-decorator-props': { + displayName: 'destroy', + }, + 'x-component': 'Action', + 'x-component-props': { + icon: 'DeleteOutlined', + confirm: { + title: '删除数据', + content: '删除后无法恢复,确定要删除吗?', + }, + useAction: '{{ Table.useTableDestroyAction }}', + }, + }, + [uid()]: { + type: 'void', + title: '添加', + 'x-align': 'right', + 'x-decorator': 'AddNew.Displayed', + 'x-decorator-props': { + displayName: 'create', + }, + 'x-component': 'CreateFieldButton', + 'x-component-props': { + type: 'primary', + icon: 'PlusOutlined', + }, + }, + }, + }, + column1: { + type: 'void', + title: '字段名称', + 'x-component': 'Table.Column', + properties: { + 'uiSchema.title': { + type: 'string', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + column2: { + type: 'void', + title: '字段标识', + 'x-component': 'Table.Column', + properties: { + name: { + type: 'string', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + column3: { + type: 'void', + title: '字段类型', + 'x-component': 'Table.Column', + properties: { + interface: { + type: 'string', + 'x-component': 'Select', + 'x-read-pretty': true, + // @ts-ignore + enum: fieldInterfaces, + }, + }, + }, + [uid()]: { + type: 'void', + title: '操作', + 'x-component': 'Table.Column', + 'x-component-props': { + width: 160, + }, + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Action.Group', + 'x-component-props': { + type: 'link', + }, + properties: { + [uid()]: { + type: 'void', + title: '编辑', + 'x-component': 'EditFieldButton', + 'x-component-props': { + type: 'link', + useAction() { + return { + async run() { + alert('abc'); + }, + }; + }, + }, + 'x-action-type': 'update', + }, + [uid()]: { + type: 'void', + title: '删除', + 'x-component': 'Action', + 'x-action-type': 'destroy', + 'x-component-props': { + type: 'link', + confirm: { + title: '删除数据', + content: '删除后无法恢复,确定要删除吗?', + }, + useAction: '{{ Table.useTableDestroyAction }}', + }, + }, + }, + }, + }, + }, + }, +}; + +function FieldConfigTitle() { + const ctx = useContext(TableRowContext); + return <>{`配置「${ctx.record.title}」表字段`}; +} + +const schema: ISchema = { + type: 'void', + name: 'action', + 'x-component': 'Action', + 'x-component-props': { + tooltip: '数据表配置', + className: 'nb-database-config', + icon: 'DatabaseOutlined', + type: 'primary', + }, + properties: { + modal1: { + type: 'void', + title: '数据表配置', + 'x-component': 'Action.Drawer', + properties: { + table: { + type: 'array', + // 'x-decorator': 'CardItem', + 'x-component': 'Table', + default: [], + 'x-component-props': { + rowKey: 'name', + dragSort: true, + showIndex: true, + refreshRequestOnChange: true, + pagination: { + pageSize: 10, + }, + useResource, + collectionName: 'collections', + }, + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Table.ActionBar', + properties: { + [uid()]: { + type: 'void', + name: 'action1', + title: '删除', + 'x-align': 'right', + 'x-decorator': 'AddNew.Displayed', + 'x-decorator-props': { + displayName: 'destroy', + }, + 'x-component': 'Action', + 'x-component-props': { + icon: 'DeleteOutlined', + confirm: { + title: '删除数据', + content: '删除后无法恢复,确定要删除吗?', + }, + useAction: '{{ Table.useTableDestroyAction }}', + }, + }, + [uid()]: { + type: 'void', + title: '创建数据表', + 'x-align': 'right', + 'x-decorator': 'AddNew.Displayed', + 'x-decorator-props': { + displayName: 'create', + }, + 'x-component': 'Action', + 'x-component-props': { + type: 'primary', + icon: 'PlusOutlined', + }, + properties: { + modal: { + type: 'void', + title: '创建数据表', + 'x-decorator': 'Form', + 'x-component': 'Action.Drawer', + 'x-component-props': { + useOkAction: '{{ Table.useTableCreateAction }}', + }, + properties: { + title: { + type: 'string', + title: '数据表名称', + 'x-component': 'Input', + 'x-decorator': 'FormilyFormItem', + }, + }, + }, + }, + }, + }, + }, + column1: { + type: 'void', + title: '数据表名称', + 'x-component': 'Table.Column', + properties: { + title: { + type: 'string', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + column2: { + type: 'void', + title: '数据表标识', + 'x-component': 'Table.Column', + properties: { + name: { + type: 'string', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + [uid()]: { + type: 'void', + title: '操作', + 'x-component': 'Table.Column', + 'x-component-props': { + width: 160, + }, + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Action.Group', + 'x-component-props': { + type: 'link', + }, + properties: { + [uid()]: { + type: 'void', + title: '配置字段', + 'x-component': 'Action', + 'x-component-props': { + type: 'link', + }, + 'x-action-type': 'view', + properties: { + [uid()]: { + type: 'void', + title: , + // 'x-decorator': 'RoleProvider', + 'x-component': 'Action.Drawer', + 'x-component-props': {}, + properties: { + [uid()]: collectionSchema, + }, + }, + }, + }, + [uid()]: { + type: 'void', + title: '编辑', + 'x-component': 'Action', + 'x-component-props': { + type: 'link', + }, + 'x-action-type': 'update', + properties: { + [uid()]: { + type: 'void', + title: '编辑数据表', + 'x-decorator': 'Form', + 'x-decorator-props': { + useResource: useDetailsResource, + }, + 'x-component': 'Action.Drawer', + 'x-component-props': { + useOkAction: '{{ Table.useTableUpdateAction }}', + }, + properties: { + title: { + type: 'string', + title: '数据表名称', + 'x-component': 'Input', + 'x-decorator': 'FormilyFormItem', + }, + }, + }, + }, + }, + [uid()]: { + type: 'void', + title: '删除', + 'x-component': 'Action', + 'x-action-type': 'destroy', + 'x-component-props': { + type: 'link', + confirm: { + title: '删除数据', + content: '删除后无法恢复,确定要删除吗?', + }, + useAction: '{{ Table.useTableDestroyAction }}', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}; + +function CreateFieldButton() { + const { refresh } = useCollectionsContext(); + const ctx = useContext(TableRowContext); + const { service } = useTable(); + const [visible, setVisible] = useState(false); + const form = useMemo(() => createForm(), []); + const [properties, setProperties] = useState({}); + const { collections = [], loading } = useCollectionsContext(); + + const loadCollections = async (field: any) => { + return collections.map((item: any) => ({ + label: item.title, + value: item.name, + })); + }; + + const loadCollectionFields = async (field: Field) => { + const target = field.query('....target').get('value'); + const f = field.query('....target').take(); + console.log('loadCollectionFields', f, field); + const collection = collections?.find((item) => item.name === target); + if (!collection) { + return []; + } + return collection?.generalFields + ?.filter((item) => item?.uiSchema?.title) + ?.map((item) => ({ + label: item?.uiSchema?.title || item.name, + value: item.name, + })); + }; + + const menu = ( + { + console.log('click', info.key); + const schema = interfaces.get(info.key); + form.setValues({ + ...schema.default, + collection_name: ctx.record.name, + key: uid(), + name: uid(), + interface: info.key, + }); + setProperties(clone(schema.properties)); + setVisible(true); + }} + > + {options.map( + (option, groupIndex) => + option.children.length > 0 && ( + + {option.children.map((item) => ( + + {item.title} + + ))} + + ), + )} + + ); + return ( + <> + + + + setVisible(false)} + footer={ + + + + + } + > + + + + + + ); +} + +function EditFieldButton() { + const { refresh } = useCollectionsContext(); + const { service } = useTable(); + const ctx = useContext(TableRowContext); + const [visible, setVisible] = useState(false); + const form = useMemo(() => createForm(), []); + const schema = interfaces.get(ctx.record.interface); + const { collections = [], loading } = useCollectionsContext(); + + const loadCollections = async (field: any) => { + return collections.map((item: any) => ({ + label: item.title, + value: item.name, + })); + }; + + const loadCollectionFields = async (field: Field) => { + const target = field.query('....target').get('value'); + const f = field.query('....target').take(); + console.log('loadCollectionFields', f, field); + const collection = collections?.find((item) => item.name === target); + if (!collection) { + return []; + } + return collection?.generalFields + ?.filter((item) => item?.uiSchema?.title) + ?.map((item) => ({ + label: item?.uiSchema?.title || item.name, + value: item.name, + })); + }; + return ( + <> + + setVisible(false)} + footer={ + + + + + } + > + + + + + + ); +} + +export const Collections = () => { + return ( + + ); +}; + +export default Collections; diff --git a/packages/client/src/components/admin-layout/index.tsx b/packages/client/src/components/admin-layout/index.tsx index 140995162..568179eef 100644 --- a/packages/client/src/components/admin-layout/index.tsx +++ b/packages/client/src/components/admin-layout/index.tsx @@ -36,6 +36,7 @@ import { } from '../../constate'; import { uid } from '@formily/shared'; import { Permissions } from './Permissions'; +import { Collections } from './Collections' import { More } from './More'; import { UserInfo } from './UserInfo'; import { @@ -160,7 +161,7 @@ function LayoutWithMenu(props: LayoutWithMenuProps) { /> - + diff --git a/packages/plugin-collections/src/actions/fields.ts b/packages/plugin-collections/src/actions/fields.ts index 048b834b7..8bdcd498b 100644 --- a/packages/plugin-collections/src/actions/fields.ts +++ b/packages/plugin-collections/src/actions/fields.ts @@ -1,13 +1,10 @@ import { Model, ModelCtor } from '@nocobase/database'; import { actions, middlewares } from '@nocobase/actions'; -import { sort } from '@nocobase/actions/src/actions/common'; import { cloneDeep, omit } from 'lodash'; export const create = async (ctx: actions.Context, next: actions.Next) => { await actions.common.create(ctx, async () => {}); - const { associated } = ctx.action.params; await ctx.body.generateReverseField(); - await associated.migrate(); - // console.log('associated.migrate'); + await ctx.body.migrate(); await next(); } diff --git a/packages/plugin-collections/src/models/field.ts b/packages/plugin-collections/src/models/field.ts index 6453b59fa..3cb750e2b 100644 --- a/packages/plugin-collections/src/models/field.ts +++ b/packages/plugin-collections/src/models/field.ts @@ -42,6 +42,11 @@ export class Field extends Model { return props; } + async migrate() { + const collection = await this.getCollection(); + await collection.migrate() + } + async getNestedFields() { const fields = await this.getChildren(); const items = []; diff --git a/packages/plugin-collections/src/server.ts b/packages/plugin-collections/src/server.ts index 08b1a7df2..f777e9bfe 100644 --- a/packages/plugin-collections/src/server.ts +++ b/packages/plugin-collections/src/server.ts @@ -96,4 +96,6 @@ export default async function (this: Application, options = {}) { this.resourcer.registerActionHandler('collections.fields:create', create); this.resourcer.registerActionHandler('collections:findAll', findAll); this.resourcer.registerActionHandler('collections:createOrUpdate', createOrUpdate); + this.resourcer.registerActionHandler('fields:create', create); + this.resourcer.registerActionHandler('collections:create', createOrUpdate); }