feat: menu permissions

This commit is contained in:
chenos 2021-08-11 14:27:45 +08:00
parent 66549dc4bf
commit 23db248743
8 changed files with 178 additions and 96 deletions

View File

@ -0,0 +1,121 @@
import React from 'react';
import { observer } from '@formily/react';
import { Table, Checkbox, Spin } from 'antd';
import { useRequest } from 'ahooks';
import { request } from '../../../schemas';
import { Resource } from '@nocobase/client/src/resource';
import { useContext } from 'react';
import { RoleContext } from '.';
import { useState } from 'react';
const getKeys = (items: any[]) => {
const keys = [];
for (const item of items) {
keys.push(item.key);
const children = getKeys(item.children || []);
keys.push(...children);
}
return keys;
};
export const MenuPermissionTable = observer((props) => {
const role = useContext(RoleContext);
const [allUiSchemaKyes, setAllUiSchemaKyes] = useState([]);
const [uiSchemaKyes, setUiSchemaKeys] = useState([]);
const { data, loading } = useRequest(
() => request('ui_schemas:getMenuItems'),
{
formatResult: (data) => data?.data,
onSuccess(data) {
setAllUiSchemaKyes(getKeys(data));
},
},
);
console.log('allUiSchemaKyes', allUiSchemaKyes);
const resource = Resource.make({
associatedName: 'roles',
associatedKey: role.name,
resourceName: 'ui_schemas',
});
useRequest(() => resource.list(), {
formatResult: (data) => data?.data,
onSuccess(data) {
setUiSchemaKeys(getKeys(data));
},
});
if (loading) {
return <Spin />;
}
return (
<div>
<Table
loading={loading}
dataSource={data}
pagination={false}
expandable={{
defaultExpandAllRows: true,
}}
columns={[
{
title: '菜单名称',
dataIndex: 'title',
},
{
title: (
<div>
<Checkbox
checked={
allUiSchemaKyes.length > 0 &&
allUiSchemaKyes.length === uiSchemaKyes.length
}
onChange={async (e) => {
const resource = Resource.make({
resourceName: 'roles',
resourceKey: role.name,
});
if (e.target.checked) {
await resource.save({
ui_schemas: allUiSchemaKyes,
});
setUiSchemaKeys(allUiSchemaKyes);
} else {
await resource.save({
ui_schemas: [],
});
setUiSchemaKeys([]);
}
}}
/>{' '}
访
</div>
),
dataIndex: 'key',
render: (value) => {
return (
<Checkbox
checked={uiSchemaKyes.includes(value)}
onChange={async (e) => {
setUiSchemaKeys((prevUiSchemaKeys) => {
if (e.target.checked) {
prevUiSchemaKeys.push(value);
} else {
const index = prevUiSchemaKeys.findIndex(
(key) => key === value,
);
prevUiSchemaKeys.splice(index, 1);
}
return [...prevUiSchemaKeys];
});
await resource.toggle({
resourceKey: value,
});
}}
/>
);
},
},
]}
/>
</div>
);
});

View File

@ -15,6 +15,7 @@ import { connect, ISchema, observer, useForm } from '@formily/react';
import { DescriptionsContext } from '../../../schemas/form'; import { DescriptionsContext } from '../../../schemas/form';
import { createContext } from 'react'; import { createContext } from 'react';
import { ActionPermissionField } from './ActionPermissionField'; import { ActionPermissionField } from './ActionPermissionField';
import { MenuPermissionTable } from './MenuPermissionTable';
export const RoleContext = createContext(null); export const RoleContext = createContext(null);
@ -246,96 +247,7 @@ const collectionSchema: ISchema = {
const menuSchema: ISchema = { const menuSchema: ISchema = {
type: 'array', type: 'array',
// 'x-decorator': 'CardItem', 'x-component': 'MenuPermissionTable',
'x-component': 'Table',
default: [],
'x-component-props': {
rowKey: 'name',
showIndex: true,
refreshRequestOnChange: true,
pagination: {
pageSize: 10,
},
useResource: useCollectionsResource,
collectionName: 'collections',
},
properties: {
[uid()]: {
type: 'void',
title: '操作',
'x-component': 'Table.Operation',
'x-component-props': {
className: 'nb-table-operation',
},
properties: {
[uid()]: {
type: 'void',
'x-component': 'Action',
'x-component-props': {
icon: 'EllipsisOutlined',
},
properties: {
[uid()]: {
type: 'void',
'x-component': 'Action.Dropdown',
'x-component-props': {},
properties: {
[uid()]: {
type: 'void',
title: '配置',
'x-component': 'Menu.Action',
'x-component-props': {
style: {
minWidth: 150,
},
},
'x-action-type': 'view',
properties: {
[uid()]: {
type: 'void',
title: '权限配置',
'x-component': 'Action.Drawer',
'x-component-props': {
bodyStyle: {
background: '#f0f2f5',
paddingTop: 0,
},
},
properties: {},
},
},
},
},
},
},
},
},
},
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,
},
},
},
},
}; };
const schema: ISchema = { const schema: ISchema = {
@ -488,7 +400,7 @@ const schema: ISchema = {
}, },
[uid()]: { [uid()]: {
type: 'void', type: 'void',
title: '菜单权限', title: '菜单访问权限',
'x-component': 'Tabs.TabPane', 'x-component': 'Tabs.TabPane',
'x-component-props': {}, 'x-component-props': {},
properties: { properties: {
@ -579,7 +491,7 @@ const schema: ISchema = {
export const Permissions = () => { export const Permissions = () => {
return ( return (
<SchemaRenderer <SchemaRenderer
components={{ RoleProvider, ActionPermissionField }} components={{ RoleProvider, ActionPermissionField, MenuPermissionTable }}
schema={schema} schema={schema}
/> />
); );

View File

@ -110,6 +110,15 @@ export class Resource {
}); });
} }
toggle(options?: any) {
const { associatedKey, associatedName, resourceName } = this.options;
const { resourceKey } = options;
let url = `${associatedName}/${associatedKey}/${resourceName}:toggle/${resourceKey}`;
return request(url, {
method: 'post',
});
}
static make(options: null | string | Resource | ResourceOptions): Resource | null { static make(options: null | string | Resource | ResourceOptions): Resource | null {
if (typeof options === 'string') { if (typeof options === 'string') {
return new Resource({ resourceName: options }); return new Resource({ resourceName: options });

View File

@ -25,5 +25,11 @@ export default {
target: 'users', target: 'users',
through: 'roles_users' through: 'roles_users'
}, },
{
type: 'belongsToMany',
name: 'ui_schemas',
target: 'ui_schemas',
through: 'roles_ui_schemas'
},
], ],
} as TableOptions; } as TableOptions;

View File

@ -135,3 +135,14 @@ export const getTree = async (ctx: actions.Context, next: actions.Next) => {
await next(); await next();
}; };
export const getMenuItems = async (ctx: actions.Context, next: actions.Next) => {
const UISchema = ctx.db.getModel('ui_schemas');
const schema = await UISchema.findOne({
where: {
'x-component': 'Menu',
}
});
ctx.body = await schema.getHierarchy();
await next();
};

View File

@ -32,6 +32,10 @@ export default {
type: 'string', type: 'string',
name: 'type', name: 'type',
}, },
{
type: 'string',
name: 'x-component',
},
{ {
type: 'json', type: 'json',
name: 'options', name: 'options',

View File

@ -86,4 +86,23 @@ export class UISchema extends Model {
} }
return properties; return properties;
} }
async getHierarchy() {
const data = [];
const children: UISchema[] = await this.getChildren({
where: {
async: false,
},
order: [['sort', 'asc']],
});
for (const child of children) {
const item: any = child.toJSON();
const nested = await child.getHierarchy();
if (nested.length) {
item.children = nested;
}
data.push(item);
}
return data;
}
} }

View File

@ -2,7 +2,7 @@ import path from 'path';
import { Application } from '@nocobase/server'; import { Application } from '@nocobase/server';
import { registerModels, Table } from '@nocobase/database'; import { registerModels, Table } from '@nocobase/database';
import * as models from './models'; import * as models from './models';
import { create, update, getTree } from './actions'; import * as actions from './actions';
export default async function (this: Application, options = {}) { export default async function (this: Application, options = {}) {
const database = this.database; const database = this.database;
@ -12,7 +12,7 @@ export default async function (this: Application, options = {}) {
directory: path.resolve(__dirname, 'collections'), directory: path.resolve(__dirname, 'collections'),
}); });
this.resourcer.registerActionHandler('ui_schemas:create', create); for (const [name, action] of Object.entries(actions)) {
this.resourcer.registerActionHandler('ui_schemas:update', update); this.resourcer.registerActionHandler(`ui_schemas:${name}`, action);
this.resourcer.registerActionHandler('ui_schemas:getTree', getTree); }
} }