dataSource.jsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. import React from 'react'
  2. import { Layout, Row, Col, Input, Button, Table, Icon, Menu, Dropdown, Card, Breadcrumb, Popover, Tree, Tag } from 'antd'
  3. import { connect } from 'dva'
  4. import './dataSource.less'
  5. import { dateFormat } from '../../utils/baseUtils'
  6. import GroupSelector from './groupSelector'
  7. import TransferBox from '../chart/transferBox';
  8. import DeleteBox from '../common/deleteBox';
  9. const { Content } = Layout
  10. const { Search } = Input
  11. const { TreeNode } = Tree
  12. class DataSource extends React.Component {
  13. constructor(props) {
  14. super(props);
  15. this.state = {
  16. selectedRecord: null, // 当前选中的dataSource
  17. visibleGroupMenu: false, // 显示分组菜单
  18. visibleSetGroupMenu: false, //
  19. visibleTransferBox: false,
  20. visibleDeleteBox: false,
  21. groupEditing: false, // 是否处于编辑状态
  22. }
  23. };
  24. hideTransferBox = () => {
  25. this.setState({ visibleTransferBox: false})
  26. }
  27. componentDidMount() {
  28. const { dispatch } = this.props;
  29. this.setScrollTableHeight();
  30. dispatch({ type: 'dataSource/fetchList' });
  31. dispatch({ type: 'dataSource/remoteGroupList' });
  32. }
  33. /**
  34. * 根据视图设置表格高度以呈现滚动条
  35. */
  36. setScrollTableHeight() {
  37. const mainContent = document.getElementsByClassName('main-content')[0];
  38. const toolbar = mainContent.getElementsByClassName('datasource-tools')[0];
  39. const tableHeader = mainContent.getElementsByClassName('ant-table-header')[0];
  40. const tableBody = mainContent.getElementsByClassName('ant-table-body')[0];
  41. tableBody.style.maxHeight=`${mainContent.offsetHeight - toolbar.offsetHeight - tableHeader.offsetHeight - 58}px`;
  42. }
  43. onGroup() {
  44. const { dataSource } = this.props;
  45. const groupList = dataSource.groupList;
  46. const list = dataSource.list;
  47. const currentGroup = dataSource.currentGroup;
  48. let groupFilter = groupList.concat({ code: '-1', label: '未分组' }).filter(g => (
  49. currentGroup[0].code === 'all' ||
  50. (
  51. currentGroup[0].code === '-1' ? g.code === '-1' : (
  52. currentGroup[1] ? (g.code === currentGroup[1].code) :
  53. (
  54. g.code === currentGroup[0].code ||
  55. g.pcode === currentGroup[0].code
  56. )
  57. )
  58. )
  59. )).map(g => g.code);
  60. return list.filter(l => groupFilter.indexOf(l.groupCode+'') !== -1);
  61. }
  62. onSearch(list, text) {
  63. const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
  64. let filterLabel = (text || '').replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
  65. return list.map(l => {
  66. let o = Object.assign({}, l);
  67. let reg = new RegExp('('+ filterLabel +'){1}', 'ig');
  68. if(o.name && o.name.search(reg) !== -1) {
  69. return o;
  70. }else if(o.description && o.description.search(reg) !== -1) {
  71. return o;
  72. }else {
  73. return null
  74. }
  75. }).filter(a => a!==null);
  76. }
  77. onSort(list) {
  78. return list.sort((a, b) => {
  79. return new Date(b.createTime) - new Date(a.createTime);
  80. });
  81. }
  82. handleVisibleChange = (flag) => {
  83. this.setState({ visibleGroupMenu: flag });
  84. }
  85. createGroupMenu = (selectedRecord) => {
  86. const { dataSource, dispatch } = this.props;
  87. const groupList = dataSource.groupList;
  88. const pGroups = groupList.filter(d => d.pcode === '-1').sort((a, b) => a.index - b.index);
  89. const cGroups = groupList.filter(d => d.pcode !== '-1');
  90. let allGroups = selectedRecord ? [
  91. { code: '-1', label: '未分组' }
  92. ].concat(pGroups) : [
  93. { code: 'all', label: '全部分组' },
  94. { code: '-1', label: '未分组' }
  95. ].concat(pGroups);
  96. return allGroups.map(p => {
  97. let c = cGroups.filter(c => c.pcode === p.code).sort((a, b) => a.index - b.index);
  98. return c.length > 0 ? (
  99. <Menu.SubMenu key={p.code} title={<span style={{ fontWeight: selectedRecord ?
  100. (p.code + '' === selectedRecord.groupCode + '' ? 'bold' : (
  101. c.find(ch => ch.code + '' === selectedRecord.groupCode + '') && c.find(ch => ch.code + '' === selectedRecord.groupCode + '').pcode === p.code ? 'bold' : 'normal'
  102. ))
  103. : dataSource.currentGroup[0].code === p.code ? 'bold' : 'normal'
  104. }}>{p.label}</span>} onTitleClick={(item) => {
  105. dispatch({ type: 'dataSource/setCurrentGroup', group1: p });
  106. if (selectedRecord) {
  107. dispatch({ type: 'dataSource/remoteSetDataSourceGroup', dataSource: selectedRecord, group: p });
  108. }
  109. this.hideGroupMenu();
  110. }}>
  111. {c.map(c => {
  112. return (<Menu.Item key={c.code} onClick={(item) => {
  113. dispatch({ type: 'dataSource/setCurrentGroup', group1: p, group2: c });
  114. if(selectedRecord) {
  115. dispatch({ type: 'dataSource/remoteSetDataSourceGroup', dataSource: selectedRecord, group: c });
  116. }
  117. }}><span style={{ fontWeight: selectedRecord ? (
  118. selectedRecord.groupCode+'' === c.code+'' ? 'bold' : 'normal'
  119. ) : (dataSource.currentGroup[1] && (dataSource.currentGroup[1].code === c.code) ? 'bold' : 'normal') }}>{c.label}</span></Menu.Item>)
  120. })}
  121. </Menu.SubMenu>
  122. ) : (
  123. <Menu.Item key={p.code} onClick={() => {
  124. dispatch({ type: 'dataSource/setCurrentGroup', group1: p });
  125. if(selectedRecord) {
  126. dispatch({ type: 'dataSource/remoteSetDataSourceGroup', dataSource: selectedRecord, group: p });
  127. }
  128. this.hideGroupMenu();
  129. }}><span className={selectedRecord ? (
  130. selectedRecord.groupCode+'' === p.code+'' ? 'selected' : ''
  131. ) : dataSource.currentGroup[0] && (dataSource.currentGroup[0].code === p.code) ? 'selected' : ''} style={{ fontWeight: selectedRecord ? (
  132. selectedRecord.groupCode+'' === p.code+'' ? 'bold' : 'normal'
  133. ) : dataSource.currentGroup[0] && (dataSource.currentGroup[0].code === p.code) ? 'bold' : 'normal' }}>{p.label}</span></Menu.Item>
  134. );
  135. });
  136. }
  137. createSubGroupMenu = () => {
  138. const { dataSource, dispatch } = this.props;
  139. const groupList = dataSource.groupList;
  140. const parentGroup = dataSource.currentGroup[0];
  141. const children = groupList.filter(d => d.pcode === parentGroup.code);
  142. const subGroup = dataSource.currentGroup[1];
  143. return children.map(c => {
  144. return (
  145. <Menu.Item key={c.code} onClick={() => {
  146. dispatch({ type: 'dataSource/setCurrentGroup', group1: parentGroup, group2: c });
  147. }}><span className={subGroup && (subGroup.code === c.code) ? 'selected' : ''} style={{ fontWeight: subGroup && (subGroup.code === c.code) ? 'bold' : 'normal' }}>{c.label}</span></Menu.Item>
  148. );
  149. })
  150. }
  151. createGroupTree(modify) {
  152. const { dispatch, dataSource } = this.props;
  153. const { groupEditing } = this.state;
  154. const groupList = dataSource.groupList;
  155. let parent = groupList.filter(d => d.pcode === '-1').sort((a, b) => a.index - b.index);
  156. let children = groupList.filter(d => d.pcode !== '-1');
  157. let groupTree = parent.map(p => {
  158. return (
  159. <TreeNode disabled={groupEditing} title={
  160. modify ? (<div><Icon style={{ cursor: 'move' }} type='drag'/>
  161. <Input value={p.label} size='small' focus={'true'} onFocus={() => {
  162. this.setState({
  163. groupEditing: true
  164. });
  165. }} onChange={(e) => {
  166. dispatch({ type: 'dataSource/modifyGroup', group: {...p, label:e.target.value} });
  167. }} onBlur={(e) => {
  168. this.setState({
  169. groupEditing: false
  170. });
  171. dispatch({ type: 'dataSource/remoteModifyGroup', group: {...p, label:e.target.value} });
  172. }} onPressEnter={(e) => {
  173. dispatch({ type: 'dataSource/remoteModifyGroup', group: {...p, label:e.target.value} });
  174. }} /><Icon type='plus-circle-o' onClick={() => {
  175. dispatch({ type: 'dataSource/remoteAddGroup', pgroup: p });
  176. }}/><Icon type='minus-circle' onClick={() => {
  177. dispatch({ type: 'dataSource/remoteDeleteGroup', group: p });
  178. }}/></div>) : p.label} key={p.code}>
  179. {
  180. children.filter(c => c.pcode === p.code).sort((a, b) => a.index - b.index).map(c => {
  181. return (
  182. <TreeNode disabled={groupEditing} title={
  183. modify ? (<div><Icon style={{ cursor: 'move' }} type='drag'/>
  184. <Input value={c.label} size='small' onFocus={() => {
  185. this.setState({
  186. groupEditing: true
  187. });
  188. }} onChange={(e) => {
  189. dispatch({ type: 'dataSource/modifyGroup', group: {...c, label:e.target.value} });
  190. }} onBlur={(e) => {
  191. this.setState({
  192. groupEditing: false
  193. });
  194. dispatch({ type: 'dataSource/remoteModifyGroup', group: {...c, label:e.target.value} });
  195. }} onPressEnter={(e) => {
  196. dispatch({ type: 'dataSource/remoteModifyGroup', group: {...c, label:e.target.value} });
  197. }} onCompositionEnd={(e) => {
  198. }}/><Icon type='minus-circle' onClick={() => {
  199. dispatch({ type: 'dataSource/remoteDeleteGroup', group: c });
  200. }}/></div>) : p.label
  201. } key={c.code} />
  202. )
  203. })
  204. }
  205. </TreeNode>
  206. )
  207. });
  208. return groupTree;
  209. }
  210. hideGroupMenu = () => {
  211. this.setState({
  212. visibleGroupMenu: false
  213. });
  214. }
  215. onDrop = (info) => {
  216. const { dispatch } = this.props;
  217. const dropCode = info.node.props.eventKey;
  218. const dragCode = info.dragNode.props.eventKey;
  219. const dropPos = info.node.props.pos.split('-');
  220. const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); // -1/0/1 -> 兄/子/弟
  221. dispatch({ type: 'dataSource/remoteMoveGroup', dragCode, dropCode, dropPosition });
  222. }
  223. render() {
  224. const { dataSource, dispatch } = this.props;
  225. const { selectedRecord, visibleTransferBox, visibleDeleteBox } = this.state;
  226. const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
  227. let filterLabel = dataSource.filterLabel.replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1'); // 添加转义符号
  228. const TAG_COLOR = ['blue'];
  229. // const TAG_COLOR1 = ['magenta', 'red', 'volcano', 'orange', 'gold', 'lime', 'green', 'cyan', 'blue', 'geekblue', 'purple'];
  230. const moreOperatingMenu = (
  231. <Menu className='operationmenu' visible={true}>
  232. <Menu.Item
  233. onClick={() => {
  234. dispatch({ type: 'chartDesigner/remoteQucikAdd', dataSource: selectedRecord });
  235. }}
  236. >
  237. <Icon type="file-add" />创建图表
  238. </Menu.Item>
  239. <Menu.Item
  240. onClick={(e) => {
  241. dispatch({ type: 'dataSource/resetNewModel' });
  242. let selectedModel = dataSource.list.find((i) => { return i.code === selectedRecord.code })
  243. dispatch({type: 'main/redirect', path: {pathname: '/datasource/'+ selectedModel.type +'/' + selectedModel.code + '/base'}});
  244. }}>
  245. <Icon type="info-circle-o" />属性设置
  246. </Menu.Item>
  247. <Menu.Item><Icon type="search" />预览数据</Menu.Item>
  248. <Menu.Divider />
  249. <Menu.SubMenu className='setgroupmenu' title={<div><Icon style={{ marginRight: '6px' }} type='profile' />移动到</div>}>
  250. {this.createGroupMenu(selectedRecord)}
  251. </Menu.SubMenu>
  252. <Menu.Divider />
  253. <Menu.Item
  254. onClick={()=>{
  255. this.setState({ visibleTransferBox: true})
  256. }}
  257. >
  258. <Icon type="swap" />移交
  259. </Menu.Item>
  260. <Menu.Item
  261. onClick={(e) => {
  262. this.setState({ visibleDeleteBox: true})
  263. }}
  264. >
  265. <Icon type="delete" />删除
  266. </Menu.Item>
  267. </Menu>
  268. );
  269. const dataSourceColumns = [{
  270. title: '名称',
  271. dataIndex: 'name',
  272. key: 'name',
  273. width: 100,
  274. render: (text, record) => {
  275. return <div className='datasource-name'>
  276. <div className={`datasource-type type-${record.type.key}`}></div>
  277. <div>
  278. <span>
  279. { filterLabel ?
  280. ((text || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
  281. return (
  282. fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
  283. <span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
  284. fragment
  285. )
  286. }
  287. )) : text
  288. }
  289. </span>
  290. </div>
  291. </div>
  292. }
  293. }, {
  294. title: '说明',
  295. dataIndex: 'description',
  296. key: 'description',
  297. width: 200,
  298. render: (text, record) => {
  299. return (
  300. <span>
  301. { filterLabel ?
  302. ((text || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
  303. return (
  304. fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
  305. <span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
  306. fragment
  307. )
  308. }
  309. )) : text
  310. }
  311. </span>
  312. )
  313. }
  314. }, {
  315. title: '创建人',
  316. dataIndex: 'creator',
  317. key: 'creator',
  318. width: 100
  319. }, {
  320. title: '创建时间',
  321. dataIndex: 'createTime',
  322. key: 'createTime',
  323. render: (text, record) => dateFormat(text, 'yyyy-MM-dd hh:mm:ss'),
  324. width: 100
  325. }, {
  326. title: '操作',
  327. key: 'action',
  328. render: (text, record, index) => (
  329. <Dropdown code={record.code} overlay={moreOperatingMenu} trigger={['click']} >
  330. <Icon type="setting" />
  331. </Dropdown>
  332. ),
  333. width: 50
  334. }];
  335. return (
  336. <Layout className='datasource-view'>
  337. <Content>
  338. <Card className='datasource-body' title={
  339. <Row className='datasource-tools' type='flex' justify='space-between'>
  340. <Col style={{ display: 'flex' }}>
  341. <Popover overlayClassName='popover-group' title={
  342. <Row className='grouptree-title' type='flex' justify='space-between'>
  343. <Col>
  344. 分组管理
  345. </Col>
  346. <Col>
  347. <div className='create-group' onClick={() => {
  348. dispatch({ type: 'dataSource/remoteAddGroup' });
  349. }}>添加分组<Icon type="plus-circle-o" /></div>
  350. </Col>
  351. </Row>
  352. } trigger="click" placement="bottomLeft" content={(
  353. <Tree
  354. className='tree-group'
  355. showLine
  356. defaultExpandAll
  357. draggable
  358. onDragStart={this.onDragStart}
  359. onDrop={this.onDrop}
  360. >
  361. {
  362. this.createGroupTree(true)
  363. }
  364. </Tree>
  365. )}>
  366. <Icon type="bars" />
  367. </Popover>
  368. <Breadcrumb className='group' separator=">">
  369. <Breadcrumb.Item>
  370. <GroupSelector model={dataSource} modelName='dataSource'>
  371. <Tag color={TAG_COLOR[Math.ceil(Math.random()*TAG_COLOR.length) - 1]} >
  372. {dataSource.currentGroup[0].label}
  373. </Tag>
  374. </GroupSelector>
  375. </Breadcrumb.Item>
  376. {dataSource.currentGroup[1] && (<Breadcrumb.Item>
  377. <GroupSelector model={dataSource} modelName='dataSource'>
  378. <Tag color={TAG_COLOR[Math.ceil(Math.random()*TAG_COLOR.length) - 1]}>
  379. {dataSource.currentGroup[1].label}
  380. </Tag>
  381. </GroupSelector>
  382. </Breadcrumb.Item>)}
  383. </Breadcrumb>
  384. </Col>
  385. <Col className='search'>
  386. <Col>
  387. <Search
  388. value={dataSource.filterLabel}
  389. placeholder="请输入关键字"
  390. onChange={e => {
  391. dispatch({ type: 'dataSource/setFilterLabel', label: e.target.value });
  392. }}
  393. />
  394. </Col>
  395. <Col>
  396. <Dropdown overlay={(
  397. <Menu onClick={(item, key, keyPath) => {
  398. const type = item.key;
  399. dispatch({ type: 'dataSource/resetNewModel' });
  400. dispatch({ type: 'dataConnect/resetSelected' });
  401. dispatch({ type: 'dataSource/setNewModelField', name: 'type', value: type });
  402. dispatch({type: 'main/redirect', path: {pathname: '/datasource/'+ type +'/create/base'}});
  403. }}>
  404. <Menu.Item key='database'>数据库</Menu.Item>
  405. <Menu.Item key='file'>文件</Menu.Item>
  406. </Menu>
  407. )} trigger={['click']}>
  408. <Button>
  409. <Icon type="plus" />添加数据源
  410. </Button>
  411. </Dropdown>
  412. </Col>
  413. </Col>
  414. </Row>
  415. }>
  416. <Table
  417. className='datasource-table datasource-table'
  418. columns={dataSourceColumns}
  419. dataSource={
  420. this.onSort(
  421. this.onSearch(this.onGroup(), dataSource.filterLabel)
  422. )
  423. }
  424. size='small'
  425. scroll={{x: false, y: true}}
  426. pagination={false}
  427. onRow={(record) => {
  428. return {
  429. onClick: () => {this.setState({ selectedRecord: record})}
  430. }
  431. }}
  432. />
  433. <TransferBox
  434. visibleTransferBox={visibleTransferBox}
  435. onOk={(obj) => {
  436. console.log(obj);
  437. }}
  438. hideBox={() => {
  439. this.setState({
  440. visibleTransferBox: false
  441. })
  442. }}
  443. />
  444. <DeleteBox
  445. visibleDeleteBox={visibleDeleteBox}
  446. type='datasource'
  447. hideBox={() => {
  448. this.setState({
  449. visibleDeleteBox: false
  450. })
  451. }}
  452. selectedRecord={selectedRecord}
  453. onOk={() =>{
  454. dispatch({ type: 'dataSource/remoteDelete', code: selectedRecord.code })
  455. this.setState({visibleDeleteBox: false})
  456. }} />
  457. </Card>
  458. </Content>
  459. </Layout>
  460. )
  461. }
  462. }
  463. function mapStateToProps({present: {dataSource, dataConnect}}) {
  464. return { dataSource, dataConnect }
  465. }
  466. export default connect(mapStateToProps)(DataSource)