|
|
@@ -1,106 +1,273 @@
|
|
|
import React from 'react'
|
|
|
-import { Layout, Input, Select, Spin } from 'antd'
|
|
|
+import { Tabs, Layout, Input, Menu, Table, Checkbox } from 'antd'
|
|
|
import { connect } from 'dva'
|
|
|
-import * as service from '../../services/index'
|
|
|
-import URLS from '../../constants/url'
|
|
|
import './index.less'
|
|
|
-const { Header, Content } = Layout
|
|
|
-const InputGroup = Input.Group
|
|
|
-const Option = Select.Option
|
|
|
+const { Sider, Content } = Layout
|
|
|
+const { Search } = Input
|
|
|
+const { TabPane } = Tabs
|
|
|
|
|
|
class Authority extends React.Component {
|
|
|
- constructor(props) {
|
|
|
- super(props);
|
|
|
- this.state = {
|
|
|
- dataList: [],
|
|
|
- fetching: false,
|
|
|
- };
|
|
|
+
|
|
|
+ componentDidMount() {
|
|
|
+ const { dispatch } = this.props;
|
|
|
+ dispatch({ type: 'userGroup/fetchList' });
|
|
|
+ dispatch({ type: 'user/fetchList' });
|
|
|
+ dispatch({ type: 'dashboard/remoteMenuTree' });
|
|
|
}
|
|
|
|
|
|
- fetchData = (keyword) => {
|
|
|
- this.setState({ dataList: [], fetching: true }, () => {
|
|
|
- let condition = keyword || '';
|
|
|
- service.fetch({
|
|
|
- url: URLS.USER_QUERY,
|
|
|
- method: 'GET',
|
|
|
- body: {
|
|
|
- condition
|
|
|
- },
|
|
|
- }).then(r => {
|
|
|
- if(!r.err && r.data.code > 0) {
|
|
|
- return r;
|
|
|
- }else {
|
|
|
- let obj = {};
|
|
|
- throw obj;
|
|
|
- }
|
|
|
- }).then(r => {
|
|
|
- const resData = r.data.data || [];
|
|
|
- this.setState({
|
|
|
- dataList: 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({
|
|
|
- dataList: [],
|
|
|
- fetching: false
|
|
|
- });
|
|
|
- console.error('fetch error', ex);
|
|
|
- });
|
|
|
- });
|
|
|
+ checkIndeterminate = (treeList) => {
|
|
|
+ const { authority } = this.props;
|
|
|
+ const { dashboardList } = authority;
|
|
|
+ let arr = treeList && treeList.length > 0 ? [ ...treeList ] : null;
|
|
|
+ for(let i = 0;arr && i < arr.length; i++) {
|
|
|
+ let l = arr[i];
|
|
|
+ if(l.type === 'menu') {
|
|
|
+ l.children = this.checkIndeterminate(l.children);
|
|
|
+ let checkedCount = l.children ? l.children.filter(c => c.type === 'dashboard').map(c => c.checked).filter(c => !!c).length : 0;
|
|
|
+ l.indeterminate = l.children ? (checkedCount > 0 && checkedCount < l.children.filter(c => c.type === 'dashboard').length) : false;
|
|
|
+ l.checked = l.children ? checkedCount !== 0 && checkedCount === l.children.filter(c => c.type === 'dashboard').length : false;
|
|
|
+ }else {
|
|
|
+ l.children = null;
|
|
|
+ l.indeterminate = false;
|
|
|
+ l.checked = dashboardList.findIndex(d => d === l.code) > -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return arr;
|
|
|
}
|
|
|
|
|
|
- generateOptions = () => {
|
|
|
- const { dataList } = this.state;
|
|
|
- let arr = dataList.filter(u => u.role !== 'super').map((s, i) => <Option key={s.code} value={s.code}>{s.name}</Option>)
|
|
|
- console.log(arr);
|
|
|
- return arr
|
|
|
+ changeTab = (key) => {
|
|
|
+ const { dispatch } = this.props;
|
|
|
+ dispatch({ type: 'authority/setFields', fields: [
|
|
|
+ { name: 'tabActiveKey', value: key },
|
|
|
+ { name: 'menuSelectedKeys', value: [] },
|
|
|
+ { name: 'dashboardList', value: [] },
|
|
|
+ ] });
|
|
|
}
|
|
|
|
|
|
render() {
|
|
|
- // const { authority, dispatch } = this.props;
|
|
|
- const { fetching } = this.state;
|
|
|
+ const { dashboard, authority, userGroup, user, dispatch } = this.props;
|
|
|
+ const { menuSelectedKeys, tabActiveKey, groupFilterLabel, userFilterLabel, groupLimit, userLimit } = authority;
|
|
|
+ const dashboardTreeList = this.checkIndeterminate(dashboard.menuTree);
|
|
|
return <Layout className='layout-authority'>
|
|
|
- <Header
|
|
|
- theme='light'
|
|
|
- >
|
|
|
- <InputGroup compact>
|
|
|
- <Select
|
|
|
- defaultValue="group"
|
|
|
- >
|
|
|
- <Option value="group">用户组</Option>
|
|
|
- <Option value="user">用户</Option>
|
|
|
- </Select>
|
|
|
- <Select
|
|
|
- notFoundContent={fetching ? <Spin size="small" /> : '无'}
|
|
|
- showSearch
|
|
|
- filterOption={false}
|
|
|
- onSearch={(value) => {
|
|
|
- const timeout = this.timeout;
|
|
|
- timeout && window.clearTimeout(timeout);
|
|
|
- this.timeout = window.setTimeout(() => {
|
|
|
- this.fetchData(value)
|
|
|
- }, 500)
|
|
|
- }}
|
|
|
- onFocus={() => {
|
|
|
- this.fetchData('');
|
|
|
- }}
|
|
|
- >
|
|
|
- { this.generateOptions() }
|
|
|
- </Select>
|
|
|
- </InputGroup>
|
|
|
- </Header>
|
|
|
+ <Sider>
|
|
|
+ <Tabs
|
|
|
+ className='tabs-authority'
|
|
|
+ type='card'
|
|
|
+ tabPosition='top'
|
|
|
+ defaultActiveKey="userGroup"
|
|
|
+ activeKey={tabActiveKey}
|
|
|
+ onChange={this.changeTab}
|
|
|
+ >
|
|
|
+ <TabPane tab="用户组" key="userGroup" >
|
|
|
+ <MenuList
|
|
|
+ refName='group'
|
|
|
+ selectedKeys={menuSelectedKeys}
|
|
|
+ list={userGroup.list}
|
|
|
+ filterLabel={groupFilterLabel}
|
|
|
+ filterLabelChange={(val) => {
|
|
|
+ dispatch({ type: 'authority/setFields', fields: [
|
|
|
+ { name: 'groupLimit', value: 30 },
|
|
|
+ { name: 'groupFilterLabel', value: val },
|
|
|
+ ] })
|
|
|
+ }}
|
|
|
+ displayField='name'
|
|
|
+ valueField='code'
|
|
|
+ onItemClick={(item) => {
|
|
|
+ dispatch({ type: 'authority/setFields', fields: [
|
|
|
+ { name: 'menuSelectedKeys', value: [item.code] },
|
|
|
+ ] })
|
|
|
+ dispatch({ type: 'authority/fetchDashboardTree', dtype: '0', code: item.code });
|
|
|
+ }}
|
|
|
+ limit={groupLimit}
|
|
|
+ onScrollToBottom={() => {
|
|
|
+ window.clearTimeout(this.groupLimitKey);
|
|
|
+ this.groupLimitKey = window.setTimeout(() => {
|
|
|
+ dispatch({ type: 'authority/setFields', fields: [
|
|
|
+ { name: 'groupLimit', value: (groupLimit + 10 > userGroup.list.length ? userGroup.list.length : groupLimit + 10) },
|
|
|
+ ] })
|
|
|
+ }, 500);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </TabPane>
|
|
|
+ <TabPane tab="用户" key="user">
|
|
|
+ <MenuList
|
|
|
+ refName='user'
|
|
|
+ selectedKeys={menuSelectedKeys}
|
|
|
+ list={user.list}
|
|
|
+ filterLabel={userFilterLabel}
|
|
|
+ filterLabelChange={(val) => {
|
|
|
+ dispatch({ type: 'authority/setFields', fields: [
|
|
|
+ { name: 'userLimit', value: 30 },
|
|
|
+ { name: 'userFilterLabel', value: val },
|
|
|
+ ] })
|
|
|
+ }}
|
|
|
+ displayField='fullName'
|
|
|
+ valueField='code'
|
|
|
+ onItemClick={(item) => {
|
|
|
+ dispatch({ type: 'authority/setFields', fields: [
|
|
|
+ { name: 'menuSelectedKeys', value: [item.code] },
|
|
|
+ ] })
|
|
|
+ dispatch({ type: 'authority/fetchDashboardTree', dtype: '1', code: item.code });
|
|
|
+ }}
|
|
|
+ limit={userLimit}
|
|
|
+ onScrollToBottom={() => {
|
|
|
+ window.clearTimeout(this.userLimitKey);
|
|
|
+ this.userLimitKey = window.setTimeout(() => {
|
|
|
+ dispatch({ type: 'authority/setFields', fields: [
|
|
|
+ { name: 'userLimit', value: (userLimit + 10 > user.list.length ? user.list.length : userLimit + 10) },
|
|
|
+ ] })
|
|
|
+ }, 500);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </TabPane>
|
|
|
+ </Tabs>
|
|
|
+ </Sider>
|
|
|
<Content>
|
|
|
- Content
|
|
|
+ <DashBoardTree dataSource={dashboardTreeList} onRowCheckChange={(record, checked) => {
|
|
|
+ if(!!menuSelectedKeys[0]) {
|
|
|
+ if(record.type === 'menu') {
|
|
|
+ let dashboardCodes = record.children.filter(c => c.type === 'dashboard').map(c => c.code)
|
|
|
+ dispatch({ type: 'authority/' + (checked ? 'addAll': 'removeAll'), dtype: tabActiveKey === 'user' ? 1 : 0, code: menuSelectedKeys[0], dashboardCodes });
|
|
|
+ }else {
|
|
|
+ dispatch({ type: 'authority/' + (checked ? 'add': 'remove'), dtype: tabActiveKey === 'user' ? 1 : 0, code: menuSelectedKeys[0], dashboardCode: record.code });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }}/>
|
|
|
</Content>
|
|
|
</Layout>
|
|
|
}
|
|
|
}
|
|
|
-export default connect(({ present: { authority } }) => ({ authority }))(Authority)
|
|
|
+
|
|
|
+class MenuList extends React.Component {
|
|
|
+ componentDidMount() {
|
|
|
+ this.addEvents();
|
|
|
+ }
|
|
|
+
|
|
|
+ addEvents = () => {
|
|
|
+ const { refName } = this.props;
|
|
|
+ let tabs = this['menuList-' + refName];
|
|
|
+ let menu = tabs.getElementsByClassName('ant-menu')[0];
|
|
|
+ menu.removeEventListener('scroll', this.onMenuScroll);
|
|
|
+ menu.addEventListener('scroll', this.onMenuScroll);
|
|
|
+ }
|
|
|
+
|
|
|
+ onMenuScroll = (e) => {
|
|
|
+ const { onScrollToBottom } = this.props;
|
|
|
+ let target = e.target;
|
|
|
+ if(target.scrollHeight - target.offsetHeight === target.scrollTop) { // 滚动到底了
|
|
|
+ typeof onScrollToBottom === 'function' && onScrollToBottom()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ render() {
|
|
|
+ const { refName, selectedKeys, list, filterLabel: pFilterLabel, filterLabelChange, displayField, valueField, onItemClick, limit } = this.props;
|
|
|
+ const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
|
|
|
+ let filterLabel = (pFilterLabel || '').replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
|
|
|
+
|
|
|
+ return <div ref={node => this['menuList-' + refName] = node} className='menu-list'>
|
|
|
+ <div className='search-area'>
|
|
|
+ <Search
|
|
|
+ value={pFilterLabel}
|
|
|
+ placeholder="请输入关键字"
|
|
|
+ onChange={e => {
|
|
|
+ let val = e.target.value + '';
|
|
|
+ typeof filterLabelChange === 'function' && filterLabelChange(val);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <Menu
|
|
|
+ selectedKeys={selectedKeys}
|
|
|
+ >
|
|
|
+ {list.filter(l => {
|
|
|
+ let reg = new RegExp('(' + filterLabel + '){1}', 'ig');
|
|
|
+ return (l[displayField] || '').search(reg) !== -1;
|
|
|
+ }).slice(0, limit).map(l => (
|
|
|
+ <Menu.Item key={l[valueField]+''} onClick={() => {
|
|
|
+ typeof onItemClick === 'function' && onItemClick(l);
|
|
|
+ }} >
|
|
|
+ <div className='item-title'>
|
|
|
+ <span className='label' title={l[displayField]}>
|
|
|
+ { filterLabel ?
|
|
|
+ ((l[displayField] || '').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
|
|
|
+ )
|
|
|
+ }
|
|
|
+ )) : <span>{l[displayField]}</span>
|
|
|
+ }
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </Menu.Item>
|
|
|
+ ))}
|
|
|
+ </Menu>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class DashBoardTree extends React.Component {
|
|
|
+
|
|
|
+ constructor(props) {
|
|
|
+ super(props);
|
|
|
+ this.state = {
|
|
|
+ tableScrollHeight: 0,
|
|
|
+ pageSize: 0,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ componentDidMount() {
|
|
|
+ this.tableSize();
|
|
|
+ window.addEventListener('resize', this.tableSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ componentWillUnmount() {
|
|
|
+ window.removeEventListener('resize', this.tableSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ tableSize = () => {
|
|
|
+ const tableWrapper = document.getElementsByClassName('ant-table-wrapper')[0];
|
|
|
+ const content = tableWrapper.getElementsByClassName('ant-spin-nested-loading')[0];
|
|
|
+ const tableHeader = content.querySelector('thead');
|
|
|
+ const padding = content.getBoundingClientRect().top - tableWrapper.getBoundingClientRect().top;
|
|
|
+ // 容器高度 - padding * 2 - 分页器高度 - 表头高度
|
|
|
+ let tableScrollHeight = tableWrapper.offsetHeight - padding * 2 - 42 - tableHeader.offsetHeight;
|
|
|
+ this.setState({
|
|
|
+ tableScrollHeight,
|
|
|
+ pageSize: Math.ceil((tableScrollHeight) / 38)
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ render() {
|
|
|
+ const { dataSource, onRowCheckChange } = this.props;
|
|
|
+ const { tableScrollHeight, pageSize } = this.state;
|
|
|
+
|
|
|
+ const columns = [{
|
|
|
+ title: '名称',
|
|
|
+ dataIndex: 'name',
|
|
|
+ key: 'name',
|
|
|
+ width: '80%',
|
|
|
+ render: (text, record, index) => <span style={{ fontWeight: record.type === 'dashboard' ? 'bold' : 'normal' }}>{text}</span>
|
|
|
+ }, {
|
|
|
+ title: '查看',
|
|
|
+ dataIndex: 'view',
|
|
|
+ key: 'view',
|
|
|
+ width: '20%',
|
|
|
+ render: (text, record, index) => {
|
|
|
+ return <Checkbox disabled={record.type === 'menu' && (!record.children || record.children.filter(c => c.type === 'dashboard').length === 0)} indeterminate={record.indeterminate} checked={record.checked} onChange={(e) => {
|
|
|
+ let checked = e.target.checked;
|
|
|
+ typeof onRowCheckChange === 'function' && onRowCheckChange(record, checked);
|
|
|
+ }} />
|
|
|
+ }
|
|
|
+ }];
|
|
|
+
|
|
|
+ return <Table columns={columns} dataSource={dataSource} scroll={{x: false, y: tableScrollHeight}}
|
|
|
+ size='small'
|
|
|
+ pagination={{
|
|
|
+ pageSize: pageSize
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export default connect(({ present: { authority, userGroup, user, dashboard } }) => ({ authority, userGroup, user, dashboard }))(Authority)
|