dataSource.jsx 21 KB

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