index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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) { return [] }
  98. const regex = new RegExp(viewName, 'gi')
  99. const filterViews = views.filter((v) => v.name.match(regex) || v.description.match(regex))
  100. return filterViews
  101. })
  102. private static getViewPermission = memoizeOne((project: IProject) => ({
  103. viewPermission: initializePermission(project, 'viewPermission'),
  104. AdminButton: ModulePermission<ButtonProps>(project, 'view', true)(Button),
  105. EditButton: ModulePermission<ButtonProps>(project, 'view', false)(Button)
  106. }))
  107. private getTableColumns = (
  108. { viewPermission, AdminButton, EditButton }: ReturnType<typeof ViewList.getViewPermission>
  109. ) => {
  110. const { views } = this.props
  111. const { tempFilterViewName, filterViewName, filterDropdownVisible, tableSorter } = this.state
  112. const sourceNames = views.map(({ sourceName }) => sourceName)
  113. const columns: Array<ColumnProps<IViewBase>> = [{
  114. title: '名称',
  115. dataIndex: 'name',
  116. filterDropdown: (
  117. <SearchFilterDropdown
  118. placeholder="名称"
  119. value={tempFilterViewName}
  120. onChange={this.filterViewNameChange}
  121. onSearch={this.searchView}
  122. />
  123. ),
  124. filterDropdownVisible,
  125. onFilterDropdownVisibleChange: (visible: boolean) => this.setState({ filterDropdownVisible: visible }),
  126. sorter: (a, b) => (a.name > b.name ? 1 : -1),
  127. sortOrder: tableSorter && tableSorter.columnKey === 'name' ? tableSorter.order : void 0
  128. }, {
  129. title: '描述',
  130. dataIndex: 'description'
  131. }, {
  132. title: 'Source',
  133. dataIndex: 'sourceName',
  134. filterMultiple: false,
  135. onFilter: (val, record) => record.sourceName === val,
  136. filters: sourceNames
  137. .filter((name, idx) => sourceNames.indexOf(name) === idx)
  138. .map((name) => ({ text: name, value: name }))
  139. }]
  140. if (filterViewName) {
  141. const regex = new RegExp(`(${filterViewName})`, 'gi')
  142. columns[0].render = (text: string) => (
  143. <span
  144. dangerouslySetInnerHTML={{
  145. __html: text.replace(regex, `<span class="${utilStyles.highlight}">$1</span>`)
  146. }}
  147. />
  148. )
  149. }
  150. if (viewPermission) {
  151. columns.push({
  152. title: '操作',
  153. width: 150,
  154. className: utilStyles.textAlignCenter,
  155. render: (_, record) => (
  156. <span className="ant-table-action-column">
  157. <Tooltip title="复制">
  158. <EditButton icon="copy" shape="circle" type="ghost" onClick={this.copyView(record)} />
  159. </Tooltip>
  160. <Tooltip title="修改">
  161. <EditButton icon="edit" shape="circle" type="ghost" onClick={this.editView(record.id)} />
  162. </Tooltip>
  163. <Popconfirm
  164. title="确定删除?"
  165. placement="bottom"
  166. onConfirm={this.deleteView(record.id)}
  167. >
  168. <Tooltip title="删除">
  169. <AdminButton icon="delete" shape="circle" type="ghost" />
  170. </Tooltip>
  171. </Popconfirm>
  172. </span>
  173. )
  174. })
  175. }
  176. return columns
  177. }
  178. private tableChange = (_1, _2, sorter: SorterResult<IViewBase>) => {
  179. this.setState({ tableSorter: sorter })
  180. }
  181. private filterViewNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  182. this.setState({
  183. tempFilterViewName: e.target.value,
  184. filterViewName: ''
  185. })
  186. }
  187. private searchView = (value: string) => {
  188. this.setState({
  189. filterViewName: value,
  190. filterDropdownVisible: false
  191. })
  192. window.event.preventDefault()
  193. }
  194. private basePagination: PaginationConfig = {
  195. defaultPageSize: 20,
  196. showSizeChanger: true
  197. }
  198. private addView = () => {
  199. const { history, match } = this.props
  200. history.push(`/project/${match.params.projectId}/view`)
  201. }
  202. private copyView = (fromView: IViewBase) => () => {
  203. this.setState({
  204. copyModalVisible: true,
  205. copyFromView: fromView
  206. })
  207. }
  208. private copy = (view: IViewBase) => {
  209. const { onCopyView } = this.props
  210. onCopyView(view, () => {
  211. this.setState({
  212. copyModalVisible: false
  213. })
  214. message.info('View 复制成功')
  215. })
  216. }
  217. private cancelCopy = () => {
  218. this.setState({ copyModalVisible: false })
  219. }
  220. private editView = (viewId: number) => () => {
  221. const { history, match } = this.props
  222. history.push(`/project/${match.params.projectId}/view/${viewId}`)
  223. }
  224. private deleteView = (viewId: number) => () => {
  225. const { onDeleteView } = this.props
  226. onDeleteView(viewId, () => {
  227. this.loadViews()
  228. })
  229. }
  230. private checkViewUniqueName = (viewName: string, resolve: () => void, reject: (err: string) => void) => {
  231. const { currentProject, onCheckName } = this.props
  232. onCheckName({ name: viewName, projectId: currentProject.id }, resolve, reject)
  233. }
  234. public render () {
  235. const { currentProject, views, loading } = this.props
  236. const { screenWidth, filterViewName } = this.state
  237. const { viewPermission, AdminButton, EditButton } = ViewList.getViewPermission(currentProject)
  238. const tableColumns = this.getTableColumns({ viewPermission, AdminButton, EditButton })
  239. const tablePagination: PaginationConfig = {
  240. ...this.basePagination,
  241. simple: screenWidth <= 768
  242. }
  243. const filterViews = this.getFilterViews(filterViewName, views)
  244. const { copyModalVisible, copyFromView } = this.state
  245. return (
  246. <>
  247. <Container>
  248. <Helmet title="View" />
  249. <ContainerTitle>
  250. <Row>
  251. <Col span={24} className={utilStyles.shortcut}>
  252. <Breadcrumb className={utilStyles.breadcrumb}>
  253. <Breadcrumb.Item>
  254. <Link to="">View</Link>
  255. </Breadcrumb.Item>
  256. </Breadcrumb>
  257. <Link to={`/account/organization/${currentProject.orgId}`}>
  258. <i className='iconfont icon-organization' />
  259. </Link>
  260. </Col>
  261. </Row>
  262. </ContainerTitle>
  263. <ContainerBody>
  264. <Box>
  265. <Box.Header>
  266. <Box.Title>
  267. <Icon type="bars" />
  268. View List
  269. </Box.Title>
  270. <Box.Tools>
  271. <Tooltip placement="bottom" title="新增">
  272. <AdminButton type="primary" icon="plus" onClick={this.addView} />
  273. </Tooltip>
  274. </Box.Tools>
  275. </Box.Header>
  276. <Box.Body>
  277. <Row>
  278. <Col span={24}>
  279. <Table
  280. bordered
  281. rowKey="id"
  282. loading={loading.view}
  283. dataSource={filterViews}
  284. columns={tableColumns}
  285. pagination={tablePagination}
  286. onChange={this.tableChange}
  287. />
  288. </Col>
  289. </Row>
  290. </Box.Body>
  291. </Box>
  292. </ContainerBody>
  293. </Container>
  294. <CopyModal
  295. visible={copyModalVisible}
  296. loading={loading.copy}
  297. fromView={copyFromView}
  298. onCheckUniqueName={this.checkViewUniqueName}
  299. onCopy={this.copy}
  300. onCancel={this.cancelCopy}
  301. />
  302. </>
  303. )
  304. }
  305. }
  306. const mapDispatchToProps = (dispatch: Dispatch<ViewActionType>) => ({
  307. onLoadViews: (projectId) => dispatch(ViewActions.loadViews(projectId)),
  308. onDeleteView: (viewId, resolve) => dispatch(ViewActions.deleteView(viewId, resolve)),
  309. onCopyView: (view, resolve) => dispatch(ViewActions.copyView(view, resolve)),
  310. onCheckName: (data, resolve, reject) => dispatch(checkNameUniqueAction('view', data, resolve, reject))
  311. })
  312. const mapStateToProps = createStructuredSelector({
  313. views: makeSelectViews(),
  314. currentProject: makeSelectCurrentProject(),
  315. loading: makeSelectLoading()
  316. })
  317. const withConnect = connect<IViewListStateProps, IViewListDispatchProps, RouteComponentWithParams>(mapStateToProps, mapDispatchToProps)
  318. const withReducer = injectReducer({ key: 'view', reducer })
  319. const withSaga = injectSaga({ key: 'view', saga: sagas })
  320. export default compose(
  321. withReducer,
  322. withSaga,
  323. withConnect
  324. )(ViewList)