list.jsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. import React from 'react'
  2. import { Layout, Button, Icon, Table, Menu, Dropdown, Card, Col, Row, Select } from 'antd'
  3. import { connect } from 'dva'
  4. import TransferBox from '../common/selectUserBox/selectUserBox';
  5. import AccessObjectBox from '../common/accessObjectBox/accessObjectBox'
  6. import { dateFormat } from '../../utils/baseUtils'
  7. import DeleteBox from '../common/deleteBox/deleteBox'
  8. import ShareBox from './shareBox'
  9. import CopyBox from './copyBox'
  10. import ListFilter from '../common/listFilter/index'
  11. import Loading from '../common/loading/index'
  12. import CusIcon from '../common/cusIcon/index'
  13. import './list.less'
  14. const { Content } = Layout
  15. const { Option } = Select
  16. class DashboardList extends React.Component {
  17. constructor(props) {
  18. super(props);
  19. this.state = {
  20. selectedRecord: null,
  21. visibleChooseDataSourceBox: false,
  22. visibleDistributeBox: false,
  23. visibleShareBox: false,
  24. shareUrl: '',
  25. visibleTransferBox: false,
  26. visibleGroupMenu: false, // 显示分组菜单
  27. visibleDeleteBox: false,
  28. visibleCopyBox: false,
  29. defaultSelectedGroups: [],
  30. defaultSelectedUsers: [],
  31. }
  32. }
  33. componentDidMount() {
  34. const { dashboard, dispatch } = this.props;
  35. const { currentMenu } = dashboard;
  36. this.setScrollTableHeight();
  37. if(currentMenu && currentMenu.code !== '-1') {
  38. dispatch({ type: 'dashboard/remoteMenuDashboardList', menuCode: currentMenu.code });
  39. }else {
  40. // dispatch({ type: 'dashboard/fetchList' });
  41. }
  42. let tableBody = document.getElementsByClassName('ant-table-body')[0];
  43. tableBody.scrollTo && tableBody.scrollTo(0, dashboard.listScrollTop);
  44. }
  45. componentWillUnmount() {
  46. const { dispatch } = this.props;
  47. window.removeEventListener('resize', this.setBodyWidth);
  48. dispatch({ type: 'dashboard/setField', name: 'listScrollTop', value: document.getElementsByClassName('ant-table-body')[0].scrollTop });
  49. }
  50. /**
  51. * 根据视图设置表格高度以呈现滚动条
  52. */
  53. setScrollTableHeight() {
  54. const view = document.getElementsByClassName('dashboard-view')[0];
  55. const mainContentBody = view.getElementsByClassName('dashboard-body')[0];
  56. const tableContentBody = mainContentBody.getElementsByClassName('ant-card-body')[0];
  57. const tableHeader = tableContentBody.getElementsByClassName('ant-table-header')[0];
  58. const tableBody = tableContentBody.getElementsByClassName('ant-table-body')[0];
  59. // 如果上下padding不一致就有问题了
  60. const padding = tableContentBody.children[0].getBoundingClientRect().top - tableContentBody.getBoundingClientRect().top;
  61. // table容器高度 - 上下padding - 表头高度 - 边框线宽
  62. tableBody.style.maxHeight = `${tableContentBody.offsetHeight - padding * 2 - tableHeader.offsetHeight - 2}px`;
  63. }
  64. getShareList = () => {
  65. new Promise((resolve, reject) => {
  66. const { dispatch } = this.props;
  67. const { selectedRecord } = this.state;
  68. dispatch({ type: 'dashboard/shareList', code: selectedRecord.code })
  69. .then(
  70. (resolve) => {
  71. const resData = resolve.data;
  72. const { groupNames: defaultSelectedGroups, userNames: defaultSelectedUsers } = resData;
  73. this.setState({
  74. visibleDistributeBox: true,
  75. defaultSelectedGroups: defaultSelectedGroups.map(g => ({
  76. code: g.id + '',
  77. name: g.name
  78. })),
  79. defaultSelectedUsers: defaultSelectedUsers.map(u => ({
  80. code: u.id + '',
  81. name: u.name
  82. })),
  83. });
  84. }
  85. ).catch(reject => {
  86. console.error(reject);
  87. });
  88. });
  89. }
  90. handleVisibleChange = (flag) => {
  91. this.setState({ visibleGroupMenu: flag });
  92. }
  93. hideGroupMenu = () => {
  94. this.setState({
  95. visibleGrouMenu: false
  96. });
  97. }
  98. distribute = (group, geren) => {
  99. const { dispatch } = this.props;
  100. const { selectedRecord } = this.state;
  101. let targets = group.map(g => ({
  102. code: g.code,
  103. name: g.name,
  104. isGroup: true
  105. })).concat(geren.map(g => ({
  106. code: g.code,
  107. name: g.name,
  108. isGroup: false
  109. })))
  110. dispatch({ type: 'dashboard/share', code: selectedRecord.code, targets });
  111. }
  112. onSearch(list, dashboard) {
  113. const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
  114. let filterLabel = dashboard.filterLabel ? (dashboard.filterLabel + '').replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') : ''; // 添加转义符号
  115. let filterItem = dashboard.filterItem;
  116. let filterReg = new RegExp('(' + filterLabel + '){1}', 'ig');
  117. return list.map(l => {
  118. let o = Object.assign({}, l);
  119. if(filterItem.type === 'date') {
  120. if(filterLabel===""){
  121. return o;
  122. }else if(filterLabel.indexOf('#')>-1){
  123. let start = filterLabel.split('#')[0]
  124. let end = filterLabel.split('#')[1]
  125. let nowTime = new Date(o[filterItem.name]).getTime();
  126. if(nowTime>=start && nowTime<=end){
  127. return o;
  128. }
  129. return null
  130. }else{
  131. return null
  132. }
  133. }else {
  134. return ((o[filterItem.name] + '').search(filterReg) > -1) ? o : null
  135. }
  136. }).filter(a => a!==null);
  137. }
  138. onSort(list) {
  139. return list.sort((a, b) => {
  140. return new Date(b.createTime) - new Date(a.createTime);
  141. });
  142. }
  143. generateFilterItems = () => {
  144. const { filterItems } = this.props.dashboard;
  145. return filterItems.map(t => <Option key={t.name} value={t.name}>{t.label}</Option>);
  146. }
  147. generateMenuItems = (menuTree) => {
  148. const { dispatch } = this.props;
  149. const { selectedRecord } = this.state;
  150. return menuTree.filter(t => t.type === 'menu').map(t => {
  151. if(t.children && t.children.filter(c => c.type === 'menu').length > 0) {
  152. return <Menu.SubMenu
  153. key={t.code}
  154. title={selectedRecord.menuCode === t.code ? <span className='current' style={{ fontWeight: 'bold' }}>{t.name}</span> : t.name}
  155. onTitleClick={() => {
  156. dispatch({ type: 'dashboard/remoteSetMenu', dashboard: selectedRecord, menu: t });
  157. let obj = {selectedRecord: null};
  158. obj['visibleOperatingMenu' + selectedRecord.code] = false;
  159. this.setState(obj);
  160. }}
  161. >
  162. {this.generateMenuItems(t.children)}
  163. </Menu.SubMenu>
  164. }else {
  165. return <Menu.Item key={t.code} onClick={() => {
  166. dispatch({ type: 'dashboard/remoteSetMenu', dashboard: selectedRecord, menu: t });
  167. let obj = {selectedRecord: null};
  168. obj['visibleOperatingMenu' + selectedRecord.code] = false;
  169. this.setState(obj);
  170. }}>{selectedRecord.menuCode === t.code ? <span className='current' style={{ fontWeight: 'bold' }}>{t.name}</span> : t.name}</Menu.Item>
  171. }
  172. })
  173. }
  174. render() {
  175. const { dispatch, dashboard, main } = this.props;
  176. const { visibleShareBox, shareUrl, visibleDistributeBox, visibleTransferBox, visibleDeleteBox,
  177. visibleCopyBox, selectedRecord, defaultSelectedGroups, defaultSelectedUsers } = this.state
  178. const { currentUser } = main;
  179. const { listLoading, menuTree, filterItem, currentMenu, currentMenuParents } = dashboard;
  180. const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
  181. let filterLabel = dashboard.filterLabel ? (dashboard.filterLabel + '').replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') : ''; // 添加转义符号
  182. const moreOperatingMenu = (
  183. <Menu className='menu-operation'>
  184. { selectedRecord && (currentUser.code === selectedRecord.creatorCode || currentUser.role === 'superAdmin') && <Menu.Item onClick={() => {
  185. dispatch({ type: 'dashboard/getShareKey', record: selectedRecord, delay: 7 }).then((key) => {
  186. let obj = { visibleShareBox: true, shareUrl: window.location.origin + window.location.pathname + '#/dashboard/share_key/' + key };
  187. obj['visibleOperatingMenu' + selectedRecord.code] = false;
  188. this.setState(obj);
  189. });
  190. }}>
  191. <Icon type='share-alt'/>分享
  192. </Menu.Item>}
  193. { selectedRecord && (currentUser.code === selectedRecord.creatorCode || currentUser.role === 'superAdmin') && <Menu.Divider />}
  194. {/* { selectedRecord && (currentUser.code === selectedRecord.creatorCode || currentUser.role === 'superAdmin') && <Menu.Item onClick={this.getShareList}>
  195. <Icon type='share-alt'/>分发
  196. </Menu.Item>} */}
  197. { selectedRecord && (currentUser.code === selectedRecord.creatorCode || currentUser.role === 'superAdmin') && <Menu.Item
  198. onClick={()=>{
  199. let obj = {visibleTransferBox: true};
  200. obj['visibleOperatingMenu' + selectedRecord.code] = false;
  201. this.setState(obj);
  202. }}
  203. >
  204. <Icon type="swap" />移交
  205. </Menu.Item>}
  206. { selectedRecord && (currentUser.code === selectedRecord.creatorCode || currentUser.role === 'superAdmin') && <Menu.Divider />}
  207. { selectedRecord && (currentUser.code === selectedRecord.creatorCode || currentUser.role === 'superAdmin') && <Menu.SubMenu className='setgroupmenu' title={<div><Icon style={{ marginRight: '6px' }} type='profile' />移动到</div>}>
  208. {this.generateMenuItems(menuTree)}
  209. </Menu.SubMenu>}
  210. { selectedRecord && (selectedRecord.dataConnects.length <= 1) && <Menu.Item
  211. onClick={()=>{
  212. let obj = {visibleCopyBox: true};
  213. obj['visibleOperatingMenu' + selectedRecord.code] = false;
  214. this.setState(obj);
  215. }}
  216. >
  217. <Icon type="copy" />复制
  218. </Menu.Item> }
  219. { selectedRecord && (currentUser.code === selectedRecord.creatorCode || currentUser.role === 'superAdmin') && <Menu.Item
  220. onClick={(e) => {
  221. let obj = {visibleDeleteBox: true};
  222. obj['visibleOperatingMenu' + selectedRecord.code] = false;
  223. this.setState(obj);
  224. }}
  225. >
  226. <Icon type="delete" />删除
  227. </Menu.Item>}
  228. </Menu>
  229. )
  230. const dashboardColumns = [{
  231. title: '名称',
  232. dataIndex: 'name',
  233. key: 'name',
  234. width: 100,
  235. render: (text, record) => {
  236. return (
  237. <span style={{ color: '#1890ff', cursor: 'pointer' }} onClick={() => {
  238. dispatch({ type: 'dashboardDesigner/reset' });
  239. dispatch({ type: 'main/redirect', path: '/dashboard/' + record.code });
  240. dispatch({ type: 'recent/addRecentRecord', tarId: record.code, recordType: 1});
  241. }}>
  242. <CusIcon type='bi-dashboard-list-item' style={{ marginRight: '8px' }}/>
  243. { filterLabel && filterItem.name === 'name' ?
  244. ((text || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
  245. return (
  246. fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
  247. <span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
  248. fragment
  249. )
  250. }
  251. )) : text
  252. }
  253. </span>
  254. )
  255. }
  256. // }, {
  257. // title: '备注',
  258. // dataIndex: 'description',
  259. // key: 'description',
  260. // width: 200,
  261. // onCell: () => {
  262. // return {
  263. // style: {
  264. // whiteSpace: 'nowrap',
  265. // maxWidth: 200,
  266. // }
  267. // }
  268. // },
  269. // render: (text) => <EllipsisTooltip title={text}>{
  270. // filterLabel && filterItem.name === 'description' ? ((text || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
  271. // return (
  272. // fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
  273. // <span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
  274. // fragment
  275. // )
  276. // })) : text
  277. // }</EllipsisTooltip>,
  278. }, {
  279. title: '创建人',
  280. dataIndex: 'creatorName',
  281. key: 'creatorName',
  282. width: 100,
  283. render: (text, record) => {
  284. return (
  285. <span>
  286. { filterLabel && filterItem.name === 'creatorName' ?
  287. ((text || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
  288. return (
  289. fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
  290. <span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
  291. fragment
  292. )
  293. }
  294. )) : text
  295. }
  296. </span>
  297. )
  298. }
  299. }, {
  300. title: '创建时间',
  301. dataIndex: 'createTime',
  302. key: 'createTime',
  303. render: (text, record) => dateFormat(text, 'yyyy-MM-dd hh:mm:ss'),
  304. width: 100
  305. }, {
  306. title: '操作',
  307. key: 'action',
  308. render: (text, record, index) => (
  309. <Dropdown key={record.code} code={record.code} overlay={moreOperatingMenu} trigger={['click']} visible={this.state['visibleOperatingMenu' + record.code]} onVisibleChange={visible => {
  310. let obj = {};
  311. obj['visibleOperatingMenu' + record.code] = visible;
  312. this.setState(obj)
  313. }}>
  314. <Icon type="setting" />
  315. </Dropdown>
  316. ),
  317. width: 50
  318. }];
  319. return (
  320. <Layout className='dashboard-view'>
  321. <Loading visible={listLoading}/>
  322. <Content>
  323. <Card bordered={false} className="dashboard-body" title={
  324. <Row className='dashboard-tools' type='flex' justify='space-between'>
  325. <Col className='menus' style={{ display: 'flex' }}>
  326. { [].concat(currentMenuParents).reverse().map(m => m.name).join(' > ') }
  327. </Col>
  328. <Col className='search'>
  329. <Col>
  330. <Button className='btn-refresh' onClick={() => {
  331. dispatch({ type: 'dashboard/setFilterLabel', label: '' });
  332. if(!currentMenu || currentMenu.code === '-1') {
  333. // dispatch({ type: 'dashboard/fetchList', mandatory: true });
  334. }else {
  335. dispatch({ type: 'dashboard/remoteMenuDashboardList', menuCode: currentMenu.code });
  336. }
  337. }}>
  338. {/* <CusIcon type='bi-refresh'/> */}
  339. <Icon type='sync'/>
  340. </Button>
  341. </Col>
  342. <Col>
  343. <ListFilter modelName='dashboard' model={dashboard}/>
  344. </Col>
  345. <Col>
  346. <Button className='tool-link' disabled={!currentMenu || currentMenu.code === '-1'} onClick={() => {
  347. dispatch({ type: 'dashboardDesigner/reset' });
  348. dispatch({ type: 'dashboard/remoteQucikAdd', menuCode: currentMenu.code });
  349. }}>
  350. <CusIcon type='bi-nav-dashboard'/>创建报表
  351. </Button>
  352. </Col>
  353. </Col>
  354. </Row>
  355. }>
  356. <Table
  357. className='dashboard-table'
  358. columns={dashboardColumns}
  359. dataSource={
  360. this.onSort(
  361. this.onSearch(dashboard.list, dashboard)
  362. )
  363. }
  364. size='small'
  365. scroll={{x: false, y: true}}
  366. pagination={false}
  367. onRow={(record) => {
  368. return {
  369. onClick: () => {
  370. this.setState({ selectedRecord: record})
  371. }
  372. }
  373. }}
  374. />
  375. </Card>
  376. </Content>
  377. {visibleDistributeBox && <AccessObjectBox
  378. visibleBox={visibleDistributeBox}
  379. hideBox={() => {
  380. this.setState({
  381. visibleDistributeBox: false
  382. })
  383. }}
  384. okHandler={this.distribute}
  385. defaultSelectedGroups={defaultSelectedGroups}
  386. defaultSelectedUsers={defaultSelectedUsers}
  387. />}
  388. {visibleTransferBox && <TransferBox
  389. visibleBox={visibleTransferBox}
  390. title='选择移交对象'
  391. okHandler={(user) => {
  392. dispatch({ type: 'dashboard/transfer', dashboardCode: this.state.selectedRecord.code, userCode: user.code });
  393. }}
  394. hideBox={() => {
  395. this.setState({
  396. visibleTransferBox: false
  397. })
  398. }}
  399. />}
  400. {visibleDeleteBox && <DeleteBox
  401. visibleBox={visibleDeleteBox}
  402. text={`确定要删除报表【${selectedRecord.name}】吗?`}
  403. hideBox={() => {
  404. this.setState({
  405. visibleDeleteBox: false
  406. })
  407. }}
  408. okHandler={() => {
  409. dispatch({ type: 'dashboard/remoteDelete', code: this.state.selectedRecord.code })
  410. }}
  411. />}
  412. {visibleShareBox && <ShareBox
  413. visibleBox={visibleShareBox}
  414. shareUrl={shareUrl}
  415. hideBox={() => {
  416. this.setState({
  417. visibleShareBox: false
  418. })
  419. }}
  420. onRefreshKey={(delay) => {
  421. return dispatch({ type: 'dashboard/getShareKey', record: this.state.selectedRecord, delay: delay })
  422. .then((key) => {
  423. this.setState({
  424. shareUrl: window.location.origin + '/#/dashboard/share_key/' + key
  425. })
  426. })
  427. }}
  428. />}
  429. {visibleCopyBox && <CopyBox
  430. visibleBox={visibleCopyBox}
  431. hideBox={()=>{this.setState({visibleCopyBox: false})}}
  432. currentDashboardCode={selectedRecord.code}
  433. currentDataConnect={selectedRecord.dataConnects[0]}
  434. />}
  435. </Layout>
  436. )
  437. }
  438. }
  439. export default connect(({ present: { main, dashboard } }) => ({ main, dashboard }))(DashboardList)