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 = (
+
+ );
+ 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);
}