diff --git a/admin-web/src/models/admin/adminList.js b/admin-web/src/models/admin/adminList.js
index a21faa0d3218015f990e25abb5afca8e5f857f7c..6adf934fe791482425db1f675cdc2e949b903eba 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 32071b535d2e51183722bf641a28506a3d62471e..fcec9a9b7ae19573e9bb7c3dc52647b00cc460a5 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 ab25d77c742b372ed44c0b13b3f2c9a80f8283ae..d2da4e2ab09a8d0b6c78d7c9991ce57e42f7d29c 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 (
+          <TreeNode title={item.title} key={item.key} dataRef={item}>
+            {renderTreeNodes(item.children)}
+          </TreeNode>
+        );
+      }
+      return <TreeNode title={item.title} key={item.key} dataRef={item} />;
+    });
+  };
+
+  const renderModalContent = treeData => {
+    const RenderTreeNodes = renderTreeNodes(treeData);
+    if (RenderTreeNodes) {
+      return (
+        <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="角色">
+          {form.getFieldDecorator('name', {})(
+            <Tree
+              defaultExpandAll={true}
+              checkable={true}
+              multiple={true}
+              checkedKeys={checkedKeys}
+              onCheck={handleCheckBoxClick}
+            >
+              {renderTreeNodes(treeData)}
+            </Tree>
+          )}
+        </FormItem>
+      );
+    } else {
+      return null;
+    }
+  };
+
+  const okHandle = () => {
+    form.validateFields((err, fieldsValue) => {
+      if (err) return;
+      form.resetFields();
+      handleOk({
+        fields: fieldsValue,
+      });
+    });
+  };
+
+  return (
+    <Modal
+      destroyOnClose
+      title="分配角色"
+      visible={modalVisible}
+      onOk={okHandle}
+      onCancel={() => handleModalVisible()}
+    >
+      <Spin spinning={loading}>{renderModalContent(treeData)}</Spin>
+    </Modal>
+  );
+});
+
 @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 (
             <Fragment>
               <a onClick={() => this.handleModalVisible(true, 'update', record)}>æ›´æ–°</a>
               <Divider type="vertical" />
+              <a onClick={() => this.handleRoleAssign(record)}>角色分配</a>
+              <Divider type="vertical" />
               <a className={styles.tableDelete} onClick={() => this.handleStatus(record)}>
                 {statusText}
               </a>
@@ -235,6 +390,16 @@ class ResourceList extends PureComponent {
           />
         </Card>
         <CreateForm {...parentMethods} modalVisible={modalVisible} />
+
+        <RoleAssignModal
+          loading={roleAssignLoading}
+          treeData={roleList}
+          checkedKeys={roleCheckedKeys}
+          handleOk={this.handleRoleAssignOK}
+          modalVisible={modalRoleVisible}
+          handleCheckBoxClick={this.handleRoleAssignCheckBoxClick}
+          handleModalVisible={() => this.handleRoleAssignModalVisibleClose(false)}
+        />
       </PageHeaderWrapper>
     );
   }
diff --git a/admin-web/src/services/admin.js b/admin-web/src/services/admin.js
index deb177f4c16320b207b965217e6fdcc3e97005aa..35efff580661729b678aca4f832b1a8b83810b81 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 0000000000000000000000000000000000000000..bb0653ff5b568831f0ff6e398d9723932c496eb2
--- /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;
+}