123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688 |
- /*
- * <<
- * 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 { makeSelectLoading, makeSelectViews } from './selectors'
- import { makeSelectCurrentProject } from 'containers/Projects/selectors'
- import ModulePermission from '../Account/components/checkModulePermission'
- import { initializePermission } from '../Account/components/checkUtilPermission'
- import {
- Breadcrumb,
- Button,
- Col,
- Dropdown,
- Icon,
- Menu,
- message,
- Popconfirm,
- Row,
- Spin,
- Table,
- Tooltip,
- Tree
- } from 'antd'
- import { ColumnProps, PaginationConfig, SorterResult } from 'antd/lib/table'
- import { ButtonProps } from 'antd/lib/button'
- import Container, { ContainerBody, ContainerTitle } from 'components/Container'
- import Box from 'components/Box'
- import SearchFilterDropdown from 'components/SearchFilterDropdown'
- import CopyModal from './components/CopyModal'
- import CatalogueModal from './components/CatalogueModal'
- import { ICatalogue, IViewBase, IViewLoading } from './types'
- import { IProject } from '../Projects/types'
- import utilStyles from 'assets/less/util.less'
- import styles from './index.less'
- import request from 'utils/request'
- import api from 'utils/api'
- import classnames from 'classnames'
- interface IViewListStateProps {
- views: IViewBase[]
- currentProject: IProject
- loading: IViewLoading
- }
- interface IViewListDispatchProps {
- onLoadViews: (projectId: number, parentId: number) => void
- onDeleteView: (viewId: number, resolve: () => void) => void
- onCopyView: (view: IViewBase, resolve: () => void) => void
- onCheckName: (data, resolve, reject) => void
- }
- type IViewListProps = IViewListStateProps &
- IViewListDispatchProps &
- RouteComponentWithParams
- // tslint:disable-next-line:interface-name
- interface Catalogue {
- description?: string // 资源描述
- extConfig?: string // 扩展信息
- id?: number
- industry?: string // 行业分类
- name?: string // 资源名称
- originDept?: string // 来源部门
- originSystem?: string // 来源系统
- parentId?: string
- projectId?: number
- }
- interface IViewListStates {
- screenWidth: number
- tempFilterViewName: string
- filterViewName: string
- filterDropdownVisible: boolean
- tableSorter: SorterResult<IViewBase>
- copyModalVisible: boolean
- copyFromView: IViewBase
- viewList: IViewBase[]
- catalogueModalVisible: boolean
- catalogueFromView: ICatalogue
- saveCatalogueLoading: boolean
- catalogues: ICatalogue[]
- selectedCatalogueKeys: string[]
- treeLoading: boolean
- tableLoading: boolean
- }
- const { TreeNode, DirectoryTree } = Tree
- export class ViewList extends React.PureComponent<
- IViewListProps,
- IViewListStates
- > {
- // @ts-ignore
- public state: Readonly<IViewListStates> = {
- screenWidth: document.documentElement.clientWidth,
- tempFilterViewName: '',
- filterViewName: '',
- filterDropdownVisible: false,
- tableSorter: null,
- copyModalVisible: false,
- copyFromView: null,
- catalogueModalVisible: false,
- catalogueFromView: null,
- saveCatalogueLoading: false,
- catalogues: [],
- viewList: [],
- treeLoading: false,
- tableLoading: false
- }
- public async componentWillMount() {
- const { projectId } = this.props.match.params
- await (projectId && this.getCatalogues())
- // const parentId = Number(this.state.selectedCatalogueKeys[0]) || null
- // tslint:disable-next-line:no-unused-expression
- projectId && this.loadViews()
- window.addEventListener('resize', this.setScreenWidth, false)
- }
- private loadViews = async () => {
- const { projectId } = this.props.match.params
- if (projectId && this.state.selectedCatalogueKeys.length > 0) {
- const parentId = Number(this.state.selectedCatalogueKeys[0])
- try {
- this.setState({ tableLoading: true })
- const data = await request(
- api.getViewsByParentId +
- `?projectId=${projectId}&parentId=${parentId}`,
- { method: 'get' }
- )
- this.setState({
- // @ts-ignore
- viewList: (data.payload as unknown as IViewBase[]) ?? []
- })
- } catch (e) {
- console.log(e)
- } finally {
- this.setState({ tableLoading: false })
- }
- }
- }
- 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')
- return views.filter(
- (v) => v.name.match(regex) || v.description.match(regex)
- )
- }
- )
- 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 { viewList } = this.state
- const {
- tempFilterViewName,
- filterViewName,
- filterDropdownVisible,
- tableSorter
- } = this.state
- const sourceNames = viewList.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?parentId=${this.state.selectedCatalogueKeys?.join()}`
- )
- }
- 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
- )
- }
- private getCatalogues = async () => {
- try {
- const { projectId } = this.props.match.params
- this.setState({ treeLoading: true })
- // @ts-ignore
- const { payload } = await request(
- api.getCatalogues + `?projectId=${projectId}`,
- { method: 'GET' }
- )
- this.setState({
- catalogues: payload as unknown as ICatalogue[],
- selectedCatalogueKeys: payload?.[0]?.id ? [`${payload?.[0]?.id}`] : []
- })
- } catch (e) {
- console.log()
- } finally {
- this.setState({ treeLoading: false })
- }
- }
- private handleSaveCatalogue = async (catalogue: ICatalogue) => {
- const { projectId } = this.props.match.params
- if (!projectId) {
- return
- }
- try {
- const parentId = Number(this.state.selectedCatalogueKeys[0]) || null
- const catalogueFromView = this.state.catalogueFromView
- this.setState({ saveCatalogueLoading: true })
- // const api = this.state.catalogueFromView ? api.updateCatalogue : api.createCatalogue
- if (catalogueFromView) {
- await request(api.updateCatalogue + `/${catalogueFromView.id}`, {
- method: 'PUT',
- data: { ...catalogue, parentId, projectId }
- })
- } else {
- await request(api.createCatalogue, {
- method: 'post',
- data: { ...catalogue, parentId, projectId }
- })
- }
- this.setState({
- saveCatalogueLoading: true,
- catalogueModalVisible: false
- })
- this.getCatalogues()
- } finally {
- this.setState({
- saveCatalogueLoading: false,
- catalogueFromView: null
- })
- }
- }
- private handleEditCatalogue = (c: ICatalogue) => {
- this.setState({
- catalogueModalVisible: true,
- catalogueFromView: c
- })
- }
- private handleDeleteCatalogue = async (c: ICatalogue) => {
- try {
- this.setState({ treeLoading: true })
- const data = await request(api.deleteCatalogue + `/${c.id}`, {
- method: 'DELETE'
- })
- // @ts-ignore
- if (data?.header?.code === 200) {
- message.error({ content: '删除成功' })
- this.getCatalogues()
- } else {
- // @ts-ignore
- // tslint:disable-next-line:no-unused-expression
- data?.header?.msg && message.error({ content: data?.header?.msg })
- }
- } finally {
- this.setState({ treeLoading: false })
- }
- }
- private renderTree = (catalogues: ICatalogue[]) => {
- const { selectedCatalogueKeys } = this.state
- // tslint:disable-next-line:jsx-wrap-multiline
- return (
- <>
- {catalogues.map((c, idx) => (
- <React.Fragment key={c.id ?? idx}>
- <div
- key={c.id ?? idx}
- className={classnames(styles.treeNode, {
- [styles.treeNodeSelected]: selectedCatalogueKeys.includes(
- `${c.id}`
- ),
- [styles.treeNodeChild]: !!c.parentId
- })}
- >
- <span
- className={styles.treeNodeLeft}
- onClick={() => {
- this.setState({ selectedCatalogueKeys: [`${c.id}`] }, () => {
- this.loadViews()
- })
- }}
- >
- <Icon type="folder-open" />
- {c.name}
- </span>
- <Dropdown
- overlay={() => (
- <Menu>
- <Menu.Item
- key="0"
- onClick={() => this.handleEditCatalogue(c)}
- >
- 编辑
- </Menu.Item>
- <Menu.Item key="1">
- <Popconfirm
- title="确定删除?"
- placement="bottom"
- onConfirm={() => this.handleDeleteCatalogue(c)}
- >
- <a> 删除</a>
- </Popconfirm>
- </Menu.Item>
- </Menu>
- )}
- trigger={['click']}
- >
- <Icon type="more" />
- </Dropdown>
- </div>
- <div style={{ marginLeft: 20 }}>
- {c.children && this.renderTree(c.children)}
- </div>
- </React.Fragment>
- ))}
- </>
- )
- }
- public render() {
- const { currentProject, views, loading } = this.props
- const { screenWidth, filterViewName, viewList } = 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, viewList)
- const {
- copyModalVisible,
- copyFromView,
- catalogueModalVisible,
- catalogueFromView
- } = 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>
- <div className={styles.treeTableContainer}>
- <div className={styles.treeContainer}>
- <div className={styles.treeTitle}>
- <h6>资源目录列表</h6>
- <div
- className={styles.treePlusNode}
- onClick={() => {
- this.setState({ catalogueModalVisible: true })
- }}
- >
- <Icon type="plus" />
- </div>
- </div>
- <div className={styles.treeContent}>
- <Spin spinning={this.state.treeLoading}>
- {this.renderTree(this.state.catalogues)}
- </Spin>
- </div>
- </div>
- <Table
- style={{ flex: 1 }}
- bordered
- rowKey="id"
- loading={this.state.tableLoading}
- dataSource={filterViews}
- columns={tableColumns}
- pagination={tablePagination}
- onChange={this.tableChange}
- />
- </div>
- <div style={{ padding: 20 }} />
- </Box.Body>
- </Box>
- </ContainerBody>
- </Container>
- <CopyModal
- visible={copyModalVisible}
- loading={loading.copy}
- fromView={copyFromView}
- onCheckUniqueName={this.checkViewUniqueName}
- onCopy={this.copy}
- onCancel={this.cancelCopy}
- />
- <CatalogueModal
- visible={catalogueModalVisible}
- loading={this.state.saveCatalogueLoading}
- fromView={catalogueFromView}
- onCheckUniqueName={this.checkViewUniqueName}
- onSave={this.handleSaveCatalogue}
- onCancel={() => this.setState({ catalogueModalVisible: false })}
- />
- </>
- )
- }
- }
- const mapDispatchToProps = (dispatch: Dispatch<ViewActionType>) => ({
- onLoadViews: (projectId, parentId) =>
- dispatch(ViewActions.loadViews(projectId, parentId)),
- onDeleteView: (viewId, resolve) =>
- dispatch(ViewActions.deleteView(viewId, resolve)),
- onCopyView: (view, resolve) => dispatch(ViewActions.copyView(view, resolve)),
- // @ts-ignore
- onCheckName: (data, resolve, reject) =>
- // @ts-ignore
- 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)
|