index.tsx 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973
  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, { Suspense } from 'react'
  21. import Helmet from 'react-helmet'
  22. import { connect } from 'react-redux'
  23. import { createStructuredSelector } from 'reselect'
  24. import { Route } from 'react-router-dom'
  25. import { compose } from 'redux'
  26. import injectReducer from 'utils/injectReducer'
  27. import injectSaga from 'utils/injectSaga'
  28. import reducer from './reducer'
  29. import saga from './sagas'
  30. import projectReducer from '../Projects/reducer'
  31. import projectSaga from '../Projects/sagas'
  32. import vizReducer from '../Viz/reducer'
  33. import vizSaga from '../Viz/sagas'
  34. import widgetReducer from 'containers/Widget/reducer'
  35. import widgetSaga from 'containers/Widget/sagas'
  36. import viewReducer from '../View/reducer'
  37. import viewSaga from '../View/sagas'
  38. import DashboardForm from './components/DashboardForm'
  39. import DashboardAction from './components/DashboardAction'
  40. import { Button, Icon, Tooltip, Popover, Modal, Input, Tree } from 'antd'
  41. const TreeNode = Tree.TreeNode
  42. import { IconProps } from 'antd/lib/icon/index'
  43. import AntdFormType from 'antd/lib/form/Form'
  44. const Search = Input.Search
  45. import { VizActions } from 'containers/Viz/actions'
  46. import DashboardActions from './actions'
  47. import WidgetActions from 'containers/Widget/actions'
  48. import { makeSelectCurrentDashboards, makeSelectCurrentPortal, makeSelectVizLoading } from 'containers/Viz/selectors'
  49. import { makeSelectCurrentDashboard } from './selectors'
  50. import { makeSelectWidgets } from 'containers/Widget/selectors'
  51. import {
  52. hideNavigator,
  53. checkNameUniqueAction,
  54. loadDownloadList,
  55. downloadFile
  56. } from '../App/actions'
  57. import { makeSelectDownloadList, makeSelectDownloadListLoading } from '../App/selectors'
  58. import { IDownloadRecord } from '../App/types'
  59. import { DownloadTypes } from '../App/constants'
  60. import { listToTree, findFirstLeaf } from './components/localPositionUtil'
  61. import { ProjectActions } from '../Projects/actions'
  62. const { loadProjectDetail, excludeRoles } = ProjectActions
  63. import {IExludeRoles} from '../Viz/components/PortalList'
  64. const styles = require('./Dashboard.less')
  65. import {makeSelectCurrentProject, makeSelectProjectRoles} from '../Projects/selectors'
  66. import ModulePermission from '../Account/components/checkModulePermission'
  67. import { initializePermission } from '../Account/components/checkUtilPermission'
  68. import { IProject } from '../Projects/types'
  69. import EditorHeader from 'components/EditorHeader'
  70. import 'assets/less/resizer.less'
  71. const SplitPane = React.lazy(() => import('react-split-pane'))
  72. import {IProjectRoles} from '../Organizations/component/ProjectRole'
  73. import { OrganizationActions } from '../Organizations/actions'
  74. const { loadProjectRoles } = OrganizationActions
  75. import { RouteComponentWithParams } from 'utils/types'
  76. import { IWidgetFormed } from '../Widget/types'
  77. import { Grid } from './Loadable'
  78. interface IDashboardProps extends RouteComponentWithParams {
  79. modalLoading: {
  80. portal: boolean
  81. display: boolean
  82. editing: boolean
  83. dashboards: boolean
  84. slides: boolean
  85. }
  86. dashboards: IDashboard[]
  87. widgets: IWidgetFormed[]
  88. currentDashboard: IDashboard,
  89. currentProject: IProject
  90. currentPortal: any
  91. projectRoles: IProjectRoles[]
  92. downloadList: IDownloadRecord[]
  93. onLoadDashboards: (portalId: number, resolve: any) => void
  94. onLoadWidgets: (projectId: number) => void
  95. onAddDashboard: (dashboard: IDashboard, resolve: any) => any
  96. onEditDashboard: (type: string, dashboard: IDashboard[], resolve: any) => void
  97. onDeleteDashboard: (id: number, portalId: number, resolve: any) => void
  98. onHideNavigator: () => void
  99. onCheckUniqueName: (pathname: string, data: any, resolve: () => any, reject: (error: string) => any) => any
  100. onLoadPortals: (projectId) => void
  101. onLoadProjectDetail: (id) => any
  102. onExcludeRoles: (type: string, id: number, resolve?: any) => any
  103. onLoadProjectRoles: (id: number) => any
  104. onInitiateDownloadTask: (id: number, type: DownloadTypes, downloadParams?: any[]) => void
  105. onLoadDownloadList: () => void
  106. onDownloadFile: (id) => void
  107. onLoadViews: (projectId: number) => void
  108. }
  109. export interface IDashboard {
  110. id?: number
  111. name?: string
  112. config?: string
  113. parentId?: number
  114. dashboardPortalId?: number
  115. index?: number
  116. type?: number
  117. children?: any[]
  118. }
  119. interface IDashboardStates {
  120. formType: 'add' | 'edit' | 'copy' | 'move' | 'delete' | ''
  121. formVisible: boolean
  122. expandedKeys: string[],
  123. autoExpandParent: boolean
  124. searchValue: IDashboard[]
  125. dashboardData: any
  126. itemId: number
  127. dataList: any[]
  128. isExpand: boolean
  129. searchVisible: boolean
  130. isGrid: boolean
  131. checkedKeys: any[]
  132. splitSize: number
  133. portalTreeWidth: number
  134. exludeRoles: IExludeRoles[]
  135. }
  136. export class Dashboard extends React.Component<IDashboardProps, IDashboardStates> {
  137. private defaultSplitSize = 190
  138. private maxSplitSize = this.defaultSplitSize * 1.5
  139. constructor (props) {
  140. super(props)
  141. const splitSize = +localStorage.getItem('dashboardSplitSize') || this.defaultSplitSize
  142. this.state = {
  143. formType: '',
  144. formVisible: false,
  145. expandedKeys: [],
  146. autoExpandParent: true,
  147. searchValue: [],
  148. dashboardData: [],
  149. itemId: 0,
  150. dataList: [],
  151. isExpand: true,
  152. searchVisible: false,
  153. isGrid: true,
  154. checkedKeys: [],
  155. splitSize,
  156. portalTreeWidth: 0,
  157. exludeRoles: []
  158. }
  159. }
  160. private dashboardForm: AntdFormType = null
  161. private refHandlers = {
  162. dashboardForm: (ref) => this.dashboardForm = ref
  163. }
  164. public componentWillMount () {
  165. const {
  166. match,
  167. onLoadWidgets,
  168. onLoadPortals,
  169. onLoadProjectDetail,
  170. onLoadProjectRoles
  171. } = this.props
  172. const { projectId, portalId } = match.params
  173. onLoadWidgets(Number(projectId))
  174. onLoadPortals(projectId)
  175. onLoadProjectDetail(projectId)
  176. onLoadProjectRoles(Number(projectId))
  177. this.initPortal(projectId, portalId)
  178. }
  179. public componentWillReceiveProps (nextProps: IDashboardProps) {
  180. const { match, dashboards } = nextProps
  181. const { projectId, portalId } = match.params
  182. if (portalId !== this.props.match.params.portalId) {
  183. this.initPortal(projectId, portalId)
  184. }
  185. if (dashboards !== this.props.dashboards) {
  186. this.initalDashboardData(dashboards)
  187. }
  188. }
  189. public componentDidMount () {
  190. this.props.onHideNavigator()
  191. }
  192. private initPortal = (projectId, portalId) => {
  193. const { history, onLoadDashboards } = this.props
  194. const dashboardId = this.getDashboardIdFromLocation()
  195. onLoadDashboards(+portalId, (result) => {
  196. let defaultDashboardId = 0
  197. const dashboardData = listToTree(result, 0)
  198. const treeData = {
  199. id: -1,
  200. type: 2,
  201. children: dashboardData
  202. }
  203. defaultDashboardId = findFirstLeaf(treeData)
  204. if (defaultDashboardId >= 0 && !dashboardId) {
  205. history.replace(`/project/${projectId}/portal/${portalId}/dashboard/${defaultDashboardId}`)
  206. }
  207. this.setState({
  208. dashboardData,
  209. isGrid: defaultDashboardId >= 0,
  210. portalTreeWidth: Number(localStorage.getItem('dashboardSplitSize'))
  211. })
  212. this.expandAll(result)
  213. })
  214. }
  215. private initalDashboardData = (dashboards) => {
  216. this.setState({
  217. dashboardData: listToTree(dashboards, 0)
  218. })
  219. this.expandAll(dashboards)
  220. }
  221. private getDashboardIdFromLocation = () => {
  222. const urlPieces = location.href.split('/')
  223. const lastModuleName = urlPieces[urlPieces.length - 2]
  224. const lastModuleId = urlPieces[urlPieces.length - 1]
  225. return lastModuleName === 'dashboard' ? Number(lastModuleId) : 0
  226. }
  227. private changeDashboard = (dashboardId) => (e) => {
  228. const { match, history } = this.props
  229. const { projectId, portalId } = match.params
  230. const currentDashboardId = this.getDashboardIdFromLocation()
  231. if (currentDashboardId === dashboardId) {
  232. return
  233. }
  234. this.setState({
  235. isGrid: true
  236. }, () => {
  237. history.replace(`/project/${projectId}/portal/${portalId}/dashboard/${dashboardId}`)
  238. })
  239. }
  240. private hideDashboardForm = () => {
  241. this.setState({
  242. formVisible: false,
  243. checkedKeys: []
  244. }, () => {
  245. this.dashboardForm.props.form.resetFields()
  246. })
  247. }
  248. private onModalOk = () => {
  249. const { formType, checkedKeys } = this.state
  250. if (formType === 'delete') {
  251. const id = this.dashboardForm.props.form.getFieldValue('id')
  252. this.confirmDeleteDashboard(id)
  253. } else {
  254. this.dashboardForm.props.form.validateFieldsAndScroll((err, values) => {
  255. if (!err) {
  256. const { dashboards, match, history, onEditDashboard, onAddDashboard } = this.props
  257. const { id, name, folder, selectType, index, config } = values
  258. const dashArr = folder === '0'
  259. ? dashboards.filter((d) => d.parentId === 0)
  260. : dashboards.filter((d) => d.parentId === Number(folder))
  261. const indexTemp = dashArr.length === 0 ? 0 : dashArr[dashArr.length - 1].index + 1
  262. const obj = {
  263. config,
  264. dashboardPortalId: +match.params.portalId,
  265. name,
  266. // type: selectType ? 1 : 0 // todo selectType 更改位置
  267. type: Number(selectType)
  268. }
  269. const addObj = {
  270. ...obj,
  271. parentId: Number(folder),
  272. index: indexTemp,
  273. roleIds: this.state.exludeRoles.filter((role) => !role.permission).map((p) => p.id)
  274. }
  275. const editObj = [{
  276. ...obj,
  277. parentId: Number(folder),
  278. id,
  279. index,
  280. roleIds: this.state.exludeRoles.filter((role) => !role.permission).map((p) => p.id)
  281. }]
  282. const currentArr = dashboards.filter((d) => d.parentId === Number(folder))
  283. const moveObj = [{
  284. ...obj,
  285. parentId: Number(folder),
  286. id,
  287. index: currentArr.length ? currentArr[currentArr.length - 1].index + 1 : 0
  288. }]
  289. switch (formType) {
  290. case 'add':
  291. // case 'copy':
  292. onAddDashboard(addObj, (dashboardId) => {
  293. this.hideDashboardForm()
  294. this.setState({ isGrid: true })
  295. const { projectId, portalId } = match.params
  296. addObj.type === 0
  297. ? history.replace(`/project/${projectId}/portal/${portalId}`)
  298. : history.replace(`/project/${projectId}/portal/${portalId}/dashboard/${dashboardId}`)
  299. })
  300. break
  301. case 'edit':
  302. onEditDashboard('edit', editObj, () => { this.hideDashboardForm() })
  303. break
  304. case 'move':
  305. onEditDashboard('move', moveObj, () => { this.hideDashboardForm() })
  306. break
  307. }
  308. }
  309. })
  310. }
  311. }
  312. private onExpand = (expandedKeys) => {
  313. this.setState({
  314. expandedKeys,
  315. autoExpandParent: false
  316. })
  317. }
  318. private onDrop = (info) => {
  319. const { dashboards } = this.props
  320. const dragKey = info.dragNode.props.eventKey // 开始(要拖拽元素)id Number(dragKey) === dragNodesKeys[0] === 每一项的id
  321. // const dragPos = info.dragNode.props.pos // 开始的位置'0-1-0'
  322. const dropKey = info.node.props.eventKey // 结束的id
  323. // const dropPos = info.node.props.pos.split('-')// 结束位置的树形结构层级'0-1'
  324. // const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1])
  325. // const dragNodesKeys = info.dragNodesKeys;
  326. const loop = (data, key, callback) => {
  327. data.forEach((item, index, arr) => {
  328. if (item.id === Number(key)) {
  329. return callback(item, index, arr)
  330. }
  331. if (item.children) {
  332. return loop(item.children, key, callback)
  333. }
  334. })
  335. }
  336. const data = [...this.state.dashboardData]
  337. let dragItem = null
  338. loop(data, dragKey, (item) => {
  339. dragItem = item
  340. })
  341. // let dragObj
  342. loop(data, dropKey, (item, i, arr) => {
  343. const { config, dashboardPortalId, id, name, type } = dragItem
  344. let dropObj = dashboards.find((d) => d.id === Number(dropKey))
  345. const dropObjParentId = dropObj.parentId
  346. let value = []
  347. if (!info.dropToGap && dropObj.type === 1) {
  348. if (dragItem.type === 0) {
  349. return
  350. }
  351. }
  352. if (!info.dropToGap && dropObj.type === 0) {
  353. const currentArr = dropObj.children
  354. value = [{
  355. config,
  356. dashboardPortalId,
  357. id,
  358. index: currentArr.length ? currentArr[currentArr.length - 1].index + 1 : 0,
  359. name,
  360. parentId: Number(dropKey),
  361. type
  362. }]
  363. this.props.onEditDashboard('move', value, (result) => {
  364. // dragObj = result
  365. })
  366. return
  367. }
  368. let partArr = Number(dropObj.parentId) === 0
  369. ? dashboards.filter((d) => d.parentId === 0)
  370. : (dashboards.find((d) => d.id === Number(dropObj.parentId))).children
  371. dropObj = i > info.dropPosition ? partArr[i] : partArr[i + 1] // a trick 判断拖拽位置是哪条线,如果是下面那条线则上移一位
  372. const dropObjIndex = dropObj ? dropObj.index : partArr[i].index + 1
  373. if (!dropObj) {
  374. value.unshift({
  375. config,
  376. dashboardPortalId,
  377. id,
  378. index: dropObjIndex,
  379. name,
  380. parentId: dropObjParentId,
  381. type
  382. })
  383. }
  384. if (!!dropObj && (info.dropToGap || dropObj.type === 1)) {
  385. // const dropObj = dashboards.find((d) => d.id === Number(dropKey))
  386. partArr = Number(dropObj.parentId) === 0
  387. ? dashboards.filter((d) => d.parentId === 0)
  388. : (dashboards.find((d) => d.id === Number(dropObj.parentId))).children
  389. const othersArr = partArr.filter((p) => p.index >= dropObj.index).filter((o) => o.id !== id)
  390. value = othersArr.map((o) => {
  391. const { config, dashboardPortalId, id, index, name, parentId, type } = o
  392. return {
  393. config,
  394. dashboardPortalId,
  395. id,
  396. index: index + 1,
  397. name,
  398. parentId,
  399. type
  400. }
  401. })
  402. value.unshift({
  403. config,
  404. dashboardPortalId,
  405. id,
  406. index: dropObjIndex,
  407. name,
  408. parentId: dropObj.parentId,
  409. type
  410. })
  411. }
  412. this.props.onEditDashboard('move', value, (result) => {
  413. // dragObj = result
  414. })
  415. })
  416. this.setState({
  417. dashboardData: data
  418. })
  419. }
  420. private onAddItem = () => {
  421. this.setState({
  422. formVisible: true,
  423. formType: 'add'
  424. }, () => {
  425. this.setState({
  426. exludeRoles: this.props.projectRoles.map((role) => {
  427. return {
  428. ...role,
  429. permission: true
  430. }
  431. })
  432. })
  433. })
  434. }
  435. private onCollapseAll = () => {
  436. this.onExpand([])
  437. this.setState({
  438. isExpand: false
  439. })
  440. }
  441. private onExpandAll = () => {
  442. const { dashboards } = this.props
  443. if (dashboards) {
  444. this.expandAll(dashboards)
  445. }
  446. }
  447. private expandAll (dashboards) {
  448. const expandArr = []
  449. dashboards.filter((d) => d.type === 0)
  450. .forEach((i) => expandArr.push(`${i.id}`))
  451. this.onExpand(expandArr)
  452. this.setState({
  453. isExpand: true
  454. })
  455. }
  456. private onShowDashboardForm (item, formType) {
  457. const { dashboards, match, onLoadDashboards } = this.props
  458. this.setState({
  459. formVisible: true,
  460. itemId: item.id
  461. }, () => {
  462. onLoadDashboards(+match.params.portalId, (result) => {
  463. setTimeout(() => {
  464. const {
  465. config,
  466. id,
  467. name,
  468. parentId,
  469. type,
  470. index
  471. } = (result as any[]).find((g) => g.id === item.id)
  472. this.dashboardForm.props.form.setFieldsValue({
  473. id,
  474. folder: parentId ? `${(dashboards as any[]).find((g) => g.id === parentId).id}` : '0',
  475. config,
  476. name: formType === 'copy' ? `${name}_copy` : name,
  477. // selectType: type === 1,
  478. selectType: type,
  479. index
  480. })
  481. }, 0)
  482. })
  483. const { onExcludeRoles, projectRoles } = this.props
  484. if (onExcludeRoles && item && item.id) {
  485. onExcludeRoles('dashboard', item.id, (result: number[]) => {
  486. this.setState({
  487. exludeRoles: projectRoles.map((role) => {
  488. return result.some((re) => re === role.id) ? {...role, permission: false} : {...role, permission: true}
  489. })
  490. })
  491. })
  492. }
  493. })
  494. }
  495. private onOperateMore = (item, type) => {
  496. if (type === 'download') {
  497. this.props.onInitiateDownloadTask(item.id, item.type === 0 ? DownloadTypes.Folder : DownloadTypes.Dashboard, [])
  498. } else {
  499. this.setState({
  500. formType: type
  501. }, () => {
  502. this.onShowDashboardForm(item, this.state.formType)
  503. })
  504. }
  505. }
  506. private searchDashboard = (e) => {
  507. const { dashboards } = this.props
  508. const { value } = e.target
  509. this.setState({
  510. searchValue: value ? dashboards.filter((d) => d.name.includes(value)) : []
  511. })
  512. }
  513. private pickSearchDashboard = (dashboardId) => (e) => {
  514. const { dashboards } = this.props
  515. this.setState({
  516. searchVisible: false
  517. })
  518. const currentDashoboard = dashboards.find((d) => d.id === dashboardId)
  519. if (currentDashoboard.type === 1) {
  520. this.changeDashboard(dashboardId)(e)
  521. } else if (currentDashoboard.type === 0) {
  522. const currentFolderArr = dashboards.filter((d) => d.parentId === dashboardId)
  523. if (currentFolderArr.length !== 0) {
  524. this.changeDashboard(currentFolderArr[0].id)(e)
  525. }
  526. }
  527. }
  528. private confirmDeleteDashboard = (id) => {
  529. const { match, history, onDeleteDashboard, dashboards } = this.props
  530. const { dashboardData } = this.state
  531. const dashboardId = this.getDashboardIdFromLocation()
  532. onDeleteDashboard(id, +match.params.portalId, () => {
  533. const { projectId, portalId } = match.params
  534. const paramsDashboard = dashboards.find((d) => d.id === dashboardId)
  535. const noCurrentDashboards = dashboardData.filter((d) => d.id !== id)
  536. if (noCurrentDashboards.length !== 0 && paramsDashboard) {
  537. const remainDashboards = noCurrentDashboards.filter((r) => r.parentId !== id)
  538. const treeData = {
  539. id: -1,
  540. type: 2,
  541. children: remainDashboards
  542. }
  543. if (dashboardId === id || paramsDashboard.parentId === id) {
  544. const defaultDashboardId = findFirstLeaf(treeData)
  545. history.replace(`/project/${projectId}/portal/${portalId}/dashboard/${defaultDashboardId}`)
  546. }
  547. } else {
  548. history.replace(`/project/${projectId}/portal/${portalId}/dashboard/-1`)
  549. this.setState({
  550. isGrid: false
  551. })
  552. }
  553. this.hideDashboardForm()
  554. })
  555. }
  556. private searchVisibleChange = (visible) => {
  557. this.setState({
  558. searchVisible: visible
  559. })
  560. }
  561. private handleTree = (clickKey, obj) => {
  562. const { expandedKeys } = this.state
  563. this.setState({
  564. autoExpandParent: false
  565. })
  566. if (obj.selected) {
  567. if (expandedKeys.indexOf(clickKey[0]) < 0) {
  568. expandedKeys.push(clickKey[0])
  569. this.setState({
  570. expandedKeys
  571. })
  572. } else {
  573. this.setState({
  574. expandedKeys: expandedKeys.filter((e) => e !== clickKey[0])
  575. })
  576. }
  577. } else {
  578. let currentKey = []
  579. if (expandedKeys.length === 0) {
  580. expandedKeys.push(obj.node.props.eventKey)
  581. currentKey = expandedKeys
  582. } else {
  583. currentKey = expandedKeys.filter((e) => e !== obj.node.props.title)
  584. }
  585. this.setState({
  586. expandedKeys: currentKey
  587. })
  588. }
  589. }
  590. private cancel = () => {
  591. const { history, match } = this.props
  592. history.replace(`/project/${match.params.projectId}/vizs`)
  593. }
  594. private initCheckNodes = (checkedKeys) => {
  595. this.setState({
  596. checkedKeys
  597. })
  598. }
  599. private changePortalTreeWidth = (newSize: number) => {
  600. this.setState({
  601. portalTreeWidth: newSize
  602. })
  603. }
  604. private saveSplitSize = (newSize: number) => {
  605. localStorage.setItem('dashboardSplitSize', newSize.toString())
  606. this.changePortalTreeWidth(newSize)
  607. // triggering ResponsiveReactGridLayout readjustment
  608. const resizeEvent = document.createEvent('Event')
  609. resizeEvent.initEvent('resize', false, true)
  610. window.dispatchEvent(resizeEvent)
  611. }
  612. private changePermission = (scope: IExludeRoles, event) => {
  613. scope.permission = event.target.checked
  614. this.setState({
  615. exludeRoles: this.state.exludeRoles.map((role) => role && role.id === scope.id ? scope : role)
  616. })
  617. }
  618. public render () {
  619. const {
  620. match,
  621. currentDashboard,
  622. dashboards,
  623. widgets,
  624. modalLoading,
  625. currentProject,
  626. currentPortal,
  627. downloadList,
  628. onCheckUniqueName,
  629. onLoadDownloadList,
  630. onDownloadFile
  631. } = this.props
  632. const {
  633. formType,
  634. formVisible,
  635. searchValue,
  636. dashboardData,
  637. isGrid,
  638. searchVisible,
  639. checkedKeys,
  640. splitSize,
  641. portalTreeWidth
  642. } = this.state
  643. const items = searchValue.map((s) => {
  644. return <li key={s.id} onClick={this.pickSearchDashboard(s.id)}>{s.name}</li>
  645. })
  646. const dashboardId = this.getDashboardIdFromLocation().toString()
  647. let modalTitle = ''
  648. switch (formType) {
  649. case 'add':
  650. modalTitle = '新增'
  651. break
  652. case 'edit':
  653. modalTitle = '修改'
  654. break
  655. case 'copy':
  656. modalTitle = '复制'
  657. break
  658. case 'move':
  659. modalTitle = '移动'
  660. break
  661. case 'delete':
  662. modalTitle = '提示'
  663. break
  664. }
  665. const modalButtons = [(
  666. <Button
  667. key="back"
  668. size="large"
  669. onClick={this.hideDashboardForm}
  670. >
  671. 取 消
  672. </Button>
  673. ), (
  674. <Button
  675. key="submit"
  676. size="large"
  677. type="primary"
  678. loading={modalLoading.editing}
  679. onClick={this.onModalOk}
  680. >
  681. {formType === 'delete' ? '确 定' : '保 存'}
  682. </Button>
  683. )]
  684. const loop = (data, depth = 0) => data.map((item) => {
  685. const dashboardAction = (
  686. <DashboardAction
  687. currentProject={currentProject}
  688. depth={depth}
  689. item={item}
  690. splitWidth={portalTreeWidth || 190}
  691. onInitOperateMore={this.onOperateMore}
  692. initChangeDashboard={this.changeDashboard}
  693. />
  694. )
  695. if (item.type === 0) {
  696. return (
  697. <TreeNode icon={<Icon type="smile-o" />} key={item.id} title={dashboardAction} >
  698. {loop(item.children, depth + 1)}
  699. </TreeNode>
  700. )
  701. }
  702. return <TreeNode icon={<Icon type="smile-o" />} key={item.id} title={dashboardAction} />
  703. })
  704. const AdminIcon = ModulePermission<IconProps>(currentProject, 'viz', true)(Icon)
  705. const portalName = currentPortal && currentPortal.name
  706. const portalDesc = currentPortal && currentPortal.description
  707. return (
  708. <div className={styles.portal}>
  709. <EditorHeader
  710. className={styles.portalHeader}
  711. currentType="dashboard"
  712. name={portalName}
  713. description={portalDesc}
  714. downloadList={downloadList}
  715. onCancel={this.cancel}
  716. onLoadDownloadList={onLoadDownloadList}
  717. onDownloadFile={onDownloadFile}
  718. />
  719. <Helmet title={portalName} />
  720. <div className={styles.portalBody}>
  721. <Suspense fallback={null}>
  722. <SplitPane
  723. split="vertical"
  724. defaultSize={splitSize}
  725. minSize={this.defaultSplitSize}
  726. maxSize={this.maxSplitSize}
  727. onChange={this.changePortalTreeWidth}
  728. onDragFinished={this.saveSplitSize}
  729. >
  730. <div className={styles.portalTree} style={{ width: portalTreeWidth || 190 }}>
  731. <div className={styles.portalRow}>
  732. <span className={styles.portalAction}>
  733. <Popover
  734. placement="bottom"
  735. content={
  736. <div className={styles.portalTreeSearch}>
  737. <Search
  738. placeholder="Search"
  739. onChange={this.searchDashboard}
  740. />
  741. <ul>
  742. {items}
  743. </ul>
  744. </div>}
  745. trigger="click"
  746. visible={searchVisible}
  747. onVisibleChange={this.searchVisibleChange}
  748. >
  749. <Tooltip placement="top" title="搜索">
  750. <Icon
  751. type="search"
  752. className={styles.search}
  753. />
  754. </Tooltip>
  755. </Popover>
  756. <Tooltip placement="top" title="新增">
  757. <AdminIcon
  758. type="plus"
  759. className={styles.plus}
  760. onClick={this.onAddItem}
  761. />
  762. </Tooltip>
  763. <Popover
  764. placement="bottom"
  765. content={
  766. <ul className={styles.menu}>
  767. <li onClick={this.onCollapseAll}>收起全部</li>
  768. <li onClick={this.onExpandAll}>展开全部</li>
  769. </ul>}
  770. trigger="click"
  771. >
  772. <Tooltip placement="top" title="更多">
  773. <Icon
  774. type="ellipsis"
  775. className={styles.more}
  776. />
  777. </Tooltip>
  778. </Popover>
  779. </span>
  780. </div>
  781. { dashboardData.length
  782. ? <div className={styles.portalTreeNode}>
  783. <Tree
  784. onExpand={this.onExpand}
  785. expandedKeys={this.state.expandedKeys}
  786. autoExpandParent={this.state.autoExpandParent}
  787. selectedKeys={[dashboardId]}
  788. draggable={initializePermission(currentProject, 'vizPermission')}
  789. onDrop={this.onDrop}
  790. onSelect={this.handleTree}
  791. >
  792. {loop(dashboardData)}
  793. </Tree>
  794. </div>
  795. : isGrid ? <h3 className={styles.loadingTreeMsg}>Loading tree......</h3> : ''
  796. }
  797. </div>
  798. <div className={styles.gridClass}>
  799. {
  800. isGrid && widgets
  801. ? <Route path="/project/:projectId/portal/:portalId/dashboard/:dashboardId" component={Grid} />
  802. : (
  803. <div className={styles.noDashboard}>
  804. <img src={require('assets/images/noDashboard.png')} onClick={this.onAddItem}/>
  805. <p>请创建文件夹或 Dashboard</p>
  806. </div>
  807. )
  808. }
  809. </div>
  810. </SplitPane>
  811. </Suspense>
  812. </div>
  813. <Modal
  814. title={modalTitle}
  815. wrapClassName="ant-modal-small"
  816. visible={formVisible}
  817. footer={modalButtons}
  818. onCancel={this.hideDashboardForm}
  819. >
  820. <DashboardForm
  821. type={formType}
  822. itemId={this.state.itemId}
  823. dashboards={dashboards}
  824. portalId={Number(match.params.portalId)}
  825. exludeRoles={this.state.exludeRoles}
  826. onCheckUniqueName={onCheckUniqueName}
  827. onChangePermission={this.changePermission}
  828. wrappedComponentRef={this.refHandlers.dashboardForm}
  829. />
  830. </Modal>
  831. </div>
  832. )
  833. }
  834. }
  835. const mapStateToProps = createStructuredSelector({
  836. dashboards: makeSelectCurrentDashboards(),
  837. widgets: makeSelectWidgets(),
  838. currentDashboard: makeSelectCurrentDashboard(),
  839. modalLoading: makeSelectVizLoading(),
  840. currentProject: makeSelectCurrentProject(),
  841. currentPortal: makeSelectCurrentPortal(),
  842. projectRoles: makeSelectProjectRoles(),
  843. downloadList: makeSelectDownloadList()
  844. })
  845. export function mapDispatchToProps (dispatch) {
  846. return {
  847. onLoadDashboards: (portalId, resolve) => dispatch(VizActions.loadPortalDashboards(portalId, resolve, false)),
  848. onLoadWidgets: (projectId: number) => dispatch(WidgetActions.loadWidgets(projectId)),
  849. onAddDashboard: (dashboard, resolve) => dispatch(VizActions.addDashboard(dashboard, resolve)),
  850. onEditDashboard: (formType, dashboard, resolve) => dispatch(VizActions.editDashboard(formType, dashboard, resolve)),
  851. onDeleteDashboard: (id, portalId, resolve) => dispatch(VizActions.deleteDashboard(id, portalId, resolve)),
  852. onHideNavigator: () => dispatch(hideNavigator()),
  853. onCheckUniqueName: (pathname, data, resolve, reject) => dispatch(checkNameUniqueAction(pathname, data, resolve, reject)),
  854. onLoadPortals: (projectId) => dispatch(VizActions.loadPortals(projectId)),
  855. onLoadProjectDetail: (id) => dispatch(loadProjectDetail(id)),
  856. onExcludeRoles: (type, id, resolve) => dispatch(excludeRoles(type, id, resolve)),
  857. onLoadProjectRoles: (id) => dispatch(loadProjectRoles(id)),
  858. onInitiateDownloadTask: (id, type, downloadParams?) => dispatch(DashboardActions.initiateDownloadTask(id, type, downloadParams)),
  859. onLoadDownloadList: () => dispatch(loadDownloadList()),
  860. onDownloadFile: (id) => dispatch(downloadFile(id))
  861. }
  862. }
  863. const withConnect = connect(mapStateToProps, mapDispatchToProps)
  864. const withReducer = injectReducer({ key: 'dashboard', reducer })
  865. const withSaga = injectSaga({ key: 'dashboard', saga })
  866. const withProjectReducer = injectReducer({ key: 'project', reducer: projectReducer })
  867. const withProjectSaga = injectSaga({ key: 'project', saga: projectSaga })
  868. const withVizReducer = injectReducer({ key: 'viz', reducer: vizReducer })
  869. const withVizSaga = injectSaga({ key: 'viz', saga: vizSaga })
  870. const withWidgetReducer = injectReducer({ key: 'widget', reducer: widgetReducer })
  871. const withWidgetSaga = injectSaga({ key: 'widget', saga: widgetSaga })
  872. const withViewReducer = injectReducer({ key: 'view', reducer: viewReducer })
  873. const withViewSaga = injectSaga({ key: 'view', saga: viewSaga })
  874. export default compose(
  875. withReducer,
  876. withProjectReducer,
  877. withVizReducer,
  878. withWidgetReducer,
  879. withViewReducer,
  880. withSaga,
  881. withProjectSaga,
  882. withVizSaga,
  883. withWidgetSaga,
  884. withViewSaga,
  885. withConnect
  886. )(Dashboard)