index.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. import React from 'react'
  2. import produce from 'immer'
  3. import { uuid } from 'utils/util'
  4. import { IDataParams } from '../../OperatingPanel'
  5. import { IDataParamSource } from '../../Dropbox'
  6. import { getFieldAlias } from 'containers/Widget/components/Config/Field'
  7. import { decodeMetricName, getAggregatorLocale } from 'containers/Widget/components/util'
  8. import {
  9. ITableConfig, ITableHeaderConfig, ITableColumnConfig,
  10. TableCellStyleTypes, DefaultTableCellStyle } from 'containers/Widget/components/Config/Table'
  11. import { pageSizeOptions } from './constants'
  12. import { Icon, Row, Col, Select, Radio, Checkbox, Modal } from 'antd'
  13. const { Option } = Select
  14. const RadioGroup = Radio.Group
  15. const RadioButton = Radio.Button
  16. const HeaderConfigModal = React.lazy(() => import('containers/Widget/components/Config/Table/Header/HeaderConfigModal'))
  17. const ColumnConfigModal = React.lazy(() => import('containers/Widget/components/Config/Table/Column/ColumnConfigModal'))
  18. const styles = require('../../Workbench.less')
  19. interface ITableSectionProps {
  20. dataParams: IDataParams
  21. config: ITableConfig
  22. onChange: (prop: string, value: any) => void
  23. }
  24. interface ITableSectionStates {
  25. headerConfigModalVisible: boolean
  26. columnConfigModalVisible: boolean
  27. validColumns: IDataParamSource[]
  28. validHeaderConfig: ITableHeaderConfig[]
  29. validColumnConfig: ITableColumnConfig[]
  30. }
  31. export class TableSection extends React.PureComponent<ITableSectionProps, ITableSectionStates> {
  32. public constructor (props) {
  33. super(props)
  34. const validColumns = this.getCurrentTableColumns(props)
  35. const validHeaderConfig = this.getValidHeaderConfig(props, validColumns)
  36. const validColumnConfig = this.getValidColumnConfig(props, validColumns)
  37. this.state = {
  38. headerConfigModalVisible: false,
  39. columnConfigModalVisible: false,
  40. validColumns,
  41. validHeaderConfig,
  42. validColumnConfig
  43. }
  44. }
  45. public componentWillReceiveProps (nextProps: ITableSectionProps) {
  46. const validColumns = this.getCurrentTableColumns(nextProps)
  47. const validHeaderConfig = this.getValidHeaderConfig(nextProps, validColumns)
  48. const validColumnConfig = this.getValidColumnConfig(nextProps, validColumns)
  49. this.setState({
  50. validColumns,
  51. validHeaderConfig,
  52. validColumnConfig
  53. })
  54. }
  55. private getCurrentTableColumns (props: ITableSectionProps) {
  56. const { dataParams } = props
  57. const keyNames = ['cols', 'metrics', 'rows']
  58. const validColumns: IDataParamSource[] = produce(dataParams, (draft) => {
  59. const columns = Object.entries(draft).reduce((acc, [key, value]) => {
  60. if (!~keyNames.indexOf(key)) { return acc }
  61. if (key !== 'metrics') { return acc.concat(value.items) }
  62. return acc.concat(value.items.map((item) => ({
  63. ...item,
  64. alias: this.getColumnDisplayName(item)
  65. })))
  66. }, [])
  67. return columns
  68. })
  69. return validColumns
  70. }
  71. private getValidHeaderConfig = (props: ITableSectionProps, validColumns: IDataParamSource[]) => {
  72. const { config } = props
  73. const columns = [...validColumns]
  74. const localHeaderConfig = produce(config.headerConfig, (draft) => {
  75. this.traverseHeaderConfig(draft, columns)
  76. let dimensionIdx = 0
  77. columns.forEach((c) => {
  78. const cfg = {
  79. key: uuid(5),
  80. headerName: c.name,
  81. alias: this.getColumnDisplayName(c),
  82. visualType: c.visualType,
  83. isGroup: false,
  84. style: { ...DefaultTableCellStyle },
  85. children: null
  86. }
  87. if (c.agg) {
  88. draft.push(cfg)
  89. } else {
  90. draft.splice(dimensionIdx++, 0, cfg)
  91. }
  92. })
  93. })
  94. return localHeaderConfig
  95. }
  96. private getValidColumnConfig = (props: ITableSectionProps, validColumns: IDataParamSource[]) => {
  97. const { config } = props
  98. const validColumnConfig = produce(config.columnsConfig, (draft) => {
  99. const config: ITableColumnConfig[] = []
  100. validColumns.forEach((column) => {
  101. const existedConfig = draft.find((item) => item.columnName === column.name)
  102. if (existedConfig) {
  103. config.push({
  104. ...existedConfig,
  105. alias: this.getColumnDisplayName(column),
  106. visualType: column.visualType
  107. })
  108. } else {
  109. config.push({
  110. columnName: column.name,
  111. alias: this.getColumnDisplayName(column),
  112. visualType: column.visualType,
  113. styleType: TableCellStyleTypes.Column,
  114. style: { ...DefaultTableCellStyle },
  115. conditionStyles: []
  116. })
  117. }
  118. })
  119. return config
  120. })
  121. return validColumnConfig
  122. }
  123. private traverseHeaderConfig = (
  124. cursorConfig: ITableHeaderConfig[],
  125. validColumns: IDataParamSource[]
  126. ) => {
  127. cursorConfig.sort((cfg1, cfg2) => {
  128. if (cfg1.isGroup || cfg2.isGroup) { return 0 }
  129. const cfg1Idx = validColumns.findIndex((column) => column.name === cfg1.headerName)
  130. const cfg2Idx = validColumns.findIndex((column) => column.name === cfg2.headerName)
  131. return cfg1Idx - cfg2Idx
  132. })
  133. for (let idx = cursorConfig.length - 1; idx >= 0; idx--) {
  134. const currentConfig = cursorConfig[idx]
  135. const { isGroup, headerName } = currentConfig
  136. if (!isGroup) {
  137. const columnIdx = validColumns.findIndex((c) => c.name === headerName)
  138. if (columnIdx < 0) {
  139. cursorConfig.splice(idx, 1)
  140. } else {
  141. const column = validColumns[columnIdx]
  142. currentConfig.alias = this.getColumnDisplayName(column)
  143. currentConfig.visualType = column.visualType
  144. validColumns.splice(columnIdx, 1)
  145. }
  146. }
  147. if (Array.isArray(currentConfig.children) && currentConfig.children.length) {
  148. this.traverseHeaderConfig(currentConfig.children, validColumns)
  149. }
  150. }
  151. }
  152. private selectChange = (prop) => (value) => {
  153. this.props.onChange(prop, value)
  154. }
  155. private switchChange = (name) => (e) => {
  156. this.props.onChange(name, e.target.value)
  157. }
  158. private checkboxChange = (name) => (e) => {
  159. this.props.onChange(name, e.target.checked)
  160. }
  161. private deleteHeaderConfig = () => {
  162. const { onChange } = this.props
  163. Modal.confirm({
  164. title: '确认删除表头样式与分组?',
  165. onOk: () => {
  166. onChange('headerConfig', [])
  167. }
  168. })
  169. }
  170. private showHeaderConfig = () => {
  171. this.setState({
  172. headerConfigModalVisible: true
  173. })
  174. }
  175. private closeHeaderConfig = () => {
  176. this.setState({
  177. headerConfigModalVisible: false
  178. })
  179. }
  180. private saveHeaderConfig = (config: ITableHeaderConfig[]) => {
  181. const { onChange } = this.props
  182. onChange('headerConfig', config)
  183. const validFixedColumns = config.map((c) => c.headerName)
  184. const { config: tableConfig } = this.props
  185. let { leftFixedColumns, rightFixedColumns } = tableConfig
  186. leftFixedColumns = leftFixedColumns.filter((col) => ~validFixedColumns.indexOf(col))
  187. rightFixedColumns = rightFixedColumns.filter((col) => ~validFixedColumns.indexOf(col))
  188. this.selectChange('leftFixedColumns')(leftFixedColumns)
  189. this.selectChange('rightFixedColumns')(rightFixedColumns)
  190. this.setState({
  191. headerConfigModalVisible: false
  192. })
  193. }
  194. private deleteColumnConfig = () => {
  195. const { onChange } = this.props
  196. Modal.confirm({
  197. title: '确认删除表格数据列设置?',
  198. onOk: () => {
  199. onChange('columnsConfig', [])
  200. }
  201. })
  202. }
  203. private showColumnConfig = () => {
  204. this.setState({
  205. columnConfigModalVisible: true
  206. })
  207. }
  208. private closeColumnConfig = () => {
  209. this.setState({
  210. columnConfigModalVisible: false
  211. })
  212. }
  213. private saveColumnConfig = (config: ITableColumnConfig[]) => {
  214. const { onChange } = this.props
  215. onChange('columnsConfig', config)
  216. this.setState({
  217. columnConfigModalVisible: false
  218. })
  219. }
  220. private getColumnDisplayName (column: IDataParamSource) {
  221. let displayName = `${decodeMetricName(column.name)}`
  222. if (column.agg) {
  223. displayName = `[${getAggregatorLocale(column.agg)}]${displayName}`
  224. }
  225. const alias = getFieldAlias(column.field, {})
  226. if (alias) {
  227. displayName = `${displayName}[${alias}]`
  228. }
  229. return displayName
  230. }
  231. private getValidFixedColumns (headerConfig: ITableHeaderConfig[], columns: IDataParamSource[]) {
  232. let options: JSX.Element[]
  233. if (!headerConfig.length) {
  234. options = columns.map((c) => {
  235. const displayName = this.getColumnDisplayName(c)
  236. return (<Option key={c.name} value={c.name}>{displayName}</Option>)
  237. })
  238. } else {
  239. options = headerConfig
  240. .filter((c) => c.isGroup || ~columns.findIndex((column) => column.name === c.headerName))
  241. .map((c) => {
  242. let displayName
  243. if (c.isGroup) {
  244. displayName = c.headerName
  245. } else {
  246. const column = columns.find((column) => column.name === c.headerName)
  247. displayName = this.getColumnDisplayName(column)
  248. }
  249. return (<Option key={c.headerName} value={c.headerName}>{displayName}</Option>)
  250. })
  251. }
  252. return options
  253. }
  254. public render () {
  255. const { config } = this.props
  256. const {
  257. leftFixedColumns, rightFixedColumns, headerFixed, bordered, size,
  258. autoMergeCell, withPaging, pageSize, withNoAggregators } = config
  259. const {
  260. validColumns, validHeaderConfig, validColumnConfig,
  261. headerConfigModalVisible, columnConfigModalVisible } = this.state
  262. const fixedColumnOptions = this.getValidFixedColumns(validHeaderConfig, validColumns)
  263. return (
  264. <div>
  265. <div className={styles.paneBlock}>
  266. <h4>
  267. <span>表头样式与分组</span>
  268. <Icon type="delete" onClick={this.deleteHeaderConfig} />
  269. <Icon type="edit" onClick={this.showHeaderConfig} />
  270. </h4>
  271. </div>
  272. <div className={styles.paneBlock}>
  273. <h4>
  274. <span>表格数据列</span>
  275. <Icon type="delete" onClick={this.deleteColumnConfig} />
  276. <Icon type="edit" onClick={this.showColumnConfig} />
  277. </h4>
  278. </div>
  279. <div className={styles.paneBlock}>
  280. <div className={styles.blockBody}>
  281. <Row gutter={8} type="flex" align="middle" className={styles.blockRow}>
  282. <Col span={12}>
  283. <Checkbox checked={headerFixed} onChange={this.checkboxChange('headerFixed')}>固定表头</Checkbox>
  284. </Col>
  285. <Col span={12}>
  286. <Checkbox checked={bordered} onChange={this.checkboxChange('bordered')}>边框</Checkbox>
  287. </Col>
  288. </Row>
  289. </div>
  290. </div>
  291. <div className={styles.paneBlock}>
  292. <h4>左固定列</h4>
  293. <div className={styles.blockBody}>
  294. <Row gutter={8} type="flex" align="middle" className={styles.rowBlock}>
  295. <Col span={24}>
  296. <Select
  297. className={styles.blockElm}
  298. mode="tags"
  299. value={leftFixedColumns}
  300. onChange={this.selectChange('leftFixedColumns')}
  301. >
  302. {fixedColumnOptions}
  303. </Select>
  304. </Col>
  305. </Row>
  306. </div>
  307. </div>
  308. <div className={styles.paneBlock}>
  309. <h4>右固定列</h4>
  310. <div className={styles.blockBody}>
  311. <Row gutter={8} type="flex" align="middle" className={styles.rowBlock}>
  312. <Col span={24}>
  313. <Select
  314. className={styles.blockElm}
  315. mode="tags"
  316. value={rightFixedColumns}
  317. onChange={this.selectChange('rightFixedColumns')}
  318. >
  319. {fixedColumnOptions}
  320. </Select>
  321. </Col>
  322. </Row>
  323. </div>
  324. </div>
  325. <div className={styles.paneBlock}>
  326. <h4>大小</h4>
  327. <div className={styles.blockBody}>
  328. <Row gutter={8} type="flex" align="middle" className={styles.blockRow}>
  329. <Col span={24}>
  330. <RadioGroup size="small" value={size} onChange={this.switchChange('size')}>
  331. <RadioButton value="small">小</RadioButton>
  332. <RadioButton value="middle">中</RadioButton>
  333. <RadioButton value="default">大</RadioButton>
  334. </RadioGroup>
  335. </Col>
  336. </Row>
  337. </div>
  338. </div>
  339. <div className={styles.paneBlock}>
  340. <h4>自动合并相同内容</h4>
  341. <div className={styles.blockBody}>
  342. <Row gutter={8} type="flex" align="middle" className={styles.blockRow}>
  343. <Col span={24}>
  344. <RadioGroup size="small" value={autoMergeCell} onChange={this.switchChange('autoMergeCell')}>
  345. <RadioButton value={true}>开启</RadioButton>
  346. <RadioButton value={false}>关闭</RadioButton>
  347. </RadioGroup>
  348. </Col>
  349. </Row>
  350. </div>
  351. </div>
  352. <div className={styles.paneBlock}>
  353. <h4>分页</h4>
  354. <div className={styles.blockBody}>
  355. <Row gutter={8} type="flex" align="middle" className={styles.blockRow}>
  356. <Col span={12}>
  357. <RadioGroup size="small" value={withPaging} onChange={this.switchChange('withPaging')}>
  358. <RadioButton value={true}>开启</RadioButton>
  359. <RadioButton value={false}>关闭</RadioButton>
  360. </RadioGroup>
  361. </Col>
  362. {!withPaging ? null :
  363. <Col span={12}>
  364. <Select
  365. size="small"
  366. className={styles.blockElm}
  367. value={pageSize}
  368. onChange={this.selectChange('pageSize')}
  369. >
  370. {pageSizeOptions}
  371. </Select>
  372. </Col>}
  373. </Row>
  374. </div>
  375. </div>
  376. <div className={styles.paneBlock}>
  377. <h4>使用原始数据</h4>
  378. <div className={styles.blockBody}>
  379. <Row gutter={8} type="flex" align="middle" className={styles.blockRow}>
  380. <Col span={24}>
  381. <RadioGroup size="small" value={withNoAggregators} onChange={this.switchChange('withNoAggregators')}>
  382. <RadioButton value={true}>开启</RadioButton>
  383. <RadioButton value={false}>关闭</RadioButton>
  384. </RadioGroup>
  385. </Col>
  386. </Row>
  387. </div>
  388. </div>
  389. <React.Suspense fallback={null}>
  390. <HeaderConfigModal
  391. visible={headerConfigModalVisible}
  392. config={validHeaderConfig}
  393. onCancel={this.closeHeaderConfig}
  394. onSave={this.saveHeaderConfig}
  395. />
  396. <ColumnConfigModal
  397. visible={columnConfigModalVisible}
  398. config={validColumnConfig}
  399. onCancel={this.closeColumnConfig}
  400. onSave={this.saveColumnConfig}
  401. />
  402. </React.Suspense>
  403. </div>
  404. )
  405. }
  406. }
  407. export default TableSection