index.tsx 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975
  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. const prefix = window.localStorage.getItem('inDataService') ?? ''
  593. const prefixPath = prefix ? '/' + prefix : prefix
  594. history.replace(`/project/${match.params.projectId}${prefixPath}/vizs`)
  595. }
  596. private initCheckNodes = (checkedKeys) => {
  597. this.setState({
  598. checkedKeys
  599. })
  600. }
  601. private changePortalTreeWidth = (newSize: number) => {
  602. this.setState({
  603. portalTreeWidth: newSize
  604. })
  605. }
  606. private saveSplitSize = (newSize: number) => {
  607. localStorage.setItem('dashboardSplitSize', newSize.toString())
  608. this.changePortalTreeWidth(newSize)
  609. // triggering ResponsiveReactGridLayout readjustment
  610. const resizeEvent = document.createEvent('Event')
  611. resizeEvent.initEvent('resize', false, true)
  612. window.dispatchEvent(resizeEvent)
  613. }
  614. private changePermission = (scope: IExludeRoles, event) => {
  615. scope.permission = event.target.checked
  616. this.setState({
  617. exludeRoles: this.state.exludeRoles.map((role) => role && role.id === scope.id ? scope : role)
  618. })
  619. }
  620. public render () {
  621. const {
  622. match,
  623. currentDashboard,
  624. dashboards,
  625. widgets,
  626. modalLoading,
  627. currentProject,
  628. currentPortal,
  629. downloadList,
  630. onCheckUniqueName,
  631. onLoadDownloadList,
  632. onDownloadFile
  633. } = this.props
  634. const {
  635. formType,
  636. formVisible,
  637. searchValue,
  638. dashboardData,
  639. isGrid,
  640. searchVisible,
  641. checkedKeys,
  642. splitSize,
  643. portalTreeWidth
  644. } = this.state
  645. const items = searchValue.map((s) => {
  646. return <li key={s.id} onClick={this.pickSearchDashboard(s.id)}>{s.name}</li>
  647. })
  648. const dashboardId = this.getDashboardIdFromLocation().toString()
  649. let modalTitle = ''
  650. switch (formType) {
  651. case 'add':
  652. modalTitle = '新增'
  653. break
  654. case 'edit':
  655. modalTitle = '修改'
  656. break
  657. case 'copy':
  658. modalTitle = '复制'
  659. break
  660. case 'move':
  661. modalTitle = '移动'
  662. break
  663. case 'delete':
  664. modalTitle = '提示'
  665. break
  666. }
  667. const modalButtons = [(
  668. <Button
  669. key="back"
  670. size="large"
  671. onClick={this.hideDashboardForm}
  672. >
  673. 取 消
  674. </Button>
  675. ), (
  676. <Button
  677. key="submit"
  678. size="large"
  679. type="primary"
  680. loading={modalLoading.editing}
  681. onClick={this.onModalOk}
  682. >
  683. {formType === 'delete' ? '确 定' : '保 存'}
  684. </Button>
  685. )]
  686. const loop = (data, depth = 0) => data.map((item) => {
  687. const dashboardAction = (
  688. <DashboardAction
  689. currentProject={currentProject}
  690. depth={depth}
  691. item={item}
  692. splitWidth={portalTreeWidth || 190}
  693. onInitOperateMore={this.onOperateMore}
  694. initChangeDashboard={this.changeDashboard}
  695. />
  696. )
  697. if (item.type === 0) {
  698. return (
  699. <TreeNode icon={<Icon type="smile-o" />} key={item.id} title={dashboardAction} >
  700. {loop(item.children, depth + 1)}
  701. </TreeNode>
  702. )
  703. }
  704. return <TreeNode icon={<Icon type="smile-o" />} key={item.id} title={dashboardAction} />
  705. })
  706. const AdminIcon = ModulePermission<IconProps>(currentProject, 'viz', true)(Icon)
  707. const portalName = currentPortal && currentPortal.name
  708. const portalDesc = currentPortal && currentPortal.description
  709. return (
  710. <div className={styles.portal}>
  711. <EditorHeader
  712. className={styles.portalHeader}
  713. currentType="dashboard"
  714. name={portalName}
  715. description={portalDesc}
  716. downloadList={downloadList}
  717. onCancel={this.cancel}
  718. onLoadDownloadList={onLoadDownloadList}
  719. onDownloadFile={onDownloadFile}
  720. />
  721. <Helmet title={portalName} />
  722. <div className={styles.portalBody}>
  723. <Suspense fallback={null}>
  724. <SplitPane
  725. split="vertical"
  726. defaultSize={splitSize}
  727. minSize={this.defaultSplitSize}
  728. maxSize={this.maxSplitSize}
  729. onChange={this.changePortalTreeWidth}
  730. onDragFinished={this.saveSplitSize}
  731. >
  732. <div className={styles.portalTree} style={{ width: portalTreeWidth || 190 }}>
  733. <div className={styles.portalRow}>
  734. <span className={styles.portalAction}>
  735. <Popover
  736. placement="bottom"
  737. content={
  738. <div className={styles.portalTreeSearch}>
  739. <Search
  740. placeholder="搜索"
  741. onChange={this.searchDashboard}
  742. />
  743. <ul>
  744. {items}
  745. </ul>
  746. </div>}
  747. trigger="click"
  748. visible={searchVisible}
  749. onVisibleChange={this.searchVisibleChange}
  750. >
  751. <Tooltip placement="top" title="搜索">
  752. <Icon
  753. type="search"
  754. className={styles.search}
  755. />
  756. </Tooltip>
  757. </Popover>
  758. <Tooltip placement="top" title="新增">
  759. <AdminIcon
  760. type="plus"
  761. className={styles.plus}
  762. onClick={this.onAddItem}
  763. />
  764. </Tooltip>
  765. <Popover
  766. placement="bottom"
  767. content={
  768. <ul className={styles.menu}>
  769. <li onClick={this.onCollapseAll}>收起全部</li>
  770. <li onClick={this.onExpandAll}>展开全部</li>
  771. </ul>}
  772. trigger="click"
  773. >
  774. <Tooltip placement="top" title="更多">
  775. <Icon
  776. type="ellipsis"
  777. className={styles.more}
  778. />
  779. </Tooltip>
  780. </Popover>
  781. </span>
  782. </div>
  783. { dashboardData.length
  784. ? <div className={styles.portalTreeNode}>
  785. <Tree
  786. onExpand={this.onExpand}
  787. expandedKeys={this.state.expandedKeys}
  788. autoExpandParent={this.state.autoExpandParent}
  789. selectedKeys={[dashboardId]}
  790. draggable={initializePermission(currentProject, 'vizPermission')}
  791. onDrop={this.onDrop}
  792. onSelect={this.handleTree}
  793. >
  794. {loop(dashboardData)}
  795. </Tree>
  796. </div>
  797. : isGrid ? <h3 className={styles.loadingTreeMsg}>加载......</h3> : ''
  798. }
  799. </div>
  800. <div className={styles.gridClass}>
  801. {
  802. isGrid && widgets
  803. ? <Route path="/project/:projectId/portal/:portalId/dashboard/:dashboardId" component={Grid} />
  804. : (
  805. <div className={styles.noDashboard}>
  806. <img src={require('assets/images/noDashboard.png')} onClick={this.onAddItem}/>
  807. <p>请创建文件夹或 仪表板</p>
  808. </div>
  809. )
  810. }
  811. </div>
  812. </SplitPane>
  813. </Suspense>
  814. </div>
  815. <Modal
  816. title={modalTitle}
  817. wrapClassName="ant-modal-small"
  818. visible={formVisible}
  819. footer={modalButtons}
  820. onCancel={this.hideDashboardForm}
  821. >
  822. <DashboardForm
  823. type={formType}
  824. itemId={this.state.itemId}
  825. dashboards={dashboards}
  826. portalId={Number(match.params.portalId)}
  827. exludeRoles={this.state.exludeRoles}
  828. onCheckUniqueName={onCheckUniqueName}
  829. onChangePermission={this.changePermission}
  830. wrappedComponentRef={this.refHandlers.dashboardForm}
  831. />
  832. </Modal>
  833. </div>
  834. )
  835. }
  836. }
  837. const mapStateToProps = createStructuredSelector({
  838. dashboards: makeSelectCurrentDashboards(),
  839. widgets: makeSelectWidgets(),
  840. currentDashboard: makeSelectCurrentDashboard(),
  841. modalLoading: makeSelectVizLoading(),
  842. currentProject: makeSelectCurrentProject(),
  843. currentPortal: makeSelectCurrentPortal(),
  844. projectRoles: makeSelectProjectRoles(),
  845. downloadList: makeSelectDownloadList()
  846. })
  847. export function mapDispatchToProps (dispatch) {
  848. return {
  849. onLoadDashboards: (portalId, resolve) => dispatch(VizActions.loadPortalDashboards(portalId, resolve, false)),
  850. onLoadWidgets: (projectId: number) => dispatch(WidgetActions.loadWidgets(projectId)),
  851. onAddDashboard: (dashboard, resolve) => dispatch(VizActions.addDashboard(dashboard, resolve)),
  852. onEditDashboard: (formType, dashboard, resolve) => dispatch(VizActions.editDashboard(formType, dashboard, resolve)),
  853. onDeleteDashboard: (id, portalId, resolve) => dispatch(VizActions.deleteDashboard(id, portalId, resolve)),
  854. onHideNavigator: () => dispatch(hideNavigator()),
  855. onCheckUniqueName: (pathname, data, resolve, reject) => dispatch(checkNameUniqueAction(pathname, data, resolve, reject)),
  856. onLoadPortals: (projectId) => dispatch(VizActions.loadPortals(projectId)),
  857. onLoadProjectDetail: (id) => dispatch(loadProjectDetail(id)),
  858. onExcludeRoles: (type, id, resolve) => dispatch(excludeRoles(type, id, resolve)),
  859. onLoadProjectRoles: (id) => dispatch(loadProjectRoles(id)),
  860. onInitiateDownloadTask: (id, type, downloadParams?) => dispatch(DashboardActions.initiateDownloadTask(id, type, downloadParams)),
  861. onLoadDownloadList: () => dispatch(loadDownloadList()),
  862. onDownloadFile: (id) => dispatch(downloadFile(id))
  863. }
  864. }
  865. const withConnect = connect(mapStateToProps, mapDispatchToProps)
  866. const withReducer = injectReducer({ key: 'dashboard', reducer })
  867. const withSaga = injectSaga({ key: 'dashboard', saga })
  868. const withProjectReducer = injectReducer({ key: 'project', reducer: projectReducer })
  869. const withProjectSaga = injectSaga({ key: 'project', saga: projectSaga })
  870. const withVizReducer = injectReducer({ key: 'viz', reducer: vizReducer })
  871. const withVizSaga = injectSaga({ key: 'viz', saga: vizSaga })
  872. const withWidgetReducer = injectReducer({ key: 'widget', reducer: widgetReducer })
  873. const withWidgetSaga = injectSaga({ key: 'widget', saga: widgetSaga })
  874. const withViewReducer = injectReducer({ key: 'view', reducer: viewReducer })
  875. const withViewSaga = injectSaga({ key: 'view', saga: viewSaga })
  876. export default compose(
  877. withReducer,
  878. withProjectReducer,
  879. withVizReducer,
  880. withWidgetReducer,
  881. withViewReducer,
  882. withSaga,
  883. withProjectSaga,
  884. withVizSaga,
  885. withWidgetSaga,
  886. withViewSaga,
  887. withConnect
  888. )(Dashboard)