123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- /*
- * <<
- * Davinci
- * ==
- * Copyright (C) 2016 - 2017 EDP
- * ==
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * >>
- */
- import React from 'react'
- import { compose, Dispatch } from 'redux'
- import { connect } from 'react-redux'
- import { createStructuredSelector } from 'reselect'
- import memoizeOne from 'memoize-one'
- import Helmet from 'react-helmet'
- import { Link } from 'react-router-dom'
- import { RouteComponentWithParams } from 'utils/types'
- import injectReducer from 'utils/injectReducer'
- import injectSaga from 'utils/injectSaga'
- import reducer from './reducer'
- import sagas from './sagas'
- import { checkNameUniqueAction } from 'containers/App/actions'
- import { ViewActions, ViewActionType } from './actions'
- import { makeSelectViews, makeSelectLoading } from './selectors'
- import { makeSelectCurrentProject } from 'containers/Projects/selectors'
- import ModulePermission from '../Account/components/checkModulePermission'
- import { initializePermission } from '../Account/components/checkUtilPermission'
- import { Table, Tooltip, Button, Row, Col, Breadcrumb, Icon, Popconfirm, message } from 'antd'
- import { ColumnProps, PaginationConfig, SorterResult } from 'antd/lib/table'
- import { ButtonProps } from 'antd/lib/button'
- import Container, { ContainerTitle, ContainerBody } from 'components/Container'
- import Box from 'components/Box'
- import SearchFilterDropdown from 'components/SearchFilterDropdown'
- import CopyModal from './components/CopyModal'
- import { IViewBase, IView, IViewLoading } from './types'
- import { IProject } from '../Projects/types'
- import utilStyles from 'assets/less/util.less'
- interface IViewListStateProps {
- views: IViewBase[]
- currentProject: IProject
- loading: IViewLoading
- }
- interface IViewListDispatchProps {
- onLoadViews: (projectId: number) => void
- onDeleteView: (viewId: number, resolve: () => void) => void
- onCopyView: (view: IViewBase, resolve: () => void) => void
- onCheckName: (data, resolve, reject) => void
- }
- type IViewListProps = IViewListStateProps & IViewListDispatchProps & RouteComponentWithParams
- interface IViewListStates {
- screenWidth: number
- tempFilterViewName: string
- filterViewName: string
- filterDropdownVisible: boolean
- tableSorter: SorterResult<IViewBase>
- copyModalVisible: boolean
- copyFromView: IViewBase
- }
- export class ViewList extends React.PureComponent<IViewListProps, IViewListStates> {
- public state: Readonly<IViewListStates> = {
- screenWidth: document.documentElement.clientWidth,
- tempFilterViewName: '',
- filterViewName: '',
- filterDropdownVisible: false,
- tableSorter: null,
- copyModalVisible: false,
- copyFromView: null
- }
- public componentWillMount() {
- this.loadViews()
- window.addEventListener('resize', this.setScreenWidth, false)
- }
- private loadViews = () => {
- const { onLoadViews, match } = this.props
- const { projectId } = match.params
- if (projectId) {
- onLoadViews(+projectId)
- }
- }
- public componentWillUnmount() {
- window.removeEventListener('resize', this.setScreenWidth, false)
- }
- private setScreenWidth = () => {
- this.setState({ screenWidth: document.documentElement.clientWidth })
- }
- private getFilterViews = memoizeOne((viewName: string, views: IViewBase[]) => {
- if (!Array.isArray(views) || !views.length) {
- return []
- }
- const regex = new RegExp(viewName, 'gi')
- const filterViews = views.filter((v) => v.name.match(regex) || v.description.match(regex))
- return filterViews
- })
- private static getViewPermission = memoizeOne((project: IProject) => ({
- viewPermission: initializePermission(project, 'viewPermission'),
- AdminButton: ModulePermission<ButtonProps>(project, 'view', true)(Button),
- EditButton: ModulePermission<ButtonProps>(project, 'view', false)(Button)
- }))
- private getTableColumns = (
- { viewPermission, AdminButton, EditButton }: ReturnType<typeof ViewList.getViewPermission>
- ) => {
- const { views } = this.props
- const { tempFilterViewName, filterViewName, filterDropdownVisible, tableSorter } = this.state
- const sourceNames = views.map(({ sourceName }) => sourceName)
- const columns: Array<ColumnProps<IViewBase>> = [{
- title: '名称',
- dataIndex: 'name',
- filterDropdown: (
- <SearchFilterDropdown
- placeholder='名称'
- value={tempFilterViewName}
- onChange={this.filterViewNameChange}
- onSearch={this.searchView}
- />
- ),
- filterDropdownVisible,
- onFilterDropdownVisibleChange: (visible: boolean) => this.setState({ filterDropdownVisible: visible }),
- sorter: (a, b) => (a.name > b.name ? 1 : -1),
- sortOrder: tableSorter && tableSorter.columnKey === 'name' ? tableSorter.order : void 0
- }, {
- title: '描述',
- dataIndex: 'description'
- }, {
- title: '数据源',
- // title: 'Source',
- dataIndex: 'sourceName',
- filterMultiple: false,
- onFilter: (val, record) => record.sourceName === val,
- filters: sourceNames
- .filter((name, idx) => sourceNames.indexOf(name) === idx)
- .map((name) => ({ text: name, value: name }))
- }]
- if (filterViewName) {
- const regex = new RegExp(`(${filterViewName})`, 'gi')
- columns[0].render = (text: string) => (
- <span
- dangerouslySetInnerHTML={{
- __html: text.replace(regex, `<span class='${utilStyles.highlight}'>$1</span>`)
- }}
- />
- )
- }
- if (viewPermission) {
- columns.push({
- title: '操作',
- width: 150,
- className: utilStyles.textAlignCenter,
- render: (_, record) => (
- <span className='ant-table-action-column'>
- <Tooltip title='复制'>
- <EditButton icon='copy' shape='circle' type='ghost' onClick={this.copyView(record)} />
- </Tooltip>
- <Tooltip title='修改'>
- <EditButton icon='edit' shape='circle' type='ghost' onClick={this.editView(record.id)} />
- </Tooltip>
- <Popconfirm
- title='确定删除?'
- placement='bottom'
- onConfirm={this.deleteView(record.id)}
- >
- <Tooltip title='删除'>
- <AdminButton icon='delete' shape='circle' type='ghost' />
- </Tooltip>
- </Popconfirm>
- </span>
- )
- })
- }
- return columns
- }
- private tableChange = (_1, _2, sorter: SorterResult<IViewBase>) => {
- this.setState({ tableSorter: sorter })
- }
- private filterViewNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this.setState({
- tempFilterViewName: e.target.value,
- filterViewName: ''
- })
- }
- private searchView = (value: string) => {
- this.setState({
- filterViewName: value,
- filterDropdownVisible: false
- })
- window.event.preventDefault()
- }
- private basePagination: PaginationConfig = {
- defaultPageSize: 20,
- showSizeChanger: true
- }
- private addView = () => {
- const { history, match } = this.props
- history.push(`/project/${match.params.projectId}/view`)
- }
- private copyView = (fromView: IViewBase) => () => {
- this.setState({
- copyModalVisible: true,
- copyFromView: fromView
- })
- }
- private copy = (view: IViewBase) => {
- const { onCopyView } = this.props
- onCopyView(view, () => {
- this.setState({
- copyModalVisible: false
- })
- message.info('View 复制成功')
- })
- }
- private cancelCopy = () => {
- this.setState({ copyModalVisible: false })
- }
- private editView = (viewId: number) => () => {
- const { history, match } = this.props
- history.push(`/project/${match.params.projectId}/view/${viewId}`)
- }
- private deleteView = (viewId: number) => () => {
- const { onDeleteView } = this.props
- onDeleteView(viewId, () => {
- this.loadViews()
- })
- }
- private checkViewUniqueName = (viewName: string, resolve: () => void, reject: (err: string) => void) => {
- const { currentProject, onCheckName } = this.props
- onCheckName({ name: viewName, projectId: currentProject.id }, resolve, reject)
- }
- public render() {
- const { currentProject, views, loading } = this.props
- const { screenWidth, filterViewName } = this.state
- const { viewPermission, AdminButton, EditButton } = ViewList.getViewPermission(currentProject)
- const tableColumns = this.getTableColumns({ viewPermission, AdminButton, EditButton })
- const tablePagination: PaginationConfig = {
- ...this.basePagination,
- simple: screenWidth <= 768
- }
- const filterViews = this.getFilterViews(filterViewName, views)
- const { copyModalVisible, copyFromView } = this.state
- const pathname = this.props.history.location.pathname
- return (
- <>
- <Container>
- <Helmet title='数据资产' />
- {
- !pathname.includes('dataManager') && <ContainerTitle>
- <Row>
- <Col span={24} className={utilStyles.shortcut}>
- <Breadcrumb className={utilStyles.breadcrumb}>
- <Breadcrumb.Item>
- <Link to=''>View</Link>
- </Breadcrumb.Item>
- </Breadcrumb>
- <Link to={`/account/organization/${currentProject.orgId}`}>
- <i className='iconfont icon-organization' />
- </Link>
- </Col>
- </Row>
- </ContainerTitle>
- }
- <ContainerBody>
- <Box>
- <Box.Header>
- <Box.Title>
- <Icon type='bars' />
- 数据资产列表
- </Box.Title>
- <Box.Tools>
- <Tooltip placement='bottom' title='新增'>
- <AdminButton type='primary' icon='plus' onClick={this.addView} />
- </Tooltip>
- </Box.Tools>
- </Box.Header>
- <Box.Body>
- <Row>
- <Col span={24}>
- <Table
- bordered
- rowKey='id'
- loading={loading.view}
- dataSource={filterViews}
- columns={tableColumns}
- pagination={tablePagination}
- onChange={this.tableChange}
- />
- </Col>
- </Row>
- </Box.Body>
- </Box>
- </ContainerBody>
- </Container>
- <CopyModal
- visible={copyModalVisible}
- loading={loading.copy}
- fromView={copyFromView}
- onCheckUniqueName={this.checkViewUniqueName}
- onCopy={this.copy}
- onCancel={this.cancelCopy}
- />
- </>
- )
- }
- }
- const mapDispatchToProps = (dispatch: Dispatch<ViewActionType>) => ({
- onLoadViews: (projectId) => dispatch(ViewActions.loadViews(projectId)),
- onDeleteView: (viewId, resolve) => dispatch(ViewActions.deleteView(viewId, resolve)),
- onCopyView: (view, resolve) => dispatch(ViewActions.copyView(view, resolve)),
- onCheckName: (data, resolve, reject) => dispatch(checkNameUniqueAction('view', data, resolve, reject))
- })
- const mapStateToProps = createStructuredSelector({
- views: makeSelectViews(),
- currentProject: makeSelectCurrentProject(),
- loading: makeSelectLoading()
- })
- const withConnect = connect<IViewListStateProps, IViewListDispatchProps, RouteComponentWithParams>(mapStateToProps, mapDispatchToProps)
- const withReducer = injectReducer({ key: 'view', reducer })
- const withSaga = injectSaga({ key: 'view', saga: sagas })
- export default compose(
- withReducer,
- withSaga,
- withConnect
- )(ViewList)
|