浏览代码

用户组、用户管理/登录跳转逻辑

zhuth 7 年之前
父节点
当前提交
85e0e3ab0c

+ 137 - 0
src/components/admin/addGroupMemberBox.jsx

@@ -0,0 +1,137 @@
+import React from 'react'
+import { Modal, Row, Col, Select, Spin } from 'antd'
+import { connect } from 'dva'
+import * as service from '../../services/index'
+import URLS from '../../constants/url'
+const SelectOption = Select.Option
+
+class AddGroupMemberBox extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.state = {
+            selectedUser: null,
+            userData: [],
+            fetching: false
+        }
+    }
+
+    okHandler = () => {
+        const { userGroup, dispatch } = this.props;
+        const { selectedGroup } = userGroup;
+        const { selectedUser } = this.state;
+
+        dispatch({ type: 'userGroup/remoteMemberAdd', user: selectedUser, group: selectedGroup });
+    }
+
+    hideBox = () => {
+        const { dispatch } = this.props;
+        dispatch({ type: 'userGroup/setNewModelField', name: 'visibleAddMemberBox', value: false });
+    }
+
+    fetchUserData = (keyword) => {
+        // this.setState({ userData: [], fetching: true }, () => {
+        //     setTimeout(() => {
+        //         this.setState({
+        //             userData: dataSource.filter(d => d.name.indexOf(keyword)!==-1),
+        //             fetching: false
+        //         })
+        //     }, 500);
+        // });
+        this.setState({ userData: [], fetching: true }, () => {
+            const body = {
+            };
+            service.fetch({
+                url: URLS.USER_LIST,
+                body: body,
+            }).then(r => {
+                if(!r.err && r.data.code > 0) {
+                    return r;
+                }else {
+                    let obj = {};
+                    throw obj;
+                }
+            }).then(r => {
+                console.log('获得用户数据', body, r);
+                const resData = r.data.data || [];
+                this.setState({
+                    userData: resData.map(d => ({
+                        code: d.id,
+                        name: d.name,
+                        account: d.userName,
+                        password: d.passWord,
+                        role: d.role,
+                        department: d.department,
+                        post: d.post,
+                    })),
+                    fetching: false
+                });
+            }).catch(ex => {
+                this.setState({
+                    userData: [],
+                    fetching: false
+                });
+                console.error('fetch error', ex);
+            });
+        });
+    }
+
+    render() {
+        const { userGroup} = this.props;
+        const { userData, fetching } = this.state;
+        const { newOne } = userGroup;
+
+        return (
+            <Modal
+                className='addmember-box'
+                title='添加用户组成员'
+                visible={newOne.visibleAddMemberBox}
+                onOk={this.okHandler}
+                onCancel={this.hideBox}
+                maskClosable={false}
+                destroyOnClose={true}
+            >
+                <Row type='flex'>
+                    <Col span={24}>
+                        <Select
+                            style={{ width: '100%' }}
+                            // mode='multiple'
+                            showSearch
+                            filterOption={false}
+                            labelInValue={true}
+                            notFoundContent={fetching ? <Spin size="small" /> : '无'}
+                            onSearch={(value) => {
+                                const timeout = this.state.timeout;
+                                timeout && window.clearTimeout(timeout);
+                                this.setState({
+                                    timeout: window.setTimeout(() => {
+                                        this.fetchUserData(value)
+                                    }, 500)
+                                });
+                            }}
+                            onChange={(value) => {
+                                console.log(value);
+                                this.setState({
+                                    selectedUser: {
+                                        code: value.key,
+                                        name: value.label
+                                    }
+                                });
+                            }}
+                        >
+                            { userData.map((s, i) => {
+                                return <SelectOption key={i} value={s.code}>{s.name}</SelectOption>
+                            }) }
+                        </Select>
+                    </Col>
+                </Row>
+                {/* <Table
+                    columns={columns}
+                    dataSource={dataSource}
+                /> */}
+            </Modal>
+        );
+    }
+}
+
+export default connect(({ present: { user, userGroup } }) => ({ user, userGroup }))(AddGroupMemberBox);

+ 9 - 8
src/components/admin/admin.jsx

@@ -1,7 +1,8 @@
 import React from 'react'
 import { Tabs } from 'antd'
-import UserManagement from './userManagement';
-import UserGroupManagement from './userGroupManagement';
+import UserManagement from './userManagement'
+import UserGroupManagement from './userGroupManagement'
+import './admin.less'
 const { TabPane } = Tabs
 
 class Admin extends React.Component {
@@ -15,15 +16,15 @@ class Admin extends React.Component {
         return (      
             <Tabs 
                 className='admin-tabs'
-                type="card"
-                defaultActiveKey="1"
+                tabPosition='left'
+                defaultActiveKey="userGroup"
             >
-                <TabPane tab="用户管理" key="1" >
-                    <UserManagement/>
-                </TabPane>
-                <TabPane tab="用户组管理" key="2" >
+                <TabPane tab="用户分组" key="userGroup" >
                     <UserGroupManagement />
                 </TabPane>
+                <TabPane tab="用户管理" key="user" >
+                    <UserManagement/>
+                </TabPane>
             </Tabs>
         )
 

+ 13 - 0
src/components/admin/admin.less

@@ -0,0 +1,13 @@
+.admin-tabs {
+    height: 100%;
+    .ant-tabs-bar {
+        margin: 0;
+    }
+    .ant-tabs-content {
+        height: 100%;
+        border: none !important;
+        .ant-tabs-tabpane {
+            height: 100%;
+        }
+    }
+}

+ 68 - 0
src/components/admin/userGroupDetailBox.jsx

@@ -0,0 +1,68 @@
+import React from 'react'
+import { Modal, Form, Input } from 'antd'
+import { connect } from 'dva'
+const FormItem = Form.Item
+
+class DetailBox extends React.Component {
+
+    okHandler = () => {
+        const { dispatch, userGroup} = this.props;
+        const { newOne } = userGroup;
+        console.log(newOne.operate);
+        if(newOne.operate === 'create') {
+            dispatch({ type: 'userGroup/remoteAdd' });
+        }else if(newOne.operate === 'modify') {
+            dispatch({ type: 'userGroup/remoteModify' });
+        }
+    }
+
+    hideBox = () => {
+        const { dispatch} = this.props;
+        dispatch({ type: 'userGroup/resetNewModel' });
+    }
+
+    render() {
+        const { dispatch, userGroup } = this.props;
+        const { newOne } = userGroup;
+
+        const formItemLayout = {
+            labelCol: { span: 4 },
+            wrapperCol: { span: 20 },
+        };
+        return (
+            <Modal
+                className='dataconnect-box'
+                title={`${newOne.operate === 'create' ? '新建' : '修改'}用户组`}
+                visible={newOne.visibleDetailBox}
+                onOk={this.okHandler}
+                onCancel={this.hideBox}
+                maskClosable={false}
+                destroyOnClose={true}
+            >
+                <Form size='small'>
+                    <FormItem label='用户组名' {...formItemLayout} >
+                        <Input
+                            placeholder="请输入用户组名称"
+                            value={newOne.name}
+                            onChange={(e) => {
+                                dispatch({ type: 'userGroup/setNewModelField', name: 'name', value: e.target.value });
+                            }}
+                        >
+                        </Input>
+                    </FormItem>
+                    <FormItem className='textarea-desc' label='描述' {...formItemLayout}>
+                        <Input.TextArea
+                            autosize={{ minRows: 2 }}
+                            value={newOne.description}
+                            onChange={(e) => {
+                                dispatch({ type: 'userGroup/setNewModelField', name: 'description', value: e.target.value });
+                            }}
+                        />
+                    </FormItem>
+                </Form>
+            </Modal>
+        )
+    }
+}
+
+export default connect(( { present: { userGroup } } ) => ({ userGroup }))(DetailBox)

+ 130 - 45
src/components/admin/userGroupManagement.jsx

@@ -1,59 +1,144 @@
+/**
+ * 用户分组
+ */
 import React from 'react';
-import { Table, Button } from 'antd'
-
-const columns = [{
-    title: '用户组名',
-    dataIndex: 'userGroupName',
-}, {
-    title: '创建时间',
-    dataIndex: 'createTime'
-}, {
-    title: '创建人',
-    dataIndex: 'createBy'
-}, {
-    title: '描述',
-    dataIndex: 'description'
-}, {
-    title: '操作',
-    dataIndex: 'operation',
-    render: () => { return <span>查看组成员  删除用户组</span>}
-}];
-
-const data = [{
-    key: 1,
-    userGroupName: 'UAS开发组',
-    createTime: '2018-08-21',
-    createBy: 'xiaoct',
-    description: '就是UAS开发组啦!'
-}]
+import { Layout, Input, Table, Button, Icon, Menu } from 'antd'
+import { connect } from 'dva'
+import './userGroupManagement.less'
+import DetailBox from './userGroupDetailBox'
+import AddMemberBox from './addGroupMemberBox'
+const { Sider, Content } = Layout
+const { Search } = Input
 
 class UserGroupManagement extends React.Component {
-    constructor(props) {
-        super(props);
-        this.state = {
-            selectedRowKeys: []
 
+    componentDidMount() {
+        const { dispatch } = this.props;
+        dispatch({ type: 'userGroup/fetchList' });
+    }
+
+    onSort(list) {
+        return list.sort((a, b) => {
+            return new Date(b.createTime) - new Date(a.createTime);
+        });
+    }
+
+    showDetailBox = (operate) => {
+        const { userGroup, dispatch } = this.props;
+        const { selectedGroup } = userGroup;
+
+        if(operate === 'create') {
+            dispatch({ type: 'userGroup/setNewModel', model: {
+                visibleDetailBox: true,
+                operate
+            }});
+        }else {
+            dispatch({ type: 'userGroup/setNewModel', model: { ...selectedGroup, visibleDetailBox: true, operate } });
         }
     }
 
-    onSelectChange = (selectedRowKeys) => {
-        console.log('selectedRowKeys changed: ', selectedRowKeys);
-        this.setState({ selectedRowKeys });
+    showAddMemberBox = () => {
+        const { dispatch } = this.props;
+        dispatch({ type: 'userGroup/setNewModelField', name: 'visibleAddMemberBox', value: true });
     }
 
     render() {
-        const { selectedRowKeys } = this.state;
-        const rowSelection = {
-            selectedRowKeys,
-            onChange: this.onSelectChange,
-        };
-        const hasSelected = selectedRowKeys.length > 0;
+        const { userGroup, dispatch } = this.props;
+        const { selectedGroup } = userGroup;
+
+        const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
+        let filterLabel = (userGroup.filterLabel || '').replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
+
+        const columns = [{
+            title: '用户',
+            dataIndex: 'name',
+            width: 200
+        }, {
+            title: '操作',
+            width: 50,
+            render: (text, record, index) => (
+                <span style={{ cursor: 'pointer', color: 'red' }} onClick={() => {
+                    dispatch({ type: 'userGroup/remoteMemberDelete', userCode: record.code, groupCode: selectedGroup.code });
+                }}>删除</span>
+            ),
+        }]
+        const selectedKeys = selectedGroup ? [selectedGroup.code] : [];
         return (
-            <div>
-                <Table rowSelection={rowSelection} columns={columns} dataSource={data}/>
-            </div>
-        );
+            <Layout className='group-layout'>
+                <Sider>
+                    <div className='search-area'>
+                        <Search
+                            value={userGroup.filterLabel}
+                            placeholder="请输入关键字"
+                            onChange={e => {
+                                dispatch({ type: 'userGroup/setFilterLabel', label: e.target.value });
+                            }}
+                        />
+                    </div>
+                    <Menu
+                        selectedKeys={selectedKeys}
+                    >
+                        {userGroup.list.map(l => {
+                           let reg = new RegExp('(' + filterLabel + '){1}', 'ig');
+                           if((l.name || '').search(reg) !== -1) {
+                               return { ...l, visible: true };
+                           }else {
+                               return { ...l, visible: false };
+                           }
+                        }).map(l => (
+                            <Menu.Item key={l.code+''} style={{ display: l.visible ? 'block' : 'none' }} onClick={() => {
+                                dispatch({ type: 'userGroup/chageSelectedGroup', group: l });
+                            }} >
+                                <span>
+                                    { filterLabel ?
+                                        ((l.name || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
+                                            return (
+                                                fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
+                                                <span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
+                                                fragment
+                                            )
+                                        }
+                                        )) : l.name
+                                    }
+                                </span>
+                            </Menu.Item>
+                        ))}
+                    </Menu>
+                    <div className='addbtn-area'>
+                        <Button className='add-btn' onClick={() => {this.showDetailBox('create')}}>
+                            <Icon type="plus-circle-o" />添加用户组
+                        </Button>
+                        <DetailBox />
+                    </div>
+                </Sider>
+                <Content>
+                    <div className='groupname'>
+                        <span>{selectedGroup ? selectedGroup.name : ''}</span>
+                        {selectedGroup && <Button className='delete-btn' onClick={() => {
+                            dispatch({ type: 'userGroup/remoteDelete', group: selectedGroup });
+                        }}>
+                            <Icon type="delete" />删除用户组
+                        </Button>}
+                        {selectedGroup && <Button className='edit-btn' onClick={() => {this.showDetailBox('modify')}}>
+                            <Icon type="edit" />编辑用户组
+                        </Button>}
+                    </div>
+                    <Table
+                        className='member-table'
+                        columns={columns}
+                        dataSource={selectedGroup ? (selectedGroup.member.map((d, i) => ({...d, key: i})) || []) : []}
+                        size='small'
+                    />
+                    <div className='addbtn-area'>
+                        {selectedGroup && <Button className='add-btn' onClick={() => {this.showAddMemberBox()}}>
+                            <Icon type="plus-circle-o" />添加用户
+                        </Button>}
+                        <AddMemberBox />
+                    </div>
+                </Content>
+            </Layout>
+        )
     }
 }
 
-export default UserGroupManagement
+export default connect(( { present: { userGroup } } ) => ({ userGroup }))(UserGroupManagement);

+ 55 - 0
src/components/admin/userGroupManagement.less

@@ -0,0 +1,55 @@
+.group-layout {
+    height: 100%;
+    background: white;
+    padding: 5px;
+    border: 1px solid #e8e8e8;
+    .ant-layout-sider {
+        background: white;
+        .ant-layout-sider-children {
+            height: 100%;
+            border-right: 1px solid #e8e8e8;
+            padding: 37px 5px;
+            .search-area {
+                margin-top: -32px;
+            }
+            .ant-menu {
+                border-right: none;
+                overflow-y: auto;
+                height: 100%;
+                border-right: none;
+            }
+        }
+    }
+    .ant-layout-content {
+        padding: 40px 5px 37px 5px;
+        overflow: hidden;
+        .groupname {
+            height: 40px;
+            margin-top: -40px;
+            line-height: 35px;
+            padding-left: 20px;
+            .edit-btn {
+                float: right;
+            }
+            .delete-btn {
+                float: right;
+            }
+        }
+        .member-table {
+            height: 100%;
+            .ant-spin-nested-loading {
+                height: 100%;
+                .ant-spin-container {
+                    height: 100%;
+                    padding-bottom: 6px;
+                    .ant-table {
+                        height: 100%;
+                    }
+                    .ant-pagination {
+                        margin: 9px 0;
+                    }
+                }
+            }
+        }
+    }
+}

+ 100 - 69
src/components/admin/userManagement.jsx

@@ -1,85 +1,116 @@
-import React from 'react';
-import { Table, Button, Tag } from 'antd'
-
-
-const columns = [{
-    title: '用户名',
-    dataIndex: 'username',
-    width: '150px',
-    render: (username, record) => {
-        return (<span>{username}<Tag style={{display: record.isAdmin ? 'inline' : 'none' }}color="magenta">管理员</Tag></span>)}
-}, {
-    title: '姓名',
-    dataIndex: 'fullname',
-}, {
-    title: '邮箱',
-    dataIndex: 'email',
-}, {
-    title: '部门',
-    dataIndex: 'department',
-}, {
-    title: '岗位',
-    dataIndex: 'position',
-}, {
-    title: '用户组',
-    dataIndex: 'userGroup',
-    render: userGroup => userGroup.map(item => <Tag color="magenta">{item}</Tag> )
-}, {
-    title: '操作',
-    dataIndex: 'operation',
-    render: () => { return <span>设置用户组  设置为管理员</span>}
-}];
-
-const data = [{
-    key: 1,
-    username: 'xiaoct',
-    fullname: '肖春腾',
-    email: 'xiaoct@usoftchina.com',
-    department: 'UAS开发部',
-    position: '软件工程师',
-    isAdmin: true,
-    userGroup: ['BI项目组', '产品组']
-}, ]
-
+import React from 'react'
+import { Layout, Card, Row, Col, Table, Dropdown, Icon, Menu, Input } from 'antd'
+import { connect } from 'dva'
+import './userManagement.less'
+const { Content } = Layout
+const { Search } = Input
 
 class UserManagement extends React.Component {
     constructor(props) {
         super(props);
         this.state = {
-            selectedRowKeys: []
-
         }
     }
 
-    onSelectChange = (selectedRowKeys) => {
-        console.log('selectedRowKeys changed: ', selectedRowKeys);
-        this.setState({ selectedRowKeys });
+    componentDidMount() {
+        const { dispatch } = this.props;
+        dispatch({ type: 'user/fetchList' });
     }
 
     render() {
-        const { selectedRowKeys } = this.state;
-        const rowSelection = {
-            selectedRowKeys,
-            onChange: this.onSelectChange,
-        };
-        const hasSelected = selectedRowKeys.length > 0;
+        const { user, dispatch } = this.props;
+
+        const moreOperatingMenu = (
+            <Menu className='operationmenu' visible={true}>
+                <Menu.Item
+                    onClick={() => {
+                    }}
+                >
+                    <Icon type="key" />设为管理员
+                </Menu.Item>
+            </Menu>
+        );
+
+        const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
+        let filterLabel = (user.filterLabel || '').replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
+        
+        const columns = [{
+            title: '姓名',
+            dataIndex: 'fullName',
+            width: 100,
+            render: (text, record, index) => (
+                <span>
+                    { filterLabel ?
+                        ((text || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
+                            return (
+                                fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
+                                <span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
+                                fragment
+                            )
+                        }
+                        )) : text
+                    }
+                </span>
+            )
+        }, {
+            title: '角色',
+            dataIndex: 'role',
+            width: 100,
+        }, {
+            title: '操作',
+            render: (text, record, index) => (
+                <Dropdown code={record.code} overlay={moreOperatingMenu} trigger={['click']} >
+                    <Icon type="setting" />
+                </Dropdown>
+            ),
+            width: 50
+        }];
+
         return (
-            <div>
-                <Table rowSelection={rowSelection} columns={columns} dataSource={data} title={() =>
-                    <div>
-                        <Button
-                            type="primary"
-                            // onClick={this.start}
-                            disabled={!hasSelected}
-                        // loading={loading}
-                        >
-                            设置用户组
-                        </Button>
-                    </div>    
-                }/>
-            </div>
+            <Layout className='usermanagement-view'>
+                <Content>
+                    <Card className='usermanagement-body' title={
+                        <Row className='usermanagement-tools' type='flex' justify='space-between'>
+                            <Col style={{ display: 'flex' }}>
+                            </Col>
+                            <Col className='search'>
+                                <Col>
+                                    <Search
+                                        placeholder="请输入关键字"
+                                        value={user.filterLabel}
+                                        onChange={e => {
+                                            dispatch({ type: 'user/setFilterLabel', label: e.target.value });
+                                        }}
+                                    />
+                                </Col>
+                            </Col>
+                        </Row>
+                    }>
+                        <Table
+                            className='usermanagement-table usermanagement-table'
+                            columns={columns}
+                            dataSource={user.list.filter(l => {
+                                let reg = new RegExp('(' + filterLabel + '){1}', 'ig');
+                                return (l.fullName || '').search(reg) !== -1;
+                            })}
+                            size='small'
+                            onRow={(record) => {
+                                return {
+                                    onClick: () => {
+                                        dispatch({ type: 'user/setNewModelFields', fields: [
+                                            { name: 'code', value: record.code },
+                                            { name: 'name', value: record.name },
+                                            { name: 'description', value: record.description }
+                                        ] });
+                                    }
+                                }
+                            }}
+                        />
+                    </Card>
+                </Content>
+            </Layout>
         );
     }
 }
 
-export default UserManagement
+export default connect(({ present: { user } }) => ({ user }))(UserManagement);

+ 179 - 0
src/components/admin/userManagement.less

@@ -0,0 +1,179 @@
+.usermanagement-view {
+    .usermanagement-body {
+        padding: 0;
+        .ant-card-head {
+            padding: 0 10px;
+            .usermanagement-tools {
+                .anticon-bars {
+                    cursor: pointer;
+                    line-height: 1.6;
+                    font-size: 20px;
+                    margin-right: 6px;
+                }
+                .group {
+                    line-height: 2.1;
+                    .ant-breadcrumb-link {
+                        .ant-tag {
+                            margin: 0;
+                        }
+                    }
+                }
+                .search {
+                    display: flex;
+                    > div:first-child {
+                        margin-right: 5px;
+                    }
+                }
+            }
+        }
+    }
+    .ant-card-body {
+        padding: 0;
+        .usermanagement-table{
+            .ant-table {
+                .ant-table-body {
+                    margin-top: 17px;
+                    overflow-y: auto !important;
+                    table {
+                        padding: 0;
+                        .ant-table-row {
+                            td {
+                                padding: 8px;
+                                .usermanagement-name {
+                                    display: flex;
+                                    .usermanagement-type {
+                                        width: 20px;
+                                        height: 20px;
+                                        background-size: cover;
+                                        background-repeat: no-repeat;
+                                        background-image: url('https://test-feapp.oss-cn-beijing.aliyuncs.com/feapp/s70f_180613_fix_a_t/images/trdservices/44_2.png');
+                                    }
+                                    .type-oracle {
+                                        background-position: 0 -731px;
+                                    }
+                                }
+                                .usermanagement-tag {
+                                    margin: 2px;
+                                    cursor: default;
+                                }
+                                .ant-dropdown-trigger {
+                                    font-size: 18px;
+                                    cursor: pointer;
+                                }
+                            }
+                            .action-col {
+                                display: flex;
+                                .operation {
+                                    cursor: pointer;
+                                }
+                                .operation:hover {
+                                    color: #40a9ff;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+.popover-group {
+    width: 300px;
+    .grouptree-title {
+        .create-group {
+            cursor: pointer;
+            color: #40a9ff;
+        }
+    }
+    .ant-popover-inner-content {
+        cursor: default;
+        max-height: 60vh;
+        overflow: auto;
+    }
+    .tree-group {
+        li.drag-over {
+            input {
+                background-color: #40a9ff;
+            }
+        }
+        li {
+            .ant-tree-node-content-wrapper {
+                width: 90%;
+                height: 28px;
+                margin: 0 !important;
+                padding: 0;
+                background-color: transparent;
+
+                :hover {
+                    background-color: transparent !important;
+                }
+                .ant-tree-node-selected {
+                    background-color: transparent !important;
+                }
+                input {
+                    max-width: 180px;
+                    border: none;
+                }
+                .anticon-plus-circle-o {
+                    margin-left: 5px;
+                }
+                .anticon-minus-circle {
+                    margin-left: 5px;
+                }
+            }
+            .drag-over {
+                span[draggable] {
+                    opacity: .7;
+                }
+            }
+        }
+    }
+}
+.tree-group li.drag-over > span[draggable] {
+    opacity: .5;
+}
+
+.operationmenu {
+    padding: 0;
+    width: 120px;
+    .ant-dropdown-menu-item {
+        .anticon {
+            margin-right: 6px;
+        }
+    }
+    .ant-dropdown-menu-item-divider {
+        margin: 0;
+    }
+    .setgroupmenu {
+        .ant-dropdown-menu-submenu-title {
+            display: flex;
+        }
+    }
+}
+
+.ant-table-body::-webkit-scrollbar {/*滚动条整体样式*/
+    width: 6px;     /*高宽分别对应横竖滚动条的尺寸*/
+    height: 4px;
+}
+.ant-table-body::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
+    border-radius: 5px;
+    box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
+    background: rgba(0,0,0,0.2);
+}
+.ant-table-body::-webkit-scrollbar-track {/*滚动条里面轨道*/
+    box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
+    border-radius: 0;
+    background: rgba(0,0,0,0.1);
+}
+
+.custom-filter-dropdown {
+    padding: 8px;
+    border-radius: 6px;
+    background: #fff;
+    box-shadow: 0 1px 6px rgba(0, 0, 0, .2);
+  }
+  
+  .custom-filter-dropdown input {
+    width: 130px;
+    margin-right: 8px;
+  }

+ 1 - 1
src/components/chart/list.jsx

@@ -156,7 +156,7 @@ class ChartList extends React.Component {
                                         }
                                         )) : l.description
                                     }
-                                </span>
+                                    </span>
                                 }</Ellipsis>
                             </Row>
                             <Row className='footer' type='flex' justify='end' align='bottom'>

+ 19 - 3
src/components/common/login.jsx

@@ -9,9 +9,16 @@ import './login.less'
 
 const { UserName, Password, Submit } = Login;
 
-function authenticate(token, expireTime, cb) {
+function authenticate(token, expireTime, user, cb) {
     window.localStorage.setItem("token", token);
     window.localStorage.setItem("expireTime", expireTime);
+
+    window.localStorage.setItem("usercode", user.code);
+    window.localStorage.setItem("account", user.account);
+    // window.localStorage.setItem("password", user.password);
+    window.localStorage.setItem("username", user.name);
+    window.localStorage.setItem("userrole", user.role);
+
     setTimeout(cb, 100); // fake async
 }
 
@@ -39,6 +46,7 @@ class LoginComponent extends React.Component {
     }
 
     login = (username, password) => {
+        const { dispatch } = this.props;
         const self = this;
         let body = {
             userName: username,
@@ -60,9 +68,17 @@ class LoginComponent extends React.Component {
         }).then(resData => {
             console.log('登录', body, resData);
             const token = resData.token;
-            // const expireTime = (new Date().getTime()+ 24 * 60 * 60 * 1000 ) + '';
             const expireTime = resData.times;
-            authenticate(token, expireTime, () => {
+            const user = resData.user;
+            const currentUser = {
+                code: user.id,
+                account: user.userName,
+                password: user.passWord,
+                name: user.name,
+                role: user.role
+            };
+            dispatch({ type: 'main/setCurrentUser', user: currentUser });
+            authenticate(token, expireTime, currentUser, () => {
                 this.setState({ redirectToReferrer: true });
             });
         }).catch(ex => {

+ 15 - 8
src/components/common/navigator.jsx

@@ -2,7 +2,6 @@ import React from 'react'
 import { Menu, Icon, Avatar, Dropdown } from 'antd'
 import { Link } from 'react-router-dom'
 import { connect } from 'dva'
-import '../../models/main'
 import './navigator.less'
 
 class Navigator extends React.Component {
@@ -15,15 +14,16 @@ class Navigator extends React.Component {
 
     render() {
         const { main, dispatch } = this.props;
+        const { currentUser, currentPage } = main;
 
-        const userMenu = (
+        const userMenu = currentUser.code ? (
             <Menu>
                 <Menu.Item key="0">
                     <span>账号信息</span>
                 </Menu.Item>
-                <Menu.Item key="1">
+                {currentUser.role === '管理员' && <Menu.Item key="1">
                     <Link to='/admin'><Icon type="setting" />系统管理</Link>
-                </Menu.Item>
+                </Menu.Item>}
                 <Menu.Divider />
                 <Menu.Item key="3" onClick={() => {
                     window.localStorage.removeItem("token");
@@ -31,6 +31,14 @@ class Navigator extends React.Component {
                     dispatch({ type: 'main/redirect', path: '/login' });
                 }}>注销</Menu.Item>
             </Menu>
+        ) : (
+            <Menu>
+                <Menu.Item key="0" onClick={() => {
+                    dispatch({ type: 'main/redirect', path: '/login' });
+                }}>
+                    <span>登录</span>
+                </Menu.Item>
+            </Menu>
         );
 
         return <div className='navigator'>
@@ -39,7 +47,7 @@ class Navigator extends React.Component {
             <div className='navigator-content'>
                 <Menu
                     className='navigator-menu'
-                    selectedKeys={[main.currentPage]}
+                    selectedKeys={[currentPage]}
                     mode="horizontal"
                 >     
                     <Menu.Item className='nav-page' key="home">
@@ -62,9 +70,8 @@ class Navigator extends React.Component {
             <div className='navigator-right'>
                 <Dropdown overlay={userMenu} trigger={['click']}>
                     <div>
-                        <Avatar icon="user" />
-                        <span> User Name </span>
-                    
+                        <Avatar className='user-icon' size="small" icon="user" />
+                        <span className='user-name'>{currentUser.code ? currentUser.name : '未登录'}</span>
                     </div>
                 </Dropdown>
             </div>

+ 9 - 1
src/components/common/navigator.less

@@ -3,7 +3,7 @@
     justify-content: space-between;
     background: white;
     height: 100%;
-    padding-bottom: 3px;
+    padding: 0 20px;
     .navigator-menu {
         border-bottom: none;
         .nav-page {
@@ -39,4 +39,12 @@
             }
         }
     }
+    .navigator-right {
+        .user-icon {
+            margin: 0 5px 2px;
+        }
+        .user-name {
+            cursor: pointer;
+        }
+    }
 }

+ 0 - 1
src/components/datasource/accessConfig.jsx

@@ -1,6 +1,5 @@
 import React from 'react'
 import { connect } from 'dva'
-import '../../models/dataSource'
 import { Table, Input, Col, Button, Switch, Icon, Tag } from 'antd'
 import PolicyRuleBox from './policyRuleBox'
 import AccessObjectBox from './accessObjectBox'

+ 1 - 3
src/components/datasource/dataSource.jsx

@@ -59,9 +59,7 @@ class DataSource extends React.Component {
                 )
             )
         )).map(g => g.code);
-
-        console.log('分组范围', groupFilter);
-
+        
         return list.filter(l => groupFilter.indexOf(l.groupCode+'') !== -1);
     }
 

+ 29 - 7
src/constants/url.js

@@ -4,10 +4,34 @@ const BASE_URL = 'http://192.168.253.189:8081/BI';
 /**后台接口地址 */
 const URLS = {
 
-    /***************************************登录***************************************/
+    /***************************************当前用户***************************************/
     
     LOGIN: BASE_URL + '/login', // 登录
+
+    LOGOUT: BASE_URL + '/logout', // 登出
+
+    /***************************************用户组***************************************/
+    
+    USERGROUP_LIST: BASE_URL + '/getUserGroupList', // 获得用户组列表
+
+    USERGROUP_ADD: BASE_URL + '/createUserGroup', // 添加用户组
+
+    USERGROUP_DELETE: BASE_URL + '/delUserGroup ', // 删除用户组
+
+    USERGROUP_UPDATE: BASE_URL + '/updateUserGroup', // 修改用户组
+
+    USERGROUP_MEMBER_LIST: BASE_URL + '/getUserInGroup', // 获得用户组成员列表
+
+    USERGROUP_MEMBER_ADD: BASE_URL + '/setUserInto', // 添加用户组成员
+
+    USERGROUP_MEMBER_DELETE: BASE_URL + '/delUserInGroup', // 删除用户组成员
     
+    /***************************************用户***************************************/
+
+    USER_LIST: BASE_URL + '/getUserList', // 获取用户列表
+
+    USER_ROLE_SET: BASE_URL + '/', // 设置用户角色
+
     /***************************************数据源***************************************/
 
     DATASOURCE_ADD: BASE_URL + '/inputDataConnector', // 新增数据源
@@ -21,6 +45,8 @@ const URLS = {
     DATASOURCE_DETAIL: BASE_URL + '/getDataConnector', // 获得单个数据源详细数据
 
     DATASOURCE_QUERY_SQLCOLUMNS: BASE_URL + '/implementSql', // 根据sql请求列数据信息
+    
+    DATASOURCE_QUERY_DATACOLUMNS: BASE_URL + '/getColumnData', // 获得数据源下的列
 
     /***************************************数据连接配置***************************************/
 
@@ -44,8 +70,6 @@ const URLS = {
 
     CHART_LIST: BASE_URL + '/getListCharts', // 获得图表列表
 
-    DATASOURCE_QUERY_DATACOLUMNS: BASE_URL + '/getColumnData', // 获得数据源下的列
-
     CHART_QUERY_COLUMNDATA: BASE_URL + '/showScreenData', // 获得列去重之后的数据
 
     CHART_DETAIL: BASE_URL + '/getChartsConfig', // 获得单个图表详细数据
@@ -62,9 +86,7 @@ const URLS = {
 
     CHART_AGGREGATETABLE_OPTION: BASE_URL + '/showPopulation', // 请求总体统计数据
 
-    /***************************************分组***************************************/
-
-    /** 数据源 **/
+    /***************************************数据源分组***************************************/
 
     GROUP_DATASOURCE_LIST: BASE_URL + '/getConnectorGroup', // 获得数据源所有分组/子分组
 
@@ -78,7 +100,7 @@ const URLS = {
 
     GROUP_DATASOURCE_DELETE: BASE_URL + '/delDataConnectorGroup', // 删除数据源分组/子分组
 
-    /** 图表 **/
+    /***************************************图表分组***************************************/
 
     GROUP_CHART_LIST: BASE_URL + '/getChartsGroup', // 获得图表所有分组/子分组
 

+ 2 - 0
src/index.js

@@ -8,6 +8,7 @@ import dataConnect from './models/dataConnect'
 import dashboard from './models/dashboard'
 import chart from './models/chart'
 import dashboardDesigner from './models/dashboardDesigner';
+import userGroup from './models/userGroup'
 import user from './models/user'
 import './utils/baseUtils'
 import './index.less'
@@ -35,6 +36,7 @@ app.model(chart);  // 图表
 app.model(chartDesigner); // 图表设计
 app.model(dashboard);  //报告与看板
 app.model(dashboardDesigner); // 看板设计
+app.model(userGroup); // 用户组
 app.model(user); // 用户
 
 // 4. Router

+ 28 - 2
src/models/main.js

@@ -1,14 +1,40 @@
 import { routerRedux } from 'dva/router'
 import { message } from 'antd'
 
+const code = window.localStorage.getItem('usercode');
+const account = window.localStorage.getItem('account');
+// const password = window.localStorage.getItem('password');
+const name = window.localStorage.getItem('username');
+const role = window.localStorage.getItem('userrole');
+
+// const lastPage = window.localStorage.getItem('lastpage');
+// window.localStorage.removeItem('lastpage');
+
 export default {
     namespace: 'main',
     state: {
-        currentPage: ''
+        currentUser: {
+            code,
+            account,
+            // password,
+            name,
+            role,
+        },
+        currentPage: '',
     },
     reducers: {
         setPage(state, action) {
-            return { state, currentPage: action.page || 'home' };
+            return { ...state, currentPage: action.page || 'home' };
+        },
+        setCurrentUser(state, action) {
+            const { user } = action;
+            return { ...state, currentUser: {
+                code: user.code,
+                account: user.account,
+                // password: user.password,
+                name: user.name,
+                role: user.role
+            } };
         }
     },
     effects: {

+ 40 - 25
src/models/user.js

@@ -1,49 +1,64 @@
 import { message } from 'antd'
+import * as service from '../services/index'
+import URLS from '../constants/url'
 
 export default {
     namespace: 'user',
     state: {
         list: [],
-        currentUser: {}
+        filterLabel: ''
     },
     reducers: {
-        setCurrentUser(state, { action }) {
-            return {
-                ...state,
-                currentUser: action.user
-            };
+        list(state, action) {
+            const { list } = action;
+            return { ...state, list }
         },
+        setFilterLabel(state, action) {
+            const { label } = action;
+            return { ...state, filterLabel: label}
+        }
     },
     effects: {
-        *login (action, { select, call, put }) {
-            // const user = yield select(state => state.present.user);
+        *fetchList(action, { put, call, select }) {
             try {
-                /*
-                let body = {
-                }
                 const res = yield call(service.fetch, {
-                    url: '',
-                    body: body
+                    url: URLS.USER_LIST
                 });
-
-                console.log('登录', body, res);
-
                 if(!res.err && res.data.code > 0) {
+                    const list = res.data.data.map(d => ({
+                        code: d.id,
+                        userName: d.userName,
+                        fullName: d.name,
+                        role: d.role === '管理员' ? '管理员' : '普通用户'
+                    }));
+                    yield put({ type: 'list', list });
                 }else {
+                    message.error('请求用户列表失败');
                 }
-                */
-                const user = {
-                    code: 'u001',
-                    name: 'zhuth',
-                    expireTime: new Date().getTime() + 1 * 60 * 1000,
-                    authority: 'admin',
-                };
-                yield put({ type: 'setCurrentUser', user });
             }catch(e) {
                 console.log(e);
-                message.error('登录失败');
+                message.error('请求用户列表失败');
             }
         },
+        *remoteModify(action, { put, call, select }) {
+            const body = {};
+            try {
+                const res = yield call(service.fetch({
+                    url: URLS.USER_UPDATE,
+                    body
+                }));
+                console.log('修改用户信息', body, res);
+                if(!res.err && res.data.code > 0) {
+                    message.success('修改成功');
+                }else {
+                    console.log(body, res.err || res.data.msg);
+                    message.error('修改失败');
+                }
+            }catch(e) {
+                console.log(body, e);
+                message.error('修改失败');
+            }
+        }
     },
     subscriptions: {
         setup({ dispatch, history }) {

+ 289 - 0
src/models/userGroup.js

@@ -0,0 +1,289 @@
+import { message } from 'antd'
+import * as service from '../services/index'
+import URLS from '../constants/url'
+
+export default {
+    namespace: 'userGroup',
+    state: {
+        list: [],
+        selectedGroup: null,
+        filterLabel: '',
+        newOne: {}
+    },
+    reducers: {
+        list(state, action) {
+            const { list } = action;
+            return { ...state, list };
+        },
+        add(state, action) {
+            const { group } = action;
+            let list = state.list;
+            list.push(group);
+            return Object.assign({}, state, {list});
+        },
+        modify(state, action) {
+            const { group } = action;
+            let list = state.list;
+            list = list.map(l => {
+                if(l.code === group.code) {
+                    l.name = group.name;
+                    l.description = group.description;
+                }
+                return l;
+            });
+            return Object.assign({}, state, {list});
+        },
+        delete(state, action) {
+            const { group } = action;
+            let list = state.list;
+            for(let i = 0; i < list.length; i ++) {
+                if(list[i].code === group.code) {
+                    list.splice(i, 1);
+                    break;
+                }
+            }
+            return Object.assign({}, state, {list});
+        },
+        setMemberList(state, action) {
+            const { groupCode, memberList } = action;
+            let list = state.list;
+            list = list.map(l => {
+                if(l.code === groupCode) {
+                    l.member = memberList
+                }
+                return l;
+            });
+            return Object.assign({}, state, {list});
+        },
+        setFilterLabel(state, action) {
+            const { label } = action;
+            return { ...state, filterLabel: label};
+        },
+        setNewModelField(state, action) {
+            const { name, value } = action;
+            let newOne = state.newOne;
+            newOne[name] = value;
+            return Object.assign({}, state, { newOne });
+        },
+        setNewModelFields(state, action) {
+            const { fields } = action;
+            let newOne = state.newOne;
+            for(let i = 0; i < fields.length; i++) {
+                newOne[fields[i]['name']] = fields[i]['value'];
+            }
+            return Object.assign({}, state, {newOne});
+        },
+        setNewModel(state, action) {
+            const { model } = action;
+            let newOne = Object.assign({}, model);
+            return Object.assign({}, state, {newOne});
+        },
+        resetNewModel(state, action) {
+            return { ...state, newOne: {} };
+        },
+        setSelectedGroup(state, action) {
+            const { group } = action;
+            return { ...state, selectedGroup: group };
+        },
+    },
+    effects: {
+        *fetchList(action, { put, call, select }) {
+            const userGroup = yield select(state => state.present.userGroup);
+            try {
+                if(!action.mandatory && userGroup.list.length > 0) {
+                    return;
+                }
+                const res = yield call(service.fetch, {
+                    url: URLS.USERGROUP_LIST,
+                });
+                console.log('请求用户组列表', res);
+                if(!res.err && res.data.code > 0) {
+                    const list = res.data.data.map(d => ({
+                        code: d.id+'',
+                        name: d.userGroupName,
+                        description: d.userGroupNote,
+                        createTime: new Date(d.createDate),
+                        member: []
+                    }));
+                    yield put({ type: 'list', list: list.sort((a, b) => a.createTime - b.createTime) });
+                    yield put({ type: 'chageSelectedGroup', group: userGroup.selectedGroup || list[0] });
+                }else {
+                    console.log(res.err || res.data.msg);
+                    message.error('请求用户组列表失败:' + res.err || res.data.msg);
+                }
+            }catch(e) {
+                console.log(e);
+                message.error('请求用户组列表失败');
+            }
+        },
+        *remoteAdd(action, { put, call, select }) {
+            try {
+                const userGroup = yield select(state => state.present.userGroup);
+                const { newOne } = userGroup;
+                let body = {
+                    userGroupName: newOne.name,
+                    userGroupNote: newOne.description
+                };
+                const res = yield call(service.fetch, {
+                    url: URLS.USERGROUP_ADD,
+                    body: body
+                });
+                if(!res.err && res.data.code > 0) {
+                    yield put({ type: 'add', group: {
+                        code: res.data.data,
+                        name: newOne.name,
+                        description: newOne.description
+                    } });
+                    yield put({ type: 'setNewModelField', name: 'visibleDetailBox', value: false });
+                    yield put({ type: 'resetNewModel' });
+                    message.success('新增成功');
+                }else {
+                    message.error('新增失败');
+                }
+            }catch(e) {
+                console.log(e);
+                message.error('新增失败');
+            }
+        },
+        *remoteModify(action, { put, call, select }) {
+            const userGroup = yield select(state => state.present.userGroup);
+            const { newOne } = userGroup;
+            const body = {
+                id: newOne.code,
+                userGroupName: newOne.name,
+                userGroupNote: newOne.description
+            };
+            try {
+                const res = yield call(service.fetch, {
+                    url: URLS.USERGROUP_UPDATE,
+                    body
+                });
+                if(!res.err && res.data.code > 0) {
+                    yield put({ type: 'modify', group: newOne });
+                    // yield put({ type: 'setNewModelField', name: 'visibleBox', value: false });
+                    // yield put({ type: 'resetNewModel' });
+                    message.success('修改成功');
+                }else {
+                    message.error('修改失败');
+                }
+            }catch(e) {
+                console.log(body, e);
+                message.error('修改失败');
+            }
+        },
+        *remoteDelete(action, { put, call, select }) {
+            const { group } = action;
+            try {
+                const res = yield call(service.fetch, {
+                    url: URLS.USERGROUP_DELETE,
+                    body: [group.code]
+                });
+                console.log('删除用户组', [group.code], res);
+                if(!res.err && res.data.code > 0) {
+                    yield put({ type: 'delete', group });
+                    message.success('删除成功');
+                }else {
+                    console.log([group.code], res.err || res.data.msg);
+                    message.error('删除失败');
+                }
+            }catch(e) {
+                console.log([group.code], e);
+                message.error('删除失败');
+            }
+        },
+        *chageSelectedGroup(action, { put, call, select }) {
+            const { group } = action;
+            yield put({ type: 'remoteMemberList', groupCode: group.code });
+            const userGroup = yield select(state => state.present.userGroup);
+            const { list } = userGroup;
+            yield put({ type: 'setSelectedGroup', group: list.filter(l => l.code === group.code)[0] });
+        },
+        *remoteMemberList(action, { put, call, select }) {
+            const { groupCode, mandatory } = action;
+            const userGroup = yield select(state => state.present.userGroup);
+            const { list } = userGroup;
+            const group = list.filter(l => l.code === groupCode)[0];
+            if(!mandatory && group && group.member.length > 0) {
+                return;
+            }
+            try {
+                const res = yield call(service.fetch, {
+                    url: URLS.USERGROUP_MEMBER_LIST,
+                    body: groupCode
+                });
+                console.log('请求用户组成员列表', groupCode, res);
+                if(!res.err && res.data.code > 0) {
+                    const memberList = res.data.data.map(d => ({
+                        code: d.id,
+                        name: d.name,
+                        role: d.role
+                    }));
+                    yield put({ type: 'setMemberList', groupCode, memberList });
+                }else {
+                    message.error('请求用户组成员列表失败');
+                }
+            }catch(e) {
+                console.log(groupCode, e);
+                message.error('请求用户组成员列表失败');
+            }
+        },
+        *remoteMemberAdd(action, { put, call, select }) {
+            const { user, group } = action;
+            const body = {
+                userId: user.code,
+                userGroupId: group.code
+            };
+            try {
+                const res = yield call(service.fetch, {
+                    url: URLS.USERGROUP_MEMBER_ADD,
+                    body
+                });
+                console.log('添加用户组成员', body, res);
+                if(!res.err && res.data.code > 0) {
+                    yield put({ type: 'remoteMemberList', groupCode: group.code, mandatory: true });
+                    yield put({ type: 'setNewModelField', name: 'visibleAddMemberBox', value: false });
+                }else {
+                    message.error('添加失败');
+                }
+            }catch(e) {
+                console.log(body, e);
+                message.error('添加失败');
+            }
+        },
+        *remoteMemberDelete(action, { put, call, select }) {
+            const { userCode, groupCode } = action;
+            const body = {
+                userId: userCode,
+                userGroupId: groupCode
+            };
+            try {
+                const res = yield call(service.fetch, {
+                    url: URLS.USERGROUP_MEMBER_DELETE,
+                    body
+                });
+                console.log('删除用户组成员', body, res);
+                if(!res.err && res.data.code > 0) {
+                    yield put({ type: 'remoteMemberList', groupCode, mandatory: true });
+                }else {
+                    message.error('删除失败');
+                }
+            }catch(e) {
+                console.log(body, e);
+                message.error('删除失败');
+            }
+        },
+    },
+    subscriptions: {
+        setup({ dispatch, history }) {
+            message.config({
+                top: 60,
+                duration: 2,
+                maxCount: 3,
+            });
+            return history.listen(({ pathname, query }) => {
+                let page = pathname.match(/\/(\w*)/)[1];
+                dispatch({ type: 'setPage', page });
+            })
+        }
+    }
+};

+ 1 - 1
src/routes/router.js

@@ -1,6 +1,6 @@
 import React from 'react'
 import { LocaleProvider } from 'antd'
-import { Router, Route, Switch, Redirect } from 'dva/router'
+import { Router, Route, Switch } from 'dva/router'
 import RootLayout from '../components/common/rootLayout'
 import Login from '../components/common/login'
 import Register from '../components/common/register'