king
2021-06-02 e543372cc70a19ff2630c79d8421c2c593e54e5f
2021-06-02
11个文件已修改
13个文件已添加
1266 ■■■■■ 已修改文件
src/assets/mobimg/menubar.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/mkIcon/index.jsx 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/mkIcon/index.scss 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/normalheader/index.scss 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/pastecomponent/index.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/sourcecomponent/index.jsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/pastecontroller/index.jsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/normal-menubar/index.jsx 263 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/normal-menubar/index.scss 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/normal-menubar/menucomponent/index.jsx 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/normal-menubar/menucomponent/index.scss 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/normal-menubar/menucomponent/settingform/index.jsx 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/normal-menubar/menucomponent/settingform/index.scss 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/normal-menubar/wrapsetting/index.jsx 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/normal-menubar/wrapsetting/index.scss 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/normal-menubar/wrapsetting/settingform/index.jsx 155 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/components/menubar/normal-menubar/wrapsetting/settingform/index.scss 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/mobshell/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/mobshell/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/modulesource/option.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/share/normalheader/index.scss 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils-custom.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobdesign/index.jsx 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/pcdesign/index.jsx 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/mobimg/menubar.png
src/components/mkIcon/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, Row, Col, Button } from 'antd'
import { minkeIconSystem } from '@/utils/option.js'
import './index.scss'
class MkIcon extends Component {
  static propTpyes = {
    onChange: PropTypes.func
  }
  state = {
    selectIcon: '',
    icons: [...minkeIconSystem.normal, ...minkeIconSystem.trademark, ...minkeIconSystem.data, ...minkeIconSystem.edit, ...minkeIconSystem.hint, ...minkeIconSystem.direction],
    visible: false
  }
  UNSAFE_componentWillMount () {
    let val = ''
    if (this.props['data-__meta']) {
      val = this.props['data-__meta'].initialValue || ''
    }
    this.setState({selectIcon: val})
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  checkIcon = (val) => {
    this.setState({selectIcon: val, visible: false})
    this.props.onChange(val)
  }
  render() {
    const { selectIcon, visible, icons } = this.state
    return (
      <div className="mk-icon-box">
        {selectIcon ? <Icon type={selectIcon}/> : null}
        <Icon onClick={() => this.setState({visible: true})} type="appstore"/>
        <Modal
          wrapClassName="popview-modal mk-icon-wrap"
          title={'图标选择'}
          visible={visible}
          width={800}
          maskClosable={false}
          onCancel={() => { this.setState({ visible: false }) }}
          footer={[
            <Button key="close" onClick={() => { this.setState({ visible: false }) }}>关闭</Button>
          ]}
          destroyOnClose
        >
          <Row>
            {icons.map(icon => <Col className={icon === selectIcon ? 'active' : ''} key={icon} span={4}>
              <Icon onClick={() => this.checkIcon(icon)} type={icon} />
            </Col>)}
          </Row>
        </Modal>
      </div>
    )
  }
}
export default MkIcon
src/components/mkIcon/index.scss
New file
@@ -0,0 +1,33 @@
.mk-icon-box {
  display: block;
  height: 32px;
  border: 1px solid #d9d9d9;
  border-radius: 4px;
  line-height: 32px;
  padding: 0px 0px 0px 10px;
  .anticon:last-child {
    float: right;
    line-height: 32px;
    padding: 0 10px;
    border-left: 1px solid #d9d9d9;
  }
}
.mk-icon-box:hover {
  border-color: #1890ff;
}
.mk-icon-wrap {
  .ant-col {
    text-align: center;
    line-height: 55px;
    .anticon {
      font-size: 30px;
      cursor: pointer;
    }
  }
  .active.ant-col {
    .anticon {
      color: #1890ff;
    }
  }
}
src/menu/components/share/normalheader/index.scss
@@ -3,13 +3,14 @@
  height: 45px;
  border-bottom: 1px solid #e8e8e8;
  overflow: hidden;
  line-height: 45px;
  .title {
    text-decoration: inherit;
    font-weight: inherit;
    font-style: inherit;
    float: left;
    line-height: 45px;
    line-height: inherit;
    margin-left: 10px;
    position: relative;
    z-index: 1;
src/menu/components/share/pastecomponent/index.jsx
@@ -186,6 +186,8 @@
        config.search.push(res)
      } else if (type === 'cardcell') {
        config.subcards.push(res)
      } else if (type === 'menucell') {
        config.subMenus.push(res)
      } else if (type === 'cols') {
        config.cols = config.cols.filter(col => !col.origin)
src/menu/components/share/sourcecomponent/index.jsx
@@ -14,12 +14,23 @@
  }
  state = {
    url: this.props.value,
    url: '',
    visible: ''
  }
  UNSAFE_componentWillMount () {
    const { value } = this.props
    let val = ''
    if (value) {
      val = value
    } else if (this.props['data-__meta']) {
      val = this.props['data-__meta'].initialValue || ''
    }
    this.setState({
      url: val,
    })
  }
  shouldComponentUpdate (nextProps, nextState) {
src/menu/pastecontroller/index.jsx
@@ -60,6 +60,11 @@
        cell = this.resetconfig(cell, item, true, copyBtns)
        return cell
      })
    } else if (item.type === 'menubar') {
      item.subMenus = item.subMenus.map(cell => {
        cell.uuid = Utils.getuuid()
        return cell
      })
    } else if (item.type === 'card' || (item.type === 'table' && item.subtype === 'tablecard')) {
      item.subcards.forEach(card => {
        card.uuid = Utils.getuuid()
@@ -196,9 +201,13 @@
    const { Tab } = this.props
    let isgroup = Tab && Tab.type === 'group' ? true : false
    let options = ['tabs', 'datacard', 'propcard', 'mainsearch', 'group', 'normaltable', 'tablecard', 'line', 'bar', 'pie', 'dashboard', 'scatter']
    if (sessionStorage.getItem('appType') === 'mob') {
      options.push('menubar')
    }
    this.pasteFormRef.handleConfirm().then(res => {
      if (!isgroup && !['tabs', 'datacard', 'propcard', 'mainsearch', 'group', 'normaltable', 'tablecard', 'line', 'bar', 'pie', 'dashboard', 'scatter'].includes(res.copyType)) {
      if (!isgroup && !options.includes(res.copyType)) {
        notification.warning({
          top: 92,
          message: '配置信息格式错误!',
src/mob/components/menubar/normal-menubar/index.jsx
New file
@@ -0,0 +1,263 @@
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 WrapComponent = asyncIconComponent(() => import('./wrapsetting'))
const MenuComponent = asyncComponent(() => import('./menucomponent'))
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 NormalHeader = asyncComponent(() => import('@/menu/components/share/normalheader'))
const { confirm } = Modal
class NoramlMenuBar 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: '',
        parentId: '',
        dataName: card.dataName || '',
        width: card.width || 24,
        name: card.name,
        subtype: card.subtype,
        wrap: { name: card.name, width: card.width || 24, title: '' },
        style: { marginLeft: '0px', marginRight: '0px', marginTop: '8px', marginBottom: '8px' },
        headerStyle: { fontSize: '16px', borderBottomWidth: '1px', borderBottomColor: '#e8e8e8' },
        subMenus: [{
          uuid: Utils.getuuid(),
          setting: { type: 'menu', width: 6, sign: 'icon', icon: 'user', name: '客户', url: '', color: '#ffffff', iconFont: 20, padding: 12, background: '#1890ff', imgWidth: '' },
          style: {
            paddingTop: '15px', paddingBottom: '15px'
          }
        }]
      }
      if (card.config) {
        let config = fromJS(card.config).toJS()
        _card.wrap = config.wrap
        _card.wrap.name = card.name
        _card.style = config.style
        _card.headerStyle = config.headerStyle
        _card.subMenus = config.subMenus.map(item => {
          item.uuid = Utils.getuuid()
          return item
        })
      }
      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)
  }
  /**
   * @description 单个卡片信息更新
   */
  updateCard = (cell) => {
    let card = fromJS(this.state.card).toJS()
    card.subMenus = card.subMenus.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.subMenus = card.subMenus.filter(item => item.uuid !== cell.uuid)
        _this.setState({card})
        _this.props.updateConfig(card)
      },
      onCancel() {}
    })
  }
  changeStyle = () => {
    const { card } = this.state
    MKEmitter.emit('changeStyle', [card.uuid], ['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)
  }
  addMenu = () => {
    let card = fromJS(this.state.card).toJS()
    let newcard = {
      uuid: Utils.getuuid(),
      setting: { type: 'menu', width: 6, sign: 'icon', icon: 'user', name: '客户', url: '', color: '#ffffff', iconFont: 20, padding: 12, background: '#1890ff', imgWidth: '' },
      style: {
        paddingTop: '15px', paddingBottom: '15px'
      }
    }
    if (card.subMenus.length > 0) {
      newcard = fromJS(card.subMenus.slice(-1)[0]).toJS()
      newcard.uuid = Utils.getuuid()
    }
    card.subMenus.push(newcard)
    this.setState({card})
    this.props.updateConfig(card)
  }
  move = (item, direction) => {
    let card = fromJS(this.state.card).toJS()
    let dragIndex = card.subMenus.findIndex(c => c.uuid === item.uuid)
    let hoverIndex = null
    if (direction === 'left') {
      hoverIndex = dragIndex - 1
    } else {
      hoverIndex = dragIndex + 1
    }
    if (hoverIndex === -1 || hoverIndex === card.subMenus.length) return
    card.subMenus.splice(hoverIndex, 0, ...card.subMenus.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 offset = 0
    if (card.wrap.cardFloat && card.wrap.cardFloat !== 'left') {
      let _width = 0
      card.subMenus.forEach(card => {
        _width += card.setting.width
      })
      offset = _width < 24 ? 24 - _width : 0
      if (card.wrap.cardFloat === 'center') {
        offset = Math.floor(offset / 2)
      }
    }
    let _style = resetStyle(card.style)
    return (
      <div className="menu-menubar-edit-box" style={_style} onClick={this.clickComponent} id={card.uuid}>
        <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.addMenu} type="plus" />
            <WrapComponent config={card} updateConfig={this.updateComponent} />
            <CopyComponent type="normalmenu" card={card}/>
            <PasteComponent config={card} options={['menucell']} updateConfig={this.updateComponent} />
            <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)} />
          </div>
        } trigger="hover">
          <Icon type="tool" />
        </Popover>
        {card.subMenus.map((menu, index) => (<MenuComponent key={menu.uuid} offset={!index ? offset : 0} cards={card} card={menu} move={this.move} updateElement={this.updateCard} deleteElement={this.deleteCard}/>))}
      </div>
    )
  }
}
export default NoramlMenuBar
src/mob/components/menubar/normal-menubar/index.scss
New file
@@ -0,0 +1,62 @@
.menu-menubar-edit-box {
  position: relative;
  box-sizing: border-box;
  background: #ffffff;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  min-height: 20px;
  .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);
  }
  .menu-item {
    overflow: hidden;
    position: relative;
    min-height: 20px;
    .menu-name {
      text-align: center;
      font-style: inherit;
      font-weight: inherit;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .menu-sign {
      text-align: center;
      .anticon {
        border-radius: 50%;
      }
      img {
        border-radius: 50%;
      }
    }
  }
}
.menu-menubar-edit-box::after {
  display: block;
  content: ' ';
  clear: both;
}
.menu-menubar-edit-box:hover {
  z-index: 1;
  box-shadow: 0px 0px 4px #1890ff;
}
src/mob/components/menubar/normal-menubar/menucomponent/index.jsx
New file
@@ -0,0 +1,187 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Modal, Popover, Icon, Col } from 'antd'
import asyncIconComponent from '@/utils/asyncIconComponent'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import SettingForm from './settingform'
import { resetStyle } from '@/utils/utils-custom.js'
import MKEmitter from '@/utils/events.js'
import './index.scss'
const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
class MenuBoxComponent extends Component {
  static propTpyes = {
    offset: PropTypes.any,           // 偏移量
    cards: PropTypes.object,         // 卡片行配置信息
    card: PropTypes.object,          // 卡片配置信息
    move: PropTypes.func,            // 卡片移动
    deleteElement: PropTypes.func,   // 卡片删除
    updateElement: PropTypes.func    // 菜单配置更新
  }
  state = {
    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    card: null,            // 卡片信息,包括正反面
    formlist: null,        // 设置表单信息
    visible: false,        // 模态框控制
  }
  /**
   * @description 搜索条件初始化
   */
  UNSAFE_componentWillMount () {
    const { card } = this.props
    this.setState({
      card: fromJS(card).toJS()
    })
  }
  componentDidMount () {
    MKEmitter.addListener('submitStyle', this.getStyle)
  }
  shouldComponentUpdate (nextProps, nextState) {
    const { cards } = this.props
    return !is(fromJS(cards.wrap), fromJS(nextProps.cards.wrap)) || !is(fromJS(this.state), fromJS(nextState))
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('submitStyle', this.getStyle)
  }
  getStyle = (comIds, style) => {
    const { cards } = this.props
    const { card } = this.state
    if (comIds.length !== 2 || comIds[0] !== cards.uuid || comIds[1] !== card.uuid) return
    let _card = fromJS(card).toJS()
    _card.style = style
    this.setState({
      card: _card
    })
    this.props.updateElement(_card)
  }
  changeStyle = () => {
    const { cards } = this.props
    const { card } = this.state
    let _style = card.style ? fromJS(card.style).toJS() : {}
    let options = ['font', 'border', 'padding']
    MKEmitter.emit('changeStyle', [cards.uuid, card.uuid], options, _style)
  }
  settingSubmit = () => {
    const { card } = this.state
    this.settingRef.handleConfirm().then(res => {
      this.setState({
        visible: false,
        card: {...card, setting: res}
      })
      this.props.updateElement({...card, setting: res})
    })
  }
  changeMenu = () => {
    const { card } = this.state
    if (card.setting.type === 'link') {
      window.open(card.setting.linkurl)
    } else {
      MKEmitter.emit('changeEditMenu', {
        fixed: card.setting.type === 'menu',
        MenuID: card.setting.type === 'linkmenu' ? card.setting.linkMenuId : card.uuid,
        copyMenuId: card.setting.type === 'menu' ? card.setting.copyMenuId : '',
        MenuNo: card.setting.MenuNo || '',
        MenuName: card.setting.name,
      })
    }
  }
  render() {
    const { cards, offset } = this.props
    const { card, visible, dict } = this.state
    let _style = {...card.style}
    if (_style.shadow) {
      _style.boxShadow = '0 0 4px ' + _style.shadow
    }
    _style = resetStyle(_style)
    return (
      <Col span={card.setting.width || 6} offset={offset || 0}>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            <Icon className="edit" title="编辑" type="edit" onClick={() => this.setState({visible: true})} />
            <CopyComponent type="menucell" card={card}/>
            <Icon className="style" title="调整样式" onClick={this.changeStyle} type="font-colors" />
            <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')} />
              </div>
            } trigger="hover" getPopupContainer={() => document.getElementById(card.uuid + 'swap')}>
              <Icon type="swap" id={card.uuid + 'swap'}/>
            </Popover>
            <Icon className="close" title="删除菜单" type="delete" onClick={() => this.props.deleteElement(card)} />
          </div>
        } trigger="hover">
          <div className="menu-item" onDoubleClick={() => this.changeMenu()} style={_style}>
            {card.setting.sign === 'icon' ? <div className="menu-sign">
              <Icon style={{
                fontSize: card.setting.iconFont || 20,
                padding: card.setting.padding,
                background: card.setting.background,
                color: card.setting.color
              }} type={card.setting.icon}/>
            </div> : <div className="menu-sign">
              <img style={{width: card.setting.imgWidth, height: card.setting.imgWidth}} src={card.setting.url} alt=""/>
            </div>}
            <div className="menu-name">{card.setting.name}</div>
          </div>
        </Popover>
        <Modal
          wrapClassName="popview-modal"
          title={'菜单设置'}
          visible={visible}
          width={800}
          maskClosable={false}
          okText={dict['model.submit']}
          onOk={this.settingSubmit}
          onCancel={() => { this.setState({ visible: false }) }}
          destroyOnClose
        >
          <SettingForm
            dict={dict}
            cards={cards}
            setting={card.setting}
            inputSubmit={this.settingSubmit}
            wrappedComponentRef={(inst) => this.settingRef = inst}
          />
        </Modal>
      </Col>
    )
  }
}
export default MenuBoxComponent
src/mob/components/menubar/normal-menubar/menucomponent/index.scss
src/mob/components/menubar/normal-menubar/menucomponent/settingform/index.jsx
New file
@@ -0,0 +1,302 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Radio, Tooltip, Icon, Input, InputNumber, Select } from 'antd'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const { TextArea } = Input
const SourceComponent = asyncComponent(() => import('@/menu/components/share/sourcecomponent'))
const ColorSketch = asyncComponent(() => import('@/mob/colorsketch'))
const MkIcon = asyncComponent(() => import('@/components/mkIcon'))
class SettingForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,      // 字典项
    cards: PropTypes.object,     // 卡片集
    setting: PropTypes.object,   // 数据源配置
    inputSubmit: PropTypes.func  // 回车事件
  }
  state = {
    type: this.props.setting.type,
    sign: this.props.setting.sign,
    menulist: []
  }
  UNSAFE_componentWillMount() {
    let menulist = sessionStorage.getItem('appMenus')
    if (menulist) {
      try {
        menulist = JSON.parse(menulist)
      } catch {
        menulist = []
      }
    } else {
      menulist = []
    }
    this.setState({menulist})
  }
  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 { setting } = this.props
    const { getFieldDecorator } = this.props.form
    const { menulist, type, sign } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="model-menubar-menu-card-setting-form">
        <Form {...formItemLayout}>
          <Row gutter={24}>
            <Col span={12}>
              <Form.Item label="菜单名称">
                {getFieldDecorator('name', {
                  initialValue: setting.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="菜单参数">
                {getFieldDecorator('MenuNo', {
                  initialValue: setting.MenuNo || '',
                  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: setting.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="类型">
                {getFieldDecorator('type', {
                  initialValue: type,
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.select'] + '类型!'
                    }
                  ]
                })(
                  <Radio.Group style={{whiteSpace: 'nowrap'}} onChange={(e) => this.setState({type: e.target.value})}>
                    <Radio value="menu">菜单</Radio>
                    <Radio value="link">链接</Radio>
                    <Radio value="linkmenu">关联菜单</Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col>
            {type === 'menu' ? <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="复制菜单仅在当前菜单不存在时有效。">
                  <Icon type="question-circle" style={{color: '#c49f47', marginRight: '3px'}}/>
                  复制菜单
                </Tooltip>
              }>
                {getFieldDecorator('copyMenuId', {
                  initialValue: setting.copyMenuId || ''
                })(
                  <Select allowClear>
                    {menulist.map(item => (<Select.Option key={item.MenuID} value={item.MenuID}>{item.MenuName}</Select.Option>))}
                  </Select>
                )}
              </Form.Item>
            </Col> : null}
            {type === 'linkmenu' ? <Col span={12}>
              <Form.Item label="关联菜单">
                {getFieldDecorator('linkMenuId', {
                  initialValue: setting.linkMenuId || '',
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.select'] + '关联菜单!'
                    }
                  ]
                })(
                  <Select
                    showSearch
                    filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  >
                    {menulist.map(option =>
                      <Select.Option key={option.MenuID} value={option.MenuID}>{option.MenuName}</Select.Option>
                    )}
                  </Select>
                )}
              </Form.Item>
            </Col> : null}
            {type === 'link' ? <Col span={24} className="textarea">
              <Form.Item label="链接">
                {getFieldDecorator('linkurl', {
                  initialValue: setting.linkurl || '',
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.input'] + '链接!'
                    }
                  ]
                })( <TextArea rows={2}/> )}
              </Form.Item>
            </Col> : null}
            <Col span={12}>
              <Form.Item label="标志">
                {getFieldDecorator('sign', {
                  initialValue: sign,
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.select'] + '标志!'
                    }
                  ]
                })(
                  <Radio.Group style={{whiteSpace: 'nowrap'}} onChange={(e) => this.setState({sign: e.target.value})}>
                    <Radio value="icon">图标</Radio>
                    <Radio value="image">图片</Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col>
            {sign === 'icon' ? <Col span={12}>
              <Form.Item label="图标">
                {getFieldDecorator('icon', {
                  initialValue: setting.icon || '',
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.select'] + '图标!'
                    }
                  ]
                })(
                  <MkIcon />
                )}
              </Form.Item>
            </Col> : null}
            {sign === 'icon' ? <Col span={12}>
              <Form.Item label="字体大小">
                {getFieldDecorator('iconFont', {
                  initialValue: setting.iconFont || 20
                })(
                  <InputNumber min={12} max={200} precision={0} onPressEnter={this.handleSubmit}/>
                )}
              </Form.Item>
            </Col> : null}
            {sign === 'icon' ? <Col span={12}>
              <Form.Item label="内边距">
                {getFieldDecorator('padding', {
                  initialValue: setting.padding || 12
                })(
                  <InputNumber min={0} max={200} precision={0} onPressEnter={this.handleSubmit}/>
                )}
              </Form.Item>
            </Col> : null}
            {sign === 'icon' ? <Col span={12}>
              <Form.Item label="字体颜色">
                {getFieldDecorator('color', {
                  initialValue: setting.color || '#ffffff'
                })(
                  <ColorSketch />
                )}
              </Form.Item>
            </Col> : null}
            {sign === 'icon' ? <Col span={12}>
              <Form.Item label="背景色">
                {getFieldDecorator('background', {
                  initialValue: setting.background || '#1890ff'
                })(
                  <ColorSketch />
                )}
              </Form.Item>
            </Col> : null}
            {sign === 'image' ? <Col span={12}>
              <Form.Item label="图片地址">
                {getFieldDecorator('url', {
                  initialValue: setting.url || '',
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.input'] + '图片地址!'
                    }
                  ]
                })(
                  <SourceComponent type="" placement="right"/>
                )}
              </Form.Item>
            </Col> : null}
            {sign === 'image' ? <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="图片宽度与高度相当,使用的图片比例应为1:1。">
                  <Icon type="question-circle" />
                  图片宽度
                </Tooltip>
              }>
                {getFieldDecorator('imgWidth', {
                  initialValue: setting.imgWidth || 36
                })(
                  <InputNumber min={10} max={500} precision={0} onPressEnter={this.handleSubmit}/>
                )}
              </Form.Item>
            </Col> : null}
          </Row>
        </Form>
      </div>
    )
  }
}
export default Form.create()(SettingForm)
src/mob/components/menubar/normal-menubar/menucomponent/settingform/index.scss
New file
@@ -0,0 +1,25 @@
.model-menubar-menu-card-setting-form {
  position: relative;
  .anticon-question-circle {
    color: #c49f47;
    margin-right: 3px;
  }
  .ant-input-number {
    width: 100%;
  }
  .textarea {
    .ant-form-item-label {
      width: 16%;
    }
    .ant-form-item-control-wrapper {
      width: 84%;
    }
  }
  .ant-radio-wrapper {
    margin-right: 3px;
  }
  .color-sketch-block {
    margin-top: 7px;
  }
}
src/mob/components/menubar/normal-menubar/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 DataSource 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={config.type === 'table' ? '表格设置' : '卡片设置'}
          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 DataSource
src/mob/components/menubar/normal-menubar/wrapsetting/index.scss
New file
@@ -0,0 +1,7 @@
.model-menu-setting-wrap {
  display: inline-block;
  >.anticon-edit {
    color: #1890ff;
  }
}
src/mob/components/menubar/normal-menubar/wrapsetting/settingform/index.jsx
New file
@@ -0,0 +1,155 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Radio, Tooltip, Icon, InputNumber, Select } from 'antd'
import './index.scss'
class SettingForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,      // 字典项
    config: PropTypes.object,    // 卡片行信息
    wrap: PropTypes.object,      // 数据源配置
    inputSubmit: PropTypes.func  // 回车事件
  }
  state = {
    roleList: [],
  }
  UNSAFE_componentWillMount () {
    let roleList = sessionStorage.getItem('sysRoles')
    if (roleList) {
      try {
        roleList = JSON.parse(roleList)
      } catch {
        roleList = []
      }
    } else {
      roleList = []
    }
    this.setState({roleList})
  }
  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 } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="model-menubar-menu-setting-form">
        <Form {...formItemLayout}>
          <Row gutter={24}>
            <Col span={12}>
              <Form.Item label="标题">
                {getFieldDecorator('title', {
                  initialValue: wrap.title || ''
                })(<Input placeholder={''} autoComplete="off" onPressEnter={this.handleSubmit} />)}
              </Form.Item>
            </Col>
            <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="对齐方式">
                {getFieldDecorator('float', {
                  initialValue: wrap.float || 'left'
                })(
                  <Radio.Group style={{whiteSpace: 'nowrap'}}>
                    <Radio key="left" value="left"> 左对齐 </Radio>
                    <Radio key="center" value="center"> 居中 </Radio>
                    <Radio key="right" value="right"> 右对齐 </Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col>
            <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/mob/components/menubar/normal-menubar/wrapsetting/settingform/index.scss
New file
@@ -0,0 +1,11 @@
.model-menubar-menu-setting-form {
  position: relative;
  .anticon-question-circle {
    color: #c49f47;
    margin-right: 3px;
  }
  .ant-input-number {
    width: 100%;
  }
}
src/mob/mobshell/card.jsx
@@ -23,6 +23,7 @@
const NormalLogin = asyncComponent(() => import('@/pc/components/login/normal-login'))
const NormalNavbar = asyncComponent(() => import('@/mob/components/navbar/normal-navbar'))
const NormalTopbar = asyncComponent(() => import('@/mob/components/topbar/normal-navbar'))
const NormalMenuBar = asyncComponent(() => import('@/mob/components/menubar/normal-menubar'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
@@ -113,6 +114,8 @@
      return (<NormalNavbar card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'topbar') {
      return (<NormalTopbar card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'menubar') {
      return (<NormalMenuBar card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
  }
src/mob/mobshell/index.jsx
@@ -118,6 +118,7 @@
        form: '表单',
        card: '卡片',
        navbar: '导航栏',
        menubar: '菜单栏',
        login: '登录'
      }
      let i = 1
src/mob/modulesource/option.jsx
@@ -22,11 +22,13 @@
import dashboard from '@/assets/mobimg/dashboard.png'
import NavTop from '@/assets/mobimg/navtop-mob.png'
import scatter from '@/assets/mobimg/scatter.png'
import MenuBar from '@/assets/mobimg/menubar.png'
// 组件配置信息
export const menuOptions = [
  { type: 'menu', url: NavTop, component: 'topbar', subtype: 'topbar', title: '导航栏' },
  { type: 'menu', url: Navbar, component: 'navbar', subtype: 'tabbar', title: '菜单栏' },
  { type: 'menu', url: MenuBar, component: 'menubar', subtype: 'menubar', title: '菜单' },
  { type: 'menu', url: tabs, component: 'tabs', subtype: 'tabs', title: '标签页', width: 24 },
  { type: 'menu', url: Mainsearch, component: 'search', subtype: 'mainsearch', title: '搜索条件', width: 24 },
  { type: 'menu', url: card1, component: 'card', subtype: 'datacard', title: '数据卡', width: 24 },
src/tabviews/custom/components/share/normalheader/index.scss
@@ -5,13 +5,14 @@
  border-bottom: 1px solid #e8e8e8;
  overflow: hidden;
  letter-spacing: 0px;
  line-height: 45px;
  .title {
    text-decoration: inherit;
    font-weight: inherit;
    font-style: inherit;
    float: left;
    line-height: 45px;
    line-height: inherit;
    margin-left: 10px;
    position: relative;
    z-index: 1;
src/utils/utils-custom.js
@@ -280,6 +280,11 @@
          return cell
        })
        item.components = this.resetConfig(item.components)
      } else if (item.type === 'menubar') {
        item.subMenus = item.subMenus.map(cell => {
          cell.uuid = this.getuuid()
          return cell
        })
      } else if (item.type === 'card' || item.type === 'carousel' || (item.type === 'table' && item.subtype === 'tablecard')) {
        item.subcards.forEach(card => {
          card.uuid = this.getuuid()
src/views/mobdesign/index.jsx
@@ -58,7 +58,7 @@
    delButtons: [],
    copyButtons: [],
    thawButtons: [],
    activeKey: 'basedata',
    activeKey: 'component',
    menuloading: false,
    oriConfig: null,
    config: null,
@@ -194,9 +194,9 @@
    if (menu.fixed && menu.MenuNo && menu.MenuName) {
      param.fixed = true
      param.MenuNo = menu.MenuNo
      param.MenuName = menu.MenuName
    }
    param.MenuNo = menu.MenuNo || ''
    param.MenuName = menu.MenuName || ''
    param = window.btoa(window.encodeURIComponent(JSON.stringify(param)))
@@ -443,8 +443,8 @@
            MenuID: MenuId,
            Template: 'webPage',
            enabled: false,
            MenuName: '',
            MenuNo: '',
            MenuName: urlParam.MenuName || '',
            MenuNo: urlParam.MenuNo || '',
            tables: [],
            components: [],
            viewType: 'menu',
@@ -459,10 +459,8 @@
        config.open_edition = result.open_edition || ''
        window.GLOB.urlFields = config.urlFields || []
        if (urlParam.fixed) {
        if (urlParam.fixed && urlParam.MenuName && urlParam.MenuNo) {
          config.fixed = true
          config.MenuName = urlParam.MenuName
          config.MenuNo = urlParam.MenuNo
        }
        let indeComs = []
@@ -476,6 +474,7 @@
          this.setState({
            oriConfig: isCreate ? null : config,
            config: fromJS(config).toJS(),
            activeKey: isCreate ? 'basedata' : 'component',
            loading: false
          })
          window.GLOB.customMenu = config
@@ -665,6 +664,7 @@
      this.setState({
        oriConfig: isCreate ? null : fromJS(config).toJS(),
        activeKey: isCreate ? 'basedata' : 'component',
        config: config,
        loading: false
      })
src/views/pcdesign/index.jsx
@@ -167,9 +167,9 @@
    if (menu.fixed && menu.MenuNo && menu.MenuName) {
      param.fixed = true
      param.MenuNo = menu.MenuNo
      param.MenuName = menu.MenuName
    }
    param.MenuNo = menu.MenuNo || ''
    param.MenuName = menu.MenuName || ''
    param = window.btoa(window.encodeURIComponent(JSON.stringify(param)))
@@ -485,8 +485,8 @@
            MenuID: MenuId,
            Template: 'webPage',
            enabled: false,
            MenuName: '',
            MenuNo: '',
            MenuName: urlParam.MenuName || '',
            MenuNo: urlParam.MenuNo || '',
            tables: [],
            components: [],
            viewType: 'menu',
@@ -501,10 +501,8 @@
        config.open_edition = result.open_edition || ''
        window.GLOB.urlFields = config.urlFields || []
        if (urlParam.fixed) {
        if (urlParam.fixed && urlParam.MenuNo && urlParam.MenuName) {
          config.fixed = true
          config.MenuName = urlParam.MenuName
          config.MenuNo = urlParam.MenuNo
        }
        let indeComs = []