import React from 'react' import classnames from 'classnames' import { findDOMNode } from 'react-dom' import { Icon, Row, Col, Form, Input, Checkbox, Select, Button, Popover, Modal, message } from 'antd' import { FormComponentProps } from 'antd/lib/form' import { WrappedFormUtils } from 'antd/lib/form/Form' import { CheckboxChangeEvent } from 'antd/lib/checkbox' const FormItem = Form.Item const { TextArea } = Input const { Option } = Select import 'codemirror/lib/codemirror.css' import 'assets/override/codemirror_theme.css' import 'codemirror/addon/hint/show-hint.css' const codeMirror = require('codemirror/lib/codemirror') require('codemirror/addon/edit/matchbrackets') require('codemirror/mode/javascript/javascript') require('codemirror/addon/hint/show-hint') require('codemirror/addon/hint/javascript-hint') require('codemirror/addon/display/placeholder') import { IFieldConfig } from './types' import { getDefaultFieldConfig, extractQueryVariableNames, getFieldAlias } from './util' import { IQueryVariableMap } from 'containers/Dashboard/types' import AliasExpressionTestModal from './AliasExpressionTest' const utilStyles = require('assets/less/util.less') interface IFieldConfigProps extends FormComponentProps { visible: boolean fieldConfig: IFieldConfig queryInfo: string[] onSave: (config: IFieldConfig) => void onCancel: () => void } interface IFieldConfigStates { localConfig: IFieldConfig queryVariableNames: string[] testResult: string testModalVisible: boolean } class FieldConfig extends React.PureComponent { private codeEditor = React.createRef() private codeMirrorInst: any private isInitEditor: boolean = true constructor (props: IFieldConfigProps) { super(props) const { fieldConfig } = this.props this.state = { localConfig: fieldConfig ? { ...fieldConfig } : getDefaultFieldConfig(), queryVariableNames: [], testResult: '', testModalVisible: false } } public componentDidMount () { const { form } = this.props const { localConfig } = this.state this.setFieldsValue(form, localConfig) } public componentDidUpdate () { if (this.state.localConfig.useExpression) { // @FIXME refactor to remove setTimeout usage const tId = setTimeout(() => { this.isInitEditor = this.initCodeEditor(this.isInitEditor) clearTimeout(tId) }, 0) } } public componentWillReceiveProps (nextProps: IFieldConfigProps) { const { fieldConfig, form } = nextProps if (fieldConfig !== this.props.fieldConfig) { this.isInitEditor = true form.resetFields() this.setState({ localConfig: fieldConfig ? { ...fieldConfig } : getDefaultFieldConfig(), queryVariableNames: [], testResult: '' }, () => { this.setFieldsValue(form, this.state.localConfig) }) } } private useExpressionChange = (e: CheckboxChangeEvent) => { const useExpression = e.target.checked this.setState({ localConfig: { ...this.state.localConfig, useExpression } }) } private setFieldsValue = (form: WrappedFormUtils, config: IFieldConfig) => { const { alias, desc, useExpression } = config form.setFieldsValue({ [`alias_${useExpression ? 1 : 0}`]: alias, desc, useExpression }) } private getFieldsValue (form: WrappedFormUtils): IFieldConfig { const fieldsValue: any = form.getFieldsValue() const { useExpression, desc } = fieldsValue const alias = useExpression ? this.codeMirrorInst.doc.getValue() : fieldsValue['alias_0'] const config: IFieldConfig = { alias, useExpression, desc } return config } private initCodeEditor = (isInit: boolean) => { if (!this.codeMirrorInst) { // @FIXME ref is null in componentDidUpdate if (!this.codeEditor.current) { return true } const codeEditorDom = findDOMNode(this.codeEditor.current) this.codeMirrorInst = codeMirror.fromTextArea(codeEditorDom, { mode: 'text/javascript', theme: '3024-day', lineNumbers: true, lineWrapping: true }) this.codeMirrorInst.setSize('100%', 300) } if (isInit && this.state.localConfig.useExpression) { this.codeMirrorInst.doc.setValue(this.state.localConfig.alias) } return false } private addQueryVariable = () => { const { form } = this.props const queryVariable = form.getFieldValue('queryVariable') this.codeMirrorInst.replaceSelection(` $${queryVariable}$ `) } private testExpression = () => { const expression: string = this.codeMirrorInst.doc.getValue() const queryVariableNames = extractQueryVariableNames(expression) const testModalVisible = queryVariableNames.length > 0 this.setState({ queryVariableNames, testModalVisible }) if (!testModalVisible) { this.testExpressionResult() } } private testExpressionResult = (queryVariableMap: IQueryVariableMap = {}) => { const { form } = this.props const fieldConfig = this.getFieldsValue(form) const testResult = getFieldAlias(fieldConfig, queryVariableMap) this.setState({ testResult, testModalVisible: false }) return testResult } private clearExpressionResult = () => { this.setState({ testResult: '' }) } private closeTestModal = () => { this.setState({ testModalVisible: false }) } private save = () => { const { form, onSave } = this.props form.validateFieldsAndScroll((err) => { if (err) { return } const config = this.getFieldsValue(form) onSave(config) }) } private cancel = () => { this.props.onCancel() } private modalFooter = [( ), ( )] private useExprInstr = (
{`1. 编辑器中编辑 JavaScript Function 代码片段,最后需要 return 别名值。
2. 若当前编辑的 Widget 对应的 View 中含有查询变量 queryVar 的定义,则字段别名中可添加查询变量。
  在使用该 Widget 时,由界面中提供的查询变量值参与动态别名运算,生成对应的别名结果。
3. 可使用时间辅助函数生成格式时间字符串参与别名运算。
  Moment() // 取当前时间
  Moment('2018-01-01') // 转换指定的日期
  Moment().add(1, 'days').add(-1, 'months') // 日期推算,可链式调用,其中参数分别为数量和单位('hours', days', 'weeks', 'months', 'years' 等)
  Moment().format('YYYY-MM-DD HH:mm:ss') // 时间的格式化,其中两位的重复字母依次代表“年”、“月”、“日”、“时”、“分”、“秒”
4. 编写完毕后,可点击“测试”按钮查看显示效果,若含有参数,则需要在弹窗中需要测试的参数值,若报错,请调整编写。
5. 例子
  var province = $province$ // $province 为 View 中查询变量
  var currentYearMonth = Moment().format('YYYY年MM月') // 格式化当前年月
  var alias = province + '(' + currentYearMonth + ')' // 拼写最后显示的别名
  return alias
`}
      
) public render () { const { visible, queryInfo, form } = this.props const { getFieldDecorator } = form const { localConfig, testResult, testModalVisible, queryVariableNames } = this.state const { desc, useExpression } = localConfig const variableOptions = (queryInfo || []).map((q) => ( )) const textAreaCls = classnames({ [utilStyles.hide]: !useExpression }) const inputCls = classnames({ [utilStyles.hide]: useExpression }) return (
{getFieldDecorator('alias_0')()}