list.jsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. import React from 'react'
  2. import { Layout, Row, Col, Button, Table, Icon, Menu, Dropdown, Card, Breadcrumb, Tag, Checkbox, Select } from 'antd'
  3. import { connect } from 'dva'
  4. import { arrayToTree, dateFormat } from '../../utils/baseUtils'
  5. import GroupManagementBox from '../common/groupManageMentBox/box'
  6. import GroupSelector from '../common/groupSelector/popover'
  7. import TransferBox from '../common/selectUserBox/selectUserBox'
  8. import CopyBox from './copyBox'
  9. import DeleteBox from '../common/deleteBox/deleteBox'
  10. import DataPreview from '../common/dataPreview/dataPreview'
  11. import ListFilter from '../common/listFilter/index'
  12. import './list.less'
  13. const { Content } = Layout
  14. const { Option } = Select
  15. class DataSource extends React.Component {
  16. constructor(props) {
  17. super(props);
  18. this.state = {
  19. selectedRecord: null, // 当前选中的dataSource
  20. visibleGroupMenu: false, // 显示分组菜单
  21. visibleTransferBox: false,
  22. visibleDeleteBox: false,
  23. visibleCopyBox: false,
  24. visibleDataPreviewBox: false, // 显示数据预览
  25. visibleGroupManageMentBox: false, // 显示分组管理
  26. noGroup: false, // 显示未分组数据
  27. }
  28. };
  29. hideTransferBox = () => {
  30. this.setState({ visibleTransferBox: false})
  31. }
  32. componentDidMount() {
  33. const { dispatch } = this.props;
  34. this.setScrollTableHeight();
  35. dispatch({ type: 'dataSource/fetchList' });
  36. dispatch({ type: 'dataSource/remoteGroupList' });
  37. }
  38. /**
  39. * 根据视图设置表格高度以呈现滚动条
  40. */
  41. setScrollTableHeight() {
  42. const mainContent = document.getElementsByClassName('main-content')[0];
  43. const toolbar = mainContent.getElementsByClassName('datasource-tools')[0];
  44. const tableHeader = mainContent.getElementsByClassName('ant-table-header')[0];
  45. const tableBody = mainContent.getElementsByClassName('ant-table-body')[0];
  46. tableBody.style.maxHeight=`${mainContent.offsetHeight - toolbar.offsetHeight - tableHeader.offsetHeight - 58}px`;
  47. }
  48. onGroup() {
  49. const { dataSource } = this.props;
  50. const { noGroup } = this.state;
  51. const { currentGroup, list } = dataSource;
  52. if(noGroup) {
  53. return list.filter(l => l.groupCode === '-1' );
  54. }else if(currentGroup) {
  55. return list.filter(l => l.groupCode === currentGroup.code );
  56. }else {
  57. return list;
  58. }
  59. }
  60. onSearch(list, dataSource) {
  61. const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
  62. let filterItem = dataSource.filterItem
  63. let filterLabel = dataSource.filterLabel ? (dataSource.filterLabel + '').replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') : ''; // 添加转义符号
  64. let filterReg = new RegExp('('+ filterLabel +'){1}', 'ig');
  65. return list.map(l => {
  66. let o = Object.assign({}, l);
  67. if(filterItem.type === 'date') {
  68. if(filterLabel===""){
  69. return o;
  70. }else if(filterLabel.indexOf('#')>-1){
  71. let start = filterLabel.split('#')[0]
  72. let end = filterLabel.split('#')[1]
  73. let nowTime = o[filterItem.name].getTime();
  74. if(nowTime>=start && nowTime<=end){
  75. return o;
  76. }
  77. return null
  78. }else{
  79. return null
  80. }
  81. }else {
  82. let arr = filterItem.name.split('.');
  83. let v = o;
  84. for(let i = 0; i < arr.length; i++) {
  85. v = v[arr[i]]
  86. }
  87. return ((v + '').search(filterReg) > -1) ? o : null
  88. }
  89. }).filter(a => a!==null);
  90. }
  91. onSort(list) {
  92. return list.sort((a, b) => {
  93. return new Date(b.createTime) - new Date(a.createTime);
  94. });
  95. }
  96. handleVisibleChange = (flag) => {
  97. this.setState({ visibleGroupMenu: flag });
  98. }
  99. getParens = (group) => {
  100. const groupData = this.props.dataSource.groupList;
  101. let pgroups = [group];
  102. let fgroup = groupData.find(g => g.code === group.pcode);
  103. if(fgroup) {
  104. pgroups = this.getParens(fgroup).concat(pgroups);
  105. }
  106. return pgroups;
  107. }
  108. generateGroupTags = () => {
  109. const { dataSource, dispatch } = this.props;
  110. const { noGroup } = this.state;
  111. const { currentGroup } = dataSource;
  112. const pGroups = currentGroup ? [{ code: '-1', label: '全部分组' }].concat(this.getParens(currentGroup)) : [{ code: '-1', label: '全部分组' }];
  113. return <Breadcrumb className={`group${noGroup ? ' nogroup' : ''}`} separator=">">
  114. { pGroups.map(g => (
  115. <Breadcrumb.Item key={g.code}>
  116. <GroupSelector
  117. visible={this.state['visibleGroupSelector' + g.code]}
  118. handleVisibleChange={(visible) => {
  119. let obj = {};
  120. obj['visibleGroupSelector' + g.code] = visible
  121. this.setState(obj);
  122. }}
  123. pGroup={g}
  124. groupList={dataSource.groupList}
  125. selectGroup={(group) => {
  126. let obj = {};
  127. obj['visibleGroupSelector' + g.code] = false;
  128. this.setState(obj);
  129. dispatch({ type: 'dataSource/setCurrentGroup', group });
  130. }}
  131. >
  132. <Tag color={'blue'} >
  133. {g.label}
  134. </Tag>
  135. </GroupSelector>
  136. </Breadcrumb.Item>
  137. )) }
  138. </Breadcrumb>
  139. }
  140. createGroupMenu = (treeData) => {
  141. const { dispatch } = this.props;
  142. const { selectedRecord } = this.state;
  143. return treeData.map(t => {
  144. if(t.children && t.children.length > 0) {
  145. return <Menu.SubMenu
  146. key={t.code}
  147. title={selectedRecord.groupCode === t.code ? <span className='current' style={{ fontWeight: 'bold' }}>{t.label}</span> : t.label}
  148. onTitleClick={() => {
  149. dispatch({ type: 'dataSource/remoteSetGroup', dataSource: selectedRecord, group: t });
  150. dispatch({ type: 'dataSource/setCurrentGroup', group: t });
  151. }}
  152. >
  153. {this.createGroupMenu(t.children)}
  154. </Menu.SubMenu>
  155. }else {
  156. return <Menu.Item key={t.code} onClick={() => {
  157. dispatch({ type: 'dataSource/remoteSetGroup', dataSource: selectedRecord, group: t });
  158. dispatch({ type: 'dataSource/setCurrentGroup', group: t });
  159. }}>
  160. {selectedRecord.groupCode === t.code ? <span className='current' style={{ fontWeight: 'bold' }}>{t.label}</span> : t.label}
  161. </Menu.Item>
  162. }
  163. })
  164. }
  165. hideGroupMenu = () => {
  166. this.setState({
  167. visibleGroupMenu: false
  168. });
  169. }
  170. onDrop = (info) => {
  171. const { dispatch } = this.props;
  172. const dropCode = info.node.props.eventKey;
  173. const dragCode = info.dragNode.props.eventKey;
  174. const dropPos = info.node.props.pos.split('-');
  175. const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); // -1/0/1 -> 兄/子/弟
  176. dispatch({ type: 'dataSource/remoteMoveGroup', dragCode, dropCode, dropPosition });
  177. }
  178. generateFilterItems = () => {
  179. const { filterItems } = this.props.dataSource;
  180. return filterItems.map(t => <Option key={t.name} value={t.name}>{t.label}</Option>);
  181. }
  182. render() {
  183. const { main, dataSource, dispatch } = this.props;
  184. const { selectedRecord, visibleTransferBox, visibleGroupManageMentBox, visibleCopyBox, visibleDeleteBox, visibleDataPreviewBox, noGroup } = this.state;
  185. const { currentUser } = main;
  186. const treeData = arrayToTree(dataSource.groupList, '-1', 'code', 'pcode', 'children');
  187. const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
  188. let filterItem = dataSource.filterItem;
  189. let filterLabel = dataSource.filterLabel ? (dataSource.filterLabel + '').replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') : ''; // 添加转义符号
  190. const moreOperatingMenu = (
  191. <Menu className='operationmenu' visible={true}>
  192. <Menu.Item
  193. onClick={() => {
  194. dispatch({ type: 'chartDesigner/remoteQucikAdd', dataSource: selectedRecord });
  195. }}
  196. >
  197. <Icon type="file-add" />创建图表
  198. </Menu.Item>
  199. <Menu.Item onClick={() => {
  200. this.setState({
  201. visibleDataPreviewBox: true
  202. });
  203. }}><Icon type="search" />预览数据</Menu.Item>
  204. <Menu.Divider />
  205. { selectedRecord && currentUser.code === selectedRecord.creatorCode && <Menu.SubMenu className='setgroupmenu' title={<div><Icon style={{ marginRight: '6px' }} type='profile' />移动到</div>}>
  206. {[<Menu.Item key='-1' onClick={() => {
  207. dispatch({ type: 'dataSource/remoteSetGroup', dataSource: selectedRecord, group: { code: '-1'} });
  208. this.setState({
  209. noGroup: true
  210. });
  211. }}>未分组</Menu.Item>].concat(this.createGroupMenu(treeData))}
  212. </Menu.SubMenu>}
  213. <Menu.Divider />
  214. { selectedRecord && currentUser.code === selectedRecord.creatorCode && <Menu.Item
  215. onClick={()=>{
  216. this.setState({ visibleTransferBox: true})
  217. }}
  218. >
  219. <Icon type="swap" />移交
  220. </Menu.Item>}
  221. <Menu.Divider />
  222. {/* { selectedRecord && <Menu.Item
  223. onClick={()=>{
  224. this.setState({ visibleCopyBox: true})
  225. }}
  226. >
  227. <Icon type="copy" />复制
  228. </Menu.Item>} */}
  229. { selectedRecord && currentUser.code === selectedRecord.creatorCode && <Menu.Item
  230. onClick={(e) => {
  231. this.setState({ visibleDeleteBox: true})
  232. }}
  233. >
  234. <Icon type="delete" />删除
  235. </Menu.Item>}
  236. </Menu>
  237. );
  238. const dataSourceColumns = [{
  239. title: '名称',
  240. dataIndex: 'name',
  241. key: 'name',
  242. width: 100,
  243. render: (text, record) => {
  244. return <div className='datasource-name'>
  245. <div className={`datasource-type`}>
  246. {record.type === 'database' ? <Icon type="database" theme="outlined" /> : <Icon type="file-excel" theme="outlined" />}
  247. </div>
  248. <div>
  249. <span style={{ color: '#1890ff', cursor: 'pointer' }} onClick={() => {
  250. dispatch({ type: 'dataSource/resetNewModel' });
  251. dispatch({type: 'main/redirect', path: {pathname: '/workshop/datasource/'+ record.type +'/' + record.code + '/base'}});
  252. }}>
  253. { (filterItem.name === 'name' && filterLabel) ?
  254. ((text || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
  255. return (
  256. fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
  257. <span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
  258. fragment
  259. )
  260. }
  261. )) : text
  262. }
  263. </span>
  264. </div>
  265. </div>
  266. }
  267. }, {
  268. title: '数据链接',
  269. dataIndex: 'dbConfig.name',
  270. key: 'dbConfig.name',
  271. width: 100
  272. }, {
  273. title: '说明',
  274. dataIndex: 'description',
  275. key: 'description',
  276. width: 200,
  277. render: (text, record) => {
  278. return (
  279. <span>
  280. { (filterItem.name === 'description' && 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: 'creatorName',
  296. key: 'creatorName',
  297. width: 100,
  298. render: (text, record) => {
  299. return (
  300. <span>
  301. { (filterItem.name === 'creatorName' && 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: 'createTime',
  317. key: 'createTime',
  318. render: (text, record) => dateFormat(text, 'yyyy-MM-dd hh:mm:ss'),
  319. width: 100
  320. }, {
  321. title: '操作',
  322. key: 'action',
  323. render: (text, record, index) => (
  324. <Dropdown code={record.code} overlay={moreOperatingMenu} trigger={['click']} >
  325. <Icon type="setting" />
  326. </Dropdown>
  327. ),
  328. width: 50
  329. }];
  330. return (
  331. <Layout className='layout-datasource'>
  332. <Content>
  333. <Card className='datasource-body' title={
  334. <Row className='datasource-tools' type='flex' justify='space-between'>
  335. <Col style={{ display: 'flex', width: 'calc(100% - 324px)', overflow: 'hidden' }}>
  336. <Icon type="bars" onClick={() => {
  337. this.setState({
  338. visibleGroupManageMentBox: true
  339. });
  340. }}/>
  341. <Checkbox style={{ marginTop: '4px' }} value={noGroup} onChange={(e) => {
  342. this.setState({noGroup: e.target.checked})
  343. }}>未分组</Checkbox>
  344. { this.generateGroupTags() }
  345. </Col>
  346. <Col className='search'>
  347. <Col style={{ padding: '0 5px' }}>
  348. <ListFilter modelName='dataSource' model={dataSource} />
  349. </Col>
  350. <Col>
  351. <Button style={{ marginRight: '8px' }} onClick={() => {
  352. dispatch({ type: 'dataSource/setFilterLabel', label: '' });
  353. dispatch({ type: 'dataSource/fetchList', mandatory: true });
  354. }}>
  355. <Icon type="sync" />
  356. </Button>
  357. <Dropdown overlay={(
  358. <Menu onClick={(item, key, keyPath) => {
  359. const type = item.key;
  360. dispatch({ type: 'dataSource/resetNewModel' });
  361. dispatch({ type: 'dataConnect/resetSelected' });
  362. dispatch({ type: 'dataSource/setNewModelField', name: 'type', value: type });
  363. dispatch({type: 'main/redirect', path: {pathname: '/workshop/datasource/'+ type +'/create/base'}});
  364. }}>
  365. { currentUser.role === 'admin' && <Menu.Item key='database'>来自数据库</Menu.Item>}
  366. <Menu.Item disabled key='file'>来自文件</Menu.Item>
  367. </Menu>
  368. )} trigger={['click']}>
  369. <Button>
  370. <Icon type="plus" />添加数据源
  371. </Button>
  372. </Dropdown>
  373. </Col>
  374. </Col>
  375. </Row>
  376. }>
  377. <Table
  378. className='datasource-table'
  379. columns={dataSourceColumns}
  380. dataSource={
  381. this.onSort(
  382. this.onSearch(this.onGroup(), dataSource)
  383. )
  384. }
  385. size='small'
  386. scroll={{x: false, y: true}}
  387. pagination={false}
  388. onRow={(record) => {
  389. return {
  390. onClick: () => {this.setState({ selectedRecord: record})}
  391. }
  392. }}
  393. />
  394. {visibleGroupManageMentBox && <GroupManagementBox
  395. visibleBox={visibleGroupManageMentBox}
  396. groupData={dataSource.groupList}
  397. hideBox={() => {
  398. this.setState({
  399. visibleGroupManageMentBox: false
  400. })
  401. }}
  402. okHandler={(aGroups, mGroups, dGroups) => {
  403. dispatch({ type: 'dataSource/remoteSetGroups', aGroups, mGroups, dGroups });
  404. }}
  405. />}
  406. {visibleTransferBox && <TransferBox
  407. visibleBox={visibleTransferBox}
  408. title='选择移交对象'
  409. okHandler={(user) => {
  410. dispatch({ type: 'dataSource/transfer', dataSourceCode: this.state.selectedRecord.code, userCode: user.code });
  411. }}
  412. hideBox={() => {
  413. this.setState({
  414. visibleTransferBox: false
  415. })
  416. }}
  417. onlyAdmin={true}
  418. />}
  419. {visibleCopyBox && <CopyBox
  420. visibleBox={visibleCopyBox}
  421. currentDataSourceCode={selectedRecord.code}
  422. currentDataConnectCode={selectedRecord.dbConfig.code}
  423. hideBox={() => { this.setState({ visibleCopyBox: false })
  424. }}
  425. />}
  426. {visibleDeleteBox && <DeleteBox
  427. visibleBox={visibleDeleteBox}
  428. text={<div><span>确定要删除数据源【{selectedRecord.name}】吗?</span></div>}
  429. hideBox={() => {
  430. this.setState({
  431. visibleDeleteBox: false
  432. })
  433. }}
  434. okHandler={() =>{
  435. dispatch({ type: 'dataSource/remoteDelete', code: selectedRecord.code }).then(() => {
  436. dispatch({ type: 'chart/fetchList', mandatory: true });
  437. })
  438. }}
  439. />}
  440. {visibleDataPreviewBox && <DataPreview
  441. visibleBox={visibleDataPreviewBox}
  442. hideBox={() => {
  443. this.setState({
  444. visibleDataPreviewBox: false
  445. });
  446. }}
  447. fetchFunction={(page, pageSize) => {
  448. dispatch({ type: 'dataSource/remoteDataList', code: selectedRecord.code, page, pageSize });
  449. }}
  450. />}
  451. </Card>
  452. </Content>
  453. </Layout>
  454. )
  455. }
  456. }
  457. function mapStateToProps({present: {main, dataSource, dataConnect}}) {
  458. return { main, dataSource, dataConnect }
  459. }
  460. export default connect(mapStateToProps)(DataSource)