zhuth %!s(int64=6) %!d(string=hai) anos
pai
achega
a43c370374

+ 158 - 225
src/components/chart/list.jsx

@@ -2,22 +2,23 @@
  * 图表列表
  */
 import React from 'react'
-import { Layout, Button, Icon, Input, Menu, Dropdown, Card, Col, Row, Popover, Breadcrumb, Tree, Tag, Select, DatePicker } from 'antd'
+import { Layout, Button, Icon, Input, Menu, Dropdown, Card, Col, Row, Breadcrumb, Tag, Checkbox, Select, DatePicker } from 'antd'
 import { connect } from 'dva'
-import moment from 'moment'
-import './list.less'
 import ChooseDataSourceBox from './chooseDataSourceBox'
+import GroupManagementBox from '../common/groupManageMentBox/box'
+import moment from 'moment'
 import Ellipsis from 'ant-design-pro/lib/Ellipsis'
 import 'ant-design-pro/dist/ant-design-pro.css'
-import GroupSelector from '../dataSource/groupSelector'
+import GroupSelector from '../common/groupSelector/popover'
 import Thumbnail from './thumbnail'
 import DistributeBox from './distributeBox';
 import TransferBox from '../common/selectUserBox/selectUserBox'
 import DeleteBox from '../common/deleteBox/deleteBox'
+import { arrayToTree } from '../../utils/baseUtils'
+import './list.less'
 const { Content } = Layout
 const { Search } = Input
 const CardGrid = Card.Grid
-const { TreeNode } = Tree
 const { Option } = Select
 const { RangePicker } = DatePicker
 
@@ -32,7 +33,9 @@ class ChartList extends React.Component {
             visibleTransferBox: false,
             visibleGroupMenu: false, // 显示分组菜单
             visibleDeleteBox: false,
-            visibleDataPreviewBox: false
+            visibleGroupManageMentBox: false, // 显示分组管理组件
+            visibleDataPreviewBox: false,
+            noGroup: false, // 显示未分组数据
         }
     }
 
@@ -66,19 +69,70 @@ class ChartList extends React.Component {
         cardBodyWidth > 0 ? cardBody.style.width = cardBodyWidth + 'px' : void(0);
     }
 
-    generateCard() {
+    onGroup() {
+        const { chart } = this.props;
+        const { noGroup } = this.state;
+        const { list, currentGroup } = chart;
+        
+        if(noGroup) {
+            return list.filter(l => l.groupCode === '-1' );
+        }else if(currentGroup) {
+            return list.filter(l => l.groupCode === currentGroup.code );
+        }else {
+            return list;
+        }
+    }
+
+    getParens = (group) => {
+        const groupData = this.props.chart.groupList;
+        let pgroups = [group];
+        let fgroup = groupData.find(g => g.code === group.pcode);
+        if(fgroup) {
+            pgroups = this.getParens(fgroup).concat(pgroups);
+        }
+        return pgroups;
+    }
+
+    generateGroupTags = () => {
+        const { chart, dispatch } = this.props;
+        const { noGroup } = this.state;
+        const { currentGroup } = chart;
+        const pGroups = currentGroup ? [{ code: '-1', label: '全部分组' }].concat(this.getParens(currentGroup)) : [{ code: '-1', label: '全部分组' }];
+        return <Breadcrumb className={`group${noGroup ? ' nogroup' : ''}`} separator=">">
+            { pGroups.map(g => (
+                <Breadcrumb.Item key={g.code}>
+                    <GroupSelector
+                        visible={this.state['visibleGroupSelector' + g.code]}
+                        handleVisibleChange={(visible) => {
+                            let obj = {};
+                            obj['visibleGroupSelector' + g.code] = visible
+                            this.setState(obj);
+                        }}
+                        pGroup={g}
+                        groupList={chart.groupList}
+                        selectGroup={(group) => {
+                            let obj = {};
+                            obj['visibleGroupSelector' + g.code] = false;
+                            this.setState(obj);
+                            dispatch({ type: 'chart/setCurrentGroup', group });
+                        }}
+                    >
+                        <Tag color={'blue'} >
+                            {g.label}
+                        </Tag>
+                    </GroupSelector>
+                </Breadcrumb.Item>
+            )) }
+        </Breadcrumb>
+    }
+
+    generateOperationMenu = () => {
         const { main, chart, dispatch } = this.props;
         const { currentUser } = main;
-        const groupList = chart.groupList;
         const { selectedRecord } = this.state;
-        const list = chart.list;
-        const currentGroup = chart.currentGroup;
-
-        const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
-        let filterLabel = chart.filterLabel.replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
-        let typeLabel = chart.typeLabel
-
-        const operationMenu = (
+        const treeData = arrayToTree(chart.groupList, '-1', 'code', 'pcode', 'children');
+        
+        return (
             <Menu className='menu-operation'>
                 <Menu.Item disabled>
                     <Icon type="link" />分享
@@ -95,7 +149,12 @@ class ChartList extends React.Component {
                     <Icon type='share-alt'/>分发
                 </Menu.Item>}
                 { selectedRecord && currentUser.code === selectedRecord.creatorCode && <Menu.SubMenu className='setgroupmenu' title={<div><Icon style={{ marginRight: '6px' }} type='profile' />移动到</div>}>
-                    {this.createGroupMenu(selectedRecord)}
+                    {[<Menu.Item key='-1' onClick={() => {
+                        dispatch({ type: 'chart/remoteSetGroup', chart: selectedRecord, group: { code: '-1'} });
+                        this.setState({
+                            noGroup: true
+                        });
+                    }}>未分组</Menu.Item>].concat(this.createGroupMenu(treeData))}
                 </Menu.SubMenu>}
                 <Menu.Divider />
                 { selectedRecord && currentUser.code === selectedRecord.creatorCode && <Menu.Item
@@ -115,33 +174,25 @@ class ChartList extends React.Component {
                 </Menu.Item>}
             </Menu>
         )
-        
-        let groupFilter = groupList.concat({ code: '-1', label: '未分组' }).filter(g => (
-            currentGroup[0].code === 'all' || 
-            (
-                currentGroup[0].code === '-1' ? g.code === '-1' : (
-                    currentGroup[1] ? (g.code === currentGroup[1].code) : 
-                    (
-                        g.code === currentGroup[0].code ||
-                        g.pcode === currentGroup[0].code
-                    )
-                )
-            )
-        )).map(g => g.code);
-
+    }
 
-        let cards = list.filter(l => groupFilter.indexOf(l.groupCode+'') !== -1).map(l => {
-            let reg = new RegExp('(' + filterLabel + '){1}', 'ig');
+    generateCard() {
+        const { chart, dispatch } = this.props;
+        let { filterItem } = chart
 
-            if(l[typeLabel] && typeLabel!=='createTime' && l[typeLabel].search(reg) !== -1){
-                return { ...l, bf: false };
-            }else if(l[typeLabel] && typeLabel==='createTime'){
+        const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
+        let filterLabel = chart.filterLabel.replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
+        let filterReg = new RegExp('(' + filterLabel + '){1}', 'ig');
+        
+        
+        let cards = this.onGroup().map(l => {
+            if(filterItem.type === 'date') {
                 if(filterLabel===""){
                     return { ...l, bf: false };
                 }else if(filterLabel.indexOf('#')>-1){
                     let start = filterLabel.split('#')[0]
                     let end = filterLabel.split('#')[1]
-                    let nowTime = new Date(l[typeLabel]).getTime();
+                    let nowTime = new Date(l[filterItem.name]).getTime();
                     if(nowTime>=start && nowTime<=end){
                         return { ...l, bf: false };
                     }
@@ -149,8 +200,8 @@ class ChartList extends React.Component {
                 }else{
                     return { ...l, bf: true };
                 }
-            }else{
-                return { ...l, bf: true };
+            }else {
+                return ((l[filterItem.name] + '').search(filterReg) > -1) ? { ...l, bf: false } : { ...l, bf: true }
             }
         }).sort((a, b) => {
             return new Date(b.createTime) - new Date(a.createTime)
@@ -162,7 +213,7 @@ class ChartList extends React.Component {
                     title={
                         <Row type='flex' justify='space-between'>
                             <Col span={21} style={{ overflow: 'hidden', textOverflow: 'ellipsis' }} >
-                                { filterLabel ?
+                                { (filterItem.name === 'name' && filterLabel) ?
                                     ((l.name || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
                                         return (
                                             fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
@@ -195,7 +246,7 @@ class ChartList extends React.Component {
                             <Row className='desc'>
                                 <Ellipsis tooltip={l.description&&l.description.length > 16} lines={2}>{
                                     <span>
-                                    { filterLabel ?
+                                    { (filterItem.name === 'description' && filterLabel) ?
                                         ((l.description || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
                                             return (
                                                 fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
@@ -210,10 +261,20 @@ class ChartList extends React.Component {
                             </Row>
                             <Row className='footer' type='flex' justify='end' align='bottom'>
                                 <Col style={{ textAlign: 'left', lineHeight: '28px' }} span={21}>
-                                    <Row>{l.creatorName} {moment(l.createTime).format('YYYY-MM-DD')}</Row>
+                                    <Row>{
+                                        (filterItem.name === 'creatorName' && filterLabel) ?
+                                        ((l.creatorName || '').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.creatorName
+                                    } {moment(l.createTime).format('YYYY-MM-DD')}</Row>
                                 </Col>
                                 <Col span={3} style={{ textAlign: 'right' }}>
-                                    <Dropdown overlay={operationMenu} trigger={['click']}>
+                                    <Dropdown overlay={this.generateOperationMenu()} trigger={['click']}>
                                         <Icon style={{ fontSize: '24px' }} type="ellipsis" theme="outlined" />
                                     </Dropdown>
                                 </Col>
@@ -230,138 +291,36 @@ class ChartList extends React.Component {
         return cards;
     }
 
-    createGroupMenu = (selectedRecord) => {
-        const { chart, dispatch } = this.props;
-        const groupList = chart.groupList;
-        const pGroups = groupList.filter(d => d.pcode === '-1').sort((a, b) => a.index - b.index);
-        const cGroups = groupList.filter(d => d.pcode !== '-1');
-
-        let allGroups = !!selectedRecord ? [
-            { code: '-1', label: '未分组' }
-        ].concat(pGroups) : [
-            { code: 'all', label: '全部分组' },
-            { code: '-1', label: '未分组' }
-        ].concat(pGroups);
-
-        return allGroups.map(p => {
-            let c = cGroups.filter(c => c.pcode === p.code).sort((a, b) => a.index - b.index);
-            return c.length > 0 ? (
-                <Menu.SubMenu key={p.code} title={<span style={{ fontWeight: !!selectedRecord ? 
-                    (p.code+'' === selectedRecord.groupCode+'' ? 'bold' : (
-                        c.find(ch => ch.code+'' === selectedRecord.groupCode+'') && c.find(ch => ch.code+'' === selectedRecord.groupCode+'').pcode === p.code ? 'bold' : 'normal'
-                    ))
-                : chart.currentGroup[0].code === p.code ? 'bold' : 'normal' }}>{p.label}</span>} onTitleClick={(item) => {
-                    dispatch({ type: 'chart/setCurrentGroup', group1: p });
-                    if(selectedRecord) {
-                        dispatch({ type: 'chart/remoteSetChartGroup', chart: selectedRecord, group: p });
-                    }
-                    this.hideGroupMenu();
-                }}>
-                    {c.map(c => {
-                        return (<Menu.Item key={c.code} onClick={(item) => {
-                            dispatch({ type: 'chart/setCurrentGroup', group1: p, group2: c });
-                            if(selectedRecord) {
-                                dispatch({ type: 'chart/remoteSetChartGroup', chart: selectedRecord, group: c });
-                            }
-                        }}><span style={{ fontWeight: !!selectedRecord ? (
-                            selectedRecord.groupCode+'' === c.code+'' ? 'bold' : 'normal'
-                        ) : (chart.currentGroup[1] && (chart.currentGroup[1].code === c.code) ? 'bold' : 'normal') }}>{c.label}</span></Menu.Item>)
-                    })}
+    createGroupMenu = (treeData) => {
+        const { dispatch } = this.props;
+        const { selectedRecord } = this.state;
+        return treeData.map(t => {
+            if(t.children && t.children.length > 0) {
+                return <Menu.SubMenu
+                    key={t.code}
+                    title={selectedRecord.groupCode === t.code ? <span style={{ color: '#1890ff', fontWeight: 'bold' }}>{t.label}</span> : t.label}
+                    onTitleClick={() => {
+                        dispatch({ type: 'chart/remoteSetGroup', chart: selectedRecord, group: t });
+                        dispatch({ type: 'chart/setCurrentGroup', group: t });
+                    }}
+                >
+                    {this.createGroupMenu(t.children)}
                 </Menu.SubMenu>
-            ) : (
-                <Menu.Item key={p.code} onClick={() => {
-                    dispatch({ type: 'chart/setCurrentGroup', group1: p });
-                    if(selectedRecord) {
-                        dispatch({ type: 'chart/remoteSetChartGroup', chart: selectedRecord, group: p });
-                    }
-                    this.hideGroupMenu();
-                }}><span style={{ fontWeight: !!selectedRecord ? (
-                    selectedRecord.groupCode+'' === p.code+'' ? 'bold' : 'normal'
-                ) : chart.currentGroup[0] && (chart.currentGroup[0].code === p.code) ? 'bold' : 'normal' }}>{p.label}</span></Menu.Item>
-            );
-        });
-    }
-
-    createSubGroupMenu = () => {
-        const { chart, dispatch } = this.props;
-        const groupList = chart.groupList;
-        const parentGroup = chart.currentGroup[0];
-        const children = groupList.filter(d => d.pcode === parentGroup.code);
-        const subGroup = chart.currentGroup[1];
-
-        return children.map(c => {
-            return (
-                <Menu.Item key={c.code} onClick={() => {
-                    dispatch({ type: 'chart/setCurrentGroup', group1: parentGroup, group2: c });
-                }}><span style={{ fontWeight: subGroup && (subGroup.code === c.code) ? 'bold' : 'normal' }}>{c.label}</span></Menu.Item>
-            );
+            }else {
+                return <Menu.Item key={t.code} onClick={() => {
+                    dispatch({ type: 'chart/remoteSetGroup', chart: selectedRecord, group: t });
+                    dispatch({ type: 'chart/setCurrentGroup', group: t });
+                }}>{selectedRecord.groupCode === t.code ? <span style={{ color: '#1890ff', fontWeight: 'bold' }}>{t.label}</span> : t.label}</Menu.Item>
+            }
         })
     }
 
-    createGroupTree(modify) {
-        const { dispatch, chart } = this.props;
-        const { groupEditing } = this.state;
-        const groupList = chart.groupList;
-
-        let parent = groupList.filter(d => d.pcode === '-1').sort((a, b) => a.index - b.index);
-        let children = groupList.filter(d => d.pcode !== '-1');
-
-        let groupTree = parent.map(p => {
-            return (
-                <TreeNode disabled={groupEditing} title={
-                modify ? (<div><Icon style={{ cursor: 'move' }} type='drag'/>
-                <Input value={p.label} size='small' focus={'true'} onFocus={() => {
-                    this.setState({
-                        groupEditing: true
-                    });
-                }} onChange={(e) => {
-                    dispatch({ type: 'chart/modifyGroup', group: {...p, label:e.target.value} });
-                }} onBlur={(e) => {
-                    this.setState({
-                        groupEditing: false
-                    });
-                    dispatch({ type: 'chart/remoteModifyGroup', group: {...p, label:e.target.value} });
-                }} onPressEnter={(e) => {
-                    dispatch({ type: 'chart/remoteModifyGroup', group: {...p, label:e.target.value} });
-                }} /><Icon type='plus-circle-o' onClick={() => {
-                    dispatch({ type: 'chart/remoteAddGroup', pgroup: p });
-                }}/><Icon type='minus-circle' onClick={() => {
-                    dispatch({ type: 'chart/remoteDeleteGroup', group: p });
-                }}/></div>) : p.label} key={p.code}>
-                    {
-                        children.filter(c => c.pcode === p.code).sort((a, b) => a.index - b.index).map(c => {
-                            return (
-                                <TreeNode disabled={groupEditing} title={
-                                    modify ? (<div><Icon style={{ cursor: 'move' }} type='drag'/>
-                                    <Input value={c.label} size='small' onFocus={() => {
-                                        this.setState({
-                                            groupEditing: true
-                                        });
-                                    }} onChange={(e) => {
-                                        dispatch({ type: 'chart/modifyGroup', group: {...c, label:e.target.value} });
-                                    }} onBlur={(e) => {
-                                        this.setState({
-                                            groupEditing: false
-                                        });
-                                        dispatch({ type: 'chart/remoteModifyGroup', group: {...c, label:e.target.value} });
-                                    }} onPressEnter={(e) => {
-                                        dispatch({ type: 'chart/remoteModifyGroup', group: {...c, label:e.target.value} });
-                                    }} onCompositionEnd={(e) => {
-                                        console.log(e.target.value);
-                                    }}/><Icon type='minus-circle' onClick={() => {
-                                        dispatch({ type: 'chart/remoteDeleteGroup', group: c });
-                                    }}/></div>) : p.label
-                                } key={c.code} />
-                            )
-                        })
-                    }
-                </TreeNode>
-            )
-        });
-
-        return groupTree;
+    generateFilterItems = () => {
+        const { filterItems } = this.props.chart;
+        return filterItems.map(t => <Option key={t.name}  value={t.name}>{t.label}</Option>);
     }
 
+
     handleVisibleChange = (flag) => {
         this.setState({ visibleGroupMenu: flag });
     }
@@ -383,78 +342,40 @@ class ChartList extends React.Component {
     }
 
     render() {
-        const { visibleChooseDataSourceBox, visibleDistributeBox, visibleTransferBox, visibleDeleteBox, selectedRecord } = this.state;
         const { dispatch, chart } = this.props;
-        const TAG_COLOR = ['blue'];
-        const { changeSearchType } = chart;
-        
-
+        const { visibleChooseDataSourceBox, visibleDistributeBox, visibleGroupManageMentBox, visibleTransferBox, visibleDeleteBox, selectedRecord, noGroup } = this.state;
+        const { filterItem } = chart;
         return (
             <Layout className='chart-list'>
                 <Content>
                     <Card title={
                         <Row className='tools' type='flex' justify='space-between'>
-                            <Col style={{ display: 'flex' }}>
-                            <Popover overlayClassName='popover-group' title={
-                                    <Row className='grouptree-title' type='flex' justify='space-between'>
-                                        <Col>
-                                            分组管理
-                                        </Col>
-                                        <Col>
-                                            <div className='create-group' onClick={() => {
-                                                dispatch({ type: 'chart/remoteAddGroup' });
-                                            }}>添加分组<Icon type="plus-circle-o" /></div>
-                                        </Col>
-                                    </Row>
-                                } trigger="click" placement="bottomLeft" content={(
-                                    <Tree
-                                        className='tree-group'
-                                        showLine
-                                        defaultExpandAll
-                                        draggable
-                                        onDragStart={this.onDragStart}
-                                        onDrop={this.onDrop}
-                                    >
-                                        {
-                                            this.createGroupTree(true)
-                                        }
-                                    </Tree>
-                                )}>
-                                    <Icon type="bars" />
-                                </Popover>
-                                <Breadcrumb className='group' separator=">">
-                                    <Breadcrumb.Item>
-                                        <GroupSelector model={chart} modelName='chart'>
-                                            <Tag color={TAG_COLOR[Math.ceil(Math.random()*TAG_COLOR.length) - 1]} >
-                                                {chart.currentGroup[0].label}
-                                            </Tag>
-                                        </GroupSelector>
-                                    </Breadcrumb.Item>
-                                    {chart.currentGroup[1] && (<Breadcrumb.Item>
-                                        <GroupSelector model={chart} modelName='chart'>
-                                            <Tag color={TAG_COLOR[Math.ceil(Math.random()*TAG_COLOR.length) - 1]}>
-                                                {chart.currentGroup[1].label}
-                                            </Tag>
-                                        </GroupSelector>
-                                    </Breadcrumb.Item>)}
-                                </Breadcrumb>
+                            <Col style={{ display: 'flex', width: 'calc(100% - 324px)', overflow: 'hidden' }}>
+                                <Icon type="bars" onClick={() => {
+                                    this.setState({
+                                        visibleGroupManageMentBox: true
+                                    });
+                                }}/>
+                                <Checkbox style={{ marginTop: '4px' }} value={noGroup} onChange={(e) => {
+                                    this.setState({noGroup: e.target.checked})
+                                }}>未分组</Checkbox>
+                                { this.generateGroupTags() }
                             </Col>
                             <Col className='search'>
                                 <Col style={{ padding: '0 1px', margin: '0 -5px' }}>
                                     <Select 
-                                        value={chart.typeLabel}
+                                        value={filterItem.name}
                                         style={{ width: 120 }}
-                                        onChange={e => {
-                                            dispatch({ type: 'chart/setTypeLabel', selected: e });
+                                        onChange={value => {
+                                            let item = chart.filterItems.find(i => i.name === value);
+                                            dispatch({ type: 'chart/setFilterItem', item });
                                         }}
                                     >
-                                        <Option value="name">图表名称</Option>
-                                        <Option value="creatorName">创建人</Option>
-                                        <Option value="createTime">创建时间</Option>
+                                        {this.generateFilterItems()}
                                     </Select>
                                 </Col>
                                 <Col style={{ padding: '0 5px' }}>
-                                {changeSearchType ? 
+                                {filterItem.type === 'date' ? 
                                     <RangePicker  
                                         ranges={{
                                             '今天': [moment().startOf('day'), moment().endOf('day')], 
@@ -513,6 +434,18 @@ class ChartList extends React.Component {
                         </div>
                     </Card>
                 </Content>
+                {visibleGroupManageMentBox && <GroupManagementBox
+                    visibleBox={visibleGroupManageMentBox}
+                    groupData={chart.groupList}
+                    hideBox={() => {
+                        this.setState({
+                            visibleGroupManageMentBox: false
+                        })
+                    }}
+                    okHandler={(aGroups, mGroups, dGroups) => {
+                        dispatch({ type: 'chart/remoteSetGroups', aGroups, mGroups, dGroups });
+                    }}
+                />}
                 {visibleDistributeBox && <DistributeBox visibleDistributeBox={visibleDistributeBox} selectedRecord={this.state.selectedRecord} hideBox={() => {
                     this.setState({
                         visibleDistributeBox: false

+ 10 - 1
src/components/chart/list.less

@@ -5,6 +5,7 @@
                 padding: 0 10px;
                 .tools {
                     flex: 1;
+                    flex-wrap: nowrap;
                     .anticon-bars {
                         cursor: pointer;
                         line-height: 1.6;
@@ -16,11 +17,19 @@
                     }
                     .group {
                         line-height: 2.1;
+                        white-space: normal;
                         .ant-breadcrumb-link {
                             .ant-tag {
-                                margin: 0;
+                                max-width: 200px;
+                                white-space: nowrap;
+                                text-overflow: ellipsis;
+                                overflow: hidden;
+                                margin: -6px 0;
                             }
                         }
+                        &.nogroup {
+                            opacity: .7;
+                        }
                     }
                     .search {
                         display: flex;

+ 271 - 0
src/components/common/groupManageMentBox/box.jsx

@@ -0,0 +1,271 @@
+import React from 'react'
+import { Modal, Tree, Input, Icon, Row, Col, Button } from 'antd'
+import { arrayToTree, hashcode } from '../../../utils/baseUtils'
+import './box.less'
+const { TreeNode } = Tree;
+const Search = Input.Search
+
+class GroupBox extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            groupData: props.groupData.map(g => g),
+            filterLabel: '',
+            selectedGroup: null,
+            expandedKeys: ['-1'],
+            editingKey: null,
+            autoExpandParent: true,
+            aGroups: [], // 新增的分组
+            mGroups: [], // 修改的分组
+            dGroups: [], // 删除的分组
+        }
+    }
+
+    generateTreeNode(treeData) {
+        const { filterLabel, editingKey } = this.state;
+        let list = treeData.sort((a, b) => (a.index - b.index)).map(t => {
+            const index = t.label.indexOf(filterLabel);
+            const beforeStr = t.label.substr(0, index);
+            const afterStr = t.label.substr(index + filterLabel.length);
+            const title = index > -1 ? (
+                <span>
+                    {beforeStr}
+                    <span style={{ color: '#f50' }}>{filterLabel}</span>
+                    {afterStr}
+                </span>
+            ) : <span>{t.label}</span>;
+            return <TreeNode
+                title={<div className='node-title'>
+                    {t.code === editingKey ? <div className='input'>
+                        <Input
+                            size="small"
+                            defaultValue={t.label}
+                            addonAfter={<Icon style={{ cursor: 'pointer', color: '#52C41A' }} type="check-circle" onClick={() => {
+                                this.setState({ editingKey: false });
+                            }}/>}
+                            onBlur={(e) => {
+                                this.modifyGroup({ ...t, label:e.target.value });
+                                this.setState({ editingKey: false });
+                            }} onPressEnter={(e) => {
+                                this.modifyGroup({ ...t, label:e.target.value });
+                                this.setState({ editingKey: false });
+                            }}
+                        />
+                    </div> :
+                    <div className='label'>{title}</div>}
+                    <div className='tools'>
+                        {t.code !== editingKey && <Icon type='edit' onClick={() => {
+                            this.setState({
+                                editingKey: t.code
+                            });
+                        }}/>}
+                        {!t.children && <Icon type='delete' onClick={() => {
+                            this.deleteGroup(t);
+                        }}/>}
+                    </div>
+                </div>}
+                key={t.code}
+            >
+                {(t.children && t.children.length > 0) && this.generateTreeNode(t.children)}
+            </TreeNode>
+        });
+
+        return list;
+    }
+
+    onSelect = (selectedKeys, info) => {
+        const { groupData } = this.state;
+        let group = groupData.filter(g => g.code === selectedKeys[0])[0];
+        this.setState({
+            selectedGroup: group
+        });
+    }
+
+    onExpand = (expandedKeys) => {
+        this.setState({
+          expandedKeys,
+          autoExpandParent: false,
+        });
+      }
+
+    onSearch = (e) => {
+        const value = e.target.value;
+        const expandedKeys = this.findExpandedKeys(value);
+        this.setState({
+            expandedKeys,
+            filterLabel: value,
+            autoExpandParent: true,
+        });
+    }
+    
+    findExpandedKeys = (filterLabel) => {
+        const { groupData } = this.state;
+        let result = [];
+
+        groupData.forEach(g => {
+            if(g.label.indexOf(filterLabel) > -1 && result.indexOf(g.pcode) === -1) {
+                result.push(g.pcode);
+            }
+        });
+        return result;
+    }
+
+    findGroup = (treeData, group) => {
+        let fg;
+        if(group && treeData && treeData.length > 0) {
+            for(let i = 0; i < treeData.length && !fg; i++) {
+                if(treeData[i].code === group.code) {
+                    fg = treeData[i];
+                }else {
+                    fg = this.findGroup(treeData[i].children, group);
+                }
+            }
+        }
+        return fg;
+    }
+
+    getParens = (group) => {
+        const { groupData } = this.state;
+        let pgroups = [group];
+        let fgroup = groupData.find(g => g.code === group.pcode);
+        if(fgroup) {
+            pgroups = pgroups.concat(this.getParens(fgroup));
+        }
+        return pgroups;
+    }
+
+    addGroup = (pgroup) => {
+        let { aGroups, groupData } = this.state;
+        let treeData = arrayToTree(groupData, '-1', 'code', 'pcode', 'children');
+        let fGroup = this.findGroup(treeData, pgroup);
+        let pGroups = pgroup ? this.getParens(pgroup) : [];
+        let g = {
+            label: '新分组',
+            operate: 'add',
+            code: 'new-' + Math.random() * 10,
+            index: fGroup ? ( fGroup.children ? fGroup.children.length : 0 ) : treeData.length
+        };
+        if(pgroup) {
+            g.pcode = pgroup.code
+        }else {
+            g.pcode = '-1'
+        }
+        groupData.push(g);
+        this.setState({
+            groupData,
+            expandedKeys: pGroups.concat([{ code: '-1' }]).map(g => g.code),
+            aGroups: aGroups.concat([g]),
+        });
+    }
+
+    modifyGroup = (group) => {
+        let { aGroups, mGroups, groupData } = this.state;
+        let idx = groupData.findIndex(g => g.code === group.code);
+        groupData.splice(idx, 1, group);
+
+        if(group.code.startsWith('new-')) {
+            for(let i = 0; i < aGroups.length; i++) {
+                if(aGroups[i].code === group.code) {
+                    aGroups[i] = Object.assign({}, aGroups[i], group);
+                    break;
+                }
+            }
+            this.setState({
+                groupData,
+                aGroups: aGroups
+            });
+        }else {
+            let fidx = mGroups.findIndex(g => g.code === group.code);
+            if(fidx === -1) {
+                this.setState({
+                    groupData,
+                    mGroups: mGroups.concat([{ ...group, operate: 'update' }])
+                });
+            }else {
+                mGroups[fidx] = Object.assign({}, mGroups[fidx], group);
+                this.setState({
+                    groupData,
+                    mGroups: mGroups
+                });
+            }
+        }
+    }
+
+    deleteGroup = (group) => {
+        let { aGroups, mGroups, dGroups, groupData } = this.state;
+        let idx = groupData.findIndex(g => g.code === group.code);
+        groupData.splice(idx, 1);
+
+        if(group.code.startsWith('new-')) {
+            let fidx = aGroups.findIndex(g => g.code === group.code);
+            aGroups.splice(fidx, 1);
+            this.setState({
+                groupData,
+                aGroups: aGroups
+            });
+        }else {
+            let fidx = mGroups.findIndex(g => g.code === group.code);
+            if(fidx !== -1) {
+                mGroups.splice(fidx, 1);
+            }
+            this.setState({
+                groupData,
+                mGroups: mGroups,
+                dGroups: dGroups.concat([{ ...group, operate: 'delete' }])
+            });
+        }
+    }
+
+    componentDidUpdate(prevProps, prevState, snapshot) {
+        if(hashcode(prevProps.groupData) !== hashcode(this.props.groupData)) {
+            this.setState({
+                groupData: this.props.groupData.map(g => g),
+                aGroups: [],
+                mGroups: [],
+                dGroups: [],
+            });
+        }
+    }
+
+    render() {
+        let { visibleBox, hideBox, okHandler } = this.props;
+        let { groupData, expandedKeys, selectedGroup, autoExpandParent, aGroups, mGroups, dGroups } = this.state;
+        let treeData = arrayToTree(groupData, '-1', 'code', 'pcode', 'children');
+        return <Modal
+            className='groupmanagement-box'
+            title={'分组管理'}
+            visible={visibleBox}
+            onOk={() => {
+                okHandler(aGroups, mGroups, dGroups);
+            }}
+            onCancel={hideBox}
+            maskClosable={false}
+            destroyOnClose={true}
+        >
+            <Row>
+                <Col span={18}>
+                    <Search placeholder='搜索' onChange={this.onSearch}></Search>
+                </Col>
+                <Col span={6}>
+                    <Button disabled={selectedGroup && selectedGroup.code && selectedGroup.code.startsWith('new-')} style={{ marginLeft: '8px' }} onClick={() => {
+                        this.addGroup(selectedGroup);
+                    }}><Icon type='plus'/>添加</Button>
+                </Col>
+            </Row>
+            <Row className='tree-container'>
+                <Tree
+                    onSelect={this.onSelect}
+                    onExpand={this.onExpand}
+                    expandedKeys={expandedKeys}
+                    autoExpandParent={autoExpandParent}
+                >
+                    {
+                        <TreeNode title='全部' key='-1'>{this.generateTreeNode(treeData)}</TreeNode>
+                    }
+                </Tree>
+            </Row>
+        </Modal>
+    }
+}
+
+export default GroupBox

+ 35 - 0
src/components/common/groupManageMentBox/box.less

@@ -0,0 +1,35 @@
+.groupmanagement-box {
+    .ant-modal-body {
+        max-height: 50vh;
+        overflow-y: hidden;
+        .tree-container {
+            overflow-y: auto;
+            max-height: 40vh;
+        }
+    }
+    .ant-tree li .ant-tree-node-content-wrapper {
+        width: ~'calc(100% - 24px)';
+    }
+    .ant-tree-title {
+        cursor: default;
+    }
+}
+
+.node-title {
+    display: flex;
+    justify-content: space-between;
+    .tools {
+        i {
+            display: none;
+            margin-left: 8px;
+            cursor: pointer;
+        }
+    }
+    &:hover {
+        .tools {
+            i {
+                display: inline-block;
+            }
+        }
+    }
+}

+ 85 - 0
src/components/common/groupSelector/popover.jsx

@@ -0,0 +1,85 @@
+import React from 'react'
+import { Popover, Tree } from 'antd'
+import { arrayToTree } from '../../../utils/baseUtils'
+import './popover.less'
+const { TreeNode } = Tree;
+
+class PopTree extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.state = {
+            expandedKeys: [props.pGroup.code]
+        }
+    }
+
+    generateTreeNode(treeData) {
+        let list = treeData.sort((a, b) => (a.index - b.index)).map(t => {
+            return <TreeNode
+                title={t.label}
+                key={t.code}
+            >
+                {(t.children && t.children.length > 0) && this.generateTreeNode(t.children)}
+            </TreeNode>
+        });
+
+        return list;
+    }
+
+    /**
+     * 查找节点的所有子节点
+     */
+    findTree = (tree, group) => {
+        let fGroup;
+        for(let i = 0; tree && i < tree.length; i++) {
+            if(tree[i].code === group.code) {
+                fGroup = tree[i];
+            }else {
+                fGroup = this.findTree(tree[i].children, group);
+            }
+            if(!fGroup) {
+                break;
+            }
+        }
+        return fGroup;
+    }
+
+
+    render() {
+        const { visible, handleVisibleChange, pGroup, groupList, selectGroup, children } = this.props;
+        const { expandedKeys } = this.state;
+        let treeData = arrayToTree(groupList, pGroup.code, 'code', 'pcode', 'children');
+        
+        return (
+            <Popover
+                overlayClassName='groupselector'
+                placement="bottomLeft"
+                trigger='click'
+                visible={visible}
+                onVisibleChange={handleVisibleChange}
+                content={
+                    <Tree
+                        expandedKeys={expandedKeys}
+                        onExpand={(expandedKeys) => {
+                            this.setState({
+                                expandedKeys
+                            });
+                        }}
+                        onSelect={(selectedKeys) => {
+                            let group = groupList.filter(g => g.code === selectedKeys[0])[0];
+                            selectGroup(group);
+                        }}
+                    >
+                        {
+                            <TreeNode title={pGroup.label} key={pGroup.code}>{this.generateTreeNode(treeData)}</TreeNode>
+                        }
+                    </Tree>
+                }
+            >
+                { children }
+            </Popover>
+        );
+    }
+}
+
+export default PopTree;

+ 21 - 0
src/components/common/groupSelector/popover.less

@@ -0,0 +1,21 @@
+.groupselector {
+    .group {
+        cursor: pointer;
+        margin: 0 6px;
+        &:hover {
+            transition: color 0.25s cubic-bezier(0.31, 0.93, 1, 1);
+            color: #1890ff;
+            opacity: .7;
+        }
+        .anticon-right {
+            margin-left: 6px;
+        }
+        .group-divider {
+            color: rgba(0, 0, 0, 0.45);
+            margin: 0 8px 0 14px;
+        }
+    }
+    .selected {
+        color: #1890ff;
+    }
+}

+ 2 - 2
src/components/dataConnect/list.jsx

@@ -236,7 +236,7 @@ class DataConnect extends React.Component {
                                             { name: 'boxOperation', value: 'create' }
                                         ] });
                                     }}>
-                                        <Icon type="plus" />添加数据
+                                        <Icon type="plus" />添加数据
                                     </Button>
                                 </Col>
                             </Col>
@@ -249,7 +249,7 @@ class DataConnect extends React.Component {
                 </Content>
                 {visibleDeleteBox && <DeleteBox
                     visibleBox={visibleDeleteBox}
-                    text={<div><span>确定要删除数据接【{selected.name}】吗?</span></div>}
+                    text={<div><span>确定要删除数据接【{selected.name}】吗?</span></div>}
                     hideBox={() => {
                         this.setState({
                             visibleDeleteBox: false

+ 147 - 227
src/components/dataSource/list.jsx

@@ -1,9 +1,10 @@
 import React from 'react'
-import { Layout, Row, Col, Input, Button, Table, Icon, Menu, Dropdown, Card, Breadcrumb, Popover, Tree, Tag, Select, DatePicker  } from 'antd'
-import { connect } from 'dva'
 import moment from 'moment'
-import { dateFormat } from '../../utils/baseUtils'
-import GroupSelector from './groupSelector'
+import { Layout, Row, Col, Input, Button, Table, Icon, Menu, Dropdown, Card, Breadcrumb, Tag, Checkbox, Select, DatePicker } from 'antd'
+import { connect } from 'dva'
+import { arrayToTree, dateFormat } from '../../utils/baseUtils'
+import GroupManagementBox from '../common/groupManageMentBox/box'
+import GroupSelector from '../common/groupSelector/popover'
 import TransferBox from '../common/selectUserBox/selectUserBox'
 import CopyBox from './copyBox'
 import DeleteBox from '../common/deleteBox/deleteBox'
@@ -11,7 +12,6 @@ import DataPreview from '../common/dataPreview/dataPreview'
 import './list.less'
 const { Content } = Layout
 const { Search } = Input
-const { TreeNode } = Tree
 const { Option } = Select
 const { RangePicker } = DatePicker
 
@@ -21,12 +21,12 @@ class DataSource extends React.Component {
         this.state = {
             selectedRecord: null, // 当前选中的dataSource
             visibleGroupMenu: false, // 显示分组菜单
-            visibleSetGroupMenu: false, //
             visibleTransferBox: false,
             visibleDeleteBox: false,
             visibleCopyBox: false,
             visibleDataPreviewBox: false, // 显示数据预览
-            groupEditing: false, // 是否处于编辑状态
+            visibleGroupManageMentBox: false, // 显示分组管理
+            noGroup: false, // 显示未分组数据
         }
     };
     hideTransferBox = () => {
@@ -52,42 +52,34 @@ class DataSource extends React.Component {
 
     onGroup() {
         const { dataSource } = this.props;
-        const { groupList, currentGroup, list } = dataSource;
-        
-        let groupFilter = groupList.concat({ code: '-1', label: '未分组' }).filter(g => (
-            currentGroup[0].code === 'all' || 
-            (
-                currentGroup[0].code === '-1' ? g.code === '-1' : (
-                    currentGroup[1] ? (g.code === currentGroup[1].code) : 
-                    (
-                        g.code === currentGroup[0].code ||
-                        g.pcode === currentGroup[0].code
-                    )
-                )
-            )
-        )).map(g => g.code);
+        const { noGroup } = this.state;
+        const { currentGroup, list } = dataSource;
         
-        return list.filter(l => groupFilter.indexOf(l.groupCode+'') !== -1);
+        if(noGroup) {
+            return list.filter(l => l.groupCode === '-1' );
+        }else if(currentGroup) {
+            return list.filter(l => l.groupCode === currentGroup.code );
+        }else {
+            return list;
+        }
     }
 
     onSearch(list, dataSource) {
         const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
+        let filterItem = dataSource.filterItem
         let filterLabel = (dataSource.filterLabel || '').replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
-        let typeLabel = dataSource.typeLabel
+        let filterReg = new RegExp('('+ filterLabel +'){1}', 'ig');
         
         return list.map(l => {
             let o = Object.assign({}, l);
-            let reg = new RegExp('('+ filterLabel +'){1}', 'ig');
 
-            if(o[typeLabel] && typeLabel!=='createTime' && o[typeLabel].search(reg) !== -1){
-                return o;
-            }else if(o[typeLabel] && typeLabel==='createTime'){
+            if(filterItem.type === 'date') {
                 if(filterLabel===""){
                     return o;
                 }else if(filterLabel.indexOf('#')>-1){
                     let start = filterLabel.split('#')[0]
                     let end = filterLabel.split('#')[1]
-                    let nowTime = o[typeLabel].getTime();
+                    let nowTime = o[filterItem.name].getTime();
                     if(nowTime>=start && nowTime<=end){
                         return o;
                     }
@@ -95,8 +87,8 @@ class DataSource extends React.Component {
                 }else{
                     return null 
                 }
-            }else{
-                return null 
+            }else {
+                return ((o[filterItem.name] + '').search(filterReg) > -1) ? o : null
             }
         }).filter(a => a!==null);
     }
@@ -111,140 +103,76 @@ class DataSource extends React.Component {
         this.setState({ visibleGroupMenu: flag });
     }
 
-    createGroupMenu = (selectedRecord) => {
+    getParens = (group) => {
+        const groupData = this.props.dataSource.groupList;
+        let pgroups = [group];
+        let fgroup = groupData.find(g => g.code === group.pcode);
+        if(fgroup) {
+            pgroups = this.getParens(fgroup).concat(pgroups);
+        }
+        return pgroups;
+    }
 
+    generateGroupTags = () => {
         const { dataSource, dispatch } = this.props;
-        const groupList = dataSource.groupList;
-        const pGroups = groupList.filter(d => d.pcode === '-1').sort((a, b) => a.index - b.index);
-        const cGroups = groupList.filter(d => d.pcode !== '-1');
-
-        let allGroups = selectedRecord ? [
-            { code: '-1', label: '未分组' }
-        ].concat(pGroups) : [
-            { code: 'all', label: '全部分组' },
-            { code: '-1', label: '未分组' }
-        ].concat(pGroups);
-
-        return allGroups.map(p => {
-            let c = cGroups.filter(c => c.pcode === p.code).sort((a, b) => a.index - b.index);
-            return c.length > 0 ? (
-                <Menu.SubMenu key={p.code} title={<span style={{ fontWeight: selectedRecord ? 
-                    (p.code + '' === selectedRecord.groupCode + '' ? 'bold' : (
-                        c.find(ch => ch.code + '' === selectedRecord.groupCode + '') && c.find(ch => ch.code + '' === selectedRecord.groupCode + '').pcode === p.code ? 'bold' : 'normal'
-                    ))
-                    : dataSource.currentGroup[0].code === p.code ? 'bold' : 'normal'
-                }}>{p.label}</span>} onTitleClick={(item) => {
-                    // dispatch({ type: 'dataSource/setCurrentGroup', group1: p });  // 尝试禁用分组后跳转逻辑
-                    if (selectedRecord) {
-                        dispatch({ type: 'dataSource/remoteSetDataSourceGroup', dataSource: selectedRecord, group: p });
-                    }
-                    this.hideGroupMenu();
-                }}>
-                    {c.map(c => {
-                        return (<Menu.Item key={c.code} onClick={(item) => {
-                            // dispatch({ type: 'dataSource/setCurrentGroup', group1: p, group2: c }); // 尝试禁用分组后跳转逻辑
-                            if(selectedRecord) {
-                                dispatch({ type: 'dataSource/remoteSetDataSourceGroup', dataSource: selectedRecord, group: c });
-                            }
-                        }}><span style={{ fontWeight: selectedRecord ? (
-                            selectedRecord.groupCode+'' === c.code+'' ? 'bold' : 'normal'
-                        ) : (dataSource.currentGroup[1] && (dataSource.currentGroup[1].code === c.code) ? 'bold' : 'normal') }}>{c.label}</span></Menu.Item>)
-                    })}
-                </Menu.SubMenu>
-            ) : (
-                <Menu.Item key={p.code} onClick={() => {
-                    // dispatch({ type: 'dataSource/setCurrentGroup', group1: p });  // 尝试禁用分组后跳转逻辑
-                    if(selectedRecord) {
-                        dispatch({ type: 'dataSource/remoteSetDataSourceGroup', dataSource: selectedRecord, group: p });
-                    }
-                    this.hideGroupMenu();
-                }}><span className={selectedRecord ? (
-                    selectedRecord.groupCode+'' === p.code+'' ? 'selected' : ''
-                ) : dataSource.currentGroup[0] && (dataSource.currentGroup[0].code === p.code) ? 'selected' : ''} style={{ fontWeight: selectedRecord ? (
-                    selectedRecord.groupCode+'' === p.code+'' ? 'bold' : 'normal'
-                ) : dataSource.currentGroup[0] && (dataSource.currentGroup[0].code === p.code) ? 'bold' : 'normal' }}>{p.label}</span></Menu.Item>
-            );
-        });
+        const { noGroup } = this.state;
+        const { currentGroup } = dataSource;
+        const pGroups = currentGroup ? [{ code: '-1', label: '全部分组' }].concat(this.getParens(currentGroup)) : [{ code: '-1', label: '全部分组' }];
+        return <Breadcrumb className={`group${noGroup ? ' nogroup' : ''}`} separator=">">
+            { pGroups.map(g => (
+                <Breadcrumb.Item key={g.code}>
+                    <GroupSelector
+                        visible={this.state['visibleGroupSelector' + g.code]}
+                        handleVisibleChange={(visible) => {
+                            let obj = {};
+                            obj['visibleGroupSelector' + g.code] = visible
+                            this.setState(obj);
+                        }}
+                        pGroup={g}
+                        groupList={dataSource.groupList}
+                        selectGroup={(group) => {
+                            let obj = {};
+                            obj['visibleGroupSelector' + g.code] = false;
+                            this.setState(obj);
+                            dispatch({ type: 'dataSource/setCurrentGroup', group });
+                        }}
+                    >
+                        <Tag color={'blue'} >
+                            {g.label}
+                        </Tag>
+                    </GroupSelector>
+                </Breadcrumb.Item>
+            )) }
+        </Breadcrumb>
     }
 
-    createSubGroupMenu = () => {
-        const { dataSource, dispatch } = this.props;
-        const groupList = dataSource.groupList;
-        const parentGroup = dataSource.currentGroup[0];
-        const children = groupList.filter(d => d.pcode === parentGroup.code);
-        const subGroup = dataSource.currentGroup[1];
+    createGroupMenu = (treeData) => {
+        const { dispatch } = this.props;
+        const { selectedRecord } = this.state;
 
-        return children.map(c => {
-            return (
-                <Menu.Item key={c.code} onClick={() => {
-                    dispatch({ type: 'dataSource/setCurrentGroup', group1: parentGroup, group2: c });
-                }}><span className={subGroup && (subGroup.code === c.code) ? 'selected' : ''} style={{ fontWeight: subGroup && (subGroup.code === c.code) ? 'bold' : 'normal' }}>{c.label}</span></Menu.Item>
-            );
+        return treeData.map(t => {
+            if(t.children && t.children.length > 0) {
+                return <Menu.SubMenu
+                    key={t.code}
+                    title={selectedRecord.groupCode === t.code ? <span style={{ color: '#1890ff', fontWeight: 'bold' }}>{t.label}</span> : t.label}
+                    onTitleClick={() => {
+                        dispatch({ type: 'dataSource/remoteSetGroup', dataSource: selectedRecord, group: t });
+                        dispatch({ type: 'dataSource/setCurrentGroup', group: t });
+                    }}
+                >
+                    {this.createGroupMenu(t.children)}
+                </Menu.SubMenu>
+            }else {
+                return <Menu.Item key={t.code} onClick={() => {
+                    dispatch({ type: 'dataSource/remoteSetGroup', dataSource: selectedRecord, group: t });
+                    dispatch({ type: 'dataSource/setCurrentGroup', group: t });
+                }}>
+                    {selectedRecord.groupCode === t.code ? <span style={{ color: '#1890ff', fontWeight: 'bold' }}>{t.label}</span> : t.label}
+                </Menu.Item>
+            }
         })
     }
 
-    createGroupTree(modify) {
-        const { dispatch, dataSource } = this.props;
-        const { groupEditing } = this.state;
-        const groupList = dataSource.groupList;
-
-        let parent = groupList.filter(d => d.pcode === '-1').sort((a, b) => a.index - b.index);
-        let children = groupList.filter(d => d.pcode !== '-1');
-
-        let groupTree = parent.map(p => {
-            return (
-                <TreeNode disabled={groupEditing} title={
-                modify ? (<div><Icon style={{ cursor: 'move' }} type='drag'/>
-                <Input value={p.label} size='small' focus={'true'} onFocus={() => {
-                    this.setState({
-                        groupEditing: true
-                    });
-                }} onChange={(e) => {
-                    dispatch({ type: 'dataSource/modifyGroup', group: {...p, label:e.target.value} });
-                }} onBlur={(e) => {
-                    this.setState({
-                        groupEditing: false
-                    });
-                    dispatch({ type: 'dataSource/remoteModifyGroup', group: {...p, label:e.target.value} });
-                }} onPressEnter={(e) => {
-                    dispatch({ type: 'dataSource/remoteModifyGroup', group: {...p, label:e.target.value} });
-                }} /><Icon type='plus-circle-o' onClick={() => {
-                    dispatch({ type: 'dataSource/remoteAddGroup', pgroup: p });
-                }}/><Icon type='minus-circle' onClick={() => {
-                    dispatch({ type: 'dataSource/remoteDeleteGroup', group: p });
-                }}/></div>) : p.label} key={p.code}>
-                    {
-                        children.filter(c => c.pcode === p.code).sort((a, b) => a.index - b.index).map(c => {
-                            return (
-                                <TreeNode disabled={groupEditing} title={
-                                    modify ? (<div><Icon style={{ cursor: 'move' }} type='drag'/>
-                                    <Input value={c.label} size='small' onFocus={() => {
-                                        this.setState({
-                                            groupEditing: true
-                                        });
-                                    }} onChange={(e) => {
-                                        dispatch({ type: 'dataSource/modifyGroup', group: {...c, label:e.target.value} });
-                                    }} onBlur={(e) => {
-                                        this.setState({
-                                            groupEditing: false
-                                        });
-                                        dispatch({ type: 'dataSource/remoteModifyGroup', group: {...c, label:e.target.value} });
-                                    }} onPressEnter={(e) => {
-                                        dispatch({ type: 'dataSource/remoteModifyGroup', group: {...c, label:e.target.value} });
-                                    }} onCompositionEnd={(e) => {
-                                    }}/><Icon type='minus-circle' onClick={() => {
-                                        dispatch({ type: 'dataSource/remoteDeleteGroup', group: c });
-                                    }}/></div>) : p.label
-                                } key={c.code} />
-                            )
-                        })
-                    }
-                </TreeNode>
-            )
-        });
-
-        return groupTree;
-    }
 
     hideGroupMenu = () => {
         this.setState({
@@ -262,18 +190,20 @@ class DataSource extends React.Component {
         dispatch({ type: 'dataSource/remoteMoveGroup', dragCode, dropCode, dropPosition });
     }
 
+    generateFilterItems = () => {
+        const { filterItems } = this.props.dataSource;
+        return filterItems.map(t => <Option key={t.name}  value={t.name}>{t.label}</Option>);
+    }
+
     render() {
         const { main, dataSource, dispatch } = this.props;
-        const { selectedRecord, visibleTransferBox, visibleCopyBox, visibleDeleteBox, visibleDataPreviewBox } = this.state;
+        const { selectedRecord, visibleTransferBox, visibleGroupManageMentBox, visibleCopyBox, visibleDeleteBox, visibleDataPreviewBox, noGroup } = this.state;
         const { currentUser } = main;
-        const { changeSearchType } = dataSource;
-
+        const treeData = arrayToTree(dataSource.groupList, '-1', 'code', 'pcode', 'children');
         const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
+        let filterItem = dataSource.filterItem;
         let filterLabel = dataSource.filterLabel.replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
 
-        const TAG_COLOR = ['blue'];
-        // const TAG_COLOR1 = ['magenta', 'red', 'volcano', 'orange', 'gold', 'lime', 'green', 'cyan', 'blue', 'geekblue', 'purple'];
-
         const moreOperatingMenu = (
             <Menu className='operationmenu' visible={true}>
                 <Menu.Item
@@ -283,13 +213,6 @@ class DataSource extends React.Component {
                 >
                     <Icon type="file-add" />创建图表
                 </Menu.Item>
-                {/* {selectedRecord && currentUser.code === selectedRecord.creatorCode && <Menu.Item onClick={(e) => {
-                        dispatch({ type: 'dataSource/resetNewModel' });
-                        let selectedModel = dataSource.list.find((i) => { return i.code === selectedRecord.code })
-                        dispatch({type: 'main/redirect', path: {pathname: '/datasource/'+ selectedModel.type +'/' + selectedModel.code + '/base'}});
-                    }}>
-                    <Icon type="info-circle-o" />属性设置
-                </Menu.Item>} */}
                 <Menu.Item onClick={() => {
                     this.setState({
                         visibleDataPreviewBox: true
@@ -297,7 +220,12 @@ class DataSource extends React.Component {
                 }}><Icon type="search" />预览数据</Menu.Item>
                 <Menu.Divider />
                 { selectedRecord && currentUser.code === selectedRecord.creatorCode && <Menu.SubMenu className='setgroupmenu' title={<div><Icon style={{ marginRight: '6px' }} type='profile' />移动到</div>}>
-                    {this.createGroupMenu(selectedRecord)}
+                    {[<Menu.Item key='-1' onClick={() => {
+                        dispatch({ type: 'dataSource/remoteSetGroup', dataSource: selectedRecord, group: { code: '-1'} });
+                        this.setState({
+                            noGroup: true
+                        });
+                    }}>未分组</Menu.Item>].concat(this.createGroupMenu(treeData))}
                 </Menu.SubMenu>}
                 <Menu.Divider />
                 { selectedRecord && currentUser.code === selectedRecord.creatorCode && <Menu.Item
@@ -340,7 +268,7 @@ class DataSource extends React.Component {
                             dispatch({ type: 'dataSource/resetNewModel' });
                             dispatch({type: 'main/redirect', path: {pathname: '/datasource/'+ record.type +'/' + record.code + '/base'}});
                         }}>
-                            { filterLabel ?
+                            { (filterItem.name === 'name' && filterLabel) ?
                                 ((text || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
                                     return (
                                         fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
@@ -362,7 +290,7 @@ class DataSource extends React.Component {
             render: (text, record) => {
                 return (
                     <span>
-                        { filterLabel ?
+                        { (filterItem.name === 'description' && filterLabel) ?
                             ((text || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
                                 return (
                                     fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
@@ -379,7 +307,23 @@ class DataSource extends React.Component {
             title: '创建人',
             dataIndex: 'creatorName',
             key: 'creatorName',
-            width: 100
+            width: 100,
+            render: (text, record) => {
+                return (
+                    <span>
+                        { (filterItem.name === 'creatorName' && 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: 'createTime',
@@ -402,68 +346,32 @@ class DataSource extends React.Component {
                 <Content>
                     <Card className='datasource-body' title={
                         <Row className='datasource-tools' type='flex' justify='space-between'>
-                            <Col style={{ display: 'flex' }}>
-                                <Popover overlayClassName='popover-group' title={
-                                    <Row className='grouptree-title' type='flex' justify='space-between'>
-                                        <Col>
-                                            分组管理
-                                        </Col>
-                                        <Col>
-                                            <div className='create-group' onClick={() => {
-                                                dispatch({ type: 'dataSource/remoteAddGroup' });
-                                            }}>添加分组<Icon type="plus-circle-o" /></div>
-                                        </Col>
-                                    </Row>
-                                } trigger="click" placement="bottomLeft" content={(
-                                    <Tree
-                                        className='tree-group'
-                                        showLine
-                                        defaultExpandAll
-                                        draggable
-                                        onDragStart={this.onDragStart}
-                                        onDrop={this.onDrop}
-                                    >
-                                        {
-                                            this.createGroupTree(true)
-                                        }
-                                    </Tree>
-                                )}>
-                                    <Icon type="bars" />
-                                </Popover>
-                                <Breadcrumb className='group' separator=">">
-                                    <Breadcrumb.Item>
-                                        <GroupSelector model={dataSource} modelName='dataSource'>
-                                            <Tag color={TAG_COLOR[Math.ceil(Math.random()*TAG_COLOR.length) - 1]} >
-                                                {dataSource.currentGroup[0].label}
-                                            </Tag>
-                                        </GroupSelector>
-                                    </Breadcrumb.Item>
-                                    {dataSource.currentGroup[1] && (<Breadcrumb.Item>
-                                        <GroupSelector model={dataSource} modelName='dataSource'>
-                                            <Tag color={TAG_COLOR[Math.ceil(Math.random()*TAG_COLOR.length) - 1]}>
-                                                {dataSource.currentGroup[1].label}
-                                            </Tag>
-                                        </GroupSelector>
-                                    </Breadcrumb.Item>)}
-                                </Breadcrumb>
+                            <Col style={{ display: 'flex', width: 'calc(100% - 324px)', overflow: 'hidden' }}>
+                                <Icon type="bars" onClick={() => {
+                                    this.setState({
+                                        visibleGroupManageMentBox: true
+                                    });
+                                }}/>
+                                <Checkbox style={{ marginTop: '4px' }} value={noGroup} onChange={(e) => {
+                                    this.setState({noGroup: e.target.checked})
+                                }}>未分组</Checkbox>
+                                { this.generateGroupTags() }
                             </Col>
                             <Col className='search'>
                                 <Col style={{ padding: '0 1px', margin: '0 -5px' }}>
                                     <Select 
-                                        value={dataSource.typeLabel}
+                                        value={filterItem.name}
                                         style={{ width: 120 }}
-                                        onChange={e => {
-                                            dispatch({ type: 'dataSource/setTypeLabel', selected: e });
+                                        onChange={value => {
+                                            let item = dataSource.filterItems.find(i => i.name === value);
+                                            dispatch({ type: 'dataSource/setFilterItem', item });
                                         }}
                                     >
-                                        <Option value="name">名称</Option>
-                                        <Option hidden value="description">说明</Option>
-                                        <Option value="creatorName">创建人</Option>
-                                        <Option value="createTime">创建时间</Option>
+                                        { this.generateFilterItems() }
                                     </Select>
                                 </Col>
                                 <Col style={{ padding: '0 5px' }}>
-                                    {changeSearchType ? 
+                                    {filterItem.type === 'date' ? 
                                         <RangePicker  
                                             ranges={{
                                                 '今天': [moment().startOf('day'), moment().endOf('day')], 
@@ -537,7 +445,19 @@ class DataSource extends React.Component {
                                 }
                             }}
                         />
-                        <TransferBox
+                        {visibleGroupManageMentBox && <GroupManagementBox
+                            visibleBox={visibleGroupManageMentBox}
+                            groupData={dataSource.groupList}
+                            hideBox={() => {
+                                this.setState({
+                                    visibleGroupManageMentBox: false
+                                })
+                            }}
+                            okHandler={(aGroups, mGroups, dGroups) => {
+                                dispatch({ type: 'dataSource/remoteSetGroups', aGroups, mGroups, dGroups });
+                            }}
+                        />}
+                        {visibleTransferBox && <TransferBox
                             visibleBox={visibleTransferBox}
                             title='选择移交对象'
                             okHandler={(user) => {
@@ -549,7 +469,7 @@ class DataSource extends React.Component {
                                 })
                             }}
                             onlyAdmin={true}
-                        />
+                        />}
                         {visibleCopyBox && <CopyBox
                             visibleBox={visibleCopyBox}
                             currentDataSourceCode={selectedRecord.code}

+ 10 - 1
src/components/dataSource/list.less

@@ -5,6 +5,7 @@
             padding: 0 10px;
             .datasource-tools {
                 flex: 1;
+                flex-wrap: nowrap;
                 .anticon-bars {
                     cursor: pointer;
                     line-height: 1.6;
@@ -16,11 +17,19 @@
                 }
                 .group {
                     line-height: 2.1;
+                    white-space: normal;
                     .ant-breadcrumb-link {
                         .ant-tag {
-                            margin: 0;
+                            max-width: 200px;
+                            white-space: nowrap;
+                            text-overflow: ellipsis;
+                            overflow: hidden;
+                            margin: -6px 0;
                         }
                     }
+                    &.nogroup {
+                        opacity: .7;
+                    }
                 }
                 .search {
                     display: flex;

+ 2 - 2
src/components/dataSourceDetail/baseConfig.jsx

@@ -70,8 +70,8 @@ class DataSourceBaseConfig extends React.Component {
                     ):(
                         <div>
                             <Divider orientation="left">连接配置</Divider>
-                            <div style={{ textAlign: 'end', color: '#F5222D' }}>*若只修改数据接,请确认不同数据库取数逻辑一致</div>
-                            <FormItem label='数据接' {...formItemLayout}>
+                            <div style={{ textAlign: 'end', color: '#F5222D' }}>*若只修改数据接,请确认不同数据库取数逻辑一致</div>
+                            <FormItem label='数据接' {...formItemLayout}>
                                 <Select
                                     value={dataSourceDetail.connectCode}
                                     onChange={(value) => {

+ 1 - 1
src/components/dataSourceDetail/content.jsx

@@ -48,7 +48,7 @@ class DataSourceDetailContent extends React.Component {
 
         const steps = [{
             tabName: 'base',
-            title: type === 'database' ? '数据接配置' : '文件选择',
+            title: type === 'database' ? '数据接配置' : '文件选择',
             content: <DataConnectConfig mode={mode} />,
         }, {
             tabName: 'column',

+ 1 - 1
src/components/workshop/index.jsx

@@ -35,7 +35,7 @@ class Workshop extends React.Component {
                 theme='light'
                 className='sider-workshop'
             >
-                <Link to='/workshop/dataconnect'><Button className='ant-btn-block' type={(paths[1] === 'dataconnect' || !paths[1]) ? 'primary' : 'default'} >数据接</Button></Link>
+                <Link to='/workshop/dataconnect'><Button className='ant-btn-block' type={(paths[1] === 'dataconnect' || !paths[1]) ? 'primary' : 'default'} >数据接</Button></Link>
                 <Link to='/workshop/datasource'><Button className='ant-btn-block' type={paths[1] === 'datasource' ? 'primary' : 'default'} >数据源</Button></Link>
                 <Link to='/workshop/chart'><Button className='ant-btn-block' type={paths[1] === 'chart' ? 'primary' : 'default'} >图表</Button></Link>
                 <Link to='/workshop/dashboard'><Button className='ant-btn-block' type={paths[1] === 'dashboard' ? 'primary' : 'default'} >报表</Button></Link>

+ 15 - 10
src/constants/url.js

@@ -1,5 +1,6 @@
-const BASE_URL = 'http://10.1.1.168:8094/BI';
+// const BASE_URL = 'http://10.1.1.168:8094/BI';
 // const BASE_URL = 'http://10.1.80.36:8011';
+const BASE_URL = 'http://218.18.115.198:8888/BI'
 
 /**后台接口地址 */
 const URLS = {
@@ -72,15 +73,15 @@ const URLS = {
 
     DATASOURCE_DATA_LIST_BY_CHART: BASE_URL + '/getChartsData', // 通过图表code获取数据列表
 
-    /***************************************数据接配置***************************************/
+    /***************************************数据接配置***************************************/
 
-    DATACONNECT_ADD: BASE_URL + '/DataBase/inputDatabases', // 新增数据接配置
+    DATACONNECT_ADD: BASE_URL + '/DataBase/inputDatabases', // 新增数据接配置
 
-    DATACONNECT_UPDATE: BASE_URL + '/DataBase/updatabases', // 修改数据接配置
+    DATACONNECT_UPDATE: BASE_URL + '/DataBase/updatabases', // 修改数据接配置
 
-    DATACONNECT_DELETE: BASE_URL + '/DataBase/delDatabases', // 删除数据接配置
+    DATACONNECT_DELETE: BASE_URL + '/DataBase/delDatabases', // 删除数据接配置
 
-    DATACONNECT_LIST: BASE_URL + '/DataBase/getDatabases', // 获得数据接列表
+    DATACONNECT_LIST: BASE_URL + '/DataBase/getDatabases', // 获得数据接列表
 
     DATACONNECT_VALIDATE: BASE_URL + '/DataBase/testConnect', // 校验数据库连接是否合法
 
@@ -130,20 +131,24 @@ const URLS = {
 
     GROUP_DATASOURCE_LIST: BASE_URL + '/Connector/getConnectorGroup', // 获得数据源所有分组/子分组
 
-    GROUP_DATASOURCE_ADD: BASE_URL + '/Connector/setConnectorGroup', // 新增数据源分组/子分组
+    GROUP_DATASOURCE_BATCH_SET: BASE_URL + '/Connector/batchUpdateConnectorGroup', // 数据源分组批量设置
 
-    GROUP_DATASOURCE_UPDATE: BASE_URL + '/Connector/updataDataConnectorGroup', // 修改数据源分组信息
+    GROUP_DATASOURCE_ADD: BASE_URL + '/Connector/setConnectorGroup', // 新增数据源分组/子分组(已废弃)
+
+    GROUP_DATASOURCE_UPDATE: BASE_URL + '/Connector/updataDataConnectorGroup', // 修改数据源分组信息(已废弃)
 
     GROUP_DATASOURCE_SET_GROUP: BASE_URL + '/Connector/updateConnectConfigGroup', // 设置数据源所属分组
 
-    GROUP_DATASOURCE_LIST_UPDATE: BASE_URL + '/Connector/updataConnectorGroup', // 批量修改数据源分组信息
+    GROUP_DATASOURCE_LIST_UPDATE: BASE_URL + '/Connector/updataConnectorGroup', // 批量修改数据源分组信息(已废弃)
 
-    GROUP_DATASOURCE_DELETE: BASE_URL + '/Connector/delDataConnectorGroup', // 删除数据源分组/子分组
+    GROUP_DATASOURCE_DELETE: BASE_URL + '/Connector/delDataConnectorGroup', // 删除数据源分组/子分组(已废弃)
 
     /***************************************图表分组***************************************/
 
     GROUP_CHART_LIST: BASE_URL + '/getChartsGroup', // 获得图表所有分组/子分组
 
+    GROUP_CHART_BATCH_SET: BASE_URL + '/batchUpdateChartsGroup', // 图表分组批量设置
+
     GROUP_CHART_ADD: BASE_URL + '/setChartsGroup', // 新增图表分组/子分组
 
     GROUP_CHART_UPDATE: BASE_URL + '/updataChartsGroup', // 修改图表分组信息

+ 1 - 1
src/index.js

@@ -36,7 +36,7 @@ app.use(createLoading());
 
 // 3. Model
 app.model(mainModel); // 通用action
-app.model(dataConnect); // 数据
+app.model(dataConnect); // 数据
 app.model(dataSource); // 数据源
 app.model(dataSourceDetail); // 数据源详细数据
 app.model(chart);  // 图表

+ 24 - 9
src/models/chart.js

@@ -12,10 +12,7 @@ export default {
             typeLabel: 'name',
             changeSearchType:false,
             groupList: [],
-            currentGroup: [{
-                code: 'all',
-                label: '全部分组'
-            }],
+            currentGroup: null,
             groupDirty: false
         },
     },
@@ -46,10 +43,8 @@ export default {
          * 设置数据源过滤用分组
          */
         setCurrentGroup(state, action) {
-            const { group1, group2 } = action;
-            let g = [group1];
-            group2 && g.push(group2);
-            return Object.assign({}, state, {currentGroup: g});
+            const { group } = action;
+            return Object.assign({}, state, {currentGroup: group});
         },
         addGroup(state, action) {
             const { group } = action;
@@ -412,6 +407,26 @@ export default {
                 message.error('读取图表分组列表错误: ' + e);
             }
         },
+        *remoteSetGroups(action, { select, call, put }) {
+            const { aGroups, mGroups, dGroups } = action;
+            const body = aGroups.concat(mGroups).concat(dGroups).map(g => ({
+                id: g.operate === 'add' ? null : g.code,
+                fatherId: g.pcode,
+                groupIndex: g.index,
+                operate: g.operate,
+                groupName: g.label
+            }))
+            const res = yield call(service.fetch, {
+                url: URLS.GROUP_CHART_BATCH_SET,
+                body
+            });
+            if(!res.err && res.data.code > 0) {
+                message.success('修改分组信息成功');
+                yield put({ type: 'remoteGroupList', mandatory: true });
+            }else {
+                message.error('修改分组信息失败: ' + (res.data.msg)); 
+            }
+        },
         /**
          * 新增分组/子分组,需要传入父节点code
          */
@@ -699,7 +714,7 @@ export default {
         /**
          * 设置图表所属分组
          */
-        *remoteSetChartGroup(action, { select, call, put }) {
+        *remoteSetGroup(action, { select, call, put }) {
             const { chart, group } = action;
             const chartCode = chart.code;
             const groupCode = group.code;

+ 2 - 4
src/models/dataConnect.js

@@ -138,7 +138,6 @@ export default {
                     method: 'GET',
                     body
                 });
-                console.log('请求数据连接配置列表', body,  res);
                 if(!res.err && res.data.code > 0) {
                     let data = res.data.data.list.map((r, i) => {
                         return {
@@ -157,11 +156,10 @@ export default {
                     });
                     yield put({ type: 'list', data });
                 }else {
-                    message.error('读取数据接配置列表错误: ' + (res.err || res.data.msg));
+                    message.error('读取数据接配置列表错误: ' + (res.err || res.data.msg));
                 }
             }catch(e) {
-                message.error('读取数据连接配置列表错误: ' + e);
-                console.error(body, e);
+                message.error('读取数据链接配置列表错误: ' + e);
             }
         },
         *remoteValidate(action, { select, call, put, takeEvery, takeLatest }) {

+ 25 - 20
src/models/dataSource.js

@@ -13,10 +13,7 @@ export default {
             invalidSQL: false,
             changeSearchType:false,
             groupList: [],
-            currentGroup: [{
-                code: 'all',
-                label: '全部分组'
-            }],
+            currentGroup: null,
             groupDirty: false
         }
     },
@@ -52,10 +49,8 @@ export default {
          * 设置数据源过滤用分组
          */
         setCurrentGroup(state, action) {
-            const { group1, group2 } = action;
-            let g = [group1];
-            group2 && g.push(group2);
-            return Object.assign({}, state, {currentGroup: g});
+            const { group } = action;
+            return Object.assign({}, state, {currentGroup: group});
         },
         addGroup(state, action) {
             const { group } = action;
@@ -376,7 +371,6 @@ export default {
                     url: URLS.GROUP_DATASOURCE_LIST,
                 });
                 
-                console.log('请求数据源分组列表', res);
                 if(!res.err && res.data.code > 0) {
                     const resData = res.data.data;
                     let data = resData.map(d => {
@@ -392,10 +386,30 @@ export default {
                     message.error('请求数据源列表错误: ' + (res.err || res.data.msg));
                 }
             }catch(e) {
-                console.log(e);
                 message.error('请求数据源列表错误: ' + e);
             }
         },
+
+        *remoteSetGroups(action, { select, call, put }) {
+            const { aGroups, mGroups, dGroups } = action;
+            const body = aGroups.concat(mGroups).concat(dGroups).map(g => ({
+                id: g.operate === 'add' ? null : g.code,
+                fatherId: g.pcode,
+                groupIndex: g.index,
+                operate: g.operate,
+                groupName: g.label
+            }))
+            const res = yield call(service.fetch, {
+                url: URLS.GROUP_DATASOURCE_BATCH_SET,
+                body
+            });
+            if(!res.err && res.data.code > 0) {
+                message.success('修改分组信息成功');
+                yield put({ type: 'remoteGroupList', mandatory: true });
+            }else {
+                message.error('修改分组信息失败: ' + (res.data.msg)); 
+            }
+        },
         /**
          * 新增分组/子分组,需要传入父节点code
          */
@@ -425,7 +439,6 @@ export default {
                     url: URLS.GROUP_DATASOURCE_ADD,
                     body: body
                 });
-                console.log('新增数据源分组', body, res);
                 if(!res.err && res.data.code > 0) {
                     let group = {
                         code: res.data.data + '',
@@ -438,7 +451,6 @@ export default {
                     message.error('新增分组失败: ' + (res.err || res.data.msg));
                 }
             }catch(e) {
-                console.log(e);
                 message.error('新增分组失败: ' + e);
             }
         },
@@ -447,13 +459,8 @@ export default {
          */
         *remoteModifyGroup(action, { select, call, put }) {
             try {
-                const dataSource = yield select((state) => state.present.dataSource);
-                const groupDirty = dataSource.groupDirty;
                 const group = action.group;
 
-                if(!groupDirty) { // 如果属性无改动则取消修改请求
-                    return;
-                }
                 let body = {
                     id: group.code,
                     fatherId: group.pcode,
@@ -679,7 +686,7 @@ export default {
         /**
          * 为数据源设置所属分组
          */
-        *remoteSetDataSourceGroup(action, { select, call, put }) {
+        *remoteSetGroup(action, { select, call, put }) {
 
             const { dataSource, group } = action;
             const dataSourceCode = dataSource.code;
@@ -693,14 +700,12 @@ export default {
                     url: URLS.GROUP_DATASOURCE_SET_GROUP,
                     body: body
                 });
-                console.log('设置数据源所属分组', body, res);
                 if(!res.err && res.data.code > 0) {
                     yield put({ type: 'setDataSourceGroup', dataSourceCode, groupCode });
                 }else {
                     message.error('设置分组失败: ' + (res.err || res.data.msg));
                 }
             } catch(e) {
-                console.log(e);
                 message.error('设置分组失败: ' + e);
             }
         },

+ 31 - 3
src/utils/baseUtils.js

@@ -124,8 +124,6 @@ function delay(timeout) {
     });
 }
 
-export { remove, isEqual, getUrlParam, hashcode, delay, dateFormat };
-
 function dateFormat(date, fmt) {
     date = new Date(date);
     var o = {
@@ -143,4 +141,34 @@ function dateFormat(date, fmt) {
         if (new RegExp("(" + k + ")").test(fmt))
             fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
     return fmt;
-}
+}
+
+/**
+ * 将数组转为树
+ * @param data: 数组
+ * @param parent: 父节点
+ * @param $id: 节点id名
+ * @param $pid: 节点父节点id名
+ * @param $sub: 子节点名
+ */
+function arrayToTree(data, parent, $id, $pid, $sub) {
+    let tree = [];
+    let temp;
+    if(data instanceof Array) {
+        for (var i = 0; i < data.length; i++) {
+            if (data[i][$pid] === parent) {
+                let obj = data[i];
+                temp = arrayToTree(data, data[i][$id], $id, $pid, $sub);
+                if (temp.length > 0) {
+                    obj[$sub] = temp;
+                }else {
+                    delete obj[$sub];
+                }
+                tree.push(obj);
+            }
+        }
+    }
+    return tree;
+}
+
+export { remove, isEqual, getUrlParam, hashcode, delay, dateFormat, arrayToTree };