1
0
mirror of https://github.com/chillzhuang/Sword synced 2024-11-21 17:59:26 +08:00

🎉 3.2.0.RELEASE 新增灵活数据权限特性

This commit is contained in:
smallchill 2021-11-05 01:26:59 +08:00
parent a7ea876307
commit adcaaf5016
23 changed files with 822 additions and 56 deletions

View File

@ -102,6 +102,29 @@ export default [
}, },
], ],
}, },
{
path: '/authority',
routes: [
{
path: '/authority/role',
routes: [
{ path: '/authority/role', redirect: '/authority/role/list' },
{ path: '/authority/role/list', component: './Authority/Role/Role' },
{ path: '/authority/role/add', component: './Authority/Role/RoleAdd' },
{ path: '/authority/role/add/:id', component: './Authority/Role/RoleAdd' },
{ path: '/authority/role/edit/:id', component: './Authority/Role/RoleEdit' },
{ path: '/authority/role/view/:id', component: './Authority/Role/RoleView' },
],
},
{
path: '/authority/datascope',
routes: [
{ path: '/authority/datascope', redirect: '/authority/datascope/list' },
{ path: '/authority/datascope/list', component: './Authority/DataScope/DataScope' },
],
},
],
},
{ {
path: '/system', path: '/system',
routes: [ routes: [
@ -148,17 +171,6 @@ export default [
{ path: '/system/post/view/:id', component: './System/Post/PostView' }, { path: '/system/post/view/:id', component: './System/Post/PostView' },
], ],
}, },
{
path: '/system/role',
routes: [
{ path: '/system/role', redirect: '/system/role/list' },
{ path: '/system/role/list', component: './System/Role/Role' },
{ path: '/system/role/add', component: './System/Role/RoleAdd' },
{ path: '/system/role/add/:id', component: './System/Role/RoleAdd' },
{ path: '/system/role/edit/:id', component: './System/Role/RoleEdit' },
{ path: '/system/role/view/:id', component: './System/Role/RoleView' },
],
},
{ {
path: '/system/menu', path: '/system/menu',
routes: [ routes: [

View File

@ -1,6 +1,6 @@
{ {
"name": "sword", "name": "sword",
"version": "3.1.0", "version": "3.2.0",
"description": "An out-of-box UI solution for enterprise applications", "description": "An out-of-box UI solution for enterprise applications",
"private": true, "private": true,
"scripts": { "scripts": {

View File

@ -2,6 +2,16 @@ import { getAuthority } from '../utils/authority';
export const MENU_NAMESPACE = 'menu'; export const MENU_NAMESPACE = 'menu';
export function MENU_REFRESH_ROUTE(topMenuId, callback) {
return {
type: `${MENU_NAMESPACE}/refreshMenuData`,
payload: {
topMenuId,
},
callback,
};
}
export function MENU_REFRESH_DATA() { export function MENU_REFRESH_DATA() {
return { return {
type: `${MENU_NAMESPACE}/fetchMenuData`, type: `${MENU_NAMESPACE}/fetchMenuData`,
@ -16,6 +26,13 @@ export function MENU_LIST(payload) {
}; };
} }
export function MENU_PARENT_LIST(payload) {
return {
type: `${MENU_NAMESPACE}/fetchParentList`,
payload,
};
}
export function MENU_INIT() { export function MENU_INIT() {
return { return {
type: `${MENU_NAMESPACE}/fetchInit`, type: `${MENU_NAMESPACE}/fetchInit`,
@ -59,3 +76,24 @@ export function MENU_SELECT_ICON(icon) {
}, },
}; };
} }
export function MENU_SHOW_DRAWER(payload) {
return {
type: `${MENU_NAMESPACE}/showDrawer`,
payload,
};
}
export function MENU_LOAD_DATA_SCOPE_DRAWER(payload) {
return {
type: `${MENU_NAMESPACE}/loadDataScopeDrawer`,
payload,
};
}
export function MENU_LOAD_DATA_SCOPE_DICT() {
return {
type: `${MENU_NAMESPACE}/loadDataScopeDict`,
payload: { code: 'data_scope_type' },
};
}

View File

@ -14,6 +14,13 @@ export function ROLE_INIT() {
}; };
} }
export function ROLE_INIT_BY_ID(roleId) {
return {
type: `${ROLE_NAMESPACE}/fetchInitById`,
payload: { roleId },
};
}
export function ROLE_DETAIL(id) { export function ROLE_DETAIL(id) {
return { return {
type: `${ROLE_NAMESPACE}/fetchDetail`, type: `${ROLE_NAMESPACE}/fetchDetail`,

View File

@ -8,7 +8,7 @@ const FooterView = () => (
<GlobalFooter <GlobalFooter
copyright={ copyright={
<Fragment> <Fragment>
Copyright <Icon type="copyright" /> 2020 SpringBlade{' '} Copyright <Icon type="copyright" /> 2021 SpringBlade{' '}
<a <a
key="github" key="github"
title="git" title="git"

View File

@ -27,7 +27,7 @@ const links = [
const copyright = ( const copyright = (
<Fragment> <Fragment>
Copyright <Icon type="copyright" /> 2020 SpringBlade{' '} Copyright <Icon type="copyright" /> 2021 SpringBlade{' '}
<a <a
key="github" key="github"
title="git" title="git"

View File

@ -13,4 +13,5 @@ export default {
'button.role.name': 'grant role', 'button.role.name': 'grant role',
'button.reset-password.name': 'reset password', 'button.reset-password.name': 'reset password',
'button.back.name': 'back', 'button.back.name': 'back',
'button.setting.name': 'setting',
}; };

View File

@ -8,6 +8,9 @@ export default {
'menu.desk.notice': 'notice', 'menu.desk.notice': 'notice',
'menu.base': 'base', 'menu.base': 'base',
'menu.base.region': 'region', 'menu.base.region': 'region',
'menu.authority': 'authority',
'menu.authority.role': 'role',
'menu.authority.data_scope': 'data scope',
'menu.system': 'system', 'menu.system': 'system',
'menu.system.user': 'user', 'menu.system.user': 'user',
'menu.system.dept': 'department', 'menu.system.dept': 'department',

View File

@ -13,4 +13,5 @@ export default {
'button.role.name': '角色配置', 'button.role.name': '角色配置',
'button.reset-password.name': '密码重置', 'button.reset-password.name': '密码重置',
'button.back.name': '返回', 'button.back.name': '返回',
'button.setting.name': '配置',
}; };

View File

@ -8,6 +8,9 @@ export default {
'menu.desk.notice': '通知公告', 'menu.desk.notice': '通知公告',
'menu.base': '基础配置', 'menu.base': '基础配置',
'menu.base.region': '行政区划', 'menu.base.region': '行政区划',
'menu.authority': '权限管理',
'menu.authority.role': '角色管理',
'menu.authority.data_scope': '数据权限',
'menu.system': '系统管理', 'menu.system': '系统管理',
'menu.system.user': '用户管理', 'menu.system.user': '用户管理',
'menu.system.dept': '部门管理', 'menu.system.dept': '部门管理',

View File

@ -13,4 +13,5 @@ export default {
'button.role.name': '角色配置', 'button.role.name': '角色配置',
'button.reset-password.name': '密碼重置', 'button.reset-password.name': '密碼重置',
'button.back.name': '返回', 'button.back.name': '返回',
'button.setting.name': '配置',
}; };

View File

@ -8,6 +8,9 @@ export default {
'menu.desk.notice': '通知公告', 'menu.desk.notice': '通知公告',
'menu.base': '基礎配置', 'menu.base': '基礎配置',
'menu.base.region': '行政區劃', 'menu.base.region': '行政區劃',
'menu.authority': '權限管理',
'menu.authority.role': '角色管理',
'menu.authority.data_scope': '數據權限',
'menu.system': '系統管理', 'menu.system': '系統管理',
'menu.system.user': '用戶管理', 'menu.system.user': '用戶管理',
'menu.system.dept': '部門管理', 'menu.system.dept': '部門管理',

View File

@ -9,11 +9,14 @@ import {
dynamicRoutes, dynamicRoutes,
dynamicButtons, dynamicButtons,
list, list,
parentList,
submit, submit,
detail, detail,
remove, remove,
tree, tree,
dataScopeList,
} from '../services/menu'; } from '../services/menu';
import { dict } from '../services/dict';
import { getRoutes, setRoutes, getButtons, setButtons } from '../utils/authority'; import { getRoutes, setRoutes, getButtons, setButtons } from '../utils/authority';
import { MENU_NAMESPACE } from '../actions/menu'; import { MENU_NAMESPACE } from '../actions/menu';
import { formatRoutes, formatButtons } from '../utils/utils'; import { formatRoutes, formatButtons } from '../utils/utils';
@ -119,7 +122,19 @@ export default {
init: { init: {
tree: [], tree: [],
}, },
dict: {
dataScopeType: [],
},
detail: {}, detail: {},
drawer: {
visible: false,
menuId: '',
menuName: '',
dataScope: {
list: [],
pagination: false,
},
},
}, },
effects: { effects: {
@ -158,6 +173,18 @@ export default {
}); });
} }
}, },
*fetchParentList({ payload }, { call, put }) {
const response = yield call(parentList, payload);
if (response.success) {
yield put({
type: 'saveList',
payload: {
list: response.data,
pagination: false,
},
});
}
},
*fetchInit({ payload }, { call, put }) { *fetchInit({ payload }, { call, put }) {
const response = yield call(tree, payload); const response = yield call(tree, payload);
if (response.success) { if (response.success) {
@ -194,6 +221,41 @@ export default {
}, },
}); });
}, },
*showDrawer({ payload }, { put }) {
yield put({
type: 'saveDrawer',
payload,
});
},
*loadDataScopeDrawer({ payload }, { call, put }) {
const response = yield call(dataScopeList, payload);
if (response.success) {
yield put({
type: 'saveLoadDataScopeDrawer',
payload: {
dataScope: {
list: response.data.records,
pagination: {
total: response.data.total,
current: response.data.current,
pageSize: response.data.size,
},
},
},
});
}
},
*loadDataScopeDict({ payload }, { call, put }) {
const response = yield call(dict, payload);
if (response.success) {
yield put({
type: 'saveDataScopeDict',
payload: {
dataScopeType: response.data,
},
});
}
},
*submit({ payload }, { call }) { *submit({ payload }, { call }) {
const response = yield call(submit, payload); const response = yield call(submit, payload);
if (response.success) { if (response.success) {
@ -245,6 +307,26 @@ export default {
...newState, ...newState,
}; };
}, },
saveDrawer(state, action) {
return {
...state,
drawer: action.payload,
};
},
saveLoadDataScopeDrawer(state, action) {
const newState = state;
newState.drawer.dataScope = action.payload.dataScope;
return {
...newState,
};
},
saveDataScopeDict(state, action) {
const newState = state;
newState.dict.dataScopeType = action.payload.dataScopeType;
return {
...newState,
};
},
removeDetail(state) { removeDetail(state) {
return { return {
...state, ...state,

View File

@ -1,7 +1,7 @@
import { message } from 'antd'; import { message } from 'antd';
import router from 'umi/router'; import router from 'umi/router';
import { ROLE_NAMESPACE } from '../actions/role'; import { ROLE_NAMESPACE } from '../actions/role';
import { list, submit, detail, remove, tree, grant } from '../services/role'; import { list, submit, detail, remove, tree, treeById, grant } from '../services/role';
import { grantTree, roleTreeKeys, dynamicRoutes, dynamicButtons } from '../services/menu'; import { grantTree, roleTreeKeys, dynamicRoutes, dynamicButtons } from '../services/menu';
import { setButtons, setRoutes } from '../utils/authority'; import { setButtons, setRoutes } from '../utils/authority';
import { formatButtons, formatRoutes } from '../utils/utils'; import { formatButtons, formatRoutes } from '../utils/utils';
@ -17,8 +17,10 @@ export default {
tree: [], tree: [],
}, },
detail: {}, detail: {},
grantTree: [], menuGrantTree: [],
roleCheckedTreeKeys: [], menuTreeKeys: [],
dataScopeGrantTree: [],
dataScopeTreeKeys: [],
}, },
effects: { effects: {
*fetchList({ payload }, { call, put }) { *fetchList({ payload }, { call, put }) {
@ -44,6 +46,17 @@ export default {
}); });
} }
}, },
*fetchInitById({ payload }, { call, put }) {
const response = yield call(treeById, payload);
if (response.success) {
yield put({
type: 'saveInit',
payload: {
tree: response.data,
},
});
}
},
*fetchDetail({ payload }, { call, put }) { *fetchDetail({ payload }, { call, put }) {
const response = yield call(detail, payload); const response = yield call(detail, payload);
if (response.success) { if (response.success) {
@ -66,7 +79,8 @@ export default {
yield put({ yield put({
type: 'save', type: 'save',
payload: { payload: {
grantTree: response.data, menuGrantTree: response.data.menu,
dataScopeGrantTree: response.data.dataScope,
}, },
}); });
}, },
@ -75,7 +89,8 @@ export default {
yield put({ yield put({
type: 'save', type: 'save',
payload: { payload: {
roleCheckedTreeKeys: response.data, menuTreeKeys: response.data.menu,
dataScopeTreeKeys: response.data.dataScope,
}, },
}); });
}, },
@ -83,7 +98,8 @@ export default {
yield put({ yield put({
type: 'save', type: 'save',
payload: { payload: {
roleCheckedTreeKeys: payload.roleCheckedTreeKeys, menuTreeKeys: payload.menuTreeKeys,
dataScopeTreeKeys: payload.dataScopeTreeKeys,
}, },
}); });
}, },

View File

@ -0,0 +1,158 @@
import React, { PureComponent } from 'react';
import { connect } from 'dva';
import { Drawer, Button, Col, Form, Input, Row } from 'antd';
import Panel from '../../../components/Panel';
import Grid from '../../../components/Sword/Grid';
import {
MENU_PARENT_LIST,
MENU_LOAD_DATA_SCOPE_DRAWER,
MENU_SHOW_DRAWER,
} from '../../../actions/menu';
import DataScopeCrud from './DataScopeCrud';
import func from '../../../utils/Func';
const FormItem = Form.Item;
@connect(({ menu, loading }) => ({
menu,
loading: loading.models.menu,
}))
@Form.create()
class DataScope extends PureComponent {
showDrawer = (menuId, menuName, menuCode) => {
const { dispatch } = this.props;
dispatch(
MENU_SHOW_DRAWER({
visible: true,
menuId,
menuName,
menuCode,
})
).then(() => {
dispatch(MENU_LOAD_DATA_SCOPE_DRAWER({ menuId }));
});
};
onClose = () => {
const { dispatch } = this.props;
dispatch(
MENU_SHOW_DRAWER({
visible: false,
})
);
};
// ============ 查询 ===============
handleSearch = params => {
const { dispatch } = this.props;
dispatch(MENU_PARENT_LIST(params));
};
// ============ 查询表单 ===============
renderSearchForm = onReset => {
const { form } = this.props;
const { getFieldDecorator } = form;
return (
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={8} sm={24}>
<FormItem label="菜单编号">
{getFieldDecorator('code')(<Input placeholder="请输入菜单编号" />)}
</FormItem>
</Col>
<Col md={8} sm={24}>
<FormItem label="菜单名称">
{getFieldDecorator('name')(<Input placeholder="请输入菜单名称" />)}
</FormItem>
</Col>
<Col>
<div style={{ float: 'right' }}>
<Button type="primary" htmlType="submit">
查询
</Button>
<Button style={{ marginLeft: 8 }} onClick={onReset}>
重置
</Button>
</div>
</Col>
</Row>
);
};
// ============ 处理按钮点击回调事件 ===============
handleBtnCallBack = payload => {
const { btn, keys, rows } = payload;
if (btn.code === 'data_scope_setting') {
this.showDrawer(keys[0], rows[0].name, rows[0].code);
}
};
render() {
const code = 'data_scope';
const {
form,
loading,
menu: { data, drawer },
} = this.props;
const { visible, menuName } = drawer;
const drawerTitle = func.isEmpty(menuName) ? '' : `[${menuName}]`;
const columns = [
{
title: '菜单名称',
dataIndex: 'name',
},
{
title: '菜单编号',
dataIndex: 'code',
width: 150,
},
{
title: '菜单别名',
dataIndex: 'alias',
width: 150,
},
{
title: '路由地址',
dataIndex: 'path',
},
{
title: '排序',
dataIndex: 'sort',
width: 60,
align: 'right',
},
];
return (
<Panel>
<Grid
code={code}
form={form}
onSearch={this.handleSearch}
renderSearchForm={this.renderSearchForm}
btnCallBack={this.handleBtnCallBack}
loading={loading}
data={data}
columns={columns}
actionColumnWidth={100}
/>
<Drawer
title={`${drawerTitle} 数据权限配置`}
placement="right"
width={1000}
closable={false}
onClose={this.onClose}
visible={visible}
>
<DataScopeCrud />
</Drawer>
</Panel>
);
}
}
export default DataScope;

View File

@ -0,0 +1,357 @@
import React, { Fragment, PureComponent } from 'react';
import { Button, Card, Col, Divider, Form, Input, message, Modal, Row, Select } from 'antd';
import { connect } from 'dva';
import Grid from '../../../components/Sword/Grid';
import styles from '../../../layouts/Sword.less';
import { submitDataScope, removeDataScope, scopeDataDetail } from '../../../services/menu';
import func from '../../../utils/Func';
import { MENU_LOAD_DATA_SCOPE_DICT, MENU_LOAD_DATA_SCOPE_DRAWER } from '../../../actions/menu';
const FormItem = Form.Item;
const { TextArea } = Input;
@connect(({ menu, loading }) => ({
menu,
loading: loading.models.menu,
}))
@Form.create()
class DataScopeCrud extends PureComponent {
state = {
stateVisible: false,
viewMode: false,
params: {},
detail: {
scopeType: 1,
},
};
componentDidMount() {
const {
dispatch
} = this.props;
dispatch(MENU_LOAD_DATA_SCOPE_DICT());
}
// ============ 查询 ===============
handleSearch = params => {
const {
dispatch,
menu: { drawer },
} = this.props;
const { menuId } = drawer;
this.setState({ params });
const search = { scopeName: params.scopeName, resourceCode: params.resourceCode, menuId};
dispatch(MENU_LOAD_DATA_SCOPE_DRAWER(search));
};
// ============ 查询表单 ===============
renderSearchForm = onReset => {
const { form } = this.props;
const { getFieldDecorator } = form;
return (
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
<Col md={8} sm={24}>
<FormItem label="权限名称">
{getFieldDecorator('scopeName')(<Input placeholder="请输入权限名称" />)}
</FormItem>
</Col>
<Col md={8} sm={24}>
<FormItem label="权限编号">
{getFieldDecorator('resourceCode')(<Input placeholder="请输入权限编号" />)}
</FormItem>
</Col>
<Col>
<div style={{ float: 'right' }}>
<Button type="primary" htmlType="submit">
查询
</Button>
<Button style={{ marginLeft: 8 }} onClick={onReset}>
重置
</Button>
</div>
</Col>
</Row>
);
};
handleSubmit = e => {
e.preventDefault();
const {
form,
menu: { drawer },
} = this.props;
const { menuId } = drawer;
const {
params,
detail: { id },
} = this.state;
form.validateFieldsAndScroll((err, values) => {
if (!err) {
let formData = Object.assign(values, { menuId });
if (!func.isEmpty(id)) {
formData = Object.assign(values, { id });
}
submitDataScope(formData).then(resp => {
if (resp.success) {
message.success(resp.msg);
} else {
message.error(resp.msg || '提交失败');
}
this.handleSearch(params);
this.handleStateCancel();
form.resetFields();
});
}
});
};
handleStateCancel = () => {
this.setState({
stateVisible: false,
viewMode: false,
detail: { id: '' }
});
};
handleClick = (code, record) => {
const {
menu: { drawer },
} = this.props;
const { menuId, menuCode } = drawer;
if (code === 'data_scope_add') {
this.setState({
stateVisible: true,
detail: {
id: '',
menuId,
resourceCode: menuCode,
},
});
} else if (code === 'data_scope_edit') {
const { id } = record;
scopeDataDetail({ id }).then(resp => {
if (resp.success) {
this.setState({ stateVisible: true, viewMode: false, detail: resp.data });
}
});
} else if (code === 'data_scope_view') {
const { id } = record;
scopeDataDetail({ id }).then(resp => {
if (resp.success) {
this.setState({ stateVisible: true, viewMode: true, detail: resp.data });
}
});
} else if (code === 'data_scope_delete') {
const { id } = record;
const { params } = this.state;
const refresh = this.handleSearch;
Modal.confirm({
title: '删除确认',
content: '确定删除该条记录?',
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk() {
removeDataScope({ ids: id }).then(resp => {
if (resp.success) {
message.success(resp.msg);
refresh(params);
} else {
message.error(resp.msg || '删除失败');
}
});
},
onCancel() {},
});
}
};
renderLeftButton = () => (
<Button icon="plus" type="primary" onClick={() => this.handleClick('data_scope_add')}>
新增
</Button>
);
render() {
const {
form,
menu: { drawer, dict },
loading,
} = this.props;
const { stateVisible, viewMode, detail } = this.state;
const { getFieldDecorator } = form;
const { dataScope } = drawer;
const { dataScopeType } = dict;
const formItemLayout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
const formAllItemLayout = {
labelCol: {
span: 4,
},
wrapperCol: {
span: 20,
},
};
const columns = [
{
title: '权限名称',
dataIndex: 'scopeName',
},
{
title: '权限编号',
dataIndex: 'resourceCode',
},
{
title: '权限字段',
dataIndex: 'scopeColumn',
},
{
title: '规则类型',
dataIndex: 'scopeTypeName',
},
{
title: '操作',
dataIndex: 'action',
render: (text, record) => (
<Fragment>
<div style={{ textAlign: 'center' }}>
<Fragment key="edit">
<a title="修改" onClick={() => this.handleClick('data_scope_edit', record)}>
修改
</a>
</Fragment>
<Divider type="vertical" />
<Fragment key="delete">
<a title="删除" onClick={() => this.handleClick('data_scope_delete', record)}>
删除
</a>
</Fragment>
<Divider type="vertical" />
<Fragment key="view">
<a title="查看" onClick={() => this.handleClick('data_scope_view', record)}>
查看
</a>
</Fragment>
</div>
</Fragment>
),
},
];
return (
<div>
<Grid
form={form}
onSearch={this.handleSearch}
renderSearchForm={this.renderSearchForm}
renderLeftButton={this.renderLeftButton}
loading={loading}
data={dataScope}
columns={columns}
/>
<Modal
title="数据权限配置"
width={1000}
visible={stateVisible}
onOk={this.handleSubmit}
onCancel={this.handleStateCancel}
>
<Form style={{ marginTop: 8 }}>
<Card className={styles.card} bordered={false}>
<Row gutter={24}>
<Col span={10}>
<FormItem {...formItemLayout} label="权限名称">
{getFieldDecorator('scopeName', {
initialValue: detail.scopeName,
})(<Input disabled={viewMode} placeholder="请输入权限名称" />)}
</FormItem>
</Col>
<Col span={10}>
<FormItem {...formItemLayout} label="权限编号">
{getFieldDecorator('resourceCode', {
initialValue: detail.resourceCode,
})(<Input disabled={viewMode} placeholder="请输入权限编号" />)}
</FormItem>
</Col>
</Row>
<Row gutter={24}>
<Col span={10}>
<FormItem {...formItemLayout} label="权限字段">
{getFieldDecorator('scopeColumn', {
initialValue: detail.scopeColumn,
})(<Input disabled={viewMode} placeholder="请输入权限字段" />)}
</FormItem>
</Col>
<Col span={10}>
<FormItem {...formItemLayout} label="规则类型">
{getFieldDecorator('scopeType', {
initialValue: detail.scopeType,
})(
<Select disabled={viewMode} placeholder="请选择规则类型">
{dataScopeType.map(d => (
<Select.Option key={d.dictKey} value={d.dictKey}>
{d.dictValue}
</Select.Option>
))}
</Select>
)}
</FormItem>
</Col>
</Row>
<Row gutter={24}>
<Col span={20}>
<FormItem {...formAllItemLayout} label="可见字段">
{getFieldDecorator('scopeField', {
initialValue: detail.scopeField,
})(<Input disabled={viewMode} placeholder="请输入可见字段" />)}
</FormItem>
</Col>
</Row>
<Row gutter={24}>
<Col span={20}>
<FormItem {...formAllItemLayout} label="权限类名">
{getFieldDecorator('scopeClass', {
initialValue: detail.scopeClass,
})(<Input disabled={viewMode} placeholder="请输入权限类名" />)}
</FormItem>
</Col>
</Row>
<Row gutter={24}>
<Col span={20}>
<FormItem {...formAllItemLayout} label="规则值">
{getFieldDecorator('scopeValue', {
initialValue: detail.scopeValue,
})(<TextArea disabled={viewMode} rows={4} placeholder="请输入规则值" />)}
</FormItem>
</Col>
</Row>
<Row gutter={24}>
<Col span={20}>
<FormItem {...formAllItemLayout} label="备注">
{getFieldDecorator('remark', {
initialValue: detail.remark,
})(<TextArea disabled={viewMode} rows={2} placeholder="请输入备注" />)}
</FormItem>
</Col>
</Row>
</Card>
</Form>
</Modal>
</div>
);
}
}
export default DataScopeCrud;

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { connect } from 'dva'; import { connect } from 'dva';
import { Button, Col, Form, Input, message, Modal, Row, Tree } from 'antd'; import { Button, Col, Form, Input, message, Modal, Row, Tree, Tabs } from 'antd';
import Panel from '../../../components/Panel'; import Panel from '../../../components/Panel';
import Grid from '../../../components/Sword/Grid'; import Grid from '../../../components/Sword/Grid';
import { import {
@ -15,6 +15,7 @@ import { tenantMode } from '../../../defaultSettings';
const FormItem = Form.Item; const FormItem = Form.Item;
const { TreeNode } = Tree; const { TreeNode } = Tree;
const { TabPane } = Tabs;
@connect(({ role, loading }) => ({ @connect(({ role, loading }) => ({
role, role,
@ -53,14 +54,9 @@ class Role extends PureComponent {
// ========== 权限配置 ============= // ========== 权限配置 =============
handleGrant = () => { handleGrant = () => {
const { const {
role: { roleCheckedTreeKeys }, role: { menuTreeKeys, dataScopeTreeKeys },
} = this.props; } = this.props;
if (roleCheckedTreeKeys.length === 0) {
message.warn('权限未变更无需操作');
return false;
}
const keys = this.getSelectKeys(); const keys = this.getSelectKeys();
this.setState({ this.setState({
@ -69,14 +65,21 @@ class Role extends PureComponent {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch( dispatch(
ROLE_GRANT({ roleIds: keys, menuIds: roleCheckedTreeKeys }, () => { ROLE_GRANT(
{
roleIds: keys,
menuIds: menuTreeKeys,
dataScopeIds: dataScopeTreeKeys,
},
() => {
this.setState({ this.setState({
visible: false, visible: false,
confirmLoading: false, confirmLoading: false,
}); });
message.success('配置成功'); message.success('配置成功');
dispatch(MENU_REFRESH_DATA()); dispatch(MENU_REFRESH_DATA());
}) }
)
); );
return true; return true;
}; };
@ -100,15 +103,30 @@ class Role extends PureComponent {
handleCancel = () => { handleCancel = () => {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch(ROLE_SET_TREE_KEYS({ roleCheckedTreeKeys: [] })); dispatch(ROLE_SET_TREE_KEYS({ menuTreeKeys: [], dataScopeTreeKeys: [], apiScopeTreeKeys: [] }));
this.setState({ this.setState({
visible: false, visible: false,
}); });
}; };
onCheck = checkedTreeKeys => { onMenuCheck = checkedTreeKeys => {
const { dispatch } = this.props; const {
dispatch(ROLE_SET_TREE_KEYS({ roleCheckedTreeKeys: checkedTreeKeys })); dispatch,
role: { dataScopeTreeKeys, apiScopeTreeKeys },
} = this.props;
dispatch(
ROLE_SET_TREE_KEYS({ menuTreeKeys: checkedTreeKeys, dataScopeTreeKeys, apiScopeTreeKeys })
);
};
onDataScopeCheck = checkedTreeKeys => {
const {
dispatch,
role: { menuTreeKeys, apiScopeTreeKeys },
} = this.props;
dispatch(
ROLE_SET_TREE_KEYS({ dataScopeTreeKeys: checkedTreeKeys, menuTreeKeys, apiScopeTreeKeys })
);
}; };
// ============ 查询表单 =============== // ============ 查询表单 ===============
@ -173,7 +191,13 @@ class Role extends PureComponent {
const { const {
form, form,
loading, loading,
role: { data, grantTree, roleCheckedTreeKeys }, role: {
data,
menuGrantTree,
menuTreeKeys,
dataScopeGrantTree,
dataScopeTreeKeys,
},
} = this.props; } = this.props;
const columns = [ const columns = [
@ -214,7 +238,7 @@ class Role extends PureComponent {
/> />
<Modal <Modal
title="权限配置" title="权限配置"
width={350} width={380}
visible={visible} visible={visible}
confirmLoading={confirmLoading} confirmLoading={confirmLoading}
onOk={this.handleGrant} onOk={this.handleGrant}
@ -222,9 +246,18 @@ class Role extends PureComponent {
okText="确认" okText="确认"
cancelText="取消" cancelText="取消"
> >
<Tree checkable checkedKeys={roleCheckedTreeKeys} onCheck={this.onCheck}> <Tabs defaultActiveKey="1" size="small">
{this.renderTreeNodes(grantTree)} <TabPane tab="菜单权限" key="1">
<Tree checkable checkedKeys={menuTreeKeys} onCheck={this.onMenuCheck}>
{this.renderTreeNodes(menuGrantTree)}
</Tree> </Tree>
</TabPane>
<TabPane tab="数据权限" key="2">
<Tree checkable checkedKeys={dataScopeTreeKeys} onCheck={this.onDataScopeCheck}>
{this.renderTreeNodes(dataScopeGrantTree)}
</Tree>
</TabPane>
</Tabs>
</Modal> </Modal>
</Panel> </Panel>
); );

View File

@ -75,8 +75,8 @@ class RoleAdd extends PureComponent {
); );
return ( return (
<Panel title="新增" back="/system/role" action={action}> <Panel title="新增" back="/authority/role" action={action}>
<Form hideRequiredMark style={{ marginTop: 8 }}> <Form style={{ marginTop: 8 }}>
<Card title="基本信息" className={styles.card} bordered={false}> <Card title="基本信息" className={styles.card} bordered={false}>
<Row gutter={24}> <Row gutter={24}>
<Col span={10}> <Col span={10}>

View File

@ -3,7 +3,7 @@ import { Form, Input, Card, Row, Col, Button, InputNumber, TreeSelect, message }
import { connect } from 'dva'; import { connect } from 'dva';
import Panel from '../../../components/Panel'; import Panel from '../../../components/Panel';
import styles from '../../../layouts/Sword.less'; import styles from '../../../layouts/Sword.less';
import { ROLE_DETAIL, ROLE_INIT, ROLE_SUBMIT } from '../../../actions/role'; import { ROLE_DETAIL, ROLE_INIT_BY_ID, ROLE_SUBMIT } from '../../../actions/role';
const FormItem = Form.Item; const FormItem = Form.Item;
const { TextArea } = Input; const { TextArea } = Input;
@ -22,7 +22,7 @@ class RoleEdit extends PureComponent {
}, },
} = this.props; } = this.props;
dispatch(ROLE_DETAIL(id)); dispatch(ROLE_DETAIL(id));
dispatch(ROLE_INIT()); dispatch(ROLE_INIT_BY_ID(id));
} }
handleSubmit = e => { handleSubmit = e => {
@ -90,8 +90,8 @@ class RoleEdit extends PureComponent {
); );
return ( return (
<Panel title="修改" back="/system/role" action={action}> <Panel title="修改" back="/authority/role" action={action}>
<Form hideRequiredMark style={{ marginTop: 8 }}> <Form style={{ marginTop: 8 }}>
<Card title="基本信息" className={styles.card} bordered={false}> <Card title="基本信息" className={styles.card} bordered={false}>
<Row gutter={24}> <Row gutter={24}>
<Col span={10}> <Col span={10}>

View File

@ -29,7 +29,7 @@ class RoleView extends PureComponent {
params: { id }, params: { id },
}, },
} = this.props; } = this.props;
router.push(`/system/role/edit/${id}`); router.push(`/authority/role/edit/${id}`);
}; };
render() { render() {
@ -62,8 +62,8 @@ class RoleView extends PureComponent {
); );
return ( return (
<Panel title="查看" back="/system/role" action={action}> <Panel title="查看" back="/authority/role" action={action}>
<Form hideRequiredMark style={{ marginTop: 8 }}> <Form style={{ marginTop: 8 }}>
<Card title="基本信息" className={styles.card} bordered={false}> <Card title="基本信息" className={styles.card} bordered={false}>
<Row gutter={24}> <Row gutter={24}>
<Col span={10}> <Col span={10}>

View File

@ -20,14 +20,14 @@ class Workplace extends PureComponent {
<Row gutter={24}> <Row gutter={24}>
<Col span={24}> <Col span={24}>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
<img src="https://img.shields.io/badge/Release-V3.1.0-green.svg" alt="Downloads" /> <img src="https://img.shields.io/badge/Release-V3.2.0-green.svg" alt="Downloads" />
<img src="https://img.shields.io/badge/JDK-1.8+-green.svg" alt="Build Status" /> <img src="https://img.shields.io/badge/JDK-1.8+-green.svg" alt="Build Status" />
<img <img
src="https://img.shields.io/badge/Spring%20Cloud-2020-blue.svg" src="https://img.shields.io/badge/Spring%20Cloud-2020-blue.svg"
alt="Coverage Status" alt="Coverage Status"
/> />
<img <img
src="https://img.shields.io/badge/Spring%20Boot-2.5.2.RELEASE-blue.svg" src="https://img.shields.io/badge/Spring%20Boot-2.5.6.RELEASE-blue.svg"
alt="Downloads" alt="Downloads"
/> />
<a href="https://bladex.vip"> <a href="https://bladex.vip">
@ -212,7 +212,24 @@ class Workplace extends PureComponent {
</Row> </Row>
<Row gutter={24}> <Row gutter={24}>
<Card className={styles.card} bordered={false}> <Card className={styles.card} bordered={false}>
<Collapse bordered={false} defaultActiveKey={['26']}> <Collapse bordered={false} defaultActiveKey={['27']}>
<Panel header="3.2.0发布 新增灵活数据权限特性" key="27">
<div>1.升级 SpringBoot 2.5.6</div>
<div>2.升级 SpringBootAdmin 2.5.3</div>
<div>3.升级 SpringCloud 2020.0.4</div>
<div>4.升级 Nacos 2.0.3</div>
<div>5.升级 Knife4j 2.0.9</div>
<div>6.升级 Mybatis-Plus 3.4.3.4</div>
<div>7.新增注解配置数据权限特性</div>
<div>8.新增Web在线配置数据权限特性</div>
<div>9.新增自定义Sql配置数据权限特性</div>
<div>10.新增懒加载表格树特性</div>
<div>11.新增部门管理祖级节点字段</div>
<div>12.新增CacheUtil工具类</div>
<div>13.优化部门管理新增逻辑</div>
<div>14.优化租户拦截器初始化逻辑</div>
<div>15.优化适配各新版本API变动</div>
</Panel>
<Panel header="3.1.0发布 底层架构升级适配" key="26"> <Panel header="3.1.0发布 底层架构升级适配" key="26">
<div>1.升级 SpringBoot 2.5.2</div> <div>1.升级 SpringBoot 2.5.2</div>
<div>2.升级 SpringBootAdmin 2.4.2</div> <div>2.升级 SpringBootAdmin 2.4.2</div>

View File

@ -16,6 +16,10 @@ export async function list(params) {
return request(`/api/blade-system/menu/list?${stringify(params)}`); return request(`/api/blade-system/menu/list?${stringify(params)}`);
} }
export async function parentList(params) {
return request(`/api/blade-system/menu/menu-list?${stringify(params)}`);
}
export async function tree(params) { export async function tree(params) {
return request(`/api/blade-system/menu/tree?${stringify(params)}`); return request(`/api/blade-system/menu/tree?${stringify(params)}`);
} }
@ -49,3 +53,29 @@ export async function detail(params) {
export async function routesAuthority() { export async function routesAuthority() {
return request('/api/blade-system/menu/auth-routes'); return request('/api/blade-system/menu/auth-routes');
} }
export async function dataScopeList(params) {
return request(`/api/blade-system/data-scope/list?${stringify(params)}`);
}
export async function removeDataScope(params) {
return request('/api/blade-system/data-scope/remove', {
method: 'POST',
body: func.toFormData(params),
});
}
export async function submitDataScope(params) {
return request('/api/blade-system/data-scope/submit', {
method: 'POST',
body: params,
});
}
export async function scopeDataDetail(params) {
return request(`/api/blade-system/data-scope/detail?${stringify(params)}`);
}
export async function apiScopeList(params) {
return request(`/api/blade-system/api-scope/list?${stringify(params)}`);
}

View File

@ -12,6 +12,10 @@ export async function tree(params) {
return request(`/api/blade-system/role/tree?${stringify(params)}`); return request(`/api/blade-system/role/tree?${stringify(params)}`);
} }
export async function treeById(params) {
return request(`/api/blade-system/role/tree-by-id?${stringify(params)}`);
}
export async function grant(params) { export async function grant(params) {
return request('/api/blade-system/role/grant', { return request('/api/blade-system/role/grant', {
method: 'POST', method: 'POST',