feat: menu permissions
This commit is contained in:
parent
66549dc4bf
commit
23db248743
@ -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>
|
||||||
|
);
|
||||||
|
});
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -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 });
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
};
|
||||||
|
@ -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',
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user