king
2021-11-13 182445f68ca03beb95a90e57867f356fe155126b
2021-11-13
47个文件已修改
12个文件已添加
1268 ■■■■■ 已修改文件
src/assets/mobimg/timeline.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/mkIcon/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/balcony/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardsimplecomponent/index.jsx 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardsimplecomponent/node-wrap/index.jsx 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardsimplecomponent/node-wrap/index.scss 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardsimplecomponent/node-wrap/menus/columnform/index.jsx 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardsimplecomponent/node-wrap/menus/columnform/index.scss 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardsimplecomponent/node-wrap/menus/index.jsx 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardsimplecomponent/node-wrap/menus/index.scss 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/data-card/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/prop-card/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/table-card/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-bar/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-bar/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-dashboard/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-dashboard/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-pie/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-pie/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-scatter/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-scatter/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/chart-custom/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/chart-custom/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/editor/braft-editor/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/group/groupcomponents/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/group/groupcomponents/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/group/paste/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/normalheader/index.jsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/normalheader/index.scss 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/edit-table/index.jsx 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/normal-table/index.jsx 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/tabs/paste/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/tabs/tabcomponents/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/tabs/tabcomponents/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/timeline/normal-timeline/index.jsx 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/timeline/normal-timeline/index.scss 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/timeline/normal-timeline/options.jsx 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/menushell/card.jsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/menushell/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modulesource/option.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/pastecontroller/index.jsx 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/tabs/tabcomponents/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/tabs/tabcomponents/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/mobshell/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/mobshell/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/modulesource/option.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pc/menushell/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pc/menushell/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pc/modulesource/option.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/timeline/normal-timeline/index.jsx 329 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/timeline/normal-timeline/index.scss 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/index.jsx 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/editTable/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/pasteform/index.jsx 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/option.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils-custom.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/menudesign/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobdesign/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/pcdesign/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/mobimg/timeline.png
src/components/mkIcon/index.jsx
@@ -43,7 +43,7 @@
      <div className="mk-icon-box">
        {selectIcon ? <Icon type={selectIcon}/> : <Icon style={{opacity: 0}} type="plus"/>}
        <Icon className="trigger" onClick={() => this.setState({visible: true})} type="swap"/>
        {allowClear ? <Icon className="close" onClick={() => this.checkIcon('')} type="close"/> : null}
        {allowClear && selectIcon ? <Icon className="close" onClick={() => this.checkIcon('')} type="close"/> : null}
        <Modal
          wrapClassName="popview-modal mk-icon-wrap"
          title={'图标选择'}
src/menu/components/card/balcony/index.jsx
@@ -212,7 +212,7 @@
          <div className="mk-popover-control">
            <Icon className="plus" title="添加元素" onClick={this.addElement} type="plus" />
            <Icon className="plus" title="添加按钮" onClick={this.addButton} type="plus-square" />
            <NormalForm title="卡片设置" width={800} update={this.updateWrap} getForms={this.getWrapForms}>
            <NormalForm title="浮动卡设置" width={800} update={this.updateWrap} getForms={this.getWrapForms}>
              <Icon type="edit" style={{color: '#1890ff'}} title="编辑"/>
            </NormalForm>
            <CopyComponent type="balcony" card={card}/>
src/menu/components/card/cardsimplecomponent/index.jsx
@@ -12,6 +12,7 @@
// import './index.scss'
const NormalForm = asyncIconComponent(() => import('@/components/normalform'))
const NodesWrap = asyncComponent(() => import('./node-wrap'))
const CardCellComponent = asyncComponent(() => import('../cardcellcomponent'))
const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
@@ -155,6 +156,16 @@
    }
  }
  updateNodes = (res) => {
    const { card } = this.state
    this.setState({
      card: {...card, nodes: res}
    })
    this.props.updateElement({...card, nodes: res})
  }
  updateSetting = (res) => {
    const { card, appType } = this.state
@@ -193,6 +204,11 @@
    if (cards.type === 'carousel') {
      _style.height = cards.style.height
    }
    let control = true
    if (cards.subtype === 'datacard' || cards.type === 'timeline') {
      control = false
    }
    
    _style = resetStyle(_style)
@@ -205,12 +221,12 @@
              <div className="mk-popover-control">
                <Icon className="plus" title="添加元素" onClick={this.addElement} type="plus" />
                <Icon className="plus" title="添加按钮" onClick={this.addButton} type="plus-square" />
                <NormalForm title="卡片设置" width={800} update={this.updateSetting} getForms={this.getSettingForms}>
                {cards.type !== 'timeline' ? <NormalForm title="卡片设置" width={800} update={this.updateSetting} getForms={this.getSettingForms}>
                  <Icon type="edit" style={{color: '#1890ff'}} title="编辑"/>
                </NormalForm>
                <CopyComponent type="cardcell" card={card}/>
                </NormalForm> : <NodesWrap card={card} updateMenus={this.updateNodes}/>}
                {cards.type !== 'timeline' ? <CopyComponent type="cardcell" card={card}/> : null}
                <Icon className="style" title="调整样式" onClick={this.changeStyle} type="font-colors" />
                {cards.subtype !== 'datacard' ? <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
                {control ? <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
                  <div className="mk-popover-control">
                    <Icon className="plus" title="前移" type="arrow-left" onClick={() => this.props.move(card, 'left')} />
                    <Icon className="close" title="后移" type="arrow-right" onClick={() => this.props.move(card, 'right')} />
@@ -218,7 +234,7 @@
                } trigger="hover" getPopupContainer={() => document.getElementById(card.uuid + 'swap')}>
                  <Icon type="swap" id={card.uuid + 'swap'}/>
                </Popover> : null}
                {cards.subtype !== 'datacard' ? <Icon className="close" title="删除卡片" type="delete" onClick={() => this.props.deleteElement(card)} /> : null}
                {control ? <Icon className="close" title="删除卡片" type="delete" onClick={() => this.props.deleteElement(card)} /> : null}
              </div>
            } trigger="hover">
              <Icon type="tool" />
src/menu/components/card/cardsimplecomponent/node-wrap/index.jsx
New file
@@ -0,0 +1,67 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Modal, Icon } from 'antd'
import MenusForm from './menus'
import './index.scss'
class NodesWrap extends Component {
  static propTpyes = {
    card: PropTypes.object,
    updateMenus: PropTypes.func
  }
  state = {
    visible: false,
    nodes: [],
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  trigger = () => {
    const { card } = this.props
    this.setState({
      visible: true,
      nodes: card.nodes || []
    })
  }
  submit = () => {
    this.setState({
      visible: false
    })
    this.props.updateMenus(this.state.nodes)
  }
  update = (nodes) => {
    this.setState({nodes})
  }
  render() {
    const { visible, nodes, appType } = this.state
    return (
      <>
        <Icon type="more" style={{color: '#1890ff'}} title="节点组" onClick={this.trigger}/>
        <Modal
          title="节点组"
          wrapClassName="nodes-field-modal"
          visible={visible}
          width={900}
          maskClosable={false}
          onOk={this.submit}
          onCancel={() => { this.setState({ visible: false })}}
          destroyOnClose
        >
          <MenusForm menus={nodes} appType={appType} update={this.update}/>
        </Modal>
      </>
    )
  }
}
export default NodesWrap
src/menu/components/card/cardsimplecomponent/node-wrap/index.scss
New file
@@ -0,0 +1,21 @@
.nodes-field-modal {
  .ant-modal {
    top: 70px;
  }
  .ant-modal-body {
    min-height: 150px;
    padding-top: 40px;
    .node-form {
      .color-sketch-block {
        margin-top: 6px;
        .color-sketch-block-box {
          width: calc(100% - 140px);
        }
        .color-sketch-value {
          width: 140px;
        }
      }
    }
  }
}
src/menu/components/card/cardsimplecomponent/node-wrap/menus/columnform/index.jsx
New file
@@ -0,0 +1,94 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Button, Input, notification } from 'antd'
// import './index.scss'
import asyncComponent from '@/utils/asyncComponent'
const ColorSketch = asyncComponent(() => import('@/mob/colorsketch'))
const MkIcon = asyncComponent(() => import('@/components/mkIcon'))
class MenusColumn extends Component {
  static propTpyes = {
    menus: PropTypes.array,
    columnChange: PropTypes.func
  }
  handleConfirm = () => {
    const { menus } = this.props
    // 表单提交时检查输入值是否正确
    this.props.form.validateFieldsAndScroll((err, values) => {
      if (!err) {
        if (menus.filter(item => item.sign === values.sign).length > 0) {
          notification.warning({
            top: 92,
            message: '标识已存在!',
            duration: 2
          })
          return
        }
        this.props.columnChange(values)
        this.props.form.setFieldsValue({
          sign: ''
        })
      }
    })
  }
  render() {
    const { getFieldDecorator } = this.props.form
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <Form {...formItemLayout} className="node-form">
        <Row gutter={24}>
          <Col span={6}>
            <Form.Item label="标识">
              {getFieldDecorator('sign', {
                initialValue: ''
              })(<Input placeholder="" autoComplete="off" />)}
            </Form.Item>
          </Col>
          <Col span={8}>
            <Form.Item label="颜色">
              {getFieldDecorator('color', {
                initialValue: '',
                rules: [
                  {
                    required: true,
                    message: '请选择颜色!'
                  }
                ]
              })(<ColorSketch />)}
            </Form.Item>
          </Col>
          <Col span={7}>
            <Form.Item label="图标">
              {getFieldDecorator('icon', {
                initialValue: ''
              })(
                <MkIcon allowClear/>
              )}
            </Form.Item>
          </Col>
          <Col span={3} className="add">
            <Button onClick={this.handleConfirm} type="primary" className="mk-green">
              添加
            </Button>
          </Col>
        </Row>
      </Form>
    )
  }
}
export default Form.create()(MenusColumn)
src/menu/components/card/cardsimplecomponent/node-wrap/menus/columnform/index.scss
src/menu/components/card/cardsimplecomponent/node-wrap/menus/index.jsx
New file
@@ -0,0 +1,86 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Icon } from 'antd'
import Utils from '@/utils/utils.js'
import ColumnForm from './columnform'
import asyncComponent from '@/utils/asyncComponent'
// import './index.scss'
const EditTable = asyncComponent(() => import('@/templates/zshare/editTable'))
class CardMenus extends Component {
  static propTpyes = {
    appType: PropTypes.string,
    menulist: PropTypes.array,
    menus: PropTypes.array,
    update: PropTypes.func
  }
  state = {
    columns: [
      {
        title: '标识',
        dataIndex: 'sign',
        inputType: 'input',
        editable: true,
        unique: true,
        required: false,
        width: '35%'
      },
      {
        title: '颜色',
        dataIndex: 'color',
        inputType: 'color',
        editable: true,
        required: true,
        width: '35%'
      },
      {
        title: '图标',
        dataIndex: 'icon',
        inputType: 'icon',
        editable: true,
        required: false,
        width: '35%',
        render: (text, record) => record.icon ? <Icon type={record.icon}/> : ''
      }
    ]
  }
  UNSAFE_componentWillMount() {
    const { menus } = this.props
    this.setState({
      menus: fromJS(menus).toJS()
    })
  }
  columnChange = (values) => {
    const { menus } = this.state
    values.uuid = Utils.getuuid()
    let _menus = [...menus, values]
    this.setState({menus: _menus})
    this.props.update(_menus)
  }
  changeColumns = (columns) => {
    this.setState({menus: columns})
    this.props.update(columns)
  }
  render() {
    const { menus, columns } = this.state
    return (
      <div style={{minHeight: '250px'}}>
        <ColumnForm menus={menus} columnChange={this.columnChange}/>
        <EditTable actions={['edit', 'move', 'copy', 'del']} type={'timenodes'} data={menus} columns={columns} onChange={this.changeColumns}/>
      </div>
    )
  }
}
export default CardMenus
src/menu/components/card/cardsimplecomponent/node-wrap/menus/index.scss
src/menu/components/card/data-card/index.jsx
@@ -620,7 +620,7 @@
    return (
      <div className={'menu-data-card-edit-box ' + appType} style={_style} onClick={this.clickComponent} id={card.uuid}>
        <NormalHeader defaultshow="hidden" config={card} updateComponent={this.updateComponent}/>
        <NormalHeader config={card} updateComponent={this.updateComponent}/>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            <Icon className="plus" title="添加卡片" onClick={() => this.addCard()} type="plus" />
src/menu/components/card/prop-card/index.jsx
@@ -478,7 +478,7 @@
    return (
      <div className="menu-prop-card-edit-box" style={_style} onClick={this.clickComponent} id={card.uuid}>
        <NormalHeader defaultshow="hidden" config={card} updateComponent={this.updateComponent}/>
        <NormalHeader config={card} updateComponent={this.updateComponent}/>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            <Icon className="plus" title="添加卡片" onClick={() => this.addCard()} type="plus" />
src/menu/components/card/table-card/index.jsx
@@ -519,7 +519,7 @@
    return (
      <div className="menu-table-card-edit-box" style={_style} onClick={this.clickComponent} id={card.uuid}>
        <NormalHeader defaultshow="hidden" config={card} updateComponent={this.updateComponent}/>
        <NormalHeader config={card} updateComponent={this.updateComponent}/>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            <Icon className="plus" title="添加卡片" onClick={() => this.addCard()} type="plus" />
src/menu/components/chart/antv-bar/index.jsx
@@ -1208,7 +1208,7 @@
  updateComponent = (component) => {
    const card = fromJS(this.state.card).toJS()
    if (!is(fromJS(component.plot), fromJS(card.plot)) || !is(fromJS(component.style), fromJS(card.style))) {
    if (!is(fromJS(component.plot), fromJS(card.plot)) || !is(fromJS(component.style), fromJS(card.style)) || !is(fromJS(component.search), fromJS(card.search))) {
      let _element = document.getElementById(card.uuid + 'canvas')
      if (_element) {
        _element.innerHTML = ''
@@ -1348,7 +1348,7 @@
        } trigger="hover">
          <Icon type="tool" />
        </Popover>
        {card.plot.title || card.search.length > 0 ? <NormalHeader config={card} updateComponent={this.updateComponent}/> : null}
        <NormalHeader config={card} updateComponent={this.updateComponent}/>
        <div className="canvas" id={card.uuid + 'canvas'} ref={ref => this.wrap = ref}></div>
        {appType !== 'mob' ? <ActionComponent
          type="chart"
src/menu/components/chart/antv-bar/index.scss
@@ -38,7 +38,7 @@
    position: absolute;
    right: 1px;
    top: 1px;
    z-index: 2;
    z-index: 3;
    font-size: 16px;
    padding: 5px;
    cursor: pointer;
src/menu/components/chart/antv-dashboard/index.jsx
@@ -466,7 +466,7 @@
  updateComponent = (component) => {
    const card = fromJS(this.state.card).toJS()
    if (!is(fromJS(component.plot), fromJS(card.plot)) || !is(fromJS(component.style), fromJS(card.style))) {
    if (!is(fromJS(component.plot), fromJS(card.plot)) || !is(fromJS(component.style), fromJS(card.style)) || !is(fromJS(component.search), fromJS(card.search))) {
      let _element = document.getElementById(card.uuid + 'dashboard')
      if (_element) {
        _element.innerHTML = ''
@@ -528,7 +528,7 @@
        } trigger="hover">
          <Icon type="tool" />
        </Popover>
        {card.plot.title ? <NormalHeader config={card} updateComponent={this.updateComponent}/> : null}
        <NormalHeader hideSearch="true" config={card} updateComponent={this.updateComponent}/>
        <div className="canvas" id={card.uuid + 'dashboard'} ref={ref => this.wrap = ref}></div>
      </div>
    )
src/menu/components/chart/antv-dashboard/index.scss
@@ -19,7 +19,7 @@
    position: absolute;
    right: 1px;
    top: 1px;
    z-index: 2;
    z-index: 3;
    font-size: 16px;
    padding: 5px;
    cursor: pointer;
src/menu/components/chart/antv-pie/index.jsx
@@ -598,7 +598,7 @@
  updateComponent = (component) => {
    const card = fromJS(this.state.card).toJS()
    if (!is(fromJS(component.plot), fromJS(card.plot)) || !is(fromJS(component.style), fromJS(card.style))) {
    if (!is(fromJS(component.plot), fromJS(card.plot)) || !is(fromJS(component.style), fromJS(card.style)) || !is(fromJS(component.search), fromJS(card.search))) {
      let _element = document.getElementById(card.uuid + 'canvas')
      if (_element) {
        _element.innerHTML = ''
@@ -681,7 +681,7 @@
        } trigger="hover">
          <Icon type="tool" />
        </Popover>
        {card.plot.title || card.search.length > 0 ? <NormalHeader config={card} updateComponent={this.updateComponent}/> : null}
        <NormalHeader config={card} updateComponent={this.updateComponent}/>
        <div className="canvas" id={card.uuid + 'canvas'} ref={ref => this.wrap = ref}></div>
      </div>
    )
src/menu/components/chart/antv-pie/index.scss
@@ -19,7 +19,7 @@
    position: absolute;
    right: 1px;
    top: 1px;
    z-index: 2;
    z-index: 3;
    font-size: 16px;
    padding: 5px;
    cursor: pointer;
src/menu/components/chart/antv-scatter/index.jsx
@@ -246,7 +246,7 @@
  updateComponent = (component) => {
    const card = fromJS(this.state.card).toJS()
    if (!is(fromJS(component.plot), fromJS(card.plot)) || !is(fromJS(component.style), fromJS(card.style))) {
    if (!is(fromJS(component.plot), fromJS(card.plot)) || !is(fromJS(component.style), fromJS(card.style)) || !is(fromJS(component.search), fromJS(card.search))) {
      let _element = document.getElementById(card.uuid + 'canvas')
      if (_element) {
        _element.innerHTML = ''
@@ -386,7 +386,7 @@
        } trigger="hover">
          <Icon type="tool" />
        </Popover>
        {card.plot.title || card.search.length > 0 ? <NormalHeader config={card} updateComponent={this.updateComponent}/> : null}
        <NormalHeader config={card} updateComponent={this.updateComponent}/>
        <div className="canvas" id={card.uuid + 'canvas'} ref={ref => this.wrap = ref}></div>
        {appType !== 'mob' ? <ActionComponent type="chart" config={card} updateaction={this.updateComponent}/> : null}
      </div>
src/menu/components/chart/antv-scatter/index.scss
@@ -38,7 +38,7 @@
    position: absolute;
    right: 1px;
    top: 1px;
    z-index: 2;
    z-index: 3;
    font-size: 16px;
    padding: 5px;
    cursor: pointer;
src/menu/components/chart/chart-custom/index.jsx
@@ -174,7 +174,7 @@
  updateComponent = (component) => {
    const card = fromJS(this.state.card).toJS()
    if (!is(fromJS(component.plot), fromJS(card.plot)) || !is(fromJS(component.style), fromJS(card.style))) {
    if (!is(fromJS(component.plot), fromJS(card.plot)) || !is(fromJS(component.style), fromJS(card.style)) || !is(fromJS(component.search), fromJS(card.search))) {
      let _element = document.getElementById(card.uuid + 'canvas')
      if (_element) {
        _element.innerHTML = ''
@@ -258,7 +258,7 @@
        } trigger="hover">
          <Icon type="tool" />
        </Popover>
        {card.plot.title || card.search.length > 0 ? <NormalHeader config={card} updateComponent={this.updateComponent}/> : null}
        <NormalHeader config={card} updateComponent={this.updateComponent}/>
        <div className="canvas" id={card.uuid + 'canvas'} ref={ref => this.wrap = ref}></div>
      </div>
    )
src/menu/components/chart/chart-custom/index.scss
@@ -38,7 +38,7 @@
    position: absolute;
    right: 1px;
    top: 1px;
    z-index: 2;
    z-index: 3;
    font-size: 16px;
    padding: 5px;
    cursor: pointer;
src/menu/components/editor/braft-editor/index.jsx
@@ -164,7 +164,7 @@
    return (
      <div className="menu-normal-editor-box" style={style} onClick={this.clickComponent} id={card.uuid}>
        <NormalHeader defaultshow="hidden" hideSearch="true" config={card} updateComponent={this.updateComponent}/>
        <NormalHeader hideSearch="true" config={card} updateComponent={this.updateComponent}/>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            <NormalForm title="富文本设置" width={700} update={this.updateWrap} getForms={this.getWrapForms}>
src/menu/components/group/groupcomponents/card.jsx
@@ -22,6 +22,7 @@
const Balcony = asyncComponent(() => import('@/menu/components/card/balcony'))
const CodeSandbox = asyncComponent(() => import('@/menu/components/code/sandbox'))
const CustomChart = asyncComponent(() => import('@/menu/components/chart/chart-custom'))
const Timeline = asyncComponent(() => import('@/menu/components/timeline/normal-timeline'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -91,6 +92,8 @@
      return (<CodeSandbox card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'balcony') {
      return (<Balcony card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'timeline') {
      return (<Timeline card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'chart') {
      return (<CustomChart card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
src/menu/components/group/groupcomponents/index.jsx
@@ -83,6 +83,7 @@
        scatter: '散点图',
        tree: '树形列表',
        balcony: '浮动卡',
        timeline: '时间轴',
        card: '卡片'
      }
      let i = 1
src/menu/components/group/paste/index.jsx
@@ -23,7 +23,7 @@
  }
  pasteSubmit = () => {
    let options = ['datacard', 'propcard', 'balcony', 'stepform', 'tabform', 'normaltable', 'tablecard', 'line', 'bar', 'pie', 'dashboard', 'scatter', 'chart']
    let options = ['datacard', 'propcard', 'balcony', 'timeline', 'stepform', 'tabform', 'normaltable', 'tablecard', 'line', 'bar', 'pie', 'dashboard', 'scatter', 'chart']
    let types = {
      login: '登录',
      navbar: '导航栏',
src/menu/components/share/normalheader/index.jsx
@@ -12,7 +12,6 @@
class NormalHeader extends Component {
  static propTpyes = {
    defaultshow: PropTypes.any,      // 标题与搜索条件不存在时隐藏
    hideSearch: PropTypes.any,       // 隐藏搜索条件
    config: PropTypes.object,        // 配置信息
    updateComponent: PropTypes.func  // 配置更新
@@ -62,7 +61,7 @@
  }
  render() {
    const { config, defaultshow, hideSearch } = this.props
    const { config, hideSearch } = this.props
    const { appType } = this.state
    let title = config.plot ? config.plot.title : config.wrap.title
@@ -72,10 +71,8 @@
      title = ' '
    }
    if (defaultshow === 'hidden') {
      if (!title && (!config.search || config.search.length === 0)) {
        show = false
      }
    if (!title && (!config.search || config.search.length === 0)) {
      show = false
    }
    let _style = resetStyle(config.headerStyle)
src/menu/components/share/normalheader/index.scss
@@ -19,6 +19,7 @@
  }
  .model-custom-header-search-list {
    flex: 1;
    padding-top: 5px;
  }
  .ant-input-search.ant-input-affix-wrapper {
    width: 50%;
src/menu/components/table/edit-table/index.jsx
@@ -201,11 +201,9 @@
  }
  addSearch = () => {
    let card = fromJS(this.state.card).toJS()
    const { card } = this.state
    card.search.push({uuid: Utils.getuuid(), focus: true, label: 'label', type: 'text', match: '='})
    this.setState({card})
    MKEmitter.emit('plusSearch', card.uuid, {uuid: Utils.getuuid(), focus: true, label: 'label', type: 'text', match: '='}, 'simple')
  }
  addButton = () => {
@@ -309,7 +307,7 @@
    return (
      <div className="menu-normal-table-edit-box" style={_style} onClick={this.clickComponent} id={card.uuid}>
        <NormalHeader defaultshow="hidden" hideSearch="true" config={card} updateComponent={this.updateComponent}/>
        <NormalHeader hideSearch="true" config={card} updateComponent={this.updateComponent}/>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            <Icon className="plus" title="添加列" onClick={this.addColumns} type="plus" />
src/menu/components/table/normal-table/index.jsx
@@ -261,11 +261,9 @@
  }
  addSearch = () => {
    let card = fromJS(this.state.card).toJS()
    const { card } = this.state
    card.search.push({uuid: Utils.getuuid(), focus: true, label: 'label', type: 'text', match: '='})
    this.setState({card})
    MKEmitter.emit('plusSearch', card.uuid, {uuid: Utils.getuuid(), focus: true, label: 'label', type: 'text', match: '='}, 'simple')
  }
  addButton = () => {
@@ -414,7 +412,7 @@
    return (
      <div className="menu-normal-table-edit-box" style={_style} onClick={this.clickComponent} id={card.uuid}>
        <NormalHeader defaultshow="hidden" hideSearch="true" config={card} updateComponent={this.updateComponent}/>
        <NormalHeader hideSearch="true" config={card} updateComponent={this.updateComponent}/>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            <Icon className="plus" title="添加列" onClick={this.addColumns} type="plus" />
src/menu/components/tabs/paste/index.jsx
@@ -72,7 +72,7 @@
  pasteSubmit = () => {
    const { Tab } = this.props
    let options = ['tabs', 'group', 'datacard', 'propcard', 'balcony', 'normaltable', 'mainsearch', 'stepform', 'tabform', 'tablecard', 'line', 'bar', 'pie', 'dashboard', 'scatter', 'chart']
    let options = ['tabs', 'group', 'datacard', 'propcard', 'timeline', 'balcony', 'normaltable', 'mainsearch', 'stepform', 'tabform', 'tablecard', 'line', 'bar', 'pie', 'dashboard', 'scatter', 'chart']
    let types = {
      login: '登录',
      navbar: '导航栏',
src/menu/components/tabs/tabcomponents/card.jsx
@@ -25,6 +25,7 @@
const BraftEditor = asyncComponent(() => import('@/menu/components/editor/braft-editor'))
const CodeSandbox = asyncComponent(() => import('@/menu/components/code/sandbox'))
const CustomChart = asyncComponent(() => import('@/menu/components/chart/chart-custom'))
const Timeline = asyncComponent(() => import('@/menu/components/timeline/normal-timeline'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -100,6 +101,8 @@
      return (<CodeSandbox card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'balcony') {
      return (<Balcony card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'timeline') {
      return (<Timeline card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'chart') {
      return (<CustomChart card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
src/menu/components/tabs/tabcomponents/index.jsx
@@ -111,6 +111,7 @@
        scatter: '散点图',
        tree: '树形列表',
        balcony: '浮动卡',
        timeline: '时间轴',
        card: '卡片'
      }
      let i = 1
src/menu/components/timeline/normal-timeline/index.jsx
New file
@@ -0,0 +1,201 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Icon, Popover } from 'antd'
import asyncComponent from '@/utils/asyncComponent'
import asyncIconComponent from '@/utils/asyncIconComponent'
import { resetStyle } from '@/utils/utils-custom.js'
import MKEmitter from '@/utils/events.js'
import Utils from '@/utils/utils.js'
import getWrapForm from './options'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import './index.scss'
const SettingComponent = asyncIconComponent(() => import('@/menu/datasource'))
const NormalForm = asyncIconComponent(() => import('@/components/normalform'))
const CardSimpleComponent = asyncComponent(() => import('@/menu/components/card/cardsimplecomponent'))
const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
const UserComponent = asyncIconComponent(() => import('@/menu/components/share/usercomponent'))
const NormalHeader = asyncComponent(() => import('@/menu/components/share/normalheader'))
class NormalTimelineComponent extends Component {
  static propTpyes = {
    card: PropTypes.object,
    deletecomponent: PropTypes.func,
    updateConfig: PropTypes.func,
  }
  state = {
    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    card: null,
    back: false
  }
  UNSAFE_componentWillMount () {
    const { card } = this.props
    if (card.isNew) {
      let _card = {
        uuid: card.uuid,
        type: card.type,
        floor: card.floor,
        tabId: card.tabId || '',
        parentId: card.parentId || '',
        format: 'array',   // 组件属性 - 数据格式
        pageable: false,    // 组件属性 - 是否可分页
        switchable: false,  // 组件属性 - 数据是否可切换
        dataName: card.dataName || '',
        width: card.width || 24,
        name: card.name,
        subtype: card.subtype,
        setting: { interType: 'system' },
        wrap: { title: '', name: card.name, width: card.width || 24, color: '#1890ff', mode: 'left' },
        style: { marginLeft: '0px', marginRight: '0px', marginTop: '0px', marginBottom: '0px' },
        subcards: [{
          uuid: Utils.getuuid(),
          setting: { width: 24, type: 'simple'},
          style: {
            paddingTop: '5px', paddingBottom: '5px', paddingLeft: '15px', paddingRight: '15px',
          },
          elements: []
        }],
        columns: [],
        scripts: [],
      }
      if (card.config) {
        let config = fromJS(card.config).toJS()
        _card.wrap = config.wrap
        _card.wrap.name = card.name
        _card.style = config.style
        _card.subcards = config.subcards.map(scard => {
          scard.uuid = Utils.getuuid()
          scard.elements = scard.elements.map(elem => {
            elem.uuid = Utils.getuuid()
            return elem
          })
          return scard
        })
      }
      this.setState({
        card: _card
      })
      this.props.updateConfig(_card)
    } else {
      this.setState({
        card: fromJS(card).toJS()
      })
    }
  }
  componentDidMount () {
    MKEmitter.addListener('submitStyle', this.getStyle)
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('submitStyle', this.getStyle)
  }
  /**
   * @description 卡片行外层信息更新(数据源,样式等)
   */
  updateComponent = (component) => {
    this.setState({
      card: component
    })
    component.width = component.wrap.width
    component.name = component.wrap.name
    this.props.updateConfig(component)
  }
  updateCard = (cell) => {
    let card = fromJS(this.state.card).toJS()
    card.subcards = card.subcards.map(item => {
      if (item.uuid === cell.uuid) return cell
      return item
    })
    this.setState({card})
    this.props.updateConfig(card)
  }
  changeStyle = () => {
    const { card } = this.state
    MKEmitter.emit('changeStyle', [card.uuid], ['height', 'background', 'border', 'padding', 'margin', 'shadow'], card.style)
  }
  getStyle = (comIds, style) => {
    const { card } = this.state
    if (comIds.length !== 1 || comIds[0] !== card.uuid) return
    let _card = {...card, style}
    this.setState({
      card: _card
    })
    this.props.updateConfig(_card)
  }
  getWrapForms = () => {
    const { card } = this.state
    return getWrapForm(card.wrap, card.columns)
  }
  updateWrap = (res) => {
    delete res.quick
    this.updateComponent({...this.state.card, wrap: res})
  }
  render() {
    const { card } = this.state
    let _style = resetStyle(card.style)
    return (
      <div className="menu-timeline-edit-box" style={_style} id={card.uuid}>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            {/* <Icon className="plus" title="添加元素" onClick={this.addElement} type="plus" /> */}
            {/* <Icon className="plus" title="添加按钮" onClick={this.addButton} type="plus-square" /> */}
            <NormalForm title="时间轴设置" width={800} update={this.updateWrap} getForms={this.getWrapForms}>
              <Icon type="edit" style={{color: '#1890ff'}} title="编辑"/>
            </NormalForm>
            <CopyComponent type="timeline" card={card}/>
            <Icon className="style" title="调整样式" onClick={this.changeStyle} type="font-colors" />
            <UserComponent config={card}/>
            <Icon className="close" title="删除组件" type="delete" onClick={() => this.props.deletecomponent(card.uuid)} />
            <SettingComponent config={card} updateConfig={this.updateComponent} />
          </div>
        } trigger="hover">
          <Icon type="tool" />
        </Popover>
        <NormalHeader hideSearch="true" config={card} updateComponent={this.updateComponent}/>
        {card.subcards.map(subcard => (<CardSimpleComponent key={subcard.uuid} cards={card} card={subcard} updateElement={this.updateCard}/>))}
      </div>
    )
  }
}
export default NormalTimelineComponent
src/menu/components/timeline/normal-timeline/index.scss
New file
@@ -0,0 +1,80 @@
.menu-timeline-edit-box {
  position: relative;
  box-sizing: border-box;
  background: #ffffff;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  min-height: 50px;
  .model-menu-card-cell-list {
    flex: 1;
  }
  .card-control {
    position: absolute;
    top: 0px;
    left: 0px;
    .anticon-tool {
      right: auto;
      left: 1px;
      padding: 1px;
    }
  }
  .anticon-tool {
    position: absolute;
    z-index: 2;
    font-size: 16px;
    right: 1px;
    top: 1px;
    cursor: pointer;
    padding: 5px;
    background: rgba(255, 255, 255, 0.55);
  }
  .card-item {
    overflow: hidden;
    position: relative;
    background-color: #ffffff;
    background-position: center center;
    background-repeat: no-repeat;
    background-size: cover;
    min-height: 20px;
  }
  .card-item:hover {
    box-shadow: 0px 0px 2px #1890ff;
  }
  .model-menu-card-cell-list .card-detail-row > .anticon-plus {
    position: absolute;
    right: -30px;
    font-size: 16px;
  }
  .model-menu-action-list {
    line-height: 40px;
    .ant-row > .anticon-plus {
      position: absolute;
      right: -30px;
      font-size: 16px;
    }
  }
  .card-add-button {
    text-align: right;
    clear: left;
    .anticon-plus {
      font-size: 20px;
      color: #26C281;
      padding: 5px;
      margin-right: 10px;
    }
  }
}
.menu-timeline-edit-box::after {
  display: block;
  content: ' ';
  clear: both;
}
.menu-timeline-edit-box:hover {
  z-index: 1;
  box-shadow: 0px 0px 4px #1890ff;
}
src/menu/components/timeline/normal-timeline/options.jsx
New file
@@ -0,0 +1,107 @@
/**
 * @description Wrap表单配置信息
 */
export default function (wrap, columns) {
  let roleList = sessionStorage.getItem('sysRoles')
  let appType = sessionStorage.getItem('appType')
  if (roleList) {
    try {
      roleList = JSON.parse(roleList)
    } catch (e) {
      roleList = []
    }
  } else {
    roleList = []
  }
  const balconyWrapForm = [
    {
      type: 'text',
      field: 'title',
      label: '标题',
      initval: wrap.title || '',
      required: false
    },
    {
      type: 'text',
      field: 'name',
      label: '组件名称',
      initval: wrap.name || '',
      tooltip: '用于组件间的区分。',
      required: true
    },
    {
      type: 'number',
      field: 'width',
      label: '宽度',
      initval: wrap.width || 24,
      tooltip: '栅格布局,每行等分为24列。',
      min: 1,
      max: 24,
      precision: 0,
      required: true
    },
    {
      type: 'radio',
      field: 'mode',
      label: '轴线位置',
      initval: wrap.mode || 'left',
      required: false,
      options: [
        {value: 'left', label: '左侧'},
        {value: 'alternate', label: '中间'},
        {value: 'right', label: '右侧'},
      ]
    },
    {
      type: 'radio',
      field: 'reverse',
      label: '排序',
      initval: wrap.reverse || 'false',
      required: false,
      options: [
        {value: 'false', label: '正序'},
        {value: 'true', label: '倒序'},
      ]
    },
    {
      type: 'color',
      field: 'color',
      label: '节点颜色',
      initval: wrap.color || '#1890ff',
      tooltip: '节点默认颜色。',
      required: false
    },
    {
      type: 'select',
      field: 'label',
      label: '标签',
      initval: wrap.label || '',
      tooltip: '在内容对面单独展示。',
      required: false,
      options: columns,
      forbid: !appType
    },
    {
      type: 'select',
      field: 'node',
      label: '节点控制',
      initval: wrap.node || '',
      tooltip: '选择自定义节点的控制字段后,在节点组中添加节点样式。',
      required: false,
      options: columns
    },
    {
      type: 'multiselect',
      field: 'blacklist',
      label: '黑名单',
      initval: wrap.blacklist || [],
      required: false,
      options: roleList,
      forbid: !!appType
    },
  ]
  return balconyWrapForm
}
src/menu/menushell/card.jsx
@@ -25,6 +25,7 @@
const BraftEditor = asyncComponent(() => import('@/menu/components/editor/braft-editor'))
const CodeSandbox = asyncComponent(() => import('@/menu/components/code/sandbox'))
const CustomChart = asyncComponent(() => import('@/menu/components/chart/chart-custom'))
const Timeline = asyncComponent(() => import('@/menu/components/timeline/normal-timeline'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -60,7 +61,11 @@
  }
  const getCardComponent = () => {
    if (card.type === 'bar' || card.type === 'line') {
    if (card.type === 'card' && card.subtype === 'datacard') {
      return (<DataCard card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'card' && card.subtype === 'propcard') {
      return (<PropCard card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'bar' || card.type === 'line') {
      return (<AntvBar card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'search') {
      return (<MainSearch card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
@@ -78,10 +83,6 @@
      return (<TabForm card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'tabs') {
      return (<AntvTabs tabs={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'card' && card.subtype === 'datacard') {
      return (<DataCard card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'card' && card.subtype === 'propcard') {
      return (<PropCard card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'carousel' && card.subtype === 'datacard') {
      return (<CarouselDataCard card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'carousel' && card.subtype === 'propcard') {
@@ -100,6 +101,8 @@
      return (<CodeSandbox card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'balcony') {
      return (<Balcony card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'timeline') {
      return (<Timeline card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'chart') {
      return (<CustomChart card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
src/menu/menushell/index.jsx
@@ -105,6 +105,7 @@
        scatter: '散点图',
        tree: '树形列表',
        balcony: '浮动卡',
        timeline: '时间轴',
        card: '卡片'
      }
      let i = 1
src/menu/modulesource/option.jsx
@@ -24,6 +24,7 @@
import scatter from '@/assets/mobimg/scatter.png'
import chart from '@/assets/mobimg/chart.png'
import tree from '@/assets/mobimg/tree.png'
import timeline from '@/assets/mobimg/timeline.png'
// 组件配置信息
export const menuOptions = [
@@ -39,6 +40,7 @@
  { type: 'menu', url: NormalTable, component: 'table', subtype: 'normaltable', title: '常用表', width: 24 },
  { type: 'menu', url: NormalTable, component: 'table', subtype: 'editable', title: '表格(可编辑)', width: 24 },
  { type: 'menu', url: TableCard, component: 'table', subtype: 'tablecard', title: '表格(卡片)', width: 12 },
  { type: 'menu', url: timeline, component: 'timeline', subtype: 'timeline', title: '时间轴', width: 12 },
  { type: 'menu', url: tree, component: 'tree', subtype: 'normaltree', title: '树形列表', width: 12 },
  { type: 'menu', url: line, component: 'line', subtype: 'line', title: '折线图', width: 24 },
  { type: 'menu', url: line1, component: 'line', subtype: 'line1', title: '阶梯折线图', width: 24 },
src/menu/pastecontroller/index.jsx
@@ -18,10 +18,6 @@
    visible: false
  }
  handleMenuClick = () => {
    this.setState({visible: true})
  }
  resetconfig = (item, copyBtns, uuids = {}) => {
    let appType = sessionStorage.getItem('appType')
    
@@ -71,7 +67,7 @@
  }
  pasteSubmit = () => {
    let options = ['tabs', 'menubar', 'topbar', 'datacard', 'propcard', 'mainsearch', 'stepform', 'tabform', 'balcony', 'group', 'normaltable', 'tablecard', 'line', 'bar', 'pie', 'dashboard', 'scatter']
    let options = ['tabs', 'menubar', 'topbar', 'timeline', 'datacard', 'propcard', 'mainsearch', 'stepform', 'tabform', 'balcony', 'group', 'normaltable', 'tablecard', 'line', 'bar', 'pie', 'dashboard', 'scatter']
    this.pasteFormRef.handleConfirm().then(res => {
      if ((res.copyType === 'menubar' || res.copyType === 'topbar') && sessionStorage.getItem('appType') !== 'mob') {
src/mob/components/tabs/tabcomponents/card.jsx
@@ -24,6 +24,7 @@
const NormalMenuBar = asyncComponent(() => import('@/mob/components/menubar/normal-menubar'))
const Balcony = asyncComponent(() => import('@/menu/components/card/balcony'))
const CodeSandbox = asyncComponent(() => import('@/menu/components/code/sandbox'))
const Timeline = asyncComponent(() => import('@/menu/components/timeline/normal-timeline'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -99,6 +100,8 @@
      return (<CodeSandbox card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'balcony') {
      return (<Balcony card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'timeline') {
      return (<Timeline card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
  }
src/mob/components/tabs/tabcomponents/index.jsx
@@ -111,6 +111,7 @@
        navbar: '导航栏',
        menubar: '菜单栏',
        balcony: '浮动卡',
        timeline: '时间轴',
        login: '登录'
      }
      let i = 1
src/mob/mobshell/card.jsx
@@ -26,6 +26,7 @@
const NormalTopbar = asyncComponent(() => import('@/mob/components/topbar/normal-navbar'))
const NormalMenuBar = asyncComponent(() => import('@/mob/components/menubar/normal-menubar'))
const Balcony = asyncComponent(() => import('@/menu/components/card/balcony'))
const Timeline = asyncComponent(() => import('@/menu/components/timeline/normal-timeline'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -122,6 +123,8 @@
      return (<NormalMenuBar card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'balcony') {
      return (<Balcony card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'timeline') {
      return (<Timeline card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
  }
src/mob/mobshell/index.jsx
@@ -131,6 +131,7 @@
        navbar: '导航栏',
        menubar: '菜单栏',
        balcony: '浮动卡',
        timeline: '时间轴',
        login: '登录'
      }
      let i = 1
src/mob/modulesource/option.jsx
@@ -24,6 +24,7 @@
import NavTop from '@/assets/mobimg/navtop-mob.png'
import scatter from '@/assets/mobimg/scatter.png'
import MenuBar from '@/assets/mobimg/menubar.png'
import timeline from '@/assets/mobimg/timeline.png'
// 组件配置信息
export const menuOptions = [
@@ -41,6 +42,7 @@
  { type: 'menu', url: Carousel1, component: 'carousel', subtype: 'propcard', title: '轮播-静态数据', width: 24 },
  { type: 'menu', url: NormalTable, component: 'table', subtype: 'normaltable', title: '常用表', width: 24 },
  { type: 'menu', url: TableCard, component: 'table', subtype: 'tablecard', title: '表格(卡片)', width: 24 },
  { type: 'menu', url: timeline, component: 'timeline', subtype: 'timeline', title: '时间轴', width: 24 },
  { type: 'menu', url: line, component: 'line', subtype: 'line', title: '折线图', width: 24 },
  { type: 'menu', url: line1, component: 'line', subtype: 'line1', title: '阶梯折线图', width: 24 },
  { type: 'menu', url: bar, component: 'bar', subtype: 'bar', title: '柱状图', width: 24 },
src/pc/menushell/card.jsx
@@ -27,6 +27,7 @@
const CarouselPropCard = asyncComponent(() => import('@/menu/components/carousel/prop-card'))
const Balcony = asyncComponent(() => import('@/menu/components/card/balcony'))
const CustomChart = asyncComponent(() => import('@/menu/components/chart/chart-custom'))
const Timeline = asyncComponent(() => import('@/menu/components/timeline/normal-timeline'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -122,6 +123,8 @@
      return (<NormalLogin card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'balcony') {
      return (<Balcony card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'timeline') {
      return (<Timeline card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'chart') {
      return (<CustomChart card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
src/pc/menushell/index.jsx
@@ -120,6 +120,7 @@
        tree: '树形列表',
        card: '卡片',
        balcony: '浮动卡',
        timeline: '时间轴',
        login: '登录'
      }
      let i = 1
src/pc/modulesource/option.jsx
@@ -26,6 +26,7 @@
import scatter from '@/assets/mobimg/scatter.png'
import chart from '@/assets/mobimg/chart.png'
import tree from '@/assets/mobimg/tree.png'
import timeline from '@/assets/mobimg/timeline.png'
// 组件配置信息
export const menuOptions = [
@@ -42,6 +43,7 @@
  { type: 'menu', url: NormalTable, component: 'table', subtype: 'normaltable', title: '常用表', width: 24 },
  { type: 'menu', url: NormalTable, component: 'table', subtype: 'editable', title: '表格(可编辑)', width: 24 },
  { type: 'menu', url: TableCard, component: 'table', subtype: 'tablecard', title: '表格(卡片)', width: 12 },
  { type: 'menu', url: timeline, component: 'timeline', subtype: 'timeline', title: '时间轴', width: 12 },
  { type: 'menu', url: tree, component: 'tree', subtype: 'normaltree', title: '树形列表', width: 12 },
  { type: 'menu', url: line, component: 'line', subtype: 'line', title: '折线图', width: 24 },
  { type: 'menu', url: line1, component: 'line', subtype: 'line1', title: '阶梯折线图', width: 24 },
src/tabviews/custom/components/timeline/normal-timeline/index.jsx
New file
@@ -0,0 +1,329 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Spin, notification, Timeline, Empty, Icon } from 'antd'
import Api from '@/api'
// import Utils from '@/utils/utils.js'
import asyncComponent from '@/utils/asyncComponent'
import UtilsDM from '@/utils/utils-datamanage.js'
import MKEmitter from '@/utils/events.js'
import './index.scss'
const CardCellComponent = asyncComponent(() => import('@/tabviews/custom/components/card/cardcellList'))
const NormalHeader = asyncComponent(() => import('@/tabviews/custom/components/share/normalheader'))
class NormalTimeline extends Component {
  static propTpyes = {
    BID: PropTypes.any,              // 父级Id
    data: PropTypes.array,           // 统一查询数据
    config: PropTypes.object,        // 组件配置信息
    mainSearch: PropTypes.any,       // 外层搜索条件
    menuType: PropTypes.any,         // 菜单类型
  }
  state = {
    BID: '',                   // 上级ID
    config: null,              // 图表配置信息
    loading: false,            // 数据加载状态
    sync: false,               // 是否统一请求数据
    data: null,                // 数据
    BData: '',
    card: null
  }
  /**
   * @description 初始化处理
   * 1、 initdata 为打印时使用的数据集
   */
  UNSAFE_componentWillMount () {
    const { data, initdata, BID, BData } = this.props
    let _config = fromJS(this.props.config).toJS()
    let _cols = new Map()
    let _data = null
    let card = null
    let _sync = _config.setting.sync === 'true'
    if (_config.setting.sync === 'true' && data) {
      _data = data[_config.dataName] || []
      _sync = false
    } else if (_config.setting.sync === 'true' && initdata) {
      _data = initdata || []
      _sync = false
    }
    if (_data) {
      _data = _data.map((item, index) => {
        item.key = index
        item.$$uuid = item[_config.setting.primaryKey] || ''
        item.$$BID = BID || ''
        item.$Index = index + 1 + ''
        return item
      })
    }
    _config.search = []
    _config.wrap.contentHeight = _config.wrap.title ? 'calc(100% - 45px)' : '100%'
    _config.columns.forEach(item => {
      _cols.set(item.field, item)
    })
    card = _config.subcards[0]
    card.elements = card.elements.map(item => {
      if (item.field && _cols.has(item.field)) {
        item.col = _cols.get(item.field)
      }
      return item
    })
    this.setState({
      card,
      sync: _sync,
      BID: BID || '',
      BData: BData || '',
      data: _data,
      config: _config,
      arr_field: _config.columns.map(col => col.field).join(','),
    }, () => {
      if (_config.setting.sync !== 'true' && _config.setting.onload === 'true') {
        this.loadData()
      }
    })
  }
  componentDidMount () {
    MKEmitter.addListener('reloadData', this.reloadData)
    MKEmitter.addListener('resetSelectLine', this.resetParentParam)
    MKEmitter.addListener('queryModuleParam', this.queryModuleParam)
    MKEmitter.addListener('refreshByButtonResult', this.refreshByButtonResult)
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('reloadData', this.reloadData)
    MKEmitter.removeListener('resetSelectLine', this.resetParentParam)
    MKEmitter.removeListener('queryModuleParam', this.queryModuleParam)
    MKEmitter.removeListener('refreshByButtonResult', this.refreshByButtonResult)
  }
  /**
   * @description 图表数据更新,刷新内容
   */
  UNSAFE_componentWillReceiveProps (nextProps) {
    const { sync, config, BID, BData } = this.state
    if (sync && !is(fromJS(this.props.data), fromJS(nextProps.data))) {
      let _data = []
      if (nextProps.data && nextProps.data[config.dataName]) {
        _data = nextProps.data[config.dataName] || []
      }
      _data = _data.map((item, index) => {
        item.key = index
        item.$$uuid = item[config.setting.primaryKey] || ''
        item.$$BID = BID || ''
        item.$$BData = BData || ''
        item.$Index = index + 1 + ''
        return item
      })
      this.setState({sync: false, data: _data})
    } else if (config.setting.syncRefresh && nextProps.mainSearch && !is(fromJS(this.props.mainSearch), fromJS(nextProps.mainSearch))) {
      this.setState({}, () => {
        this.loadData()
      })
    }
  }
  /**
   * @description 按钮执行完成后页面刷新
   * @param {*} menuId     // 菜单Id
   * @param {*} position   // 刷新位置
   * @param {*} btn        // 执行的按钮
   */
  refreshByButtonResult = (menuId, position, btn) => {
    const { config, BID } = this.state
    if (config.uuid !== menuId) return
    this.loadData(btn)                                                         // 数据刷新
    if (btn.syncComponentId && btn.syncComponentId !== config.uuid && btn.syncComponentId !== config.setting.supModule) {
      MKEmitter.emit('reloadData', btn.syncComponentId)                        // 同级标签刷新
    }
    if (position === 'mainline' && config.setting.supModule) {                 // 主表行刷新
      MKEmitter.emit('reloadData', config.setting.supModule, (BID || 'empty'))
    } else if (position === 'popclose') {                                      // 标签关闭刷新
      config.setting.supModule && MKEmitter.emit('reloadData', config.setting.supModule, (BID || 'empty'))
      btn.$tabId && MKEmitter.emit('refreshPopButton', btn.$tabId)
    }
  }
  resetParentParam = (MenuID, id, data) => {
    const { config } = this.state
    if (!config.setting.supModule || config.setting.supModule !== MenuID) return
    if (id !== this.state.BID || id !== '') {
      this.setState({ BID: id, BData: data }, () => {
        this.loadData()
      })
    }
  }
  reloadData = (menuId) => {
    const { config } = this.state
    if (config.uuid !== menuId) return
    this.loadData()
  }
   /**
   * @description 导出Excel时,获取页面搜索排序等参数
   */
  queryModuleParam = (menuId, btnId) => {
    const { mainSearch } = this.props
    const { arr_field, config } = this.state
    if (config.uuid !== menuId) return
    let searches = []
    if (config.setting.useMSearch && mainSearch && mainSearch.length > 0) { // 主表搜索条件
      let keys = searches.map(item => item.key.toLowerCase())
      mainSearch.forEach(item => {
        if (!keys.includes(item.key.toLowerCase())) {
          searches.push(item)
        }
      })
    }
    MKEmitter.emit('returnModuleParam', config.uuid, btnId, {
      arr_field: arr_field,
      orderBy: config.setting.order || '',
      search: searches,
      menuName: config.name
    })
  }
  async loadData () {
    const { mainSearch, menuType } = this.props
    const { config, arr_field, BID, BData } = this.state
    if (config.setting.supModule && !BID) { // BID 不存在时,不做查询
      this.setState({
        data: [],
      })
      return
    }
    let searches = []
    if (config.setting.useMSearch && mainSearch && mainSearch.length > 0) { // 主表搜索条件
      let keys = searches.map(item => item.key)
      mainSearch.forEach(item => {
        if (!keys.includes(item.key)) {
          searches.push(item)
        }
      })
    }
    let requireFields = searches.filter(item => item.required && item.value === '')
    if (requireFields.length > 0) {
      return
    }
    this.setState({
      loading: true
    })
    let _orderBy = config.setting.order || ''
    let param = UtilsDM.getQueryDataParams(config.setting, arr_field, searches, _orderBy, 1, config.setting.pageSize, BID, menuType)
    let result = await Api.genericInterface(param)
    if (result.status) {
      this.setState({
        data: result.data.map((item, index) => {
          item.key = index
          item.$$uuid = item[config.setting.primaryKey] || ''
          item.$$BID = BID || ''
          item.$$BData = BData || ''
          item.$Index = index + 1 + ''
          return item
        }),
        loading: false
      })
    } else {
      this.setState({
        loading: false
      })
      notification.error({
        top: 92,
        message: result.message,
        duration: 10
      })
    }
  }
  getnodes = (data) => {
    const { config, card } = this.state
    // let label = '' // antd3.0 不支持
    // if (config.wrap.label) {
    //   label = data[config.wrap.label] || ''
    // }
    let color = config.wrap.color
    let dot = ''
    if (config.wrap.node && card.nodes && card.nodes.length > 0) {
      let sign = data[config.wrap.node]
      card.nodes.some(item => {
        if (sign === item.sign) {
          color = item.color
          if (item.icon) {
            dot = <Icon type={item.icon} style={{color}}/>
          }
          return true
        }
        return false
      })
    }
    return (<Timeline.Item key={data.$Index} color={color} dot={dot}>
      <div className="card-item-box" style={card.style}>
        <CardCellComponent data={data} cards={config} cardCell={card} elements={card.elements}/>
      </div>
    </Timeline.Item>)
  }
  render() {
    const { config, loading, data } = this.state
    return (
      <div className="normal-timeline-box" style={{...config.style}}>
        {loading ?
          <div className="loading-mask">
            {data ? <div className="ant-spin-blur"></div> : null}
            <Spin />
          </div> : null
        }
        <NormalHeader config={config} />
        {data && data.length > 0 ? <Timeline mode={config.wrap.mode} className="card-row-list" style={{height: config.wrap.contentHeight}}>
          {data.map(item => this.getnodes(item))}
        </Timeline> : null}
        {data && data.length === 0 ? <div className="card-row-list" style={{height: config.wrap.contentHeight}}>
          <Empty description={false}/>
        </div> : null}
      </div>
    )
  }
}
export default NormalTimeline
src/tabviews/custom/components/timeline/normal-timeline/index.scss
New file
@@ -0,0 +1,103 @@
.normal-timeline-box {
  background: #ffffff;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  min-height: 100px;
  position: relative;
  overflow-y: hidden;
  > .table-header {
    height: 45px;
    // border-bottom: 1px solid #e8e8e8;
    overflow: hidden;
    .table-title {
      // font-size: 16px;
      float: left;
      line-height: 45px;
      margin-left: 10px;
      text-decoration: inherit;
      font-weight: inherit;
      font-style: inherit;
    }
  }
  >.ant-pagination {
    margin-top: 10px;
    float: right;
  }
  .card-row-list::after {
    content: ' ';
    display: block;
    clear: both;
  }
  .ant-timeline-item-head-custom {
    .anticon {
      font-size: 16px;
    }
  }
  .card-row-list {
    overflow-y: auto;
    padding: 20px;
    .card-item-box {
      text-align: left;
      overflow: hidden;
      background-color: #ffffff;
      transition: all 0.3s;
    }
    >.active >.card-item-box {
      border-color: #1890ff!important;
      box-shadow: 0 0 3px #1890ff;
    }
  }
  .card-row-list::-webkit-scrollbar {
    width: 7px;
  }
  .card-row-list::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
    background: rgba(0, 0, 0, 0.13);
  }
  .card-row-list::-webkit-scrollbar-track {
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
    border-radius: 3px;
    border: 1px solid rgba(0, 0, 0, 0.07);
    background: rgba(0, 0, 0, 0);
  }
  .card-item-box {
    background-position: center center;
    background-repeat: no-repeat;
    background-size: cover;
  }
  .loading-mask {
    position: absolute;
    left: 40px;
    top: 0;
    right: 40px;
    bottom: 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: justify;
    z-index: 1;
    .ant-spin-blur {
      position: absolute;
      width: 100%;
      height: 100%;
      opacity: 0.5;
      background: #ffffff;
    }
  }
}
.normal-timeline-box::after {
  content: ' ';
  display: block;
  clear: both;
}
src/tabviews/custom/index.jsx
@@ -39,6 +39,7 @@
const SettingComponent = asyncComponent(() => import('@/tabviews/zshare/settingcomponent'))
const PagemsgComponent = asyncComponent(() => import('@/tabviews/zshare/pageMessage'))
const CustomChart = asyncComponent(() => import('./components/chart/custom-chart'))
const TimeLine = asyncComponent(() => import('./components/timeline/normal-timeline'))
class CustomPage extends Component {
  static propTpyes = {
@@ -676,7 +677,7 @@
          return cell.eleType !== 'button' || skip || permAction[cell.uuid]
        })
      } else if ((item.type === 'table' && item.subtype === 'tablecard') || item.type === 'carousel') {
      } else if ((item.type === 'table' && item.subtype === 'tablecard') || item.type === 'carousel' || item.type === 'timeline') {
        item.subcards && item.subcards.forEach(card => {
          let _hasheight = card.style.height && card.style.height !== 'auto'
          card.elements = card.elements.filter(cell => {
@@ -1091,6 +1092,12 @@
            <Balcony config={item} data={data} BID={_bid} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'timeline') {
        return (
          <Col span={item.width} key={item.uuid}>
            <TimeLine config={item} data={data} BID={_bid} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'carousel' && item.subtype === 'datacard') {
        return (
          <Col span={item.width} key={item.uuid}>
src/templates/zshare/editTable/index.jsx
@@ -7,11 +7,13 @@
import Utils from '@/utils/utils.js'
import ColorSketch from '@/mob/colorsketch'
import PasteForm from '@/templates/zshare/pasteform'
import asyncComponent from '@/utils/asyncComponent'
import CusSwitch from './cusSwitch'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import './index.scss'
const MkIcon = asyncComponent(() => import('@/components/mkIcon'))
let eTDict = sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS
const EditableContext = React.createContext()
const { confirm } = Modal
@@ -85,6 +87,8 @@
      return <InputNumber min={min} max={max} precision={0} onPressEnter={() => this.getValue(form)} />
    } else if (inputType === 'color') {
      return <ColorSketch />
    } else if (inputType === 'icon') {
      return <MkIcon allowClear/>
    } else if (inputType === 'switch') {
      return <CusSwitch />
    } else if (inputType === 'select') {
src/templates/zshare/pasteform/index.jsx
@@ -49,6 +49,14 @@
    })
  }
  submit = () => {
    if (this.props.inputSubmit) {
      setTimeout(() => {
        this.props.inputSubmit()
      }, 10)
    }
  }
  render() {
    const { getFieldDecorator } = this.props.form
    const formItemLayout = {
@@ -74,7 +82,7 @@
                    message: '请输入配置信息!'
                  }
                ]
              })(<TextArea autoSize={{ minRows: 6, maxRows: 6 }} onPressEnter={() => this.props.inputSubmit && this.props.inputSubmit()}/>)}
              })(<TextArea autoSize={{ minRows: 6, maxRows: 6 }} onPressEnter={this.submit}/>)}
            </Form.Item>
          </Col>
        </Row>
src/utils/option.js
@@ -716,7 +716,6 @@
    'close-circle',
    'check',
    'check-circle',
    'check-square',
    'clock-circle',
    'warning',
    'stop'
src/utils/utils-custom.js
@@ -274,7 +274,7 @@
          uuids.push(act.uuid)
        })
        if (item.type === 'card' || item.type === 'carousel' || (item.type === 'table' && item.subtype === 'tablecard')) {
        if (['card', 'carousel', 'timeline'].includes(item.type) || (item.type === 'table' && item.subtype === 'tablecard')) {
          item.subcards.forEach(_card => {
            _card.elements && _card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
@@ -365,7 +365,7 @@
          cell.uuid = this.getuuid()
          return cell
        })
      } else if (item.type === 'card' || item.type === 'carousel' || (item.type === 'table' && item.subtype === 'tablecard')) {
      } else if (['card', 'carousel', 'timeline'].includes(item.type) || (item.type === 'table' && item.subtype === 'tablecard')) {
        item.subcards.forEach(card => {
          card.uuid = this.getuuid()
          if (card.elements) {
@@ -561,7 +561,7 @@
        cell.uuid = this.getuuid()
        return cell
      })
    } else if (item.type === 'card' || item.type === 'carousel' || (item.type === 'table' && item.subtype === 'tablecard')) {
    } else if (['card', 'carousel', 'timeline'].includes(item.type) || (item.type === 'table' && item.subtype === 'tablecard')) {
      item.subcards.forEach(card => {
        card.uuid = this.getuuid()
        if (card.elements) {
src/views/menudesign/index.jsx
@@ -411,7 +411,7 @@
              _sort++
            })
          })
        } else if (item.type === 'carousel') {
        } else if (item.type === 'carousel' || item.type === 'timeline') {
          item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
src/views/mobdesign/index.jsx
@@ -742,7 +742,7 @@
              })
            })
          })
        } else if (item.type === 'carousel') {
        } else if (item.type === 'carousel' || item.type === 'timeline') {
          item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
src/views/pcdesign/index.jsx
@@ -823,7 +823,7 @@
              }
            })
          })
        } else if (item.type === 'carousel') {
        } else if (item.type === 'carousel' || item.type === 'timeline') {
          item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return