king
2022-01-14 ab10e496feff0d7de81af939cd7ca1973e1ea220
2022-01-14
10个文件已修改
2个文件已添加
6个文件已删除
1079 ■■■■■ 已修改文件
src/components/normalform/modalform/index.jsx 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/normalform/modalform/mkTable/index.jsx 417 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/normalform/modalform/mkTable/index.scss 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcellcomponent/dragaction/index.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcomponent/index.jsx 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcomponent/menus-wrap/index.jsx 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcomponent/menus-wrap/index.scss 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcomponent/menus-wrap/menus/columnform/index.jsx 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcomponent/menus-wrap/menus/columnform/index.scss 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcomponent/menus-wrap/menus/index.jsx 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcomponent/menus-wrap/menus/index.scss 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcomponent/options.jsx 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/dragaction/index.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pc/components/login/normal-login/index.jsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pc/components/login/normal-login/loginform.jsx 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pc/components/login/normal-login/options.jsx 62 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobdesign/index.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/pcdesign/index.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/normalform/modalform/index.jsx
@@ -13,6 +13,7 @@
const { TextArea } = Input
const MKRadio = asyncComponent(() => import('./mkRadio'))
const MKTable = asyncComponent(() => import('./mkTable'))
const MKCheckbox = asyncComponent(() => import('./mkCheckbox'))
const StyleInput = asyncComponent(() => import('./styleInput'))
const MKFileUpload = asyncComponent(() => import('@/tabviews/zshare/fileupload'))
@@ -66,13 +67,19 @@
          validator: (rule, value, callback) => this.handleConfirmPassword(rule, value, callback, item)
        }]
      } else if (item.type === 'textarea') {
        let _rules = [
        item.rules = [
          {
            required: item.required,
            message: item.label + '不可为空!'
          }
        ]
        item.rules = _rules
      } else if (item.type === 'table') {
        item.rules = [
          {
            required: item.required,
            message: '请添加' + item.label + '!'
          }
        ]
      } else {
        item.rules = [
          {
@@ -253,6 +260,8 @@
        content = (<MkEditIcon allowClear={item.allowClear}/>)
      } else if (item.type === 'source') {
        content = (<SourceComponent type="" placement="right"/>)
      } else if (item.type === 'table') {
        content = (<MKTable columns={item.columns || []}/>)
      }
      if (!content) return
src/components/normalform/modalform/mkTable/index.jsx
New file
@@ -0,0 +1,417 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { DndProvider, DragSource, DropTarget } from 'react-dnd'
import { Table, Input, InputNumber, Popconfirm, Form, Select, Radio, Cascader, notification, Typography, Button } from 'antd'
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'
import Utils from '@/utils/utils.js'
import ColorSketch from '@/mob/colorsketch'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const MkEditIcon = asyncComponent(() => import('@/components/mkIcon'))
const EditableContext = React.createContext()
let dragingIndex = -1
const { Paragraph } = Typography
class BodyRow extends React.Component {
  render() {
    const { isOver, moveAble, connectDragSource, connectDropTarget, moveRow, ...restProps } = this.props
    let { className } = restProps
    if (isOver && moveAble) {
      if (restProps.index > dragingIndex) {
        className += ' drop-over-downward'
      }
      if (restProps.index < dragingIndex) {
        className += ' drop-over-upward'
      }
    }
    if (moveAble) {
      return connectDragSource(
        connectDropTarget(<tr {...restProps} className={className} style={{ ...restProps.style, cursor: 'move' }} />),
      )
    } else {
      return (<tr {...restProps} className={className} style={restProps.style} />)
    }
  }
}
const rowSource = {
  beginDrag(props) {
    dragingIndex = props.index
    return {
      index: props.index,
    }
  }
}
const rowTarget = {
  drop(props, monitor) {
    const dragIndex = monitor.getItem().index
    const hoverIndex = props.index
    if (dragIndex === hoverIndex) {
      return
    }
    props.moveRow(dragIndex, hoverIndex)
    monitor.getItem().index = hoverIndex
  },
}
const DragableBodyRow = DropTarget('row', rowTarget, (connect, monitor) => ({
  connectDropTarget: connect.dropTarget(),
  isOver: monitor.isOver(),
}))(
  DragSource('row', rowSource, connect => ({
    connectDragSource: connect.dragSource(),
  }))(BodyRow),
)
class EditableCell extends Component {
  getInput = (form) => {
    const { inputType, options, min, max, unlimit } = this.props
    if (inputType === 'number' && unlimit) {
      return <InputNumber onPressEnter={() => this.getValue(form)} />
    } else if (inputType === 'number') {
      return <InputNumber min={min} max={max} precision={0} onPressEnter={() => this.getValue(form)} />
    } else if (inputType === 'color') {
      return <ColorSketch />
    } else if (inputType === 'icon') {
      return <MkEditIcon allowClear/>
    } else if (inputType === 'select') {
      return (
        <Select>
          {options.map((item, i) => (<Select.Option key={i} value={item.field || item.value}> {item.label || item.text} </Select.Option>))}
        </Select>
      )
    } else if (inputType === 'multiStr') {
      return (
        <Select mode="multiple">
          {options.map((item, i) => (<Select.Option key={i} value={item.field || item.value}> {item.label || item.text} </Select.Option>))}
        </Select>
      )
    } else if (inputType === 'cascader') {
      return (
        <Cascader options={options} placeholder=""/>
      )
    } else if (inputType === 'radio') {
      return (
        <Radio.Group>
          {options.map((item, i) => (<Radio key={i} value={item.field || item.value}> {item.label || item.text} </Radio>))}
        </Radio.Group>
      )
    } else {
      return <Input onPressEnter={() => this.getValue(form)}/>
    }
  }
  getValue = (form) => {
    form.validateFields((error, row) => {
      if (error) {
        return
      }
      this.props.onSave(row)
    })
  }
  renderCell = (form) => {
    const { getFieldDecorator } = form
    const { editing, dataIndex, title, record, children, className, required, inputType } = this.props
    return (
      <td className={className}>
        {editing ? (
          <Form.Item style={{ margin: 0 }}>
            {getFieldDecorator(dataIndex, {
              rules: [
                {
                  required: required,
                  message: ['number', 'text', 'input'].includes(inputType) ? `请输入 ${title}!` : `请选择 ${title}!`,
                }
              ],
              initialValue: inputType === 'multiStr' ? (record[dataIndex] ? record[dataIndex].split(',') : []) : record[dataIndex],
            })(this.getInput(form))}
          </Form.Item>
        ) : (
          children
        )}
      </td>
    )
  }
  render() {
    return <EditableContext.Consumer>{this.renderCell}</EditableContext.Consumer>
  }
}
class EditTable extends Component {
  static propTpyes = {
    columns: PropTypes.array,       // 显示列
    onChange: PropTypes.func        // 数据变化
  }
  state = {
    data: [],
    editLine: null,
    editingKey: '',
    visible: false,
    columns: []
  }
  UNSAFE_componentWillMount () {
    let columns = fromJS(this.props.columns).toJS()
    let operation = {
      title: '操作',
      dataIndex: 'operation',
      width: '120px',
      render: (text, record) => {
        const { editingKey } = this.state
        const editable = this.isEditing(record)
        return editable ? (
          <div style={{textAlign: 'center', minWidth: '110px'}}>
            <EditableContext.Consumer>
              {form => (
                <span onClick={() => this.save(form)} style={{ marginRight: 8 , color: '#1890ff', cursor: 'pointer'}}>
                  保存
                </span>
              )}
            </EditableContext.Consumer>
            <span style={{ color: '#1890ff', cursor: 'pointer'}} onClick={() => this.cancel(record.uuid)}>取消</span>
          </div>
        ) : (
          <div className={'edit-operation-btn' + (editingKey !== '' ? ' disabled' : '')} style={{minWidth: '110px', whiteSpace: 'nowrap'}}>
            <span className="primary" onClick={() => {editingKey === '' && this.edit(record)}}><EditOutlined /></span>
            {editingKey === '' ? <Popconfirm
              overlayClassName="popover-confirm"
              title="确定删除吗?"
              onConfirm={() => this.handleDelete(record.uuid)
            }>
              <span className="danger"><DeleteOutlined /></span>
            </Popconfirm> : null}
            {editingKey !== '' ? <span className="danger"><DeleteOutlined /></span> : null}
          </div>
        )
      }
    }
    columns.push(operation)
    this.setState({
      data: fromJS(this.props.value || []).toJS(),
      operation,
      columns
    })
  }
  // shouldComponentUpdate (nextProps, nextState) {
  //   return !is(fromJS(this.state), fromJS(nextState))
  // }
  isEditing = record => record.uuid === this.state.editingKey
  cancel = () => {
    this.setState({ editingKey: '', data: this.state.data.filter(item => !item.isnew) })
  }
  handleDelete = (uuid) => {
    const { data } = this.state
    let _data = data.filter(item => uuid !== item.uuid)
    this.setState({
      data: _data
    }, () => {
      this.props.onChange(_data)
    })
  }
  save(form) {
    form.validateFields((error, row) => {
      if (error) {
        return;
      }
      this.execSave(row)
    })
  }
  execSave = (row) => {
    const { columns, editLine } = this.state
    let newData = [...this.state.data]
    let record = {...editLine, ...row}
    let index = newData.findIndex(item => record.uuid === item.uuid)
    if (index === -1) return
    let unique = true
    columns.forEach(col => {
      if (col.unique !== true || !unique) return
      let _index = newData.findIndex(item => record.uuid !== item.uuid && record[col.dataIndex] === item[col.dataIndex])
      if (_index > -1) {
        notification.warning({
          top: 92,
          message: col.title + '不可重复!',
          duration: 5
        })
        unique = false
      }
    })
    if (!unique) return
    columns.forEach(col => {
      if (!col.extends) return
      if (col.extends === 'Menu') {
        let menu = record[col.dataIndex]
        let fId = menu[0] || ''
        let sId = menu[1] || ''
        let tId = menu[2] || ''
        let label = ''
        col.options.forEach(f => {
          if (!fId || fId !== f.value) return
          label = f.label
          f.children.forEach(s => {
            if (!sId || sId !== s.value) return
            label += ' / ' + s.label
            s.children.forEach(t => {
              if (!tId || tId !== t.value) return
              label += ' / ' + t.label
              record.MenuID = t.MenuID
              record.MenuName = t.MenuName
              record.MenuNo = t.MenuNo
              record.tabType = t.type
              record.label = label
            })
          })
        })
      } else {
        let key = record[col.dataIndex]
        let option = col.options.filter(m => m.value === key)[0]
        if (option) {
          col.extends.forEach(n => {
            record[n.value] = option[n.key]
          })
        }
      }
    })
    delete record.isnew
    newData.splice(index, 1, record)
    this.setState({ data: newData, editingKey: '', editLine: null }, () => {
      this.props.onChange(newData)
    })
  }
  edit(item) {
    this.setState({ editLine: item, editingKey: item.uuid })
  }
  addline = () => {
    let item = {
      uuid: Utils.getuuid(),
      isnew: true
    }
    this.setState({ data: [...this.state.data, item], editingKey: '' }, () => {
      this.setState({ editLine: item, editingKey: item.uuid })
    })
  }
  moveRow = (dragIndex, hoverIndex) => {
    const { editingKey } = this.state
    let _data = fromJS(this.state.data).toJS()
    if (editingKey) return
    _data.splice(hoverIndex, 0, ..._data.splice(dragIndex, 1))
    this.setState({
      data: _data
    }, () => {
      this.props.onChange(_data)
    })
  }
  render() {
    let components = {
      body: {
        cell: EditableCell
      }
    }
    let moveprops = {}
    components.body.row = DragableBodyRow
    moveprops.moveAble = !this.state.editingKey
    moveprops.moveRow = this.moveRow
    let  columns = this.state.columns.map(col => {
      if (col.copy) {
        col.render = (text) => (<Paragraph copyable>{text}</Paragraph>)
      }
      if (!col.editable) return col
      return {
        ...col,
        onCell: record => ({
          record,
          inputType: col.inputType,
          dataIndex: col.dataIndex,
          options: col.options || [],
          min: col.min || 0,
          max: col.max || 500,
          unlimit: col.unlimit,
          required: col.required !== false ? true : false,
          title: col.title,
          editing: this.isEditing(record),
          onSave: this.execSave,
        }),
      }
    })
    const data = this.state.data.map((item, index) => {
      item.$index = index + 1
      return item
    })
    return (
      <EditableContext.Provider value={this.props.form}>
        <div className="modal-editable-table">
          <Button disabled={!!this.state.editingKey} type="link" onClick={this.addline}><PlusOutlined style={{}}/></Button>
          <DndProvider>
            <Table
              bordered
              rowKey="uuid"
              size="middle"
              components={components}
              dataSource={data}
              columns={columns}
              rowClassName="editable-row"
              pagination={false}
              onRow={(record, index) => ({
                index,
                ...moveprops
              })}
            />
          </DndProvider>
        </div>
      </EditableContext.Provider>
    )
  }
}
export default Form.create()(EditTable)
src/components/normalform/modalform/mkTable/index.scss
New file
@@ -0,0 +1,108 @@
.modal-editable-table {
  position: relative;
  margin-bottom: 5px;
  >.ant-btn {
    position: absolute;
    right: 0px;
    top: -26px;
    font-size: 18px;
    color: #26C281;
    z-index: 1;
    height: 28px;
  }
  .editable-row {
    .ant-form-explain {
      position: absolute;
      font-size: 12px;
      margin-top: -4px;
    }
    .color-sketch-block {
      width: 200px;
      position: relative;
      top: 8px;
    }
    .ant-select {
      width: 100%;
    }
    > td {
      padding: 14px 10px;
    }
    > td:last-child {
      padding: 0px;
    }
  }
  .mk-index {
    text-align: center;
    white-space: nowrap;
  }
  thead tr th:last-child {
    text-align: center;
  }
  .edit-operation-btn {
    display: block;
    text-align: center;
    span {
      margin-right: 10px;
      cursor: pointer;
      font-size: 16px;
      padding: 5px;
    }
    .primary {
      color: #1890ff;
    }
    .copy {
      color: #26C281;
    }
    .danger {
      color: #ff4d4f;
    }
    span:last-child {
      margin-right: 0px;
    }
  }
  .edit-operation-btn.disabled {
    cursor: default;
    span {
      cursor: default;
    }
    .primary {
      color: rgba(0, 0, 0, .25);
    }
    .danger {
      color: rgba(0, 0, 0, .25);
    }
    .copy {
      color: rgba(0, 0, 0, .25);
    }
  }
  .ant-empty {
    margin: 0;
  }
  tr.drop-over-downward td {
    border-bottom: 2px dashed #1890ff;
  }
  tr.drop-over-upward td {
    border-top: 2px dashed #1890ff;
  }
  .copy-control {
    display: inline-block;
    position: absolute;
    right: 10px;
    top: 2px;
    .anticon-copy {
      margin-right: 7px;
      color: #26C281;
    }
    .anticon-snippets {
      color: purple;
    }
    .anticon-delete {
      margin-left: 7px;
      color: #ff4d4f;
    }
  }
}
src/menu/components/card/cardcellcomponent/dragaction/index.jsx
@@ -47,6 +47,13 @@
    let _val = fromJS(copycard).toJS()
    if (_val.control) {
      _val.control = ''
      delete _val.controlField
      delete _val.controlVal
    }
    copycard.uuid = Utils.getuuid()
    copycard.originCard = card
src/menu/components/card/cardcomponent/index.jsx
@@ -14,7 +14,6 @@
const NormalForm = asyncIconComponent(() => import('@/components/normalform'))
const CardCellComponent = asyncComponent(() => import('../cardcellcomponent'))
const CardMenus = asyncComponent(() => import('./menus-wrap'))
const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
const PasteController = asyncIconComponent(() => import('@/components/paste'))
@@ -195,7 +194,7 @@
        }
      })
    }
    return getSettingForm(card.setting, cards.subtype, buttons, card.$cardType, cards.columns)
    return getSettingForm(card.setting, cards.subtype, buttons, card.$cardType, cards.columns, card.menus)
  }
  updateSetting = (res) => {
@@ -221,18 +220,26 @@
      })
    }
    let _card = {...card, setting: res}
    if (res.menus) {
      _card.menus = res.menus
      delete res.menus
    }
    this.setState({
      card: {...card, setting: res}
      card: _card
    })
    if (side === 'back' && res.type === 'simple') {
      this.setState({
        side: 'front',
        elements: fromJS(card.elements).toJS()
        elements: fromJS(_card.elements).toJS()
      })
    }
    this.props.updateElement({...card, setting: res})
    this.props.updateElement(_card)
  }
  paste = (element, resolve) => {
@@ -281,19 +288,9 @@
        return
      }
      MKEmitter.emit('changeEditMenu', {MenuID: card.setting.menu})
    } else if (card.setting.click === 'menus' && card.menus && card.menus.length > 0 && cards.subtype === 'datacard' && card.$cardType !== 'extendCard' && (appType === 'mob' || appType === 'pc')) {
    } else if (card.setting.click === 'menus' && card.menus && card.menus.length > 0 && cards.subtype === 'datacard' && appType) {
      this.setState({visible: true})
    }
  }
  updateMenus = (res) => {
    const { card } = this.state
    this.setState({
      card: {...card, menus: res}
    })
    this.props.updateElement({...card, menus: res})
  }
  render() {
@@ -326,8 +323,6 @@
                <NormalForm title="卡片设置" width={800} update={this.updateSetting} getForms={this.getSettingForms}>
                  <EditOutlined className="edit" title="编辑"/>
                </NormalForm>
                {cards.subtype === 'datacard' && card.$cardType !== 'extendCard' && card.setting.click === 'menus' ?
                  <CardMenus card={card} updateMenus={this.updateMenus}/> : null}
                <CopyComponent type="cardcell" card={card}/>
                <PasteController options={['action', 'customCardElement']} updateConfig={this.paste} />
                <FontColorsOutlined className="style" title="调整样式" onClick={this.changeStyle} />
src/menu/components/card/cardcomponent/menus-wrap/index.jsx
File was deleted
src/menu/components/card/cardcomponent/menus-wrap/index.scss
File was deleted
src/menu/components/card/cardcomponent/menus-wrap/menus/columnform/index.jsx
File was deleted
src/menu/components/card/cardcomponent/menus-wrap/menus/columnform/index.scss
src/menu/components/card/cardcomponent/menus-wrap/menus/index.jsx
File was deleted
src/menu/components/card/cardcomponent/menus-wrap/menus/index.scss
src/menu/components/card/cardcomponent/options.jsx
@@ -1,26 +1,25 @@
/**
 * @description Setting表单配置信息
 */
export default function (setting, subtype, buttons = [], cardType, columns) {
export default function (setting, subtype, buttons = [], cardType, columns, menus = []) {
  let appType = sessionStorage.getItem('appType')
  let hasMenus = subtype === 'datacard' && cardType !== 'extendCard'
  let menulist = []
  let appmenulist = []
  if (appType) {
    appmenulist = sessionStorage.getItem('appMenus')
    if (appmenulist) {
    menulist = sessionStorage.getItem('appMenus')
    if (menulist) {
      try {
        appmenulist = JSON.parse(appmenulist)
        menulist = JSON.parse(menulist)
      } catch (e) {
        appmenulist = []
        menulist = []
      }
    } else {
      appmenulist = []
      menulist = []
    }
    if (appType === 'mob' && (subtype === 'datacard' || subtype === 'propcard') && cardType !== 'extendCard') { // 数据卡可打开即时通信
      appmenulist.push({
      menulist.push({
        value: 'IM', label: '即时通信(系统页)'
      })
    }
@@ -118,6 +117,7 @@
        {field: 'joint', values: ['menu', 'link', 'menus']},
        {field: 'linkbtn', values: ['button']},
        {field: 'menuType', values: ['menus']},
        {field: 'menus', values: ['menus']},
      ]
    },
    {
@@ -134,7 +134,7 @@
      label: '关联菜单',
      initval: setting.menu || (appType ? '' : []),
      required: true,
      options: appType ? appmenulist : menulist,
      options: menulist,
    },
    {
      type: 'textarea',
@@ -188,6 +188,36 @@
        {value: 'hover', label: '悬浮显示'},
      ],
      forbid: appType === 'mob'
    },
    {
      type: 'table',
      field: 'menus',
      label: '菜单组',
      initval: menus,
      required: true,
      span: 24,
      columns: [
        {
          title: '标识',
          dataIndex: 'sign',
          inputType: 'input',
          editable: true,
          unique: true,
          required: false,
          width: '35%'
        },
        {
          title: '菜单',
          dataIndex: 'menu',
          inputType: !appType ? 'cascader' : 'select',
          editable: true,
          required: true,
          extends: !appType ? 'Menu' : [{key: 'label', value: 'label'}],
          width: '35%',
          render: (text, record) => record.label,
          options: menulist
        }
      ]
    }
  ]
src/menu/components/share/actioncomponent/dragaction/index.jsx
@@ -63,6 +63,13 @@
    let _val = fromJS(copycard).toJS()
    if (_val.control) {
      _val.control = ''
      delete _val.controlField
      delete _val.controlVal
    }
    copycard.uuid = Utils.getuuid()
    copycard.originCard = card
src/pc/components/login/normal-login/index.jsx
@@ -75,6 +75,9 @@
      if (_card.loginWays.length === 2) {
        _card.loginWays.push({type: 'app_scan', label: '扫码登录'})
      }
      if (_card.wrap.link && _card.wrap.link === 'menu') {
        _card.wrap.linkmenu = _card.uuid
      }
      this.setState({
        card: _card
      })
@@ -141,9 +144,9 @@
  }
  getWrapForms = () => {
    const { wrap } = this.state.card
    const { card } = this.state
    return getWrapForm(wrap)
    return getWrapForm(card.wrap)
  }
  updateWrap = (res) => {
@@ -185,7 +188,7 @@
        } trigger="hover">
          <ToolOutlined />
        </Popover>
        <LoginForm loginWays={card.loginWays} wrap={card.wrap} menuId={card.uuid} />
        <LoginForm loginWays={card.loginWays} wrap={card.wrap} />
      </div>
    )
  }
src/pc/components/login/normal-login/loginform.jsx
@@ -1,7 +1,7 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Form, Input, Button, Checkbox } from 'antd'
import { Form, Input, Button, Checkbox, notification } from 'antd'
import { QrcodeOutlined, UserOutlined, LockOutlined } from '@ant-design/icons'
import asyncElementComponent from '@/utils/asyncComponent'
@@ -12,7 +12,6 @@
class LoginTabForm extends Component {
  static propTpyes = {
    menuId: PropTypes.string,
    loginWays: PropTypes.array,
    wrap: PropTypes.array,
  }
@@ -80,10 +79,19 @@
  }
  changeMenu = () => {
    const { wrap, menuId } = this.props
    const { wrap } = this.props
    if (!wrap.linkmenu) {
      notification.warning({
        top: 92,
        message: '请设置关联菜单!',
        duration: 5
      })
      return
    }
    MKEmitter.emit('changeEditMenu', {
      MenuID: wrap.link === 'linkmenu' ? wrap.linkmenu : menuId,
      MenuID: wrap.linkmenu,
      copyMenuId: '',
      MenuNo: '',
      MenuName: ''
src/pc/components/login/normal-login/options.jsx
@@ -57,6 +57,23 @@
      ]
    },
    {
      type: 'select',
      field: 'linkmenu',
      label: '关联菜单',
      initval: wrap.linkmenu || '',
      required: true,
      options: menulist
    },
    {
      type: 'select', // $验证码$  $mob$  $send_type$
      field: 'tempId',
      label: '短信模板',
      initval: wrap.tempId || '',
      tooltip: '短信模板可在 云系统->应用服务->开发者中心->短信模板 处添加。',
      required: true,
      options: msgTemps
    },
    {
      type: 'number',
      field: 'width',
      label: '宽度',
@@ -87,37 +104,20 @@
      required: false,
      options: ['px', 'vh', 'vw', '%']
    },
    {
      type: 'radio',
      field: 'link',
      label: '链接',
      initval: wrap.link || 'menu',
      required: false,
      options: [
        {value: 'menu', label: '菜单'},
        {value: 'linkmenu', label: '关联菜单'},
      ],
      controlFields: [
        {field: 'linkmenu', values: ['linkmenu']}
      ]
    },
    {
      type: 'select',
      field: 'linkmenu',
      label: '关联菜单',
      initval: wrap.linkmenu || '',
      required: true,
      options: menulist
    },
    {
      type: 'select', // $验证码$  $mob$  $send_type$
      field: 'tempId',
      label: '短信模板',
      initval: wrap.tempId || '',
      tooltip: '短信模板可在 云系统->应用服务->开发者中心->短信模板 处添加。',
      required: true,
      options: msgTemps
    },
    // {
    //   type: 'radio',
    //   field: 'link',
    //   label: '链接',
    //   initval: wrap.link || 'menu',
    //   required: false,
    //   options: [
    //     {value: 'menu', label: '菜单'},
    //     {value: 'linkmenu', label: '关联菜单'},
    //   ],
    //   controlFields: [
    //     {field: 'linkmenu', values: ['linkmenu']}
    //   ]
    // },
    {
      type: 'radio',
      field: 'shortcut',
src/views/mobdesign/index.jsx
@@ -1277,6 +1277,9 @@
        } else if (item.subtype === 'propcard' && item.subcards.length === 0) {
          error = `组件《${item.name}》中卡片不可为空!`
          return
        } else if (item.type === 'login' && !item.wrap.linkmenu && item.wrap.link !== 'menu') {
          error = '登录组件未设置关联菜单!'
          return
        }
        if (item.wrap && item.wrap.pagestyle === 'slide') {
          swipes.push(item.name)
src/views/pcdesign/index.jsx
@@ -1479,6 +1479,9 @@
        } else if (item.subtype === 'propcard' && item.subcards.length === 0) {
          error = `组件《${item.name}》中卡片不可为空!`
          return
        } else if (item.type === 'login' && !item.wrap.linkmenu && item.wrap.link !== 'menu') {
          error = '登录组件未设置关联菜单!'
          return
        }
        
        if (['propcard', 'brafteditor', 'sandbox', 'stepform', 'tabform'].includes(item.subtype) && item.wrap.datatype === 'static') return