From e35e6309bee3a50b98eb93049663d6984cb08379 Mon Sep 17 00:00:00 2001 From: sin <2943460818@qq.com> Date: Tue, 12 Mar 2019 22:52:50 +0800 Subject: [PATCH 1/3] =?UTF-8?q?-=20=E5=A2=9E=E5=8A=A0=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98=20=E8=A7=92=E8=89=B2=E5=88=86=E9=85=8D=EF=BC=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin-web/src/models/admin/adminList.js | 54 ++++++++ admin-web/src/models/admin/roleList.js | 66 +-------- admin-web/src/pages/Admin/AdminList.js | 175 +++++++++++++++++++++++- admin-web/src/services/admin.js | 12 ++ admin-web/src/utils/tree.utils.js | 67 +++++++++ 5 files changed, 304 insertions(+), 70 deletions(-) create mode 100644 admin-web/src/utils/tree.utils.js diff --git a/admin-web/src/models/admin/adminList.js b/admin-web/src/models/admin/adminList.js index a21faa0d3..6adf934fe 100644 --- a/admin-web/src/models/admin/adminList.js +++ b/admin-web/src/models/admin/adminList.js @@ -1,11 +1,15 @@ import { message } from 'antd'; +import { buildTreeNode, findCheckedKeys } from '../../utils/tree.utils'; import { addAdmin, updateAdmin, updateAdminStatus, deleteAdmin, queryAdmin, + queryAdminRoleList, + adminRoleAssign, } from '../../services/admin'; +import { arrayToStringParams } from '../../utils/request.qs'; export default { namespace: 'adminList', @@ -15,6 +19,10 @@ export default { count: 0, pageNo: 0, pageSize: 10, + + roleList: [], + roleCheckedKeys: [], + roleAssignLoading: false, }, effects: { @@ -78,6 +86,40 @@ export default { }, }); }, + *queryRoleList({ payload }, { call, put }) { + yield put({ + type: 'changeRoleAssignLoading', + payload: true, + }); + + const response = yield call(queryAdminRoleList, payload); + const roleList = response.data; + const roleTreeData = buildTreeNode(roleList, 'name', 'id'); + const roleCheckedKeys = findCheckedKeys(roleList); + + yield put({ + type: 'querySuccess', + payload: { + roleList: roleTreeData, + roleCheckedKeys, + }, + }); + + yield put({ + type: 'changeRoleAssignLoading', + payload: false, + }); + }, + *roleAssign({ payload }, { call }) { + const params = { + id: payload.id, + roleIds: arrayToStringParams(payload.roleIds), + }; + const response = yield call(adminRoleAssign, params); + if (response.code === 0) { + message.info('操作成功!'); + } + }, }, reducers: { @@ -87,5 +129,17 @@ export default { ...payload, }; }, + changeRoleCheckedKeys(state, { payload }) { + return { + ...state, + roleCheckedKeys: payload, + }; + }, + changeRoleAssignLoading(state, { payload }) { + return { + ...state, + roleAssignLoading: payload, + }; + }, }, }; diff --git a/admin-web/src/models/admin/roleList.js b/admin-web/src/models/admin/roleList.js index 32071b535..fcec9a9b7 100644 --- a/admin-web/src/models/admin/roleList.js +++ b/admin-web/src/models/admin/roleList.js @@ -1,5 +1,6 @@ import { message } from 'antd'; import { arrayToStringParams } from '../../utils/request.qs'; +import { buildTreeNode, findAllNodes, findCheckedKeys } from '../../utils/tree.utils'; import { addRole, updateRole, @@ -9,71 +10,6 @@ import { roleAssignResource, } from '../../services/admin'; -function buildTreeNode(nodes, titleKey, nodeKey) { - return nodes.map(item => { - const res = {}; - if (item.children) { - res.children = buildTreeNode(item.children, titleKey, nodeKey); - } - res.title = `${item.id}-${item[titleKey]}`; - res.key = item[nodeKey]; - return res; - }); -} - -function findNodes(id, nodes) { - const res = []; - for (let i = 0; i < nodes.length; i += 1) { - const node = nodes[i]; - if (node.key === id) { - res.push(node.key); - break; - } else { - const childNodes = findNodes(id, node.children); - if (childNodes.length) { - res.push(node.key); - for (let j = 0; j < childNodes.length; j += 1) { - res.push(childNodes[j]); - } - break; - } - } - } - return res; -} - -function findAllNodes(resourceIds, nodes) { - const findNodesArray = []; - for (let i = 0; i < resourceIds.length; i += 1) { - const findNodesData = findNodes(resourceIds[i], nodes); - if (findNodesData) { - for (let j = 0; j < findNodesData.length; j += 1) { - const jD = findNodesData[j]; - if (findNodesArray.indexOf(jD) === -1) { - findNodesArray.push(jD); - } - } - } - } - return findNodesArray; -} - -function findCheckedKeys(nodes) { - let res = []; - for (let i = 0; i < nodes.length; i += 1) { - const node = nodes[i]; - if (node.children) { - const findChildrenNodes = findCheckedKeys(node.children); - if (findChildrenNodes) { - res = res.concat(findChildrenNodes); - } - } else if (node.assigned === true) { - res.push(node.id); - } - } - return res; -} - export default { namespace: 'roleList', diff --git a/admin-web/src/pages/Admin/AdminList.js b/admin-web/src/pages/Admin/AdminList.js index ab25d77c7..d2da4e2ab 100644 --- a/admin-web/src/pages/Admin/AdminList.js +++ b/admin-web/src/pages/Admin/AdminList.js @@ -2,14 +2,13 @@ import React, { PureComponent, Fragment } from 'react'; import { connect } from 'dva'; -import moment from 'moment'; -import { Card, Form, Input, Select, Button, Modal, message, Table, Divider } from 'antd'; +import { Card, Form, Input, Button, Modal, message, Table, Divider, Tree, Spin } from 'antd'; import PageHeaderWrapper from '@/components/PageHeaderWrapper'; import styles from './AdminList.less'; const FormItem = Form.Item; -const { Option } = Select; +const { TreeNode } = Tree; const status = ['未知', '正常', '禁用']; // 添加 form 表单 @@ -64,6 +63,78 @@ const CreateForm = Form.create()(props => { ); }); +// 角色分配 +const RoleAssignModal = Form.create()(props => { + const { + modalVisible, + form, + handleOk, + handleModalVisible, + treeData, + checkedKeys, + loading, + handleCheckBoxClick, + } = props; + + const renderTreeNodes = data => { + return data.map(item => { + if (item.children) { + return ( + + {renderTreeNodes(item.children)} + + ); + } + return ; + }); + }; + + const renderModalContent = treeData => { + const RenderTreeNodes = renderTreeNodes(treeData); + if (RenderTreeNodes) { + return ( + + {form.getFieldDecorator('name', {})( + + {renderTreeNodes(treeData)} + + )} + + ); + } else { + return null; + } + }; + + const okHandle = () => { + form.validateFields((err, fieldsValue) => { + if (err) return; + form.resetFields(); + handleOk({ + fields: fieldsValue, + }); + }); + }; + + return ( + handleModalVisible()} + > + {renderModalContent(treeData)} + + ); +}); + @connect(({ adminList, loading }) => ({ list: adminList.list, data: adminList, @@ -75,6 +146,9 @@ class ResourceList extends PureComponent { modalVisible: false, modalType: 'add', //add update initValues: {}, + + modalRoleVisible: false, + modalRoleRow: {}, }; componentDidMount() { @@ -160,9 +234,87 @@ class ResourceList extends PureComponent { }); } + handleDelete(row) { + const { dispatch, data } = this.props; + const queryParams = { + pageNo: data.pageNo, + pageSize: data.pageSize, + }; + + Modal.confirm({ + title: `确认删除?`, + content: `${row.username}`, + onOk() { + dispatch({ + type: 'adminList/delete', + payload: { + body: { + id: row.id, + }, + queryParams, + }, + }); + }, + onCancel() {}, + }); + } + + handleRoleAssign = row => { + const { dispatch } = this.props; + this.setState({ + modalRoleVisible: !!true, + modalRoleRow: row, + }); + + dispatch({ + type: 'adminList/queryRoleList', + payload: { + id: row.id, + }, + }); + }; + + handleRoleAssignCheckBoxClick = checkedKeys => { + const { dispatch } = this.props; + const newCheckedKeys = checkedKeys.map(item => { + return parseInt(item); + }); + dispatch({ + type: 'adminList/changeRoleCheckedKeys', + payload: newCheckedKeys, + }); + }; + + handleRoleAssignOK = () => { + const { dispatch, data } = this.props; + const { modalRoleRow } = this.state; + dispatch({ + type: 'adminList/roleAssign', + payload: { + id: modalRoleRow.id, + roleIds: data.roleCheckedKeys, + }, + }); + this.handleRoleAssignModalVisibleClose(false); + }; + + handleRoleAssignModalVisibleClose = fag => { + this.setState({ + modalRoleVisible: !!fag, + }); + }; + render() { - const { list } = this.props; - const { modalVisible, modalType, initValues, defaultExpandAllRows } = this.state; + const { list, data } = this.props; + const { roleList, roleCheckedKeys, roleAssignLoading } = data; + const { + modalVisible, + modalType, + initValues, + defaultExpandAllRows, + modalRoleVisible, + } = this.state; + const parentMethods = { handleAdd: this.handleAdd, handleModalVisible: this.handleModalVisible, @@ -194,12 +346,15 @@ class ResourceList extends PureComponent { }, { title: '操作', + width: 300, render: (text, record) => { const statusText = record.status === 1 ? '确认禁用' : '取消禁用'; return ( this.handleModalVisible(true, 'update', record)}>更新 + this.handleRoleAssign(record)}>角色分配 + this.handleStatus(record)}> {statusText} @@ -235,6 +390,16 @@ class ResourceList extends PureComponent { /> + + this.handleRoleAssignModalVisibleClose(false)} + /> ); } diff --git a/admin-web/src/services/admin.js b/admin-web/src/services/admin.js index deb177f4c..35efff580 100644 --- a/admin-web/src/services/admin.js +++ b/admin-web/src/services/admin.js @@ -43,6 +43,18 @@ export async function deleteAdmin(params) { }); } +export async function queryAdminRoleList(params) { + return request(`/admin-api/admins/admin/role_list?${stringify(params)}`, { + method: 'GET', + }); +} + +export async function adminRoleAssign(params) { + return request(`/admin-api/admins/admin/assign_role?${stringify(params)}`, { + method: 'POST', + }); +} + // resource export async function addResource(params) { diff --git a/admin-web/src/utils/tree.utils.js b/admin-web/src/utils/tree.utils.js new file mode 100644 index 000000000..bb0653ff5 --- /dev/null +++ b/admin-web/src/utils/tree.utils.js @@ -0,0 +1,67 @@ +// tree 工具 + +export function buildTreeNode(nodes, titleKey, nodeKey) { + return nodes.map(item => { + const res = {}; + if (item.children) { + res.children = buildTreeNode(item.children, titleKey, nodeKey); + } + res.title = `${item.id}-${item[titleKey]}`; + res.key = item[nodeKey]; + return res; + }); +} + +// @primary +function findNodes(id, nodes) { + const res = []; + for (let i = 0; i < nodes.length; i += 1) { + const node = nodes[i]; + if (node.key === id) { + res.push(node.key); + break; + } else { + const childNodes = findNodes(id, node.children); + if (childNodes.length) { + res.push(node.key); + for (let j = 0; j < childNodes.length; j += 1) { + res.push(childNodes[j]); + } + break; + } + } + } + return res; +} + +export function findAllNodes(resourceIds, nodes) { + const findNodesArray = []; + for (let i = 0; i < resourceIds.length; i += 1) { + const findNodesData = findNodes(resourceIds[i], nodes); + if (findNodesData) { + for (let j = 0; j < findNodesData.length; j += 1) { + const jD = findNodesData[j]; + if (findNodesArray.indexOf(jD) === -1) { + findNodesArray.push(jD); + } + } + } + } + return findNodesArray; +} + +export function findCheckedKeys(nodes) { + let res = []; + for (let i = 0; i < nodes.length; i += 1) { + const node = nodes[i]; + if (node.children) { + const findChildrenNodes = findCheckedKeys(node.children); + if (findChildrenNodes) { + res = res.concat(findChildrenNodes); + } + } else if (node.assigned === true) { + res.push(node.id); + } + } + return res; +} From 2385f239abf5b67c36d1ad0b905e4dcdf25b609b Mon Sep 17 00:00:00 2001 From: sin <2943460818@qq.com> Date: Tue, 12 Mar 2019 22:53:18 +0800 Subject: [PATCH 2/3] =?UTF-8?q?-=20=E4=BC=98=E5=8C=96=20role=20tree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin-web/src/pages/Admin/RoleList.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/admin-web/src/pages/Admin/RoleList.js b/admin-web/src/pages/Admin/RoleList.js index 99abfcf3d..2a35c29e9 100644 --- a/admin-web/src/pages/Admin/RoleList.js +++ b/admin-web/src/pages/Admin/RoleList.js @@ -21,7 +21,6 @@ import PageHeaderWrapper from '@/components/PageHeaderWrapper'; import styles from './RoleList.less'; const FormItem = Form.Item; -const { Option } = Select; const { TreeNode } = Tree; // 添加 form 表单 @@ -65,7 +64,7 @@ const CreateForm = Form.create()(props => { ); }); -// 添加 form 表单 +// 角色分配 const AssignModal = Form.create()(props => { const { modalVisible, From 8d6cfce8732def24ee53fab6e97c9f206adfcd95 Mon Sep 17 00:00:00 2001 From: sin <2943460818@qq.com> Date: Tue, 12 Mar 2019 22:53:39 +0800 Subject: [PATCH 3/3] =?UTF-8?q?-=20=E4=BF=AE=E6=94=B9=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin-web/src/models/{menu.jsx => menu.js} | 109 ++++++++++++++++----- 1 file changed, 85 insertions(+), 24 deletions(-) rename admin-web/src/models/{menu.jsx => menu.js} (59%) diff --git a/admin-web/src/models/menu.jsx b/admin-web/src/models/menu.js similarity index 59% rename from admin-web/src/models/menu.jsx rename to admin-web/src/models/menu.js index becae1d44..759930e74 100644 --- a/admin-web/src/models/menu.jsx +++ b/admin-web/src/models/menu.js @@ -72,20 +72,87 @@ const filterMenuData = menuData => { .filter(item => item); }; -/** - * 递归构建服务端,配置的菜单 - * @param resultMenuData - */ -const recursionBuildResultMenu = resultMenuData => { - const res = {}; - for (let i = 0; i < resultMenuData.length; i += 1) { - const menuItem = resultMenuData[i]; - // 存在子节点 - res[menuItem.handler] = { - ...menuItem, +// 用于生成uuid +function S4() { + return ((1 + Math.random()) * 0x10000 || 0).toString(16).substring(1); +} +function guid() { + return S4() + S4() + S4() + S4() + S4() + S4() + S4() + S4(); +} + +const findRootMenu = (antDataMenus, rootAntDataMenu, requestDataMenu) => { + let res; + for (let i = 0; i < antDataMenus.length; i += 1) { + const antDataMenu = antDataMenus[i]; + if (antDataMenu.path === requestDataMenu.handler) { + res = rootAntDataMenu; + break; + } + if (antDataMenu.children) { + res = findRootMenu(antDataMenu.children, antDataMenu, requestDataMenu); + break; + } + } + return res; +}; + +const buildTreeMenu = (antMenuData, moveChildrenMenusData, requestDataMenus) => { + return requestDataMenus.map(item => { + if (!item.handler) { + // root 节点 + const uuid = `sms${guid()}`; + const res = { + icon: 'user', + name: item.displayName, + path: uuid, + }; + + // 子节点 + if (item.children) { + // 通过子节点找到对于的父节点,设置 path,没有则是 uuid + const rootMenu = findRootMenu(antMenuData, {}, item.children[0]); + if (rootMenu) { + res.path = rootMenu.path; + } + + // 开始递归构建数据结构 + const childrenMenus = buildTreeMenu(antMenuData, moveChildrenMenusData, item.children); + res.children = childrenMenus; + } + return res; + } + + // moveChildrenMenusData 是一个 map,对比 url 地址是否存在,不存在就给一个 404 的页面 + const handleMapperData = moveChildrenMenusData[item.handler]; + if (handleMapperData) { + return { + ...handleMapperData, + icon: 'user', + name: item.displayName, + path: item.handler, + }; + } + + // 没有就返回404页面 + return moveChildrenMenusData['/exception/404']; + }); +}; + +const moveChildrenMenus = antDataMenus => { + let res = {}; + for (let i = 0; i < antDataMenus.length; i += 1) { + const antDataMenu = antDataMenus[i]; + res[antDataMenu.path] = { + ...res, + ...antDataMenu, }; - if (menuItem.children) { - res[menuItem.handler].children = recursionBuildResultMenu(menuItem.children); + + if (antDataMenu.children) { + const childrenMenus = moveChildrenMenus(antDataMenu.children); + res = { + ...res, + ...childrenMenus, + }; } } return res; @@ -126,21 +193,15 @@ export default { *getMenuData({ payload }, { put, call }) { const { data } = yield call(getAdminMenus); const { routes, authority } = payload; + // authority 已经不适用 const antMenuData = filterMenuData(memoizeOneFormatter(routes, authority)); - let menuData = antMenuData; - const resultMenuData = data; + // const resultMenuData = data; if (data !== 'all') { - // 处理后台数据结构 - const buildResultMenu = recursionBuildResultMenu(resultMenuData); - // 过滤没有权限的菜单 - menuData = antMenuData.filter(item => { - if (buildResultMenu[item.path]) { - return item; - } - return false; - }); + const moveChildrenMenusData = moveChildrenMenus(antMenuData); + const buildTreeMenuData = buildTreeMenu(antMenuData, moveChildrenMenusData, data); + menuData = buildTreeMenuData; } // 生成 menu 和 router mapping