feat(client): improve code

This commit is contained in:
chenos 2022-02-17 01:06:42 +08:00
parent 0ee81dc221
commit 8b4d59ee97
16 changed files with 730 additions and 75 deletions

View File

@ -4,7 +4,7 @@ import { uid } from '@formily/shared';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { PluginManager } from '../plugin-manager'; import { PluginManager } from '../plugin-manager';
import { ActionContext, SchemaComponent, useActionContext } from '../schema-component'; import { ActionContext, SchemaComponent, useActionContext } from '../schema-component';
import { RoleTable } from './RolePermissionManager'; import * as components from './Configuration';
const useCloseAction = () => { const useCloseAction = () => {
const { setVisible } = useActionContext(); const { setVisible } = useActionContext();
@ -47,7 +47,7 @@ export const ACLShortcut = () => {
setVisible(true); setVisible(true);
}} }}
/> />
<SchemaComponent components={{ RoleTable }} scope={{ useCloseAction }} schema={schema} /> <SchemaComponent components={components} scope={{ useCloseAction }} schema={schema} />
</ActionContext.Provider> </ActionContext.Provider>
); );
}; };

View File

@ -0,0 +1,25 @@
import { Table } from 'antd';
import React from 'react';
import { useRoute } from '../../route-switch';
export const MenuConfigure = () => {
const route = useRoute();
console.log(route);
return (
<div>
<Table
columns={[
{
dataIndex: 'title',
title: '菜单项',
},
{
dataIndex: 'accessible',
title: '允许访问',
},
]}
dataSource={[]}
/>
</div>
);
};

View File

@ -0,0 +1,37 @@
import { FormLayout } from '@formily/antd';
import React from 'react';
import { SchemaComponent } from '../../schema-component';
export const RoleConfigure = () => {
return (
<div>
<FormLayout layout={'vertical'}>
<SchemaComponent
schema={{
type: 'object',
properties: {
allowConfigure: {
title: '配置权限',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-content': '允许配置系统,包括界面配置、数据表配置、权限配置、系统配置等全部配置项',
},
strategy: {
title: '通用数据操作权限',
description: '所有数据表都默认使用通用数据操作权限;同时,可以针对每个数据表单独配置权限。',
'x-component': 'StrategyActions',
'x-decorator': 'FormItem',
},
allowNewMenu: {
title: '新增菜单项默认访问权限',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-content': '新增菜单项默认允许访问',
},
},
}}
/>
</FormLayout>
</div>
);
};

View File

@ -0,0 +1,11 @@
import React from 'react';
import { SchemaComponent } from '../../schema-component';
import { roleSchema } from './schemas/roles';
export const RoleTable = () => {
return (
<div>
<SchemaComponent schema={roleSchema} />
</div>
);
};

View File

@ -0,0 +1,55 @@
import { FormItem, FormLayout } from '@formily/antd';
import { Checkbox, Select, Table } from 'antd';
import React from 'react';
export const RolesResourcesActions = () => {
return (
<div>
<FormLayout layout={'vertical'}>
<FormItem label={'操作权限'}>
<Table
size={'small'}
pagination={false}
columns={[
{
dataIndex: 'displayName',
title: '操作',
},
{
dataIndex: 'type',
title: '类型',
},
{
dataIndex: 'enable',
title: '允许操作',
render: () => <Checkbox />,
},
{
dataIndex: 'scope',
title: '可操作的数据范围',
render: () => <Select size={'small'} />,
},
]}
dataSource={[
{
displayName: '添加',
type: 'new-data',
},
{
displayName: '导入',
type: 'new-data',
},
{
displayName: '查看',
type: 'old-data',
},
]}
/>
</FormItem>
<FormItem label={'字段权限'}>
<Table />
</FormItem>
</FormLayout>
</div>
);
};

View File

@ -0,0 +1,47 @@
import { Checkbox, Select, Table } from 'antd';
import React from 'react';
export const StrategyActions = () => {
return (
<div>
<Table
size={'small'}
pagination={false}
columns={[
{
dataIndex: 'displayName',
title: '操作',
},
{
dataIndex: 'type',
title: '类型',
},
{
dataIndex: 'enable',
title: '允许操作',
render: () => <Checkbox />,
},
{
dataIndex: 'scope',
title: '可操作的数据范围',
render: () => <Select size={'small'}/>,
},
]}
dataSource={[
{
displayName: '添加',
type: 'new-data',
},
{
displayName: '导入',
type: 'new-data',
},
{
displayName: '查看',
type: 'old-data',
},
]}
/>
</div>
);
};

View File

@ -0,0 +1,6 @@
export * from './MenuConfigure';
export * from './RoleConfigure';
export * from './RolesResourcesActions';
export * from './RoleTable';
export * from './StrategyActions';

View File

@ -0,0 +1,175 @@
import { ISchema } from '@formily/react';
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 roleCollectionsSchema: ISchema = {
type: 'void',
'x-decorator': 'ResourceActionProvider',
'x-decorator-props': {
association: {
sourceKey: 'name',
targetKey: 'name',
},
resourceName: 'roles.collections',
request: {
resource: 'roles.collections',
action: 'list',
params: {
pageSize: 5,
filter: {},
sort: ['sort'],
appends: [],
},
},
},
'x-component': 'CollectionProvider',
'x-component-props': {
collection,
},
properties: {
table1: {
type: 'void',
'x-uid': 'input',
'x-component': 'VoidTable',
'x-component-props': {
rowKey: 'name',
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: {
configure: {
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: {
usingActionsConfig: {
'x-component': 'Radio.Group',
'x-decorator': 'FormItem',
enum: [
{ value: false, label: '使用通用权限:只能查看、添加、修改数据' },
{ value: true, label: '单独配置权限' },
],
},
actions: {
'x-component': 'RolesResourcesActions',
'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: '{{ useUpdateAction }}',
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
};

View File

@ -0,0 +1,288 @@
import { ISchema } from '@formily/react';
import { roleCollectionsSchema } from './roleCollections';
const collection = {
name: 'roles',
filterTargetKey: 'name',
targetKey: '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,
},
],
};
export const roleSchema: ISchema = {
type: 'object',
properties: {
block1: {
type: 'void',
'x-decorator': 'ResourceActionProvider',
'x-decorator-props': {
collection: {
targetKey: 'name',
},
resourceName: 'roles',
request: {
resource: 'roles',
action: 'list',
params: {
pageSize: 5,
filter: {},
sort: ['createdAt'],
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: {
tabs1: {
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
properties: {
tab1: {
type: 'void',
title: '系统全局配置',
'x-component': 'Tabs.TabPane',
'x-component-props': {},
properties: {
role: {
'x-component': 'RoleConfigure',
},
},
},
tab2: {
type: 'void',
title: '数据权限',
'x-component': 'Tabs.TabPane',
'x-component-props': {},
properties: {
roleCollectionsSchema,
},
},
tab3: {
type: 'void',
title: '菜单访问权限',
'x-component': 'Tabs.TabPane',
'x-component-props': {},
properties: {
menu: {
'x-component': 'MenuConfigure',
},
},
},
},
},
},
},
},
},
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

@ -7,11 +7,12 @@ import {
ResourceActionProvider, ResourceActionProvider,
useDataSourceFromRAC useDataSourceFromRAC
} from './'; } from './';
import * as hooks from './action-hooks';
export const CollectionManagerSchemaComponentProvider: React.FC = (props) => { export const CollectionManagerSchemaComponentProvider: React.FC = (props) => {
return ( return (
<SchemaComponentOptions <SchemaComponentOptions
scope={{ useDataSourceFromRAC }} scope={{ ...hooks, useDataSourceFromRAC }}
components={{ CollectionField, CollectionFieldProvider, CollectionProvider, ResourceActionProvider }} components={{ CollectionField, CollectionFieldProvider, CollectionProvider, ResourceActionProvider }}
> >
{props.children} {props.children}

View File

@ -1,81 +1,12 @@
import { useForm } from '@formily/react';
import React from 'react'; import React from 'react';
import { useResourceActionContext, useResourceContext } from '..'; import { SchemaComponent } from '../../schema-component';
import { useRequest } from '../../api-client';
import { useRecord } from '../../record-provider';
import { SchemaComponent, useActionContext } from '../../schema-component';
import { collectionSchema } from './schemas/collections'; 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 = () => { export const ConfigurationTable = () => {
return ( return (
<div> <div>
<SchemaComponent <SchemaComponent
schema={collectionSchema} schema={collectionSchema}
scope={{ useCancelAction, useCreateAction, useUpdateAction, useDestroyAction, useValues }}
/> />
</div> </div>
); );

View File

@ -0,0 +1,69 @@
import { useForm } from '@formily/react';
import { useRequest } from '../api-client';
import { useRecord } from '../record-provider';
import { useActionContext } from '../schema-component';
import { useResourceActionContext, useResourceContext } from './ResourceActionProvider';
export const useCancelAction = () => {
const form = useForm();
const ctx = useActionContext();
return {
async run() {
ctx.setVisible(false);
form.reset();
},
};
};
export 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();
},
};
};
export 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();
},
};
};
export const useDestroyAction = () => {
const { refresh } = useResourceActionContext();
const { resource, targetKey } = useResourceContext();
const { [targetKey]: filterByTk } = useRecord();
return {
async run() {
await resource.destroy({ filterByTk });
refresh();
},
};
};
export const useValues = (options) => {
const record = useRecord();
return useRequest(() => Promise.resolve({ data: record }), {
...options,
refreshDeps: [record],
});
};

View File

@ -7,7 +7,7 @@ export const ActionBar = () => {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const ActionInitializer = useComponent(fieldSchema['x-action-initializer']); const ActionInitializer = useComponent(fieldSchema['x-action-initializer']);
return ( return (
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}> <div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
<Space> <Space>
{fieldSchema.mapProperties((schema, key) => { {fieldSchema.mapProperties((schema, key) => {

View File

@ -6,6 +6,7 @@ export default {
sortable: 'sort', sortable: 'sort',
autoGenId: false, autoGenId: false,
model: 'CollectionModel', model: 'CollectionModel',
repository: 'CollectionRepository',
timestamps: false, timestamps: false,
filterTargetKey: 'name', filterTargetKey: 'name',
fields: [ fields: [

View File

@ -1,5 +1,6 @@
import { Plugin } from '@nocobase/server'; import { Plugin } from '@nocobase/server';
import path from 'path'; import path from 'path';
import { CollectionRepository } from '.';
import { import {
afterCreateForReverseField, afterCreateForReverseField,
beforeCreateForChildrenCollection, beforeCreateForChildrenCollection,
@ -15,6 +16,10 @@ export class CollectionManagerPlugin extends Plugin {
FieldModel, FieldModel,
}); });
this.app.db.registerRepositories({
CollectionRepository,
});
// 要在 beforeInitOptions 之前处理 // 要在 beforeInitOptions 之前处理
this.app.db.on('fields.beforeCreate', beforeCreateForReverseField(this.app.db)); this.app.db.on('fields.beforeCreate', beforeCreateForReverseField(this.app.db));
this.app.db.on('fields.beforeCreate', beforeCreateForChildrenCollection(this.app.db)); this.app.db.on('fields.beforeCreate', beforeCreateForChildrenCollection(this.app.db));
@ -41,6 +46,10 @@ export class CollectionManagerPlugin extends Plugin {
await model.migrate({ transaction }); await model.migrate({ transaction });
} }
}); });
this.app.on('beforeStart', async () => {
await this.app.db.getRepository<CollectionRepository>('collections').load();
});
} }
async load() { async load() {

View File

@ -7,7 +7,7 @@ interface LoadOptions {
} }
export class CollectionRepository extends Repository { export class CollectionRepository extends Repository {
async load(options?: LoadOptions) { async load(options: LoadOptions = {}) {
const { filter, skipExist } = options; const { filter, skipExist } = options;
const instances = (await this.find({ filter })) as CollectionModel[]; const instances = (await this.find({ filter })) as CollectionModel[];
for (const instance of instances) { for (const instance of instances) {