DrillPathSetting.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /*
  2. * <<
  3. * Davinci
  4. * ==
  5. * Copyright (C) 2016 - 2017 EDP
  6. * ==
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. * >>
  19. */
  20. import React from 'react'
  21. import classnames from 'classnames'
  22. import { Radio, Select, Button, Icon, message } from 'antd'
  23. const RadioGroup = Radio.Group
  24. const Option = Select.Option
  25. import { SQL_NUMBER_TYPES, SQL_DATE_TYPES } from 'app/globalConstants'
  26. const utilStyles = require('assets/less/util.less')
  27. const styles = require('../Dashboard.less')
  28. interface IDrillPathSettingProps {
  29. widgets: any[]
  30. views: any[]
  31. itemId: number | boolean
  32. selectedWidget: number[]
  33. drillpathSetting: any[]
  34. saveDrillPathSetting: (flag: any) => any
  35. cancel: () => any
  36. }
  37. interface IDrillPathSettingStates {
  38. screenWidth: number
  39. value: number
  40. pathNodes: IPathNode[]
  41. pathOrFree: string
  42. }
  43. export interface IPathNode {
  44. views: any[]
  45. widget: string
  46. enter: string
  47. out: string
  48. enterType: string
  49. outType: string
  50. nextRelation: string
  51. enterErrorMessage: string
  52. outErrorMessage: string
  53. }
  54. export class DrillPathSetting extends React.PureComponent<IDrillPathSettingProps, IDrillPathSettingStates> {
  55. constructor (props) {
  56. super(props)
  57. this.state = {
  58. screenWidth: 0,
  59. value: 1,
  60. pathNodes: [],
  61. pathOrFree: 'free'
  62. }
  63. }
  64. private getViewList = (widgetId) => {
  65. const {views, widgets} = this.props
  66. const view = views.find((view) => view.id === (widgets.find((widget) => widget.id === widgetId))['viewId'])
  67. const { model } = view
  68. const modelObj = JSON.parse(model)
  69. const viewLists = []
  70. Object.entries(modelObj).forEach(([key, m]) => {
  71. m['name'] = key
  72. viewLists.push(m)
  73. })
  74. return viewLists
  75. }
  76. private init = () => {
  77. const {selectedWidget, drillpathSetting} = this.props
  78. const viewLists = this.getViewList(selectedWidget[0])
  79. let newPathNodes = void 0
  80. if (drillpathSetting && drillpathSetting.length) {
  81. newPathNodes = drillpathSetting
  82. this.setState({
  83. pathOrFree: 'path'
  84. })
  85. } else {
  86. newPathNodes = [{
  87. widget: `${selectedWidget[0]}`,
  88. views: viewLists,
  89. enter: '',
  90. out: '',
  91. enterType: '',
  92. outType: '',
  93. nextRelation: '=',
  94. enterErrorMessage: '',
  95. outErrorMessage: ''
  96. }]
  97. }
  98. this.setState({
  99. pathNodes: newPathNodes
  100. })
  101. }
  102. public componentWillMount () {
  103. this.init()
  104. }
  105. public componentWillReceiveProps (nextProps) {
  106. window.addEventListener('resize', this.getScreenWidth, false)
  107. const {selectedWidget, drillpathSetting} = this.props
  108. const nextSelectedWidget = nextProps.selectedWidget
  109. const nextDrillpathSetting = nextProps.drillpathSetting
  110. if (selectedWidget[0] !== nextSelectedWidget [0]) {
  111. const startPathObj = this.state.pathNodes[0]
  112. startPathObj['widget'] = nextSelectedWidget[0]
  113. const newPathNodes = [...this.state.pathNodes]
  114. newPathNodes.splice(0, 1, startPathObj)
  115. this.setState({
  116. pathNodes: newPathNodes
  117. })
  118. }
  119. }
  120. public componentWillUnmount () {
  121. window.removeEventListener('resize', this.getScreenWidth, false)
  122. }
  123. private getScreenWidth = () => {
  124. this.setState({ screenWidth: document.documentElement.clientWidth })
  125. }
  126. private onChange = (e) => {
  127. this.setState({
  128. pathOrFree: e.target.value
  129. })
  130. }
  131. private generateFilterOperatorOptions = (type) => {
  132. const operators = [
  133. ['=', 'like', '>', '<', '>=', '<=', '!='],
  134. ['=', '>', '<', '>=', '<=', '!=']
  135. ]
  136. const stringOptions = operators[0].slice().map((o) => (
  137. <Option key={o} value={o}>{o}</Option>
  138. ))
  139. const numbersAndDateOptions = operators[1].slice().map((o) => (
  140. <Option key={o} value={o}>{o}</Option>
  141. ))
  142. if (SQL_NUMBER_TYPES.indexOf(type) >= 0 || SQL_DATE_TYPES.indexOf(type) >= 0) {
  143. return numbersAndDateOptions
  144. } else {
  145. return stringOptions
  146. }
  147. }
  148. private add = () => {
  149. const obj = {
  150. widget: '',
  151. views: [],
  152. enter: '',
  153. out: '',
  154. enterType: '',
  155. outType: '',
  156. nextRelation: '=',
  157. enterErrorMessage: '',
  158. outErrorMessage: ''
  159. }
  160. const {pathNodes} = this.state
  161. const newNodes = pathNodes.concat(obj)
  162. this.setState({
  163. pathNodes: newNodes
  164. })
  165. }
  166. private remove = (index: number) => () => {
  167. const pathNodes = [...this.state.pathNodes]
  168. pathNodes.splice(index, 1)
  169. this.setState({
  170. pathNodes
  171. })
  172. }
  173. private saveDrillPathSetting = () => {
  174. const {saveDrillPathSetting} = this.props
  175. const result = this.checkDrillPathSettingValidate()
  176. if (result && saveDrillPathSetting) {
  177. if (this.state.pathOrFree === 'free') {
  178. saveDrillPathSetting([])
  179. } else {
  180. saveDrillPathSetting(this.state.pathNodes)
  181. }
  182. this.hideDrillPathSettingModal()
  183. }
  184. }
  185. private getNewPathNodes = (index, field, value) => {
  186. const startPathObj = this.state.pathNodes[index]
  187. startPathObj[field] = value
  188. const newPathNodes = [...this.state.pathNodes]
  189. newPathNodes.splice(index, 1, startPathObj)
  190. return newPathNodes
  191. }
  192. private checkDrillPathSettingValidate = () => {
  193. const { pathNodes, pathOrFree } = this.state
  194. let validate = true
  195. if (pathOrFree === 'path' && pathNodes.length === 1) {
  196. validate = false
  197. message.error('至少设置两个路径')
  198. }
  199. for (let index = 0, l = pathNodes.length; index < l; index++) {
  200. const node = pathNodes[index]
  201. if (index !== 0) {
  202. if (node.enter === '') {
  203. const newPathNodes = this.getNewPathNodes(index, 'enterErrorMessage', '入参为必填项')
  204. this.setState({
  205. pathNodes: newPathNodes
  206. })
  207. validate = false
  208. return validate
  209. } else {
  210. const newPathNodes = this.getNewPathNodes(index, 'enterErrorMessage', '')
  211. this.setState({
  212. pathNodes: newPathNodes
  213. })
  214. }
  215. }
  216. if (index !== (this.state.pathNodes.length - 1)) {
  217. if (node.out === '') {
  218. const newPathNodes = this.getNewPathNodes(index, 'outErrorMessage', '出参为必填项')
  219. this.setState({
  220. pathNodes: newPathNodes
  221. })
  222. validate = false
  223. return validate
  224. } else {
  225. const newPathNodes = this.getNewPathNodes(index, 'outErrorMessage', '')
  226. this.setState({
  227. pathNodes: newPathNodes
  228. })
  229. }
  230. }
  231. }
  232. return validate
  233. }
  234. private hideDrillPathSettingModal = () => {
  235. const {cancel} = this.props
  236. if (cancel) {
  237. cancel()
  238. }
  239. }
  240. private changeWidget = (index) => (value) => {
  241. const startPathObj = this.state.pathNodes[index]
  242. const viewLists = this.getViewList(Number(value))
  243. startPathObj['widget'] = value
  244. startPathObj['views'] = viewLists
  245. const newPathNodes = [...this.state.pathNodes]
  246. newPathNodes.splice(index, 1, startPathObj)
  247. this.setState({
  248. pathNodes: newPathNodes
  249. })
  250. const result = this.checkDrillPathSettingValidate()
  251. }
  252. private changeParams = (index, category, views?: any[]) => (value) => {
  253. const startPathObj = this.state.pathNodes[index]
  254. startPathObj[category] = value
  255. if (views && views.length) {
  256. startPathObj[`${category}Type`] = (views.find((view) => view.name === value))['sqlType']
  257. }
  258. const newPathNodes = [...this.state.pathNodes]
  259. newPathNodes.splice(index, 1, startPathObj)
  260. this.setState({
  261. pathNodes: newPathNodes
  262. })
  263. const result = this.checkDrillPathSettingValidate()
  264. }
  265. public render () {
  266. const {
  267. itemId,
  268. widgets,
  269. views,
  270. selectedWidget,
  271. drillpathSetting
  272. } = this.props
  273. const {
  274. screenWidth,
  275. pathNodes
  276. } = this.state
  277. const formItemStyle = {
  278. marginBottom: '8px'
  279. }
  280. const drillSettingButtons =
  281. [(
  282. <Button
  283. key="forward"
  284. size="large"
  285. type="primary"
  286. onClick={this.hideDrillPathSettingModal}
  287. >
  288. 取 消
  289. </Button>
  290. ), (
  291. <Button
  292. key="submit"
  293. size="large"
  294. type="primary"
  295. onClick={this.saveDrillPathSetting}
  296. >
  297. 保 存
  298. </Button>
  299. )]
  300. const widgetOptions = widgets.length && widgets.map((widget) => (
  301. <Option value={`${widget.id}`} key={`widgetoption${widget.viewId}`}>{widget.name}</Option>
  302. ))
  303. const pathDrill = pathNodes.length && pathNodes.map((node, index) => {
  304. const paramOptions = node && node.views && node.views.map((view) => {
  305. return (
  306. <Option value={`${view.name}`} key={`widgetoption${view.name}`}>{view.name}</Option>
  307. )})
  308. const relationOptions = this.generateFilterOperatorOptions(node.outType)
  309. return (
  310. <div key={`pathnodes${index}`} className={styles.pathNodeWrap}>
  311. <div className={styles.pathNode}>
  312. <div className={styles.pathBox}>
  313. <p className={styles.delete}>
  314. {this.state.pathNodes.length > 1 ? <Icon type="delete" onClick={this.remove(index)}/> : null}
  315. </p>
  316. <h4 className={styles.title}>
  317. {index + 1}
  318. </h4>
  319. <div
  320. style={{...formItemStyle}}
  321. >
  322. <Select
  323. defaultValue={`${node['widget']}`}
  324. placeholder="初始 可视化组件"
  325. style={{width: '100%'}}
  326. onChange={this.changeWidget(index)}
  327. >
  328. {widgetOptions}
  329. </Select>
  330. </div>
  331. <div
  332. style={{...formItemStyle}}
  333. >
  334. <Select
  335. placeholder="入参"
  336. style={{width: '100%'}}
  337. disabled={index === 0}
  338. defaultValue={node.enter}
  339. onChange={this.changeParams(index, 'enter', node.views)}
  340. >
  341. {paramOptions}
  342. </Select>
  343. </div>
  344. <div className={styles.errorMessage}>
  345. {node.enterErrorMessage && node.enterErrorMessage.length ? node.enterErrorMessage : ''}
  346. </div>
  347. <div
  348. style={{...formItemStyle}}
  349. >
  350. <Select
  351. placeholder="出参"
  352. style={{width: '100%'}}
  353. defaultValue={node.out}
  354. disabled={pathNodes.length > 0 && index === (pathNodes.length - 1)}
  355. onChange={this.changeParams(index, 'out', node.views)}
  356. >
  357. {paramOptions}
  358. </Select>
  359. </div>
  360. <div className={styles.errorMessage}>
  361. {node.outErrorMessage && node.outErrorMessage.length ? node.outErrorMessage : ''}
  362. </div>
  363. </div>
  364. <div className={styles.relation}>
  365. <div
  366. style={{margin: '0px'}}
  367. >
  368. <Select
  369. defaultValue={`=`}
  370. style={{width: '60px'}}
  371. onChange={this.changeParams(index, 'nextRelation')}
  372. >
  373. {relationOptions}
  374. </Select>
  375. </div>
  376. </div>
  377. </div>
  378. </div>
  379. )})
  380. const pathStyle = classnames({
  381. [utilStyles.hide]: this.state.pathOrFree !== 'path'
  382. })
  383. return (
  384. <div className={styles.drillPathSetting}>
  385. <div className={styles.drillStyle}>
  386. <b className={styles.label}>钻取模式: </b>
  387. <RadioGroup value={this.state.pathOrFree} onChange={this.onChange}>
  388. <Radio value={`free`} checked>自由钻取</Radio>
  389. <Radio value={`path`}>路径钻取</Radio>
  390. </RadioGroup>
  391. </div>
  392. <div className={pathStyle}>
  393. <div className={styles.path}>
  394. {pathDrill}
  395. <div className={styles.add}>
  396. <Button type="dashed" onClick={this.add} style={{ width: '60px', height: '60px' }}>
  397. <Icon type="plus"/>
  398. </Button>
  399. </div>
  400. </div>
  401. <div style={{height: '30px'}}/>
  402. {/* <div className={styles.footer}>
  403. {drillSettingButtons}
  404. </div> */}
  405. </div>
  406. <div className={styles.footer}>
  407. {drillSettingButtons}
  408. </div>
  409. </div>
  410. )
  411. }
  412. }
  413. export default DrillPathSetting