| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892 | /* * << * Davinci * == * Copyright (C) 2016 - 2017 EDP * == * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * >> */import React from 'react'import classnames from 'classnames'import LocalControlPanel from 'containers/ControlPanel/Local'import DashboardItemMask from './DashboardItemMask'import DownloadCsv, { IDownloadCsvProps } from 'components/DownloadCsv'import {  DrillCharts,  WidgetDimension,  DrillType,  IDrillDetail} from 'components/DataDrill/types'import DataDrill from 'components/DataDrill/Panel'import DataDrillHistory from 'components/DataDrill/History'import { operationWidgetProps } from 'components/DataDrill/abstract/widgetOperating'import {  strategiesOfDrillUpHasDrillHistory,  strategiesOfDrillUpNullDrillHistory,  strategiesOfDrillDownHasDrillHistory,  strategiesOfDrillDownNullDrillHistory} from 'components/DataDrill/strategies'import { getLastItemValueOfArray } from 'components/DataDrill/util'import {  IFormedView,  IFormedViews,  IShareFormedViews,  IViewModel} from 'containers/View/types'import Widget, {  IWidgetConfig,  IPaginationParams,  RenderType} from 'containers/Widget/components/Widget'import { ChartTypes } from 'containers/Widget/config/chart/ChartTypes'import { DrillableChart } from 'containers/Widget/config/chart/DrillableChart'import { IconProps } from 'antd/lib/icon'import { Icon, Tooltip, Popconfirm, Popover, Dropdown, Menu } from 'antd'import ModulePermission from 'containers/Account/components/checkModulePermission'import ShareDownloadPermission from 'containers/Account/components/checkShareDownloadPermission'import { IProject } from 'containers/Projects/types'import {  IQueryConditions,  IQueryVariableMap,  TShareVizsType,  ILoadData} from '../types'import { IWidgetFormed, IWidgetBase } from 'app/containers/Widget/types'import {  ControlPanelLayoutTypes,  ControlPanelTypes} from 'app/components/Control/constants'import { OnGetControlOptions } from 'app/components/Control/types'import styles from '../Dashboard.less'import utilStyles from 'app/assets/less/util.less'import EnhancerPanel from 'components/DataDrill/EnhancerPanel'interface IDashboardItemProps {  itemId: number  alias?: string  widget: IWidgetFormed  widgets: IWidgetFormed[]  formedViews: IFormedViews | IShareFormedViews  view?: Partial<IFormedView>  isTrigger?: boolean  datasource: any  loading: boolean  polling: boolean  interacting: boolean  frequency: number  shareToken: string  shareLoading?: boolean  downloadCsvLoading: boolean  rendered?: boolean  renderType: RenderType  selectedItems: number[]  currentProject?: IProject  queryConditions: IQueryConditions  container?: string  errorMessage: string  onSelectDrillHistory?: (history?: any, item?: number, itemId?: number) => void  onLoadData: ILoadData  onShowEdit?: (    itemId: number  ) => (e: React.MouseEvent<HTMLSpanElement>) => void  onShowDrillEdit?: (    itemId: number  ) => (e: React.MouseEvent<HTMLSpanElement>) => void  onDeleteDashboardItem?: (itemId: number) => () => void  onResizeDashboardItem: (itemId: number) => void  onRenderChartError: (itemId: number, error: Error) => void  onOpenSharePanel?: (    id: number,    type: TShareVizsType,    title: string,    itemId?: number  ) => void  onDownloadCsv: (itemId: number) => void  onTurnOffInteract: (itemId: number) => void  onShowFullScreen: (itemId: number) => void  onCheckTableInteract: (itemId: number) => boolean  onDoTableInteract: (itemId: number, triggerData: object) => void  onEditWidget?: (itemId: number, widgetId: number) => void  onDrillData?: (e: object) => void  onSelectChartsItems?: (    itemId: number,    renderType: string,    selectedItems: number[]  ) => void  onGetControlOptions: OnGetControlOptions  onControlSearch: (    type: ControlPanelTypes,    relatedItems: number[],    formValues?: object,    itemId?: number  ) => void  onMonitoredSyncDataAction?: () => void  onMonitoredSearchDataAction?: () => void}interface IDashboardItemStates {  controlPanelVisible: boolean  queryVariables: IQueryVariableMap  model: IViewModel  isDrilling: boolean  dataDrillPanelPosition: boolean | object  whichDataDrillBrushed: boolean | object[]  sourceDataOfBrushed: object[]  sourceDataGroup: string[]  // isShowDrillPanel: boolean  widgetProps: IWidgetConfig  cacheWidgetProps: IWidgetConfig  cacheWidgetId: boolean | number}export class DashboardItem extends React.PureComponent<  IDashboardItemProps,  IDashboardItemStates> {  constructor(props) {    super(props)    this.state = {      controlPanelVisible: false,      queryVariables: {},      model: null,      isDrilling: true,      dataDrillPanelPosition: false,      whichDataDrillBrushed: false,      sourceDataOfBrushed: [],      cacheWidgetProps: null,      widgetProps: null,      cacheWidgetId: false,      sourceDataGroup: []    }  }  public static defaultProps = {    onShowEdit: () => void 0,    onShowDrillEdit: () => void 0,    onDeleteDashboardItem: () => void 0  }  private pollingTimer: number  private container: HTMLDivElement = null  public componentWillMount() {    const { itemId, widget, view, onLoadData, container } = this.props    const { cacheWidgetProps, cacheWidgetId } = this.state    if (container === 'share') {      if (widget.config.autoLoadData) {        onLoadData('clear', itemId)      }      this.initPolling(this.props)    }    this.setState({      model: view.model    })    if (!cacheWidgetProps) {      this.setState({        widgetProps: { ...widget.config },        cacheWidgetProps: { ...widget.config },        cacheWidgetId: widget.id      })    }  }  public componentWillReceiveProps(nextProps: IDashboardItemProps) {    const { widget, queryConditions } = this.props    let { model } = this.state    if (nextProps.widget !== widget) {      model = nextProps.view.model      this.setState({        model      })    }    if (nextProps.queryConditions !== queryConditions) {      const {        variables,        linkageVariables,        globalVariables      } = nextProps.queryConditions      this.setState({        queryVariables: [          ...variables,          ...linkageVariables,          ...globalVariables        ].reduce((obj, { name, value }) => {          obj[`$${name}$`] = value          return obj        }, {})      })    }  }  public componentWillUpdate(    nextProps: IDashboardItemProps,    nextState: IDashboardItemStates  ) {    const {      itemId,      widget,      polling,      frequency,      rendered,      container,      onLoadData    } = nextProps    if (!container) {      if (!this.props.rendered && rendered) {        // clear        if (widget.config.autoLoadData) {          onLoadData('clear', itemId)        }        this.initPolling(this.props)      }    }    if (polling !== this.props.polling || frequency !== this.props.frequency) {      this.initPolling(nextProps)    }  }  public componentDidUpdate(prevProps, prevState) {    if (prevState.controlPanelVisible !== this.state.controlPanelVisible) {      const { itemId, onResizeDashboardItem } = this.props      onResizeDashboardItem(itemId)    }  }  public componentWillUnmount() {    clearInterval(this.pollingTimer)  }  private initPolling = (props: IDashboardItemProps) => {    const { polling, frequency, itemId, onLoadData } = props    clearInterval(this.pollingTimer)    if (polling) {      this.pollingTimer = window.setInterval(() => {        onLoadData('refresh', itemId)      }, Number(frequency) * 1000)    }  }  private syncData = () => {    const { itemId, onLoadData, onMonitoredSyncDataAction } = this.props    onLoadData('flush', itemId)    if (onMonitoredSyncDataAction) {      onMonitoredSyncDataAction()    }  }  private toggleControlPanel = () => {    this.setState({      controlPanelVisible: !this.state.controlPanelVisible    })  }  private onFullScreen = () => {    const { itemId, onShowFullScreen } = this.props    onShowFullScreen(itemId)  }  private downloadCsv = () => {    const { itemId, onDownloadCsv } = this.props    onDownloadCsv(itemId)  }  private openSharePanel = () => {    const { itemId, widget, onOpenSharePanel } = this.props    const { id, name } = widget    onOpenSharePanel(id, 'widget', name, itemId)  }  private checkTableInteract = () => {    const { itemId, onCheckTableInteract } = this.props    return onCheckTableInteract(itemId)  }  private doInteract = (triggerData) => {    const { itemId, onDoTableInteract } = this.props    onDoTableInteract(itemId, triggerData)  }  private paginationChange = (pageNo: number, pageSize: number, orders) => {    const { itemId, queryConditions, onLoadData } = this.props    const { drillHistory } = queryConditions    const pagination = {      ...queryConditions.pagination,      pageNo,      pageSize    }    if (drillHistory && drillHistory.length) {      const drillStatus = drillHistory[drillHistory.length - 1]      onLoadData('clear', itemId, { pagination, orders, drillStatus })    } else {      onLoadData('clear', itemId, { pagination, orders })    }  }  private turnOffInteract = () => {    const { onTurnOffInteract, itemId } = this.props    onTurnOffInteract(itemId)  }  private toWorkbench = () => {    const { itemId, widget } = this.props    this.props.onEditWidget(itemId, widget.id)  }  private getDataDrillDetail = (position) => {    if (position && position.length) {      try {        const ps = JSON.parse(position)        const { range, brushed, sourceData, sourceGroup } = ps        const dataDrillPanelPosition = void 0        const sourceDataOfBrushed =          sourceData && sourceData.length ? sourceData : []        const whichDataDrillBrushed = brushed && brushed.length ? brushed : []        const sourceDataGroup =          sourceGroup && sourceGroup.length ? sourceGroup : []        this.setState({          dataDrillPanelPosition,          whichDataDrillBrushed,          sourceDataOfBrushed,          sourceDataGroup        })      } catch (error) {        throw error      }    }  }  private drillDataHistory = (history, item: number, itemId) => {    const { onSelectDrillHistory, queryConditions } = this.props    const { widgetProps, cacheWidgetProps } = this.state    const { drillHistory } = queryConditions    if (onSelectDrillHistory) {      if (item === -1 && !history) {        this.setState({ widgetProps: cacheWidgetProps })      } else {        const { cols, rows } = drillHistory[item]        this.setState({ widgetProps: { ...widgetProps, cols, rows } })      }      onSelectDrillHistory(history, item, itemId)    }  }  private receiveWidgetId() {    const { widget } = this.props    operationWidgetProps.receive(widget.id)  }  private isHasDrillHistory(): boolean {    const { queryConditions } = this.props    const { drillHistory } = queryConditions    return !!(drillHistory && drillHistory.length !== 0)  }  private getLastDrillHistory() {    const { queryConditions } = this.props    const { drillHistory } = queryConditions    return [...drillHistory].pop()  }  private sendDrillDetail(e) {    const { itemId, widget, onDrillData } = this.props    if (onDrillData) {      onDrillData({        ...e,        itemId,        widgetId: widget.id      })    }  }  private drillUp = (name: string) => {    //  a group name by drill    this.receiveWidgetId()    const { sourceDataOfBrushed } = this.state    const isHasDrillHistory = this.isHasDrillHistory()    let set    if (isHasDrillHistory) {      const getLastDrillHistory = this.getLastDrillHistory()      set = strategiesOfDrillUpHasDrillHistory(        getLastDrillHistory,        this.state.widgetProps      )({ name }, sourceDataOfBrushed)    } else {      set = strategiesOfDrillUpNullDrillHistory(        operationWidgetProps,        this.state.widgetProps      )({ name }, sourceDataOfBrushed)    }    const { pivot, coustomTable } = set    if (operationWidgetProps.isPivot()) {      const {        cols,        rows,        type,        groups,        filters,        widgetProps,        currentGroup      } = pivot()      this.setState({ widgetProps })      this.sendDrillDetail({ cols, rows, type, groups, filters, currentGroup })      return    }    if (this.isCoustomTable()) {      const {        cols,        rows,        type,        groups,        filters,        widgetProps,        currentGroup      } = coustomTable()      this.setState({ widgetProps })      this.sendDrillDetail({ cols, rows, type, groups, filters, currentGroup })      return    }  }  private isCoustomTable() {    return operationWidgetProps.isCoustomTable()  }  private drillDown = (name: string, dimensions?: WidgetDimension) => {    const { sourceDataOfBrushed, sourceDataGroup } = this.state    this.receiveWidgetId()    const isHasDrillHistory = this.isHasDrillHistory()    const dimetionAxis = operationWidgetProps.getDimetionAxis()    let set    let strategiteStream    if (isHasDrillHistory) {      const getLastDrillHistory = this.getLastDrillHistory()      set = strategiesOfDrillDownHasDrillHistory(        getLastDrillHistory,        this.state.widgetProps      )({ name }, sourceDataOfBrushed, sourceDataGroup)    } else {      set = strategiesOfDrillDownNullDrillHistory(        operationWidgetProps,        this.state.widgetProps      )({ name }, sourceDataOfBrushed, sourceDataGroup)    }    const {      dimetionAxisCol,      dimetionAxisRow,      pivotCol,      pivotRow,      coustomTable,      defaultScenes    } = set    if (dimensions && dimensions.length) {      if (dimensions === WidgetDimension.COL) {        strategiteStream = pivotCol()      } else if (dimensions === WidgetDimension.ROW) {        strategiteStream = pivotRow()      }    } else if (operationWidgetProps.isCoustomTable()) {      strategiteStream = coustomTable()    } else if (dimetionAxis && dimetionAxis.length) {      if (dimetionAxis === WidgetDimension.ROW) {        strategiteStream = dimetionAxisRow()      } else if (dimetionAxis === WidgetDimension.COL) {        strategiteStream = dimetionAxisCol()      }    } else {      strategiteStream = defaultScenes()    }    const {      cols,      rows,      type,      groups,      filters,      widgetProps,      currentGroup    } = strategiteStream    this.sendDrillDetail({ cols, rows, type, groups, filters, currentGroup })    this.setState({ widgetProps })  }  private selectChartsItems = (selectedItems) => {    const { onSelectChartsItems, itemId } = this.props    if (onSelectChartsItems) {      onSelectChartsItems(itemId, 'select', selectedItems)    }  }  private renderChartError = (error: Error) => {    const { itemId, onRenderChartError } = this.props    onRenderChartError(itemId, error)  }  public render() {    const {      alias,      itemId,      widget,      formedViews,      datasource,      loading,      interacting,      shareToken,      shareLoading,      downloadCsvLoading,      renderType,      currentProject,      queryConditions,      onLoadData,      onShowEdit,      onShowDrillEdit,      onSelectDrillHistory,      onGetControlOptions,      onControlSearch,      onDeleteDashboardItem,      onMonitoredSearchDataAction,      container,      errorMessage    } = this.props    const { drillHistory } = queryConditions    const data = datasource.resultList    const {      controlPanelVisible,      queryVariables,      widgetProps,      isDrilling,      model,      sourceDataGroup,      sourceDataOfBrushed    } = this.state    let downloadButton    let shareButton    let widgetButton    let dropdownMenu    if (currentProject) {      const DownloadButton = ShareDownloadPermission<IDownloadCsvProps>(        currentProject,        'download'      )(DownloadCsv)      downloadButton = (        <Tooltip title="下载数据">          <DownloadButton            shareLoading={shareLoading}            downloadCsvLoading={downloadCsvLoading}            onDownloadCsv={this.downloadCsv}          />        </Tooltip>      )      const ShareButton = ShareDownloadPermission<IconProps>(        currentProject,        'share'      )(Icon)      shareButton = (        <Tooltip title="分享">          <ShareButton type="share-alt" onClick={this.openSharePanel} />        </Tooltip>      )      const EditButton = ModulePermission<        React.DetailedHTMLProps<          React.HTMLAttributes<HTMLSpanElement>,          HTMLSpanElement        >      >(        currentProject,        'viz',        false      )(Span)      widgetButton = (        <Tooltip title="编辑">          {/* <i className="iconfont icon-edit-2" onClick={this.toWorkbench} /> */}          <i>            <EditButton              className="iconfont icon-edit-2"              onClick={this.toWorkbench}            />          </i>        </Tooltip>      )    }    if (container === 'share') {      downloadButton = (        <Tooltip title="下载数据">          <DownloadCsv            shareLoading={shareLoading}            downloadCsvLoading={downloadCsvLoading}            onDownloadCsv={this.downloadCsv}          />        </Tooltip>      )    } else {      const InfoButton = ModulePermission<        React.DetailedHTMLProps<          React.HTMLAttributes<HTMLSpanElement>,          HTMLSpanElement        >      >(        currentProject,        'viz',        false      )(Span)      const DeleteButton = ModulePermission<        React.DetailedHTMLProps<          React.HTMLAttributes<HTMLSpanElement>,          HTMLSpanElement        >      >(        currentProject,        'viz',        true      )(Span)      const menu = (        <Menu>          <Menu.Item className={styles.menuItem}>            <InfoButton              className={styles.menuText}              onClick={onShowEdit(itemId)}            >              基本信息            </InfoButton>          </Menu.Item>          <Menu.Item className={styles.menuItem}>            <Popconfirm              title="确定删除?"              placement="bottom"              onConfirm={onDeleteDashboardItem(itemId)}            >              <DeleteButton className={styles.menuText}>删除</DeleteButton>            </Popconfirm>          </Menu.Item>        </Menu>      )      dropdownMenu = (        <Dropdown overlay={menu} placement="bottomRight" trigger={['click']}>          <Icon type="ellipsis" />        </Dropdown>      )    }    const controlToggle = !!widget.config.controls.length && (      <Tooltip title="控制器面板">        <Icon          type="control"          className={classnames({            [styles.activated]: controlPanelVisible          })}          onClick={this.toggleControlPanel}        />      </Tooltip>    )    const loadingIcon = loading && <Icon type="loading" />    const descIcon = widget.description && (      <Popover        title="备注"        content={widget.description}        placement="bottom"        overlayClassName={styles.widgetInfoContent}      >        <Icon type="info-circle" />      </Popover>    )    const errorIcon = errorMessage && (      <Tooltip        title={          <>            <p>错误信息:</p>            <p>{errorMessage}</p>          </>        }        placement="bottomLeft"        overlayClassName={styles.widgetInfoContent}      >        <Icon className={styles.error} type="warning" />      </Tooltip>    )    const gridItemClass = classnames({      [styles.gridItem]: true,      [styles.interact]: interacting    })    const isDrillableChart = DrillableChart.some(      (drillable) => drillable === widget.config.selectedChart    )    const drillInteractIcon =      this.props.isTrigger === false ? (        isDrillableChart ? (          <Tooltip title="可钻取">            <i className="iconfont icon-xiazuan" />          </Tooltip>        ) : (          void 0        )      ) : (        <Tooltip title="可联动">          <i className="iconfont icon-liandong1" />        </Tooltip>      )    const dataDrillPanel = (      <EnhancerPanel        currentData={data}        widgetConfig={widget.config}        onDataDrillDown={this.drillDown}        onDataDrillUp={this.drillUp}        drillHistory={drillHistory}        isSelectedfilter={sourceDataOfBrushed}        isSelectedGroup={sourceDataGroup}        chartStyle={widget.config.selectedChart}      />    )    const dataDrillHistoryClass = classnames({      [styles.dataDrillHistory]: true,      [utilStyles.hide]: !(drillHistory && drillHistory.length > 0)    })    const dataDrillHistory = (      <div className={dataDrillHistoryClass}>        <DataDrillHistory          itemId={itemId}          widgetId={widget.id}          drillHistory={drillHistory}          onSelectDrillHistory={this.drillDataHistory}        />      </div>    )    const { selectedChart, cols, rows, metrics } = widget.config    const hasDataConfig = !!(cols.length || rows.length || metrics.length)    const empty = (      <DashboardItemMask.Empty        loading={loading}        chartType={selectedChart}        empty={!data.length}        hasDataConfig={hasDataConfig}      />    )    const widgetName = alias || widget.name    return (      <div className={gridItemClass} ref={(f) => (this.container = f)}>        <div className={styles.header}>          <div className={styles.title}>            <h4>{widgetName}</h4>            {descIcon}            {errorIcon}            {controlToggle}            {loadingIcon}            {}          </div>          <div className={styles.tools}>            <Tooltip title="同步数据">              {!loading && <Icon type="reload" onClick={this.syncData} />}            </Tooltip>            {widgetButton}            <Tooltip title="全屏">              <Icon                type="fullscreen"                onClick={this.onFullScreen}                className={styles.fullScreen}              />            </Tooltip>            {shareButton}            {downloadButton}            {dropdownMenu}          </div>        </div>        <div className={styles.trigger}>{drillInteractIcon}</div>        <div className={styles.offInteract} onClick={this.turnOffInteract}>          <i className="iconfont icon-unlink" />          <h3>点击取消联动</h3>        </div>        <div          className={classnames({ [utilStyles.hide]: !controlPanelVisible })}        >          <LocalControlPanel            formedViews={formedViews}            itemId={itemId}            widget={widget}            layoutType={ControlPanelLayoutTypes.DashboardItem}            onGetOptions={onGetControlOptions}            onSearch={onControlSearch}            onMonitoredSearchDataAction={onMonitoredSearchDataAction}          />        </div>        <Dropdown          overlay={dataDrillPanel}          placement="topCenter"          trigger={['contextMenu']}        >          <div className={styles.block}>            <Widget              {...widgetProps}              renderType={loading ? 'loading' : renderType}              data={data}              interacting={this.props.interacting}              queryVariables={queryVariables}              pagination={queryConditions.pagination}              empty={empty}              model={model}              onCheckTableInteract={this.checkTableInteract}              onDoInteract={this.doInteract}              onPaginationChange={this.paginationChange}              getDataDrillDetail={this.getDataDrillDetail}              isDrilling={this.state.isDrilling}              whichDataDrillBrushed={this.state.whichDataDrillBrushed}              onSelectChartsItems={this.selectChartsItems}              selectedItems={this.props.selectedItems}              // onHideDrillPanel={this.onHideDrillPanel}              onError={this.renderChartError}            />            {dataDrillHistory}          </div>        </Dropdown>      </div>    )  }}function Span(  props: React.DetailedHTMLProps<    React.HTMLAttributes<HTMLSpanElement>,    HTMLSpanElement  >) {  return <span {...props}>{props.children}</span>}export default DashboardItem
 |