columnConfig.jsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. import React from 'react'
  2. import { Form, Input, Button, Select, Table, Checkbox, Icon, Tooltip, Row } from 'antd'
  3. import { connect } from 'dva'
  4. import COLUMN_TYPE from './columnType.json'
  5. import { Resizable } from 'react-resizable'
  6. import './columnConfig.less'
  7. const FormItem = Form.Item
  8. const SelectOption = Select.Option
  9. const ResizeableTitle = (props) => {
  10. const { onResize, width, ...restProps } = props;
  11. if (!width) {
  12. return <th {...restProps} />;
  13. }
  14. return (
  15. <Resizable width={width} height={0} onResize={onResize}>
  16. <th {...restProps} />
  17. </Resizable>
  18. );
  19. };
  20. class DataSourceColumnConfig extends React.Component {
  21. constructor(props) {
  22. super(props);
  23. this.state = {
  24. widths: [ 80, 300, 200, 100, 100 ],
  25. visibleConfirm: false,
  26. }
  27. }
  28. components = {
  29. header: {
  30. cell: ResizeableTitle,
  31. },
  32. };
  33. handleVisibleChange = (visible) => {
  34. const { columns } = this.props.dataSourceDetail;
  35. this.setState({ visibleConfirm: visible && (columns && columns.length > 0) });
  36. }
  37. handleResize = index => (e, { size }) => {
  38. this.setState(({ widths }) => {
  39. const nextWidths = [...widths];
  40. nextWidths[index] = size.width;
  41. return { widths: nextWidths };
  42. });
  43. };
  44. render() {
  45. const { dataSourceDetail, dispatch, fetching } = this.props;
  46. const { widths } = this.state;
  47. const columns = [{
  48. title: <div><Checkbox
  49. style={{ margin: '0 8px 0 0', display: dataSourceDetail.columns ? (dataSourceDetail.columns.length > 0 ? 'inline-block' : 'none') : 'none'}}
  50. indeterminate={dataSourceDetail.columns ? (dataSourceDetail.columns.filter(c => c.using).length > 0 && dataSourceDetail.columns.filter(c => c.using).length < dataSourceDetail.columns.length) : false}
  51. checked={dataSourceDetail.columns ? (dataSourceDetail.columns.filter(c => c.using).length === dataSourceDetail.columns.length) : false}
  52. onChange={(e) => {
  53. let target = e.target;
  54. let columns = dataSourceDetail.columns ? dataSourceDetail.columns.map(c => {
  55. c.using = target.checked;
  56. return c;
  57. }) : [];
  58. dispatch({ type: 'dataSourceDetail/setField', name: 'columns', value: columns });
  59. }}
  60. />启用</div>,
  61. dataIndex: 'using',
  62. key: 'using',
  63. width: widths[0],
  64. render: (v, r) => <Checkbox
  65. dataKey={r.key}
  66. onChange={(e) => {
  67. let target = e.target;
  68. let key = target.dataKey;
  69. let columns = dataSourceDetail.columns.map(c => {
  70. if(c.key === key) {
  71. c.using = target.checked;
  72. }
  73. return c;
  74. });
  75. dispatch({ type: 'dataSourceDetail/setField', name: 'columns', value: columns });
  76. }}
  77. checked={v}
  78. />
  79. }, {
  80. title: '列名',
  81. dataIndex: 'name',
  82. key: 'name',
  83. width: widths[1],
  84. // }, {
  85. // title: '备注',
  86. // dataIndex: 'description',
  87. // key: 'description',
  88. // width: widths[2],
  89. // }, {
  90. // title: '数据类型',
  91. // dataIndex: 'dataType',
  92. // key: 'dataType',
  93. // width: widths[3]
  94. }, {
  95. title: '分析类型',
  96. dataIndex: 'columnType',
  97. key: 'columnType',
  98. width: widths[2],
  99. render: (text, record) => {
  100. return (
  101. <Select
  102. style={{ width: '100%' }}
  103. value={text}
  104. onChange={(value) => {
  105. let columns = dataSourceDetail.columns.map(c => {
  106. if(c.key === record.key) {
  107. c.columnType = value;
  108. c.groupable = c.columnType === 'categorical';
  109. c.bucketizable = ['time', 'scale', 'ordinal'].indexOf(record.columnType) !== -1;
  110. }
  111. return c;
  112. });
  113. dispatch({ type: 'dataSourceDetail/setField', name: 'columns', value: columns });
  114. }}
  115. >
  116. {
  117. COLUMN_TYPE.map( c => {
  118. let dataType = record.dataType;
  119. if(c.dataType.indexOf(dataType) !== -1) {
  120. return <SelectOption value={c.columnType} key={c.columnType}>{c.label}</SelectOption>
  121. }else {
  122. return null
  123. }
  124. }).filter((s)=>s!==null)
  125. }
  126. </Select>
  127. )
  128. }
  129. }, {
  130. title: <div><Checkbox
  131. style={{ margin: '0 8px 0 0', display: dataSourceDetail.columns ? (dataSourceDetail.columns.length > 0 ? 'inline-block' : 'none') : 'none'}}
  132. indeterminate={dataSourceDetail.columns ? (dataSourceDetail.columns.filter(c => c.groupable).length > 0 && dataSourceDetail.columns.filter(c => c.groupable).length < dataSourceDetail.columns.length) : false}
  133. checked={dataSourceDetail.columns ? (dataSourceDetail.columns.filter(c => c.groupable).length === dataSourceDetail.columns.length) : false}
  134. onChange={(e) => {
  135. let target = e.target;
  136. let columns = dataSourceDetail.columns ? dataSourceDetail.columns.map(c => {
  137. c.groupable = target.checked;
  138. return c;
  139. }) : [];
  140. dispatch({ type: 'dataSourceDetail/setField', name: 'columns', value: columns });
  141. }}
  142. />允许分组</div>,
  143. dataIndex: 'groupable',
  144. key: 'groupable',
  145. width: widths[3],
  146. render: (v, r) => <Checkbox
  147. dataKey={r.key}
  148. onChange={(e) => {
  149. let target = e.target;
  150. let key = target.dataKey;
  151. let columns = dataSourceDetail.columns.map(c => {
  152. if(c.key === key) {
  153. c.groupable = target.checked;
  154. }
  155. return c;
  156. });
  157. dispatch({ type: 'dataSourceDetail/setField', name: 'columns', value: columns });
  158. }}
  159. checked={v}
  160. />
  161. }, {
  162. title: <div><Checkbox
  163. style={{ margin: '0 8px 0 0', display: dataSourceDetail.columns ? (dataSourceDetail.columns.length > 0 ? 'inline-block' : 'none') : 'none'}}
  164. indeterminate={dataSourceDetail.columns ? (dataSourceDetail.columns.filter(c => c.filterable).length > 0 && dataSourceDetail.columns.filter(c => c.filterable).length < dataSourceDetail.columns.length) : false}
  165. checked={dataSourceDetail.columns ? (dataSourceDetail.columns.filter(c => c.filterable).length === dataSourceDetail.columns.length) : false}
  166. onChange={(e) => {
  167. let target = e.target;
  168. let columns = dataSourceDetail.columns ? dataSourceDetail.columns.map(c => {
  169. c.filterable = target.checked;
  170. return c;
  171. }) : [];
  172. dispatch({ type: 'dataSourceDetail/setField', name: 'columns', value: columns });
  173. }}
  174. />允许过滤</div>,
  175. dataIndex: 'filterable',
  176. key: 'filterable',
  177. width: widths[4],
  178. render: (v, r) => <Checkbox
  179. dataKey={r.key}
  180. onChange={(e) => {
  181. let target = e.target;
  182. let key = target.dataKey;
  183. let columns = dataSourceDetail.columns.map(c => {
  184. if(c.key === key) {
  185. c.filterable = target.checked;
  186. }
  187. return c;
  188. });
  189. dispatch({ type: 'dataSourceDetail/setField', name: 'columns', value: columns });
  190. }}
  191. checked={v}
  192. />
  193. // }, {
  194. // title: '允许分组',
  195. // dataIndex: 'groupable',
  196. // key: 'groupable',
  197. // width: 50,
  198. // className: 'column-groupable',
  199. // render: (value, record) => <Switch disabled={record.columnType!=='categorical'} checked={value} onChange={(checked) => {
  200. // let columns = dataSourceDetail.columns.map(c => {
  201. // if(c.key === record.key) {
  202. // c.groupable = checked;
  203. // }
  204. // return c;
  205. // });
  206. // dispatch({ type: 'dataSourceDetail/setField', name: 'columns', value: columns });
  207. // }}/>
  208. // }, {
  209. // title: '允许分段',
  210. // dataIndex: 'bucketizable',
  211. // key: 'bucketizable',
  212. // width: 50,
  213. // className: 'column-bucketizable',
  214. // render: (value, record) => <Switch
  215. // disabled={['time', 'scale', 'ordinal'].indexOf(record.columnType)===-1}
  216. // checked={value}
  217. // defaultChecked={true}
  218. // onChange={(checked) => {
  219. // let columns = dataSourceDetail.columns.map(c => {
  220. // if(c.key === record.key) {
  221. // c.bucketizable = checked;
  222. // }
  223. // return c;
  224. // });
  225. // dispatch({ type: 'dataSourceDetail/setField', name: 'columns', value: columns });
  226. // }}
  227. // />
  228. }, {
  229. title: '别名',
  230. dataIndex: 'alias',
  231. key: 'alias',
  232. // width: widths[5],
  233. render: (text, record) => {
  234. return(
  235. <Input
  236. key={text}
  237. defaultValue={text}
  238. placeholder={record.description ? record.description.substring(0, 10) : record.name}
  239. onBlur={(e) => {
  240. const value = e.target.value;
  241. let columns = dataSourceDetail.columns.map(c => {
  242. if(c.key === record.key) {
  243. c['alias'] = value;
  244. }
  245. return c;
  246. });
  247. dispatch({ type: 'dataSourceDetail/setField', name: 'columns', value: columns });
  248. }}
  249. >
  250. </Input>
  251. )
  252. }
  253. // }, {
  254. // title: '允许过滤',
  255. // dataIndex: 'filterable',
  256. // key: 'filterable',
  257. // render: (value, record) => <Switch />
  258. }].map((col, index) => ({
  259. ...col,
  260. onHeaderCell: column => ({
  261. width: column.width,
  262. onResize: this.handleResize(index),
  263. }),
  264. }));
  265. return (
  266. <div className='column-config'>
  267. {
  268. dataSourceDetail.type==='database'?(
  269. <div className='sql-area'>
  270. <Row className='divider'>数据对象</Row>
  271. <Form size='small'>
  272. <FormItem className='textarea-target'>
  273. <Input.TextArea
  274. disabled={!dataSourceDetail.address}
  275. placeholder={dataSourceDetail.address ? '输入表名或查询SQL,注意不能以分号结尾' : '请先返回上一步选择数据库连接'}
  276. autosize={{ minRows: 3 }}
  277. // value={dataSourceDetail.target}
  278. defaultValue={dataSourceDetail.target}
  279. onBlur={(e) => {
  280. if(e.target.value !== dataSourceDetail.target) {
  281. dispatch({ type: 'dataSourceDetail/setFields', fields: [
  282. { name: 'target', value: e.target.value },
  283. { name: 'notice', value: '' },
  284. { name: 'targetDirty', value: true }
  285. ] });
  286. }
  287. }}
  288. // onChange={(e) => {
  289. // dispatch({ type: 'dataSourceDetail/setFields', fields: [
  290. // { name: 'target', value: e.target.value },
  291. // { name: 'notice', value: '' }
  292. // ] });
  293. // }}
  294. />
  295. </FormItem>
  296. <div className='buttons'>
  297. <div className='errormessage'>{dataSourceDetail.notice}</div>
  298. <Tooltip
  299. title={
  300. <div>
  301. <div className="ant-popover-message">
  302. <Icon type="exclamation-circle" theme="filled" style={{ color: '#faad14' }} />
  303. <div className="ant-popover-message-title">已存在列数据,确定要覆盖吗?</div>
  304. </div>
  305. <div className="ant-popover-buttons">
  306. <button type="button" className="ant-btn ant-btn-sm" onClick={() => {
  307. this.setState({
  308. visibleConfirm: false
  309. });
  310. }}>
  311. <span>取 消</span>
  312. </button>
  313. <button type="button" className="ant-btn ant-btn-primary ant-btn-sm" onClick={() => {
  314. this.setState({
  315. visibleConfirm: false
  316. });
  317. dispatch({ type: 'dataSourceDetail/importColumns', cover: true });
  318. }}>
  319. <span>全部覆盖</span>
  320. </button>
  321. <button type="button" className="ant-btn ant-btn-primary ant-btn-sm" onClick={() => {
  322. this.setState({
  323. visibleConfirm: false
  324. });
  325. dispatch({ type: 'dataSourceDetail/importColumns', cover: false });
  326. }}>
  327. <span>保留重复</span>
  328. </button>
  329. </div>
  330. </div>
  331. }
  332. visible={this.state.visibleConfirm}
  333. onVisibleChange={this.handleVisibleChange}
  334. trigger='click'
  335. >
  336. <Button type={!fetching && dataSourceDetail.targetDirty ? 'danger' : null} disabled={!dataSourceDetail.address || fetching} onClick={() => {
  337. if(!dataSourceDetail.columns || dataSourceDetail.columns.length === 0) {
  338. dispatch({ type: 'dataSourceDetail/importColumns', cover: true });
  339. }
  340. }}>
  341. {fetching && <Icon type="loading" theme="outlined" />}
  342. {!fetching && dataSourceDetail.targetDirty ? '重新获取数据列' : '获取数据列'}
  343. </Button>
  344. </Tooltip>
  345. </div>
  346. </Form>
  347. <Row className='divider'>数据列</Row>
  348. </div>
  349. ):null
  350. }
  351. <Table
  352. className='table-columnconfig'
  353. bordered
  354. components={this.components}
  355. dataSource={dataSourceDetail.columns}
  356. columns={columns}
  357. locale={{
  358. emptyText: '未连接到数据对象'
  359. }}
  360. />
  361. </div>
  362. );
  363. }
  364. }
  365. function mapStateToProps({ present: { dataSourceDetail, loading } }) {
  366. const fetching = loading.effects['dataSourceDetail/importColumns'];
  367. return {
  368. fetching,
  369. dataSourceDetail
  370. }
  371. }
  372. export default connect(mapStateToProps)(DataSourceColumnConfig);