index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. /*
  2. * <<
  3. * Davinci
  4. * ==
  5. * Copyright (C) 2016 - 2017 EDP
  6. * ==
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. * >>
  19. */
  20. import React from 'react'
  21. import { compose, Dispatch } from 'redux'
  22. import { connect } from 'react-redux'
  23. import { createStructuredSelector } from 'reselect'
  24. import memoizeOne from 'memoize-one'
  25. import Helmet from 'react-helmet'
  26. import { Link } from 'react-router-dom'
  27. import { RouteComponentWithParams } from 'utils/types'
  28. import injectReducer from 'utils/injectReducer'
  29. import injectSaga from 'utils/injectSaga'
  30. import reducer from './reducer'
  31. import sagas from './sagas'
  32. import { checkNameUniqueAction } from 'containers/App/actions'
  33. import { ViewActions, ViewActionType } from './actions'
  34. import { makeSelectViews, makeSelectLoading } from './selectors'
  35. import { makeSelectCurrentProject } from 'containers/Projects/selectors'
  36. import ModulePermission from '../Account/components/checkModulePermission'
  37. import { initializePermission } from '../Account/components/checkUtilPermission'
  38. import { Table, Tooltip, Button, Row, Col, Breadcrumb, Icon, Popconfirm, message } from 'antd'
  39. import { ColumnProps, PaginationConfig, SorterResult } from 'antd/lib/table'
  40. import { ButtonProps } from 'antd/lib/button'
  41. import Container, { ContainerTitle, ContainerBody } from 'components/Container'
  42. import Box from 'components/Box'
  43. import SearchFilterDropdown from 'components/SearchFilterDropdown'
  44. import CopyModal from './components/CopyModal'
  45. import { IViewBase, IView, IViewLoading } from './types'
  46. import { IProject } from '../Projects/types'
  47. import utilStyles from 'assets/less/util.less'
  48. interface IViewListStateProps {
  49. views: IViewBase[]
  50. currentProject: IProject
  51. loading: IViewLoading
  52. }
  53. interface IViewListDispatchProps {
  54. onLoadViews: (projectId: number) => void
  55. onDeleteView: (viewId: number, resolve: () => void) => void
  56. onCopyView: (view: IViewBase, resolve: () => void) => void
  57. onCheckName: (data, resolve, reject) => void
  58. }
  59. type IViewListProps = IViewListStateProps & IViewListDispatchProps & RouteComponentWithParams
  60. interface IViewListStates {
  61. screenWidth: number
  62. tempFilterViewName: string
  63. filterViewName: string
  64. filterDropdownVisible: boolean
  65. tableSorter: SorterResult<IViewBase>
  66. copyModalVisible: boolean
  67. copyFromView: IViewBase
  68. }
  69. export class ViewList extends React.PureComponent<IViewListProps, IViewListStates> {
  70. public state: Readonly<IViewListStates> = {
  71. screenWidth: document.documentElement.clientWidth,
  72. tempFilterViewName: '',
  73. filterViewName: '',
  74. filterDropdownVisible: false,
  75. tableSorter: null,
  76. copyModalVisible: false,
  77. copyFromView: null
  78. }
  79. public componentWillMount() {
  80. this.loadViews()
  81. window.addEventListener('resize', this.setScreenWidth, false)
  82. }
  83. private loadViews = () => {
  84. const { onLoadViews, match } = this.props
  85. const { projectId } = match.params
  86. if (projectId) {
  87. onLoadViews(+projectId)
  88. }
  89. }
  90. public componentWillUnmount() {
  91. window.removeEventListener('resize', this.setScreenWidth, false)
  92. }
  93. private setScreenWidth = () => {
  94. this.setState({ screenWidth: document.documentElement.clientWidth })
  95. }
  96. private getFilterViews = memoizeOne((viewName: string, views: IViewBase[]) => {
  97. if (!Array.isArray(views) || !views.length) {
  98. return []
  99. }
  100. const regex = new RegExp(viewName, 'gi')
  101. const filterViews = views.filter((v) => v.name.match(regex) || v.description.match(regex))
  102. return filterViews
  103. })
  104. private static getViewPermission = memoizeOne((project: IProject) => ({
  105. viewPermission: initializePermission(project, 'viewPermission'),
  106. AdminButton: ModulePermission<ButtonProps>(project, 'view', true)(Button),
  107. EditButton: ModulePermission<ButtonProps>(project, 'view', false)(Button)
  108. }))
  109. private getTableColumns = (
  110. { viewPermission, AdminButton, EditButton }: ReturnType<typeof ViewList.getViewPermission>
  111. ) => {
  112. const { views } = this.props
  113. const { tempFilterViewName, filterViewName, filterDropdownVisible, tableSorter } = this.state
  114. const sourceNames = views.map(({ sourceName }) => sourceName)
  115. const columns: Array<ColumnProps<IViewBase>> = [{
  116. title: '名称',
  117. dataIndex: 'name',
  118. filterDropdown: (
  119. <SearchFilterDropdown
  120. placeholder='名称'
  121. value={tempFilterViewName}
  122. onChange={this.filterViewNameChange}
  123. onSearch={this.searchView}
  124. />
  125. ),
  126. filterDropdownVisible,
  127. onFilterDropdownVisibleChange: (visible: boolean) => this.setState({ filterDropdownVisible: visible }),
  128. sorter: (a, b) => (a.name > b.name ? 1 : -1),
  129. sortOrder: tableSorter && tableSorter.columnKey === 'name' ? tableSorter.order : void 0
  130. }, {
  131. title: '描述',
  132. dataIndex: 'description'
  133. }, {
  134. title: '数据源',
  135. // title: 'Source',
  136. dataIndex: 'sourceName',
  137. filterMultiple: false,
  138. onFilter: (val, record) => record.sourceName === val,
  139. filters: sourceNames
  140. .filter((name, idx) => sourceNames.indexOf(name) === idx)
  141. .map((name) => ({ text: name, value: name }))
  142. }]
  143. if (filterViewName) {
  144. const regex = new RegExp(`(${filterViewName})`, 'gi')
  145. columns[0].render = (text: string) => (
  146. <span
  147. dangerouslySetInnerHTML={{
  148. __html: text.replace(regex, `<span class='${utilStyles.highlight}'>$1</span>`)
  149. }}
  150. />
  151. )
  152. }
  153. if (viewPermission) {
  154. columns.push({
  155. title: '操作',
  156. width: 150,
  157. className: utilStyles.textAlignCenter,
  158. render: (_, record) => (
  159. <span className='ant-table-action-column'>
  160. <Tooltip title='复制'>
  161. <EditButton icon='copy' shape='circle' type='ghost' onClick={this.copyView(record)} />
  162. </Tooltip>
  163. <Tooltip title='修改'>
  164. <EditButton icon='edit' shape='circle' type='ghost' onClick={this.editView(record.id)} />
  165. </Tooltip>
  166. <Popconfirm
  167. title='确定删除?'
  168. placement='bottom'
  169. onConfirm={this.deleteView(record.id)}
  170. >
  171. <Tooltip title='删除'>
  172. <AdminButton icon='delete' shape='circle' type='ghost' />
  173. </Tooltip>
  174. </Popconfirm>
  175. </span>
  176. )
  177. })
  178. }
  179. return columns
  180. }
  181. private tableChange = (_1, _2, sorter: SorterResult<IViewBase>) => {
  182. this.setState({ tableSorter: sorter })
  183. }
  184. private filterViewNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  185. this.setState({
  186. tempFilterViewName: e.target.value,
  187. filterViewName: ''
  188. })
  189. }
  190. private searchView = (value: string) => {
  191. this.setState({
  192. filterViewName: value,
  193. filterDropdownVisible: false
  194. })
  195. window.event.preventDefault()
  196. }
  197. private basePagination: PaginationConfig = {
  198. defaultPageSize: 20,
  199. showSizeChanger: true
  200. }
  201. private addView = () => {
  202. const { history, match } = this.props
  203. history.push(`/project/${match.params.projectId}/view`)
  204. }
  205. private copyView = (fromView: IViewBase) => () => {
  206. this.setState({
  207. copyModalVisible: true,
  208. copyFromView: fromView
  209. })
  210. }
  211. private copy = (view: IViewBase) => {
  212. const { onCopyView } = this.props
  213. onCopyView(view, () => {
  214. this.setState({
  215. copyModalVisible: false
  216. })
  217. message.info('View 复制成功')
  218. })
  219. }
  220. private cancelCopy = () => {
  221. this.setState({ copyModalVisible: false })
  222. }
  223. private editView = (viewId: number) => () => {
  224. const { history, match } = this.props
  225. history.push(`/project/${match.params.projectId}/view/${viewId}`)
  226. }
  227. private deleteView = (viewId: number) => () => {
  228. const { onDeleteView } = this.props
  229. onDeleteView(viewId, () => {
  230. this.loadViews()
  231. })
  232. }
  233. private checkViewUniqueName = (viewName: string, resolve: () => void, reject: (err: string) => void) => {
  234. const { currentProject, onCheckName } = this.props
  235. onCheckName({ name: viewName, projectId: currentProject.id }, resolve, reject)
  236. }
  237. public render() {
  238. const { currentProject, views, loading } = this.props
  239. const { screenWidth, filterViewName } = this.state
  240. const { viewPermission, AdminButton, EditButton } = ViewList.getViewPermission(currentProject)
  241. const tableColumns = this.getTableColumns({ viewPermission, AdminButton, EditButton })
  242. const tablePagination: PaginationConfig = {
  243. ...this.basePagination,
  244. simple: screenWidth <= 768
  245. }
  246. const filterViews = this.getFilterViews(filterViewName, views)
  247. const { copyModalVisible, copyFromView } = this.state
  248. const pathname = this.props.history.location.pathname
  249. return (
  250. <>
  251. <Container>
  252. <Helmet title='数据资产' />
  253. {
  254. !pathname.includes('dataManager') && <ContainerTitle>
  255. <Row>
  256. <Col span={24} className={utilStyles.shortcut}>
  257. <Breadcrumb className={utilStyles.breadcrumb}>
  258. <Breadcrumb.Item>
  259. <Link to=''>View</Link>
  260. </Breadcrumb.Item>
  261. </Breadcrumb>
  262. <Link to={`/account/organization/${currentProject.orgId}`}>
  263. <i className='iconfont icon-organization' />
  264. </Link>
  265. </Col>
  266. </Row>
  267. </ContainerTitle>
  268. }
  269. <ContainerBody>
  270. <Box>
  271. <Box.Header>
  272. <Box.Title>
  273. <Icon type='bars' />
  274. 数据资产列表
  275. </Box.Title>
  276. <Box.Tools>
  277. <Tooltip placement='bottom' title='新增'>
  278. <AdminButton type='primary' icon='plus' onClick={this.addView} />
  279. </Tooltip>
  280. </Box.Tools>
  281. </Box.Header>
  282. <Box.Body>
  283. <Row>
  284. <Col span={24}>
  285. <Table
  286. bordered
  287. rowKey='id'
  288. loading={loading.view}
  289. dataSource={filterViews}
  290. columns={tableColumns}
  291. pagination={tablePagination}
  292. onChange={this.tableChange}
  293. />
  294. </Col>
  295. </Row>
  296. </Box.Body>
  297. </Box>
  298. </ContainerBody>
  299. </Container>
  300. <CopyModal
  301. visible={copyModalVisible}
  302. loading={loading.copy}
  303. fromView={copyFromView}
  304. onCheckUniqueName={this.checkViewUniqueName}
  305. onCopy={this.copy}
  306. onCancel={this.cancelCopy}
  307. />
  308. </>
  309. )
  310. }
  311. }
  312. const mapDispatchToProps = (dispatch: Dispatch<ViewActionType>) => ({
  313. onLoadViews: (projectId) => dispatch(ViewActions.loadViews(projectId)),
  314. onDeleteView: (viewId, resolve) => dispatch(ViewActions.deleteView(viewId, resolve)),
  315. onCopyView: (view, resolve) => dispatch(ViewActions.copyView(view, resolve)),
  316. onCheckName: (data, resolve, reject) => dispatch(checkNameUniqueAction('view', data, resolve, reject))
  317. })
  318. const mapStateToProps = createStructuredSelector({
  319. views: makeSelectViews(),
  320. currentProject: makeSelectCurrentProject(),
  321. loading: makeSelectLoading()
  322. })
  323. const withConnect = connect<IViewListStateProps, IViewListDispatchProps, RouteComponentWithParams>(mapStateToProps, mapDispatchToProps)
  324. const withReducer = injectReducer({ key: 'view', reducer })
  325. const withSaga = injectSaga({ key: 'view', saga: sagas })
  326. export default compose(
  327. withReducer,
  328. withSaga,
  329. withConnect
  330. )(ViewList)