diff --git a/src/components/Base/Tree/index.scss b/src/components/Base/Tree/index.scss index b787aaad..a41a631c 100644 --- a/src/components/Base/Tree/index.scss +++ b/src/components/Base/Tree/index.scss @@ -170,4 +170,7 @@ box-sizing: border-box; } } + &:global(.rc-tree li span.rc-tree-checkbox) { + margin-right: 8px; + } } diff --git a/src/components/Collapse/index.jsx b/src/components/Collapse/index.jsx index 98ecaa23..4fc6b165 100644 --- a/src/components/Collapse/index.jsx +++ b/src/components/Collapse/index.jsx @@ -22,7 +22,7 @@ export default class SingleCollapse extends Component { disabled: false, iconPosition: 'left', iconType: 'chevron-right', - toggleType: 'header', + toggleType: 'icon', onChange: _.noop }; @@ -40,6 +40,7 @@ export default class SingleCollapse extends Component { props.className = styles.cursorPointer; } if (iconType === 'switch') { + props.onChange = this.toggleCheck; return ; } @@ -60,12 +61,13 @@ export default class SingleCollapse extends Component { renderHeader() { const { iconPosition, header, toggleType } = this.props; - const onClick = toggleType === 'header' ? this.toggleCheck : _.noop; + const clickable = toggleType === 'header' || this.state.isCheck; + const onClick = clickable ? this.toggleCheck : _.noop; return (
{iconPosition === 'left' && this.renderIcon()} diff --git a/src/components/Collapse/index.scss b/src/components/Collapse/index.scss index da51e9b7..07ed8e12 100644 --- a/src/components/Collapse/index.scss +++ b/src/components/Collapse/index.scss @@ -28,5 +28,6 @@ .content { opacity: 1; height: auto; + pointer-events: auto; } } diff --git a/src/config/roles.js b/src/config/roles.js index dee74907..d92e783b 100644 --- a/src/config/roles.js +++ b/src/config/roles.js @@ -38,4 +38,6 @@ export const roleToPortal = { user: 'user' }; +export const moduleDataLevels = ['self', 'group', 'all']; + export default roles; diff --git a/src/locales/en/user.json b/src/locales/en/user.json index 22d1eb10..6937898e 100644 --- a/src/locales/en/user.json +++ b/src/locales/en/user.json @@ -1,4 +1,5 @@ { + "ISV_ROLE_CREATE_TIP": "If the above roles do not meet the management requirements, you can", "Global Admin Role description": "Maximum privilege administrator, cannot delete and modify", "DELETE_GROUP_TIP": "TIPS: Organizations with bound users and organizations with children cannot be deleted" } diff --git a/src/locales/zh/user.json b/src/locales/zh/user.json index d139eafb..caa7b9eb 100644 --- a/src/locales/zh/user.json +++ b/src/locales/zh/user.json @@ -25,7 +25,9 @@ "Change password successful": "修改密码成功", "isv": "应用服务商", "user": "用户", + "Permission": "权限", "Set permission": "设置权限", + "Create a role": "创建新角色", "Delete role": "删除角色", "Disable": "禁用", "Search app name or ID": "搜索应用名称或 ID", @@ -47,6 +49,7 @@ "Do you sure to delete groupName": "您确定要删除组织 {{groupName}} 吗?", "Do you sure to delete roleName": "您确定要删除角色 {{roleName}} 吗?", "Do you sure to leaveGroup groupName": "确定为选中的 {{names}} {{count}} 账号离开组织", + "ISV_ROLE_CREATE_TIP": "如果以上角色不能满足管理需求,可以", "DELETE_GROUP_TIP": "提示:删除操作会将所有子级组织以及其绑定的用户关系", "Group name": "组织名称", "Add the child node": "添加下一级", diff --git a/src/portals/admin/pages/Roles/BindingActions/group.jsx b/src/portals/admin/pages/Roles/BindingActions/group.jsx index e1bf1c3d..eb51a9d3 100644 --- a/src/portals/admin/pages/Roles/BindingActions/group.jsx +++ b/src/portals/admin/pages/Roles/BindingActions/group.jsx @@ -1,14 +1,20 @@ -import React, { Component } from 'react'; +import React, { Component, Fragment } from 'react'; import { observer } from 'mobx-react'; import classnames from 'classnames'; import _ from 'lodash'; -import { Tree } from 'components/Base'; +import { Tree, Select } from 'components/Base'; +import { moduleDataLevels } from 'config/roles'; + import styles from './index.scss'; @observer export default class ActionGroup extends Component { + get disabled() { + return _.get(this.props, 'roleStore.handelType') !== 'setBindAction'; + } + renderActionCount() { const { t, data } = this.props; const { selectedActions } = data; @@ -26,53 +32,98 @@ export default class ActionGroup extends Component { } renderTreeTitle = node => { - const { t, roleStore } = this.props; - const { handelType } = roleStore; - const disabled = handelType !== 'setBindAction'; + const { t } = this.props; if (node.title === 'All actions') { return this.renderActionCount(); } - if (disabled) { + if (this.disabled) { return null; } return t(node.title); }; + renderHeader() { + const { data, t, hideHeader } = this.props; + const { name } = data; + if (hideHeader) { + return null; + } + + return ( + + {!_.isEmpty(name) &&

{t(name)}

} +
{t('Operation list')}
+
+ ); + } + + renderDataLevel() { + const { + data, t, roleStore, hideDataLevel + } = this.props; + const { dataLevelMap } = roleStore; + const moduleId = _.first(data.id.split('.')); + if (hideDataLevel) { + return null; + } + + if (this.disabled) { + return ( +
+ {t('Data range')}: + {t(`data_level_${data.data_level}`)} +
+ ); + } + + return ( +
+ {t('Data range')}: + +
+ ); + } + render() { const { - keys, data, index, roleStore, t + keys, data, index, roleStore } = this.props; if (_.isEmpty(data)) { return null; } - const { handelType, selectAction } = roleStore; - const { name, treeData } = data; - const disabled = handelType !== 'setBindAction'; + const { selectAction } = roleStore; + const { treeData } = data; return (
- {!_.isEmpty(name) &&

{t(name)}

} -
{t('Operation list')}
+ {this.renderHeader()} -
- {t('Data range')}: - {t(`data_level_${data.data_level}`)} -
+ {this.renderDataLevel()}
); } diff --git a/src/portals/admin/pages/Roles/BindingActions/index.scss b/src/portals/admin/pages/Roles/BindingActions/index.scss index 94ba02dd..f9c3e96c 100644 --- a/src/portals/admin/pages/Roles/BindingActions/index.scss +++ b/src/portals/admin/pages/Roles/BindingActions/index.scss @@ -74,3 +74,12 @@ display: inline-block; margin-left: 8px; } + +.dataLevel { + display: flex; + align-items: center; +} +.select { + margin-left: 5px; + width: 129px; +} diff --git a/src/portals/admin/pages/Roles/Popover/index.jsx b/src/portals/admin/pages/Roles/Popover/index.jsx index 9f149f84..af40010a 100644 --- a/src/portals/admin/pages/Roles/Popover/index.jsx +++ b/src/portals/admin/pages/Roles/Popover/index.jsx @@ -56,7 +56,9 @@ export default class RolePopover extends Component { renderHandleGroupNode = () => { const { t, roleStore } = this.props; const { role } = roleStore; - const isSystem = role.owner_path === ':system'; + if (!role) return null; + + const isSystem = role.owner === 'system'; return (
diff --git a/src/portals/admin/pages/Roles/RoleTree/index.jsx b/src/portals/admin/pages/Roles/RoleTree/index.jsx index fd57ba73..6a853a7e 100644 --- a/src/portals/admin/pages/Roles/RoleTree/index.jsx +++ b/src/portals/admin/pages/Roles/RoleTree/index.jsx @@ -18,7 +18,7 @@ export default class RoleTree extends Component { ({ portal }) => portal === 'admin' ); const normalRoles = roleStore.roles.filter( - ({ portal, owner_path }) => normalPortal.includes(portal) && owner_path === ':system' + ({ portal, owner }) => normalPortal.includes(portal) && owner === 'system' ); const navData = [ { diff --git a/src/portals/admin/pages/Users/index.jsx b/src/portals/admin/pages/Users/index.jsx index 20b22c79..8411c7d5 100644 --- a/src/portals/admin/pages/Users/index.jsx +++ b/src/portals/admin/pages/Users/index.jsx @@ -108,6 +108,8 @@ export default class Users extends Component { { + const { roleStore } = this.props; + roleStore.changeOpenModuleMap(module.key, isCheck); + }; + + getActionKeys(idx) { + const { selectedActionKeys } = this.props.roleStore; + if (!selectedActionKeys.length) { + return []; + } + + return idx < selectedActionKeys.length ? selectedActionKeys[idx] : []; + } + + renderEditPermission() { + const { roleStore, t } = this.props; + const { moduleTreeData, bindActions } = roleStore; + if (_.isEmpty(bindActions)) { + return null; + } + + const modules = _.get(moduleTreeData, '[0].children', []); + + return ( +
+
{t('Permission')}
+
+ {modules.map((m, index) => ( + this.onChange(m, isCheck)} + iconType="switch" + iconPosition="right" + > +
+ +
+
+ ))} +
+
+ ); + } + + renderModalCreateRole() { + const { modalStore, roleStore, t } = this.props; + const { hide, item } = modalStore; + const { + createISVRole, changeDataLevel, isLoading, dataLevel + } = roleStore; + const { handleType } = item; + const title = handleType === 'edit' ? t('Edit info') : t('Create a role'); + + return ( + + +
+ + +
+
+ + +
+ {handleType === 'edit' && ( + + )} + + {this.renderEditPermission()} +
+ + +
+
+
+ ); + } + + render() { + const { modalStore } = this.props; + const { isOpen, type } = modalStore; + if (!isOpen || !type) { + return null; + } + + if (typeof this[type] === 'function') { + return this[type](); + } + + return null; + } +} diff --git a/src/portals/isv/pages/Roles/Modals/index.scss b/src/portals/isv/pages/Roles/Modals/index.scss new file mode 100644 index 00000000..90a142b8 --- /dev/null +++ b/src/portals/isv/pages/Roles/Modals/index.scss @@ -0,0 +1,57 @@ +@import '~scss/vars'; + +$formLabelWidth: 100px; +$formLabelMarginRight: 20px; + +.form { + min-height: 600px; +} +.fmCtrl { + margin-bottom: 32px; + label { + display: inline-block; + text-align: right; + margin-right: $formLabelMarginRight; + width: $formLabelWidth; + } + + .input { + width: 256px; + } +} + +.fmPermission { + display: flex; + .label { + text-align: right; + margin-right: $formLabelMarginRight; + width: $formLabelWidth; + } + .permissions { + flex: 1; + margin-right: 75px; + } +} + +.roleContainer { + border-left: 0; + border-right: 0; + &:hover { + box-shadow: inset 0 -1px 0 0 $N10, inset 0 1px 0 0 $N10; + background-color: $background-color; + } +} + +.selectCtrl { + display: flex; + align-items: center; + margin-top: 32px; + .select { + width: 130px; + } +} + +.footer { + display: flex; + margin-left: $formLabelWidth + $formLabelMarginRight; +} diff --git a/src/portals/isv/pages/Roles/index.jsx b/src/portals/isv/pages/Roles/index.jsx index 2ca9ebc3..3d8191b5 100644 --- a/src/portals/isv/pages/Roles/index.jsx +++ b/src/portals/isv/pages/Roles/index.jsx @@ -4,21 +4,37 @@ import { translate } from 'react-i18next'; import Layout from 'components/Layout'; import Collapse from 'components/Collapse'; import ModuleFeature from './ModuleFeatures'; +import Modals from './Modals'; import styles from './index.scss'; @translate() @inject(({ rootStore }) => ({ - roleStore: rootStore.roleStore + roleStore: rootStore.roleStore, + modalStore: rootStore.modalStore })) @observer export default class TeamRole extends Component { async componentDidMount() { const { roleStore } = this.props; - await roleStore.fetchAll(); + await roleStore.fetchAll({ + portal: 'isv' + }); } + onChange = (role, isCheck) => { + if (!isCheck) return null; + + const { roleStore } = this.props; + roleStore.fetchRoleModuleName(role.role_id); + }; + + onClick = () => { + this.props.roleStore.initIsv('isv'); + this.props.modalStore.show('renderModalCreateRole'); + }; + renderTitle({ role_name, description }) { return (
@@ -28,15 +44,21 @@ export default class TeamRole extends Component { ); } - onChange = (role, isCheck) => { - if (!isCheck) return null; - - const { roleStore } = this.props; - roleStore.fetchRoleModuleName(role.role_id); - }; + renderCreateTip() { + const { t } = this.props; + return ( +
+ {t('Tips')} + {t('ISV_ROLE_CREATE_TIP')} + + {t('Create a role')} + +
+ ); + } render() { - const { roleStore } = this.props; + const { roleStore, modalStore, t } = this.props; const { roles } = roleStore; return ( ))} + {this.renderCreateTip()} + ); } diff --git a/src/portals/isv/pages/Roles/index.scss b/src/portals/isv/pages/Roles/index.scss index 55e1d6c3..1dfec820 100644 --- a/src/portals/isv/pages/Roles/index.scss +++ b/src/portals/isv/pages/Roles/index.scss @@ -8,3 +8,21 @@ .describtion { color: $N75; } + +.createTip { + margin-top: 12px; + @include normal-font +} +.tips { + @include note-font + border-radius: 1px; + padding: 4px; + margin-right: 4px; + background-color: $N30; +} + +.activeText { + color: $P75; + margin-left: 6px; + cursor: pointer; +} diff --git a/src/stores/user/role.js b/src/stores/user/role.js index 804d3b0a..452ce734 100644 --- a/src/stores/user/role.js +++ b/src/stores/user/role.js @@ -18,6 +18,8 @@ const defaultCheck = { name: 'All' }; +const defaultDataLevel = 'all'; + export default class RoleStore extends Store { @observable roles = []; @@ -45,6 +47,12 @@ export default class RoleStore extends Store { @observable handelType = ''; + @observable dataLevelMap = {}; + + @observable dataLevel = defaultDataLevel; + + @observable openModuleMap = {}; + selectedModuleId = ''; actions = []; @@ -70,9 +78,9 @@ export default class RoleStore extends Store { } @action - async fetchAll() { + async fetchAll(param = {}) { this.isLoading = true; - const result = await this.request.get(`roles`); + const result = await this.request.get(`roles`, param); this.roles = _.get(result, 'role'); this.isLoading = false; } @@ -85,6 +93,7 @@ export default class RoleStore extends Store { }); this.modules = _.get(result, 'role_module.module', []); this.getModuleTreeData(); + this.setDataLevelMap(); this.isLoading = false; } @@ -158,22 +167,24 @@ export default class RoleStore extends Store { this.selectedRoleKeys = []; this.selectedActionKeys = []; Object.assign(this.selectedFeatureModule, defaultCheck); + this.dataLevelMap = {}; + this.dataLevel = defaultDataLevel; + this.openModuleMap = {}; this.handelType = ''; }; @action onSelectRole = async (keys = []) => { if (_.isEmpty(keys)) { - this.selectedRole = {}; - } else { - const roleKey = _.first(keys); - - await this.fetchRoleModule(roleKey); - const roles = _.filter(this.roles, role => role.role_id === roleKey); - this.selectedRole = _.first(roles); - this.onSelectModule(['all']); - this.selectedActionKeys = []; + return null; } + const roleKey = _.first(keys); + + await this.fetchRoleModule(roleKey); + const roles = _.filter(this.roles, role => role.role_id === roleKey); + this.selectedRole = _.first(roles); + this.onSelectModule([KeyFeatureAll]); + this.selectedRoleKeys = keys; this.handelType = ''; }; @@ -181,6 +192,8 @@ export default class RoleStore extends Store { @action selectAction = index => keys => { this.selectedActionKeys[index] = keys; + + this.bindActions[index].selectedActions.selectedCount = keys.filter(a => a.includes('.a')).length; }; getSelectType = (keys, key) => { @@ -270,7 +283,7 @@ export default class RoleStore extends Store { @action getActionTreeData = () => { - const { name, type } = this.selectedFeatureModule; + const { name, id, type } = this.selectedFeatureModule; if (type === KeyFeatureAll) { this.bindActions = this.modules @@ -285,6 +298,7 @@ export default class RoleStore extends Store { }); return { name: module.module_name, + id: module.module_id, data_level: module.data_level, treeData: this.getModuleActionData(), selectedActions @@ -302,6 +316,7 @@ export default class RoleStore extends Store { this.bindActions = [ { name, + id, data_level: moduleItem.data_level, treeData: this.getModuleActionData(), selectedActions @@ -316,6 +331,7 @@ export default class RoleStore extends Store { this.bindActions = [ { name, + id, data_level: moduleItem.data_level, treeData: this.getModuleActionData(), selectedActions @@ -327,6 +343,9 @@ export default class RoleStore extends Store { @action onSelectModule = (keys = []) => { + if (_.isEmpty(keys)) { + return null; + } const key = _.first(keys); this.selectedModuleKeys = keys; const type = this.getSelectType(keys, key); @@ -340,6 +359,7 @@ export default class RoleStore extends Store { } Object.assign(this.selectedFeatureModule, { type: TypeModule, + id: module.module_id, name: module.module_name }); this.selectedModuleId = module.module_id; @@ -352,6 +372,7 @@ export default class RoleStore extends Store { const feature = _.find(module.feature, { feature_id: featureId }); Object.assign(this.selectedFeatureModule, { type: TypeFeature, + id: feature.feature_id, name: feature.feature_name }); @@ -381,7 +402,6 @@ export default class RoleStore extends Store { @action setBindAction = () => { - // this.selectedRoleKeys = [this.createRoleId]; const keys = [this.createRoleId]; this.selectedRoleKeys = keys; this.modal.hide(); @@ -462,11 +482,12 @@ export default class RoleStore extends Store { } }; this.setCheckall(module); + this.setDataLevel(module); const result = await this.request.patch(`roles:module`, data); await sleep(300); if (_.get(result, 'role_module.role_id')) { - this.fetchRoleModule(_.first(this.selectedRoleKeys)); this.onSelectModule([]); + await this.fetchRoleModule(_.first(this.selectedRoleKeys)); } this.isLoading = false; this.setHandleType(''); @@ -480,7 +501,9 @@ export default class RoleStore extends Store { }); const roleId = _.get(result, 'role_id'); if (roleId) { + this.selectedRole = {}; this.fetchAll(); + this.modal.hide(); } this.isLoading = false; @@ -493,4 +516,97 @@ export default class RoleStore extends Store { handleType: 'edit' }); }; + + @action + setDataLevel = modules => { + _.forEach(modules, module => { + const dataLevel = this.dataLevelMap[module.module_id]; + if (dataLevel) { + module.data_level = dataLevel; + } + }); + }; + + @action + setDataLevelMap = () => { + _.forEach(this.modules, module => { + this.dataLevelMap[module.module_id] = module.data_level; + }); + }; + + @action + changeDataLevelMap = (id, dataLevel) => { + this.dataLevelMap[id] = dataLevel; + }; + + @action + initIsv = async () => { + await this.fetchRoleModule('isv'); + this.emptyCheckAction(); + this.onSelectModule([KeyFeatureAll]); + this.setHandleType('setBindAction'); + }; + + @action + emptyCheckAction = () => { + _.forEach(this.modules, module => { + module.is_check_all = false; + _.forEach(module.feature, feature => { + feature.checked_action_id = []; + }); + }); + }; + + @action + createISVRole = async (e, data) => { + this.isLoading = true; + const handleType = !data.role_id ? 'post' : 'patch'; + const result = await this.request[handleType](`roles`, data); + + const roleId = _.get(result, 'role_id'); + if (roleId) { + this.createRoleId = roleId; + + await this.changeISVRoleModule(roleId); + await this.fetchAll({ portal: 'isv' }); + this.modal.hide(); + } + this.isLoading = false; + }; + + @action + changeISVRoleModule = async roleId => { + const module = this.modules + .slice() + .filter(m => this.openModuleMap[m.module_id]) + .sort(sortModule('module_id')) + .map((item, index) => { + item.data_level = this.dataLevel; + item.feature.forEach(f => { + this.getCheckedAction(f, index); + }); + return item; + }); + const data = { + role_module: { + role_id: roleId, + module + } + }; + this.setCheckall(module); + const result = await this.request.patch(`roles:module`, data); + if (_.get(result, 'role_module.role_id')) { + this.onSelectModule([]); + await this.fetchRoleModule(_.first(this.selectedRoleKeys)); + } + }; + + @action + changeDataLevel = dataLevel => { + this.dataLevel = dataLevel; + }; + + changeOpenModuleMap = (id, value) => { + this.openModuleMap[id] = value; + }; }