king
2021-07-12 ca788834ea15d6dd43bf0923757ca1d46d00ebc4
2021-07-12
12个文件已修改
8个文件已添加
1085 ■■■■■ 已修改文件
src/menu/components/card/balcony/index.jsx 300 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/balcony/index.scss 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/balcony/wrapsetting/index.jsx 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/balcony/wrapsetting/index.scss 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/balcony/wrapsetting/settingform/index.jsx 301 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/balcony/wrapsetting/settingform/index.scss 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/data-card/wrapsetting/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/data-card/wrapsetting/settingform/index.jsx 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/styleInput/index.jsx 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/styleInput/index.scss 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/datasource/verifycard/settingform/index.jsx 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/datasource/verifycard/settingform/index.scss 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/menushell/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/menushell/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modulesource/option.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/pastecontroller/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/popview/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils-custom.js 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils-datamanage.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/balcony/index.jsx
New file
@@ -0,0 +1,300 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Icon, Popover, Modal } 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 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 WrapComponent = asyncIconComponent(() => import('./wrapsetting'))
const CardCellComponent = asyncComponent(() => import('../cardcellcomponent'))
const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
const PasteComponent = asyncIconComponent(() => import('@/menu/components/share/pastecomponent'))
const UserComponent = asyncIconComponent(() => import('@/menu/components/share/usercomponent'))
const ClockComponent = asyncIconComponent(() => import('@/menu/components/share/clockcomponent'))
const NormalHeader = asyncComponent(() => import('@/menu/components/share/normalheader'))
const { confirm } = Modal
class BalconyEditComponent 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 ismob = sessionStorage.getItem('appType') === 'mob'
      let _card = {
        uuid: card.uuid,
        type: card.type,
        floor: card.floor,
        tabId: card.tabId || '',
        parentId: card.parentId || '',
        format: 'object',   // 组件属性 - 数据格式
        pageable: false,    // 组件属性 - 是否可分页
        switchable: false,  // 组件属性 - 数据是否可切换
        dataName: card.dataName || '',
        width: card.width || 24,
        name: card.name,
        subtype: card.subtype,
        setting: { interType: 'system' },
        wrap: { name: card.name, width: card.width || 24, linkType: 'static', position: 'relative', datatype: 'static' },
        style: { marginLeft: '0px', marginRight: '0px', marginTop: '8px', marginBottom: '8px' },
        columns: [],
        scripts: [],
        elements: [],
      }
      if (card.config) {
        let config = fromJS(card.config).toJS()
        _card.wrap = config.wrap
        _card.wrap.name = card.name
        _card.style = config.style
        _card.elements = _card.elements.map(elem => {
          elem.uuid = Utils.getuuid()
          return elem
        })
      }
      this.setState({
        card: _card
      })
      this.props.updateConfig(_card)
    } else {
      this.setState({
        card: fromJS(card).toJS()
      })
    }
  }
  componentDidMount () {
    MKEmitter.addListener('submitStyle', this.getStyle)
    MKEmitter.addListener('submitComponentStyle', this.updateComponentStyle)
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('submitStyle', this.getStyle)
    MKEmitter.removeListener('submitComponentStyle', this.updateComponentStyle)
  }
  updateComponentStyle = (parentId, keys, style) => {
    const { card } = this.state
    if (card.uuid !== parentId) return
    let subcards = card.subcards.map(item => {
      if (keys.includes(item.uuid)) {
        item.style = {...item.style, ...style}
      }
      return item
    })
    this.setState({card: {...card, subcards: []}}, () => {
      this.updateComponent({...card, subcards: subcards})
    })
  }
  /**
   * @description 卡片行外层信息更新(数据源,样式等)
   */
  updateComponent = (component) => {
    this.setState({
      card: component
    })
    component.width = component.wrap.width
    component.name = component.wrap.name
    this.props.updateConfig(component)
  }
  /**
   * @description 单个卡片信息更新
   */
  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)
  }
  /**
   * @description 单个卡片信息更新
   */
  deleteCard = (cell) => {
    let card = fromJS(this.state.card).toJS()
    let _this = this
    confirm({
      content: '确定删除卡片吗?',
      onOk() {
        card.subcards = card.subcards.filter(item => item.uuid !== cell.uuid)
        let uuids = []
        cell.elements && cell.elements.forEach(c => {
          if (c.eleType === 'button') {
            uuids.push(c.uuid)
          }
        })
        cell.backElements && cell.backElements.forEach(c => {
          if (c.eleType === 'button') {
            uuids.push(c.uuid)
          }
        })
        MKEmitter.emit('delButtons', uuids)
        _this.setState({card})
        _this.props.updateConfig(card)
      },
      onCancel() {}
    })
  }
  changeStyle = () => {
    const { card } = this.state
    MKEmitter.emit('changeStyle', [card.uuid], ['height', 'background', 'border', 'padding', 'margin'], 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)
  }
  addCard = () => {
    let card = fromJS(this.state.card).toJS()
    let newcard = {
      uuid: Utils.getuuid(),
      setting: { width: 6, type: 'simple'},
      style: {
        borderWidth: '1px', borderColor: '#e8e8e8',
        paddingTop: '15px', paddingBottom: '15px', paddingLeft: '15px', paddingRight: '15px',
        marginLeft: '8px', marginRight: '8px', marginTop: '8px', marginBottom: '8px'
      },
      backStyle: {},
      elements: [],
      backElements: []
    }
    if (card.subcards.length > 0) {
      newcard = fromJS(card.subcards.slice(-1)[0]).toJS()
      newcard.uuid = Utils.getuuid()
      newcard.elements = newcard.elements.map(elem => {
        elem.uuid = Utils.getuuid()
        return elem
      })
      newcard.backElements = newcard.backElements.map(elem => {
        elem.uuid = Utils.getuuid()
        return elem
      })
    }
    card.subcards.push(newcard)
    this.setState({card})
    this.props.updateConfig(card)
  }
  move = (item, direction) => {
    let card = fromJS(this.state.card).toJS()
    let dragIndex = card.subcards.findIndex(c => c.uuid === item.uuid)
    let hoverIndex = null
    if (direction === 'left') {
      hoverIndex = dragIndex - 1
    } else {
      hoverIndex = dragIndex + 1
    }
    if (hoverIndex === -1 || hoverIndex === card.subcards.length) return
    card.subcards.splice(hoverIndex, 0, ...card.subcards.splice(dragIndex, 1))
    this.setState({card})
    this.props.updateConfig(card)
  }
  clickComponent = (e) => {
    if (sessionStorage.getItem('style-control') === 'true' || sessionStorage.getItem('style-control') === 'component') {
      e.stopPropagation()
      MKEmitter.emit('clickComponent', this.state.card)
    }
  }
  render() {
    const { card } = this.state
    let _style = resetStyle(card.style)
    return (
      <div className="menu-balcony-edit-box" style={_style} onClick={this.clickComponent} id={card.uuid}>
        <NormalHeader defaultshow="hidden" 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.addElement} type="plus" />
            <Icon className="plus" title="添加按钮" onClick={this.addButton} type="plus-square" />
            <WrapComponent config={card} updateConfig={this.updateComponent} />
            <CopyComponent type="balcony" card={card}/>
            <PasteComponent config={card} options={['cardcell']} updateConfig={this.updateComponent} />
            <Icon className="style" title="调整样式" onClick={this.changeStyle} type="font-colors" />
            <ClockComponent config={card} updateConfig={this.updateComponent}/>
            <UserComponent config={card}/>
            <Icon className="close" title="删除组件" type="delete" onClick={() => this.props.deletecomponent(card.uuid)} />
            {card.wrap.datatype !== 'static' ? <SettingComponent config={card} updateConfig={this.updateComponent} /> : null}
            {card.wrap.datatype === 'static' ? <Icon style={{color: '#eeeeee', cursor: 'not-allowed'}} type="setting"/> : null}
          </div>
        } trigger="hover">
          <Icon type="tool" />
        </Popover>
        <CardCellComponent cards={card} cardCell={card} elements={card.elements} updateElement={this.updateCard}/>
      </div>
    )
  }
}
export default BalconyEditComponent
src/menu/components/card/balcony/index.scss
New file
@@ -0,0 +1,77 @@
.menu-balcony-edit-box {
  position: relative;
  box-sizing: border-box;
  background: #ffffff;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  min-height: 30px;
  .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-balcony-edit-box::after {
  display: block;
  content: ' ';
  clear: both;
}
.menu-balcony-edit-box:hover {
  z-index: 1;
  box-shadow: 0px 0px 4px #1890ff;
}
src/menu/components/card/balcony/wrapsetting/index.jsx
New file
@@ -0,0 +1,83 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Icon, Modal } from 'antd'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import SettingForm from './settingform'
import './index.scss'
class BalconyWrapSetting extends Component {
  static propTpyes = {
    config: PropTypes.any,
    updateConfig: PropTypes.func
  }
  state = {
    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    visible: false,
    wrap: null
  }
  UNSAFE_componentWillMount () {
    const { config } = this.props
    this.setState({wrap: fromJS(config.wrap).toJS()})
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  editDataSource = () => {
    this.setState({
      visible: true
    })
  }
  verifySubmit = () => {
    const { config } = this.props
    this.verifyRef.handleConfirm().then(res => {
      this.setState({
        wrap: res,
        visible: false
      })
      this.props.updateConfig({...config, wrap: res})
    })
  }
  render () {
    const { config } = this.props
    const { visible, dict, wrap } = this.state
    return (
      <div className="model-menu-setting-wrap">
        <Icon type="edit" title="编辑" onClick={() => this.editDataSource()} />
        <Modal
          wrapClassName="popview-modal"
          title="卡片设置"
          visible={visible}
          width={800}
          maskClosable={false}
          okText={dict['model.submit']}
          onOk={this.verifySubmit}
          onCancel={() => { this.setState({ visible: false }) }}
          destroyOnClose
        >
          <SettingForm
            dict={dict}
            wrap={wrap}
            config={config}
            inputSubmit={this.verifySubmit}
            wrappedComponentRef={(inst) => this.verifyRef = inst}
          />
        </Modal>
      </div>
    )
  }
}
export default BalconyWrapSetting
src/menu/components/card/balcony/wrapsetting/index.scss
New file
@@ -0,0 +1,7 @@
.model-menu-setting-wrap {
  display: inline-block;
  >.anticon-edit {
    color: #1890ff;
  }
}
src/menu/components/card/balcony/wrapsetting/settingform/index.jsx
New file
@@ -0,0 +1,301 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Row, Col, Input, Radio, Tooltip, Icon, InputNumber, Select, Cascader } from 'antd'
import MenuUtils from '@/utils/utils-custom.js'
import StyleInput from '@/menu/components/share/styleInput'
import './index.scss'
class SettingForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,
    config: PropTypes.object,
    wrap: PropTypes.object,
    inputSubmit: PropTypes.func
  }
  state = {
    roleList: [],
    modules: [],
    supmodules: [],
    appType: sessionStorage.getItem('appType'),
    linkType: this.props.wrap.linkType,
    position: this.props.wrap.position,
  }
  UNSAFE_componentWillMount () {
    let roleList = sessionStorage.getItem('sysRoles')
    if (roleList) {
      try {
        roleList = JSON.parse(roleList)
      } catch {
        roleList = []
      }
    } else {
      roleList = []
    }
    let menu = fromJS(window.GLOB.customMenu).toJS()
    let modules = MenuUtils.getLinkModules(menu.components)
    if (!modules) {
      modules = []
    }
    let _menu = fromJS(window.GLOB.customMenu).toJS()
    let supmodules = MenuUtils.getSupModules(_menu.components, '')
    if (!supmodules) {
      supmodules = []
    }
    this.setState({roleList, modules, supmodules})
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          resolve(values)
        } else {
          reject(err)
        }
      })
    })
  }
  handleSubmit = (e) => {
    e.preventDefault()
    if (this.props.inputSubmit) {
      this.props.inputSubmit()
    }
  }
  render() {
    const { wrap } = this.props
    const { getFieldDecorator } = this.props.form
    const { roleList, modules, supmodules, linkType, position } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="model-menu-setting-form">
        <Form {...formItemLayout}>
          <Row gutter={24}>
            <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="用于组件间的区分。">
                  <Icon type="question-circle" />
                  组件名称
                </Tooltip>
              }>
                {getFieldDecorator('name', {
                  initialValue: wrap.name,
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.input'] + '组件名称!'
                    }
                  ]
                })(<Input placeholder={''} autoComplete="off" onPressEnter={this.handleSubmit} />)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="栅格布局,每行等分为24列。">
                  <Icon type="question-circle" />
                  宽度
                </Tooltip>
              }>
                {getFieldDecorator('width', {
                  initialValue: wrap.width || 24,
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.input'] + '宽度!'
                    }
                  ]
                })(<InputNumber min={1} max={24} precision={0} onPressEnter={this.handleSubmit} />)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="选择静态值,无需配置数据源。">
                  <Icon type="question-circle" />
                  数据来源
                </Tooltip>
              }>
                {getFieldDecorator('datatype', {
                  initialValue: wrap.datatype || 'dynamic'
                })(
                  <Radio.Group>
                    <Radio value="dynamic">动态</Radio>
                    <Radio value="static">静态</Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="组件与其他组件之间的控制类型,独立表示与其他没有关联。">
                  <Icon type="question-circle" />
                  受控类型
                </Tooltip>
              }>
                {getFieldDecorator('linkType', {
                  initialValue: wrap.linkType || 'static'
                })(
                  <Radio.Group onChange={(e) => this.setState({linkType: e.target.value})}>
                    <Radio value="static">独立</Radio>
                    <Radio value="sync">同步</Radio>
                    <Radio value="sup">上级</Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col>
            {linkType === 'sup' ? <Col span={12}>
              <Form.Item label="上级组件">
                {getFieldDecorator('supModule', {
                  initialValue: wrap.supModule,
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.select'] + '上级组件!'
                    }
                  ]
                })(
                  <Cascader options={supmodules} expandTrigger="hover" placeholder="" />
                )}
              </Form.Item>
            </Col> : null}
            {linkType === 'sync' ? <Col span={12}>
              <Form.Item label="同步组件">
                {getFieldDecorator('syncModule', {
                  initialValue: wrap.syncModule,
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.select'] + '同步组件!'
                    }
                  ]
                })(
                  <Cascader options={modules} expandTrigger="hover" placeholder="" />
                )}
              </Form.Item>
            </Col> : null}
            {linkType === 'sync' ? <Col span={12}>
              <Form.Item label="全选">
                {getFieldDecorator('checkAll', {
                  initialValue: wrap.checkAll || 'hidden'
                })(
                  <Radio.Group>
                    <Radio key="hidden" value="hidden"> 隐藏 </Radio>
                    <Radio key="show" value="show"> 显示 </Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col> : null}
            <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="使用固定定位时,请在测试环境中查看定位效果。">
                  <Icon type="question-circle" />
                  位置
                </Tooltip>
              }>
                {getFieldDecorator('position', {
                  initialValue: wrap.position || 'relative'
                })(
                  <Radio.Group onChange={(e) => this.setState({position: e.target.value})}>
                    <Radio value="relative">相对定位</Radio>
                    <Radio value="fixed">固定定位</Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col>
            {position === 'fixed' ? <Col span={12}>
              <Form.Item label="距上">
                {getFieldDecorator('top', {
                  initialValue: wrap.top || ''
                })(<StyleInput options={['px', 'vh', 'vw', '%']} />)}
              </Form.Item>
            </Col> : null}
            {position === 'fixed' ? <Col span={12}>
              <Form.Item label="距右">
                {getFieldDecorator('right', {
                  initialValue: wrap.right || ''
                })(<StyleInput options={['px', 'vh', 'vw', '%']} />)}
              </Form.Item>
            </Col> : null}
            {position === 'fixed' ? <Col span={12}>
              <Form.Item label="距下">
                {getFieldDecorator('bottom', {
                  initialValue: wrap.bottom || ''
                })(<StyleInput options={['px', 'vh', 'vw', '%']} />)}
              </Form.Item>
            </Col> : null}
            {position === 'fixed' ? <Col span={12}>
              <Form.Item label="距左">
                {getFieldDecorator('left', {
                  initialValue: wrap.left || ''
                })(<StyleInput options={['px', 'vh', 'vw', '%']} />)}
              </Form.Item>
            </Col> : null}
            {position === 'fixed' ? <Col span={12}>
              <Form.Item label="实际宽度">
                {getFieldDecorator('realwidth', {
                  initialValue: wrap.realwidth || ''
                })(<StyleInput options={['px', 'vh', 'vw', '%']} />)}
              </Form.Item>
            </Col> : null}
            {position === 'fixed' ? <Col span={12}>
              <Form.Item label="变换">
                {getFieldDecorator('transform', {
                  initialValue: wrap.transform || ''
                })(
                  <Select>
                    <Select.Option key='1' value={''}>无</Select.Option>
                    <Select.Option key='2' value={'translateY(-50%)'}>上移50%</Select.Option>
                    <Select.Option key='3' value={'translateY(50%)'}>下移50%</Select.Option>
                    <Select.Option key='5' value={'translateX(-50%)'}>左移50%</Select.Option>
                    <Select.Option key='4' value={'translateX(50%)'}>右移50%</Select.Option>
                  </Select>
                )}
              </Form.Item>
            </Col> : null}
            <Col span={12}>
              <Form.Item label="黑名单">
                {getFieldDecorator('blacklist', {
                  initialValue: wrap.blacklist || []
                })(
                  <Select
                    showSearch
                    mode="multiple"
                    filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  >
                    {roleList.map(option =>
                      <Select.Option key={option.uuid} value={option.value}>{option.text}</Select.Option>
                    )}
                  </Select>
                )}
              </Form.Item>
            </Col>
          </Row>
        </Form>
      </div>
    )
  }
}
export default Form.create()(SettingForm)
src/menu/components/card/balcony/wrapsetting/settingform/index.scss
New file
@@ -0,0 +1,36 @@
.model-menu-setting-form {
  position: relative;
  .anticon-question-circle {
    color: #c49f47;
    margin-right: 3px;
  }
  .ant-input-number {
    width: 100%;
  }
  .css {
    padding-top: 10px;
    .css-class {
      position: absolute;
      right: 13px;
      top: -15px;
      z-index: 1;
      button {
        height: 25px;
      }
    }
    .ant-form-item {
      margin-bottom: 0;
    }
    .ant-form-item-label {
      width: 16%;
    }
    .ant-form-item-control-wrapper {
      width: 84%;
      .code-mirror-wrap .code-mirror-area .CodeMirror {
        height: 100px;
        min-height: 100px;
      }
    }
  }
}
src/menu/components/card/data-card/wrapsetting/index.jsx
@@ -8,7 +8,7 @@
import SettingForm from './settingform'
import './index.scss'
class DataSource extends Component {
class CardWrapSetting extends Component {
  static propTpyes = {
    config: PropTypes.any,
    updateConfig: PropTypes.func
@@ -80,4 +80,4 @@
  }
}
export default DataSource
export default CardWrapSetting
src/menu/components/card/data-card/wrapsetting/settingform/index.jsx
@@ -6,15 +6,16 @@
class SettingForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,      // 字典项
    config: PropTypes.object,    // 卡片行信息
    wrap: PropTypes.object,      // 数据源配置
    inputSubmit: PropTypes.func  // 回车事件
    dict: PropTypes.object,
    config: PropTypes.object,
    wrap: PropTypes.object,
    inputSubmit: PropTypes.func
  }
  state = {
    roleList: [],
    appType: sessionStorage.getItem('appType'),
    cardType: this.props.wrap.cardType,
    MenuType: ''
  }
@@ -63,7 +64,7 @@
  render() {
    const { wrap, config } = this.props
    const { getFieldDecorator } = this.props.form
    const { roleList, MenuType, appType } = this.state
    const { roleList, MenuType, appType, cardType } = this.state
    const formItemLayout = {
      labelCol: {
@@ -163,10 +164,22 @@
                {getFieldDecorator('cardType', {
                  initialValue: wrap.cardType || ''
                })(
                  <Radio.Group style={{whiteSpace: 'nowrap'}}>
                  <Radio.Group style={{whiteSpace: 'nowrap'}} onChange={(e) => this.setState({cardType: e.target.value})}>
                    <Radio key="" value=""> 不可选 </Radio>
                    <Radio key="radio" value={'radio'}> 单选 </Radio>
                    {config.subtype !== 'propcard' ? <Radio key="checkbox" value={'checkbox'}> 多选 </Radio> : null}
                  </Radio.Group>
                )}
              </Form.Item>
            </Col> : null}
            {config.subtype === 'datacard' && appType === 'mob' && cardType === 'checkbox' ? <Col span={12}>
              <Form.Item label="全选">
                {getFieldDecorator('checkAll', {
                  initialValue: wrap.checkAll || 'hidden'
                })(
                  <Radio.Group>
                    <Radio key="hidden" value="hidden"> 隐藏 </Radio>
                    <Radio key="show" value="show"> 显示 </Radio>
                  </Radio.Group>
                )}
              </Form.Item>
@@ -189,7 +202,7 @@
                )}
              </Form.Item>
            </Col> : null}
            {config.subtype !== 'tablecard' ? <Col span={12}>
            {config.subtype !== 'tablecard' && appType !== 'mob' ? <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="鼠标悬浮于卡片上方时,卡片放大1.05倍。">
                  <Icon type="question-circle" />
src/menu/components/share/styleInput/index.jsx
New file
@@ -0,0 +1,117 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Select, Input } from 'antd'
import './index.scss'
const { Option } = Select
class StyleInput extends Component {
  static propTpyes = {
    defaultValue: PropTypes.any,
    options: PropTypes.any,
    value: PropTypes.any,
    onChange: PropTypes.func,
  }
  state = {
    value: '',
    unit: '',
    options: null
  }
  UNSAFE_componentWillMount () {
    const { value, options } = this.props
    let val = ''
    let unit = ''
    if (value !== undefined) {
      val = value
    }
    unit = options[0]
    if (val) {
      if (val.indexOf('px') > -1) {
        unit = 'px'
      } else if (val.indexOf('%') > -1) {
        unit = '%'
      } else if (val.indexOf('vw') > -1) {
        unit = 'vw'
      } else if (val.indexOf('vh') > -1) {
        unit = 'vh'
      }
    }
    let _val = parseFloat(val)
    if (isNaN(_val)) {
      _val = ''
    }
    this.setState({value: _val, options: options, unit})
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  changeValue = (e) => {
    const { unit } = this.state
    let val = e.target.value
    if (/\d+\.$/.test(val)) {
      this.setState({
        value: val
      })
      return
    }
    let _val = parseFloat(val)
    if (isNaN(_val)) {
      _val = ''
    }
    this.setState({
      value: _val,
    })
    this.props.onChange(_val ? `${_val}${unit}` : '')
  }
  changeUnit = (val) => {
    const { value } = this.state
    this.setState({unit: val})
    this.props.onChange(value ? `${value}${val}` : '')
  }
  render () {
    const { value, options, unit } = this.state
    return (
      <div className="style-input-wrap">
        <Input value={value} addonAfter={
          options.length > 1 ?
          <Select value={unit} onChange={this.changeUnit}>
            {options.map(item => <Option key={item} value={item}>{item}</Option>)}
          </Select> :
          <div className="single-unit">{unit}</div>
        } onChange={this.changeValue}/>
      </div>
    )
  }
}
export default StyleInput
src/menu/components/share/styleInput/index.scss
New file
@@ -0,0 +1,11 @@
.style-input-wrap {
  line-height: 32px;
  .ant-select {
    width: 60px!important;
  }
  .single-unit {
    width: 38px;
    text-align: left;
    color: rgba(255, 255, 255, 0.65);
  }
}
src/menu/datasource/verifycard/settingform/index.jsx
@@ -8,6 +8,8 @@
import CodeMirror from '@/templates/zshare/codemirror'
import './index.scss'
const { TextArea } = Input
class SettingForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,       // 字典项
@@ -249,7 +251,7 @@
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col> : null}
            {interType === 'outer' ? <Col span={8}>
            {interType === 'outer' ? <Col className="outer-interface" span={24}>
              <Form.Item label="接口地址">
                {getFieldDecorator('interface', {
                  initialValue: setting.interface || '',
@@ -259,7 +261,18 @@
                      message: this.props.dict['form.required.input'] + '接口地址!'
                    }
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
                })(<TextArea rows={2}/>)}
              </Form.Item>
            </Col> : null}
            {interType === 'outer' ? <Col className="outer-interface" span={24}>
              <Form.Item label={<Tooltip placement="topLeft" title="正式系统接口地址,为空时使用接口地址">
                  <Icon type="question-circle" />
                  正式地址
                </Tooltip>
              }>
                {getFieldDecorator('proInterface', {
                  initialValue: setting.proInterface || ''
                })(<TextArea rows={2}/>)}
              </Form.Item>
            </Col> : null}
            {interType === 'outer' ? <Col span={8}>
@@ -340,7 +353,7 @@
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            {config.type !== 'navbar' ? <Col span={8}>
            {config.type !== 'navbar' && config.type !== 'balcony' ? <Col span={8}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={'该组件如果受其他组件控制,请选项相应的组件,没有时选“无”。'}>
                  <Icon type="question-circle" />
@@ -408,7 +421,7 @@
                )}
              </Form.Item>
            </Col> : null}
            {config.type !== 'navbar' ? <Col span={8}>
            {config.type !== 'navbar' && config.type !== 'balcony' ? <Col span={8}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={'优先使用同级的搜索条件组件,同级搜索不存在时,依次向上选取,与当前组件的搜索条件一同用作数据过滤(当前组件的搜索条件优先)。'}>
                  <Icon type="question-circle" />
@@ -425,7 +438,7 @@
                )}
              </Form.Item>
            </Col> : null}
            {config.type !== 'navbar' && useMSearch === 'true' ? <Col span={8}>
            {config.type !== 'navbar' && config.type !== 'balcony' && useMSearch === 'true' ? <Col span={8}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={'外层搜索条件改变时,是否刷新当前组件数据。'}>
                  <Icon type="question-circle" />
@@ -442,7 +455,7 @@
                )}
              </Form.Item>
            </Col> : null}
            {config.type !== 'navbar' ? <Col span={8}>
            {config.type !== 'navbar' && config.type !== 'balcony' ? <Col span={8}>
              <Form.Item label="初始化数据">
                {getFieldDecorator('onload', {
                  initialValue: setting.onload || 'true'
src/menu/datasource/verifycard/settingform/index.scss
@@ -24,4 +24,12 @@
  .ant-radio-group {
    white-space: nowrap;
  }
  .outer-interface {
    .ant-form-item-label {
      width: 10.5%;
    }
    .ant-form-item-control-wrapper {
      width: 89.5%;
    }
  }
}
src/menu/menushell/card.jsx
@@ -12,6 +12,7 @@
const AntvTabs = asyncComponent(() => import('@/menu/components/tabs/antv-tabs'))
const DataCard = asyncComponent(() => import('@/menu/components/card/data-card'))
const PropCard = asyncComponent(() => import('@/menu/components/card/prop-card'))
const Balcony = asyncComponent(() => import('@/menu/components/card/balcony'))
const NormalTree = asyncComponent(() => import('@/menu/components/tree/antd-tree'))
const CarouselDataCard = asyncComponent(() => import('@/menu/components/carousel/data-card'))
const CarouselPropCard = asyncComponent(() => import('@/menu/components/carousel/prop-card'))
@@ -90,6 +91,8 @@
      return (<BraftEditor card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'code') {
      return (<CodeSandbox card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'balcony') {
      return (<Balcony card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
  }
  return (
src/menu/menushell/index.jsx
@@ -100,6 +100,7 @@
        dashboard: '仪表盘',
        scatter: '散点图',
        tree: '树形列表',
        balcony: '浮动卡',
        card: '卡片'
      }
      let i = 1
src/menu/modulesource/option.jsx
@@ -29,6 +29,7 @@
  { type: 'menu', url: Mainsearch, component: 'search', subtype: 'mainsearch', title: '搜索条件', width: 24, forbid: ['billPrint'] },
  { type: 'menu', url: card1, component: 'card', subtype: 'datacard', title: '数据卡', width: 24 },
  { type: 'menu', url: card2, component: 'card', subtype: 'propcard', title: '属性卡', width: 24 },
  { type: 'menu', url: card2, component: 'balcony', subtype: 'balcony', title: '可浮动卡', width: 24 },
  { type: 'menu', url: form, component: 'form', subtype: 'stepform', title: '表单', width: 24 },
  { type: 'menu', url: Carousel, component: 'carousel', subtype: 'datacard', title: '轮播-动态数据', width: 24, forbid: ['billPrint'] },
  { type: 'menu', url: Carousel1, component: 'carousel', subtype: 'propcard', title: '轮播-静态数据', width: 24, forbid: ['billPrint'] },
src/menu/pastecontroller/index.jsx
@@ -66,7 +66,7 @@
        return cell
      })
    } else if (item.type === 'card' || (item.type === 'table' && item.subtype === 'tablecard')) {
      item.subcards.forEach(card => {
      item.subcards && item.subcards.forEach(card => {
        card.uuid = Utils.getuuid()
        if (card.elements) {
          if (sessionStorage.getItem('editMenuType') === 'popview') {
src/menu/popview/index.jsx
@@ -246,7 +246,7 @@
            buttons.push(`select '${btn.uuid}' as menuid, '${item.name + '-' + btn.label}' as menuname, '${_sort * 10}' as Sort`)
            _sort++
          })
          item.subcards.forEach(card => {
          item.subcards && item.subcards.forEach(card => {
            card.elements && card.elements.forEach(cell => {
              if (cell.eleType !== 'button') return
              this.checkBtn(cell)
src/tabviews/custom/index.jsx
@@ -570,7 +570,7 @@
      }
      if (item.type === 'card') {
        item.subcards.forEach(card => {
        item.subcards && item.subcards.forEach(card => {
          let _hasheight = card.style.height && card.style.height !== 'auto'
          if (card.style.shadow) { // 卡片阴影
@@ -625,7 +625,7 @@
          })
        })
      } else if ((item.type === 'table' && item.subtype === 'tablecard') || item.type === 'carousel') {
        item.subcards.forEach(card => {
        item.subcards && item.subcards.forEach(card => {
          let _hasheight = card.style.height && card.style.height !== 'auto'
          card.elements = card.elements.filter(cell => {
            if (cell.eleType === 'button') {
src/utils/utils-custom.js
@@ -166,6 +166,80 @@
  }
  /**
   * @description 获取可关联模块
   */
  static getLinkModules (components) {
    let modules = components.map(item => {
      if ((item.type === 'card' && item.subtype === 'datacard') || (item.type === 'table' && item.subtype === 'normaltable')) {
        return {
          value: item.uuid,
          label: item.name
        }
      } else if (item.type === 'tabs') {
        let _item = {
          value: item.uuid,
          label: item.name,
          children: item.subtabs.map(f_tab => {
            let subItem = {
              value: f_tab.uuid,
              label: f_tab.label,
              children: this.getLinkModules(f_tab.components)
            }
            if (!subItem.children || subItem.children.length === 0) {
              return {children: null}
            }
            return subItem
          })
        }
        _item.children = _item.children.filter(t => t.children !== null)
        if (_item.children.length === 0) {
          return {children: null}
        }
        return _item
      } else if (item.type === 'group') {
        let _item = {
          value: item.uuid,
          label: item.name,
          children: item.components.map(f_tab => {
            if ((f_tab.type === 'card' && f_tab.subtype === 'datacard') || (f_tab.type === 'table' && f_tab.subtype === 'normaltable')) {
              return {
                value: f_tab.uuid,
                label: f_tab.name
              }
            }
            return {
              children: null
            }
          })
        }
        _item.children = _item.children.filter(t => t.children !== null)
        if (_item.children.length === 0) {
          return {children: null}
        }
        return _item
      } else {
        return {
          children: null
        }
      }
    })
    modules = modules.filter(mod => mod.children !== null)
    if (modules.length === 0) {
      return null
    }
    return modules
  }
  /**
   * @description 获取删除按钮Id
   * @return {String}  name
   */
src/utils/utils-datamanage.js
@@ -65,7 +65,7 @@
        if (setting.sysInterface === 'true' && window.GLOB.mainSystemApi) {
          param.rduri = window.GLOB.mainSystemApi
        } else if (setting.sysInterface !== 'true') {
          param.rduri = setting.interface
          param.rduri = window.GLOB.systemType === 'production' ? (setting.proInterface || setting.interface) : setting.interface
        }
      }