king
2022-05-05 4677982c003e357cff8f2544be44706bf31ea6de
2022-05-05
20个文件已修改
6个文件已添加
1391 ■■■■■ 已修改文件
package-lock.json 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/options.json 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/index.js 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/w4k.js 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/actionform/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/dragaction/card.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/formconfig.jsx 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/index.jsx 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/setupProxy.js 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/card/cardcellList/index.jsx 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/share/normalTable/index.jsx 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/excelInbutton/excelin/index.jsx 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/funcMegvii/index.jsx 547 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/funcMegvii/index.scss 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/funcMegvii/mock.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/actionList/index.jsx 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/fileupload/index.jsx 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/normalTable/index.jsx 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/actionform/index.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/dragaction/card.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/index.jsx 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifymegvii/index.jsx 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifymegvii/index.scss 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/formconfig.jsx 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/rolemanage/index.jsx 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json
@@ -5888,6 +5888,11 @@
        "randomfill": "^1.0.3"
      }
    },
    "crypto-js": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
      "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
    },
    "css": {
      "version": "2.2.4",
      "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
package.json
@@ -28,6 +28,7 @@
    "camelcase": "^5.2.0",
    "case-sensitive-paths-webpack-plugin": "2.2.0",
    "codemirror": "^5.52.2",
    "crypto-js": "^4.1.1",
    "css-loader": "2.1.1",
    "dotenv": "6.2.0",
    "dotenv-expand": "4.2.0",
public/options.json
@@ -1,18 +1,18 @@
{
  "appId": "201912040924165801464FF1788654BC5AC73",
  "appkey": "20191106103859640976D6E924E464D029CF0",
  "appId": "202108312122504607B107A83F55B40C98CCF",
  "appkey": "20210831212235413F287EC3BF489424496C8",
  "mainSystemApi": "http://sso.mk9h.cn/cloud/webapi/dostars",
  "systemType": "",
  "externalDatabase": "false",
  "lineColor": "",
  "filter": "false",
  "defaultApp": "mk",
  "defaultApp": "mkindustry",
  "defaultLang": "zh-CN",
  "WXAppID": "",
  "debugger": false,
  "licenseKey": "",
  "probation": "",
  "licenseKey": "7EFE13KIKLILIJB64C12",
  "probation": "2021-12-31",
  "keepPassword": "true",
  "host": "http://qingqiumarket.cn",
  "service": "MKWMS/"
  "host": "http://demo.mk9h.cn",
  "service": "erp_new/"
}
src/api/index.js
@@ -66,16 +66,22 @@
    return Promise.resolve(response.data)
  }
}, (error) => {
  if (error && error.response) {
  let response = error.response
  if (response) {
    if (!response.data || !response.data.errors) { // 过滤旷视报错信息
    notification.error({
      className: 'notification-custom-error',
      bottom: 0,
      message: '状态码-' + error.response.status + ',请联系管理员',
        message: '状态码-' + response.status + ',请联系管理员',
      placement: 'bottomRight',
      duration: 15
    })
  }
  return Promise.reject(error.response)
    return Promise.reject(response)
  } else {
    return Promise.reject()
  }
})
class Api {
src/api/w4k.js
New file
@@ -0,0 +1,113 @@
import axios from 'axios'
import jsSHA from 'jssha'
class W4kApi {
  /**
   * @description 鉴权挑战
   * @param {Object} param 查询及提交参数
   */
  login (ip, username = 'admin', password) {
    return new Promise((resolve, reject) => {
      let challurl = ip + '/api/auth/login/challenge?username=' + username
      challurl = '/trans/redirect?rd=' + challurl + '&method=get'
      let loginurl = ip + '/api/auth/login'
      loginurl = '/trans/redirect?rd=' + loginurl + '&method=post'
      axios({
        url: challurl,
        method: 'post' // get
      }).then(res => {
        if (res.errors) {
          reject(res)
        } else {
          const shaObj = new jsSHA('SHA-256', 'TEXT', { encoding: 'UTF8' })
          shaObj.update(password + res.salt + res.challenge)
          const hash = shaObj.getHash('HEX')
          axios({
            url: loginurl,
            method: 'post',
            data: {
              session_id: res.session_id,
              username: username,
              password: hash
            }
          }).then(result => {
            resolve(result)
          }, (err) => {
            reject(err)
          })
        }
      }, (err) => {
        reject(err)
      })
    })
  }
  queryUsers (ip) {
    let url = ip + '/api/persons/query'
    url = '/trans/redirect?rd=' + url + '&method=post'
    return axios({
      url: url,
      method: 'POST',
      withCredentials: true,
      headers: { 'Content-Type': 'application/json' },
      data: {
        limit: 100,
        offset: 0,
        sort: 'desc',
        query_id: '',
        query_string: ''
      }
    })
  }
  addUsers (ip, data) {
    return new Promise((resolve, reject) => {
      let delurl = ip + '/api/persons/item/' + data.id
      delurl = '/trans/redirect?rd=' + delurl + '&method=DELETE'
      let addurl = ip + '/api/persons/item'
      addurl = '/trans/redirect?rd=' + addurl + '&method=post'
      if (data.deleted) {
        delete data.deleted
        axios({
          url: delurl,
          method: 'post' // DELETE
        }).then(res => {
          if (res.errors) {
            reject(res)
          } else {
            axios({
              url: addurl,
              method: 'post',
              data: data
            }).then(result => {
              resolve(result)
            }, (err) => {
              reject(err)
            })
          }
        }, (err) => {
          reject(err)
        })
      } else {
        delete data.deleted
        axios({
          url: addurl,
          method: 'post',
          data: data
        }).then(result => {
          resolve(result)
        }, (err) => {
          reject(err)
        })
      }
    })
  }
}
export default new W4kApi()
src/menu/components/share/actioncomponent/actionform/index.jsx
@@ -344,8 +344,8 @@
        reTooltip.linkmenu = '使用扫码登录功能或菜单跳转功能时,需选择跳转的菜单。'
      } else if (_funcType === 'goBack') {
        shows.push('reload')
      // } else if (_funcType === 'megvii') {
      //   shows.push('subFunc')
      } else if (_funcType === 'megvii') {
        shows.push('subFunc')
      }
    }
    
src/menu/components/share/actioncomponent/dragaction/card.jsx
@@ -37,6 +37,8 @@
    hasProfile = true
  } else if (card.funcType === 'print') {
    hasProfile = true
  } else if (card.funcType === 'megvii') {
    hasProfile = true
  }
  let btnElement = null
src/menu/components/share/actioncomponent/formconfig.jsx
@@ -88,6 +88,7 @@
    { value: 'changeuser', text: '切换用户' },
    { value: 'print', text: '标签打印' },
    { value: 'closetab', text: '标签关闭' },
    { value: 'megvii', text: '旷视面板机' },
  ]
  
  if (isApp) {
@@ -112,7 +113,6 @@
      { value: 'mkUnsubscribe', text: '注销账户' },
      { value: 'reAuth', text: '切换系统(清空缓存-小程序)' },
      { value: 'goBack', text: '返回' },
      // { value: 'megvii', text: '旷视面板机' },
    ]
    pageTemps = [
      { value: 'linkpage', text: '关联菜单' },
@@ -179,16 +179,16 @@
      required: true,
      options: funTypes
    },
    // {
    //   type: 'select',
    //   key: 'subFunc',
    //   label: '接口名称',
    //   initVal: card.subFunc || '',
    //   required: true,
    //   options: [
    //     { value: 'login', text: '登录' }
    //   ]
    // },
    {
      type: 'radio',
      key: 'subFunc',
      label: '接口名称',
      initVal: card.subFunc || 'addUser',
      required: true,
      options: [
        { value: 'addUser', text: '添加用户' },
      ]
    },
    {
      type: 'radio',
      key: 'formType',
src/menu/components/share/actioncomponent/index.jsx
@@ -21,6 +21,7 @@
const VerifyPrint = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifyprint'))
const VerifyExcelIn = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifyexcelin'))
const VerifyExcelOut = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifyexcelout'))
const VerifyMegvii = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifymegvii'))
class ActionComponent extends Component {
  static propTpyes = {
@@ -446,6 +447,51 @@
    })
  }
  getVerify = (card) => {
    const { config } = this.props
    const { dict } = this.state
    if (!card) return null
    if (['pop', 'prompt', 'exec'].includes(card.OpenType)) {
      return <VerifyCard
        card={card}
        dict={dict}
        config={config}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'excelIn') {
      return <VerifyExcelIn
        card={card}
        dict={dict}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'excelOut') {
      return <VerifyExcelOut
        card={card}
        dict={dict}
        config={config}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'funcbutton' && card.funcType === 'print') {
      return <VerifyPrint
        card={card}
        dict={dict}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'funcbutton' && card.funcType === 'megvii') {
      return <VerifyMegvii
        card={card}
        dict={dict}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    }
  }
  render() {
    const { config, type } = this.props
    const { actionlist, visible, appType, card, dict, profVisible } = this.state
@@ -507,39 +553,7 @@
          }}
          destroyOnClose
        >
          {card && !card.execMode && card.OpenType !== 'excelIn' && card.OpenType !== 'excelOut' ?
            <VerifyCard
              card={card}
              dict={dict}
              config={config}
              columns={config.columns}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {card && card.execMode ?
            <VerifyPrint
              card={card}
              dict={dict}
              columns={config.columns}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {card && card.OpenType === 'excelIn' ?
            <VerifyExcelIn
              card={card}
              dict={dict}
              columns={config.columns}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {card && card.OpenType === 'excelOut' ?
            <VerifyExcelOut
              card={card}
              dict={dict}
              config={config}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {this.getVerify(card)}
        </Modal>
      </div>
    )
src/setupProxy.js
@@ -1,9 +1,9 @@
// const proxy = require('http-proxy-middleware')
// const host = 'http://qingqiumarket.cn'
// const service = 'mkwms/'
const proxy = require('http-proxy-middleware')
const host = 'http://qingqiumarket.cn'
const service = 'mkwms/'
module.exports = function() {}
// module.exports = function(app) {
// module.exports = function(app) {}
module.exports = function(app) {
//   app.use(proxy('/webapi', { 
//     target: `${host}/${service}webapi`,
//     secure: false,
@@ -41,12 +41,15 @@
//     }
//   }))
//   app.use(proxy('/trans', {
//     target: `${host}/${service}trans`,
//     secure: false,
//     changeOrigin: true,
//     pathRewrite: {
//     '^/trans': '/'
//     }
//   }))
// }
  app.use(proxy('/trans', {
    target: `${host}/${service}`,
    secure: false,
    changeOrigin: true
  }))
  app.use(proxy('/api', { // 旷视面板机接口测试
    target: `http://192.168.1.66:80`,
    secure: false,
    changeOrigin: true
  }))
}
src/tabviews/custom/components/card/cardcellList/index.jsx
@@ -21,6 +21,7 @@
const NewPageButton = asyncComponent(() => import('@/tabviews/zshare/actionList/newpagebutton'))
const ChangeUserButton = asyncComponent(() => import('@/tabviews/zshare/actionList/changeuserbutton'))
const PrintButton = asyncComponent(() => import('@/tabviews/zshare/actionList/printbutton'))
const FuncMegvii = asyncComponent(() => import('@/tabviews/zshare/actionList/funcMegvii'))
const BarCode = asyncElementComponent(() => import('@/components/barcode'))
const QrCode = asyncElementComponent(() => import('@/components/qrcode'))
const MkProgress = asyncElementComponent(() => import('@/components/mkProgress'))
@@ -873,6 +874,21 @@
              />
            </Col>
          )
        } else if (card.funcType === 'megvii') {
          return (
            <Col key={card.uuid} className="mk-cell-btn" style={card.wrapStyle} span={card.width}>
              <FuncMegvii
                BID={data.$$BID}
                disabled={_disabled}
                lineId={data.$$key || ''}
                btn={card}
                show={card.show}
                style={card.style}
                setting={cards.setting}
                selectedData={_data}
              />
            </Col>
          )
        }
      }
    }
src/tabviews/custom/components/share/normalTable/index.jsx
@@ -224,7 +224,12 @@
      let photos = ''
      if (record[col.field]) {
        photos = `${record[col.field]}`
        photos = photos.split(',')
      }
      if (/^data:image/.test(photos)) {
        photos = [photos]
      } else {
        photos = photos.split(',').filter(Boolean)
      }
      let cols = 24 / (col.picSort || 1)
src/tabviews/zshare/actionList/excelInbutton/excelin/index.jsx
@@ -62,7 +62,11 @@
              let _name = typeof(header[op.Column]) === 'string' ? header[op.Column].replace(/(^\s*|\s*$)/g, '') : header[op.Column]
              let _text = op.Text ? op.Text.replace(/(^\s*|\s*$)/g, '') : op.Text
              
              if (_name !== _text && !iserror) {
              if (!_name && !iserror) {
                iserror = true
                errors = 'headerError'
                errDetail = `Excel中不存在(${_text})列!`
              } else if (_name !== _text && !iserror) {
                iserror = true
                errors = 'headerError'
                errDetail = `Excel中(${_name})与按钮列信息(${_text})不一致!`
src/tabviews/zshare/actionList/funcMegvii/index.jsx
New file
@@ -0,0 +1,547 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router'
import { is, fromJS } from 'immutable'
import { Button, Modal, notification, message } from 'antd'
import CryptoJS from 'crypto-js'
import moment from 'moment'
import NApi from '@/api'
import Utils from '@/utils/utils.js'
import Api from '@/api/w4k.js'
import MKEmitter from '@/utils/events.js'
import MkIcon from '@/components/mk-icon'
// import { mockdata } from './mock'
import './index.scss'
class FuncButton extends Component {
  static propTpyes = {
    btn: PropTypes.object,            // 按钮
    disabled: PropTypes.any,          // 行按钮禁用
  }
  state = {
    loading: false,
    disabled: false,
    loadingNumber: '',
    hidden: false,
    IpList: [],
    lines: [],
    selectIp: {},
    visible: false
  }
  UNSAFE_componentWillMount () {
    const { btn, selectedData } = this.props
    let disabled = false
    if (btn.controlField && selectedData && selectedData.length > 0) { // 表格中按钮隐藏控制
      selectedData.forEach(item => {
        let s = item[btn.controlField] !== undefined ? item[btn.controlField] + '' : ''
        if (btn.controlVals.includes(s)) {
          disabled = true
        }
      })
      this.setState({hidden: disabled && btn.control === 'hidden'})
    }
    if (this.props.disabled || disabled) {
      this.setState({disabled: true})
    }
  }
  componentDidMount () {
    MKEmitter.addListener('triggerBtnId', this.actionTrigger)
  }
  UNSAFE_componentWillReceiveProps (nextProps) {
    const { btn, selectedData } = this.props
    let disabled = false
    if (btn.controlField && !is(fromJS(nextProps.selectedData || []), fromJS(selectedData || []))) {
      if (nextProps.selectedData && nextProps.selectedData.length > 0) { // 表格中按钮隐藏控制
        nextProps.selectedData.forEach(item => {
          let s = item[btn.controlField] !== undefined ? item[btn.controlField] + '' : ''
          if (btn.controlVals.includes(s)) {
            disabled = true
          }
        })
      }
      this.setState({hidden: disabled && btn.control === 'hidden'})
    }
    if (nextProps.disabled || disabled) {
      this.setState({disabled: true})
    } else {
      this.setState({disabled: false})
    }
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('triggerBtnId', this.actionTrigger)
  }
  /**
   * @description 触发按钮操作
   */
  actionTrigger = (triggerId, record, type) => {
    const { Tab, BID, btn, selectedData, setting } = this.props
    const { loading, disabled } = this.state
    if (loading || disabled) return
    if (triggerId) {
      if (btn.uuid !== triggerId) return
      if (this.props.lineId && record && record[0] && this.props.lineId !== record[0].$$key) {
        return
      }
    }
    if (((Tab && Tab.supMenu) || setting.supModule) && !BID) {
      notification.warning({
        top: 92,
        message: '需要上级主键值!',
        duration: 5
      })
      return
    } else if (type === 'linkbtn' && selectedData && selectedData.length === 1) {
      if (record[0].$Index !== selectedData[0].$Index) {
        return
      }
    }
    let data = record || selectedData || []
    // let data = fromJS(mockdata.data).toJS()
    if (data.length === 0) {
      // 需要选择行时,校验数据
      notification.warning({
        top: 92,
        message: '请选择行!',
        duration: 5
      })
      return
    }
    this.setState({
      loading: true,
      lines: data
    })
    this.getIpList()
  }
  getIpList = () => {
    let _scriptSql = `select ID,data_code,data_name,Remark,face_ip,face_uname,face_pwd from bd_data where typecharone='face_device'  and deleted=0`
    _scriptSql = Utils.formatOptions(_scriptSql)
    let _sParam = {
      func: 'sPC_Get_SelectedList',
      LText: _scriptSql,
      obj_name: 'data',
      arr_field: 'ID,data_code,data_name,Remark,face_ip,face_uname,face_pwd'
    }
    _sParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
    _sParam.secretkey = Utils.encrypt(_sParam.LText, _sParam.timestamp)
    NApi.getSystemCacheConfig(_sParam).then(res => {
      if (res.status) {
        let IpList = res.data || []
        if (IpList.length === 0) {
          notification.warning({
            top: 92,
            message: '未获取到设备信息!',
            duration: 5
          })
          this.setState({
            loading: false
          })
        } else {
          this.setState({
            IpList: IpList.map(item => {
              if (item.face_ip) {
                if (!/^http:/.test(item.face_ip)) {
                  item.face_ip = 'http://' + item.face_ip
                }
                if (!/:\d{2,4}$/.test(item.face_ip)) {
                  item.face_ip = item.face_ip + ':80'
                }
              }
              return item
            }),
            selectIp: {},
            visible: true
          })
        }
      } else {
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
      }
    })
  }
  loginDevice = () => {
    const { lines, selectIp } = this.state
    // let ip = 'http://localhost:3001'
    let ip = selectIp.face_ip
    Api.login(ip, selectIp.face_uname, selectIp.face_pwd).then(result => {
      if (result.errors) {
        this.execPreError(result)
        return
      }
      document.cookie = 'sessionID=' + result.session_id
      Api.queryUsers(ip).then(res => {
        if (res.errors) {
          this.execPreError(res)
          return
        }
        let persons = {}
        res.data.forEach(item => {
          persons[item.id] = true
        })
        let data = lines.map(cell => {
          let item = {}
          Object.keys(cell).forEach(key => {
            item[key.toLowerCase()] = cell[key]
          })
          return {
            deleted: persons[item.id] || false,
            recognition_type: item.recognition_type || 'staff',
            is_admin: item.is_admin === 'true',
            person_name: item.person_name || '',
            id: item.id || '',
            card_number: item.card_number || '',
            person_code: item.person_code || '',
            group_list: item.group_list ? item.group_list.split(',') : [],
            password: item.password || '',
            id_number: item.id_number || '',
            face_list: [{
              idx: item.face_idx || 0,
              data: item.face_data ? item.face_data.replace(/^data:image\/(jpeg|png|jpg|gif);base64,/, '') : '',
            }]
          }
        })
        this.addUser(ip, data, result.session_id)
      })
    }, err => {
      this.execPreError(err)
    })
  }
  addUser = (ip, datas, sessionId) => {
    let data = datas.shift()
    this.setState({
      loadingNumber: datas.length || ''
    })
    let error = ''
    if (!data.person_name) {
      error = '人员名称不可为空'
    } else if (!data.id) {
      error = '人员id不可为空'
    } else if (data.group_list.length === 0) {
      error = '人员分组不可为空'
    } else if (!data.face_list[0].data) {
      error = '人员图片不可为空'
    } else if (data.password) {
      if (/^\d{6}$/.test(data.password)) {
        let key = CryptoJS.enc.Utf8.parse(sessionId)
        let srcs = CryptoJS.enc.Utf8.parse(data.password)
        let encrypted = CryptoJS.AES.encrypt(srcs, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7})
        data.password = CryptoJS.enc.Base64.stringify(encrypted.ciphertext)
      } else {
        error = '密码使用6位数字'
      }
    }
    if (error) {
      this.execError({ErrCode: 'E', ErrMesg: error})
      return
    }
    if (data.id_number) {
      let key = CryptoJS.enc.Utf8.parse(sessionId)
      let srcs = CryptoJS.enc.Utf8.parse(data.id_number)
      let encrypted = CryptoJS.AES.encrypt(srcs, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7})
      data.id_number = CryptoJS.enc.Base64.stringify(encrypted.ciphertext)
    }
    Api.addUsers(ip, data).then(res => {
      if (res.errors) {
        this.execPreError(res, data)
        return
      }
      if (datas.length > 0) {
        this.addUser(ip, datas, sessionId)
      } else {
        this.execSuccess({ErrCode: 'S', ErrMesg: '执行成功。'})
      }
    }, (err) => {
      this.execPreError(err, data)
    })
  }
  /**
   * @description 操作成功后处理
   * 1、excel导出,成功后取消导出按钮加载中状态
   * 2、状态码为 S 时,显示成功信息后系统默认信息
   * 3、状态码为 -1 时,不显示任何信息
   * 4、模态框执行成功后是否关闭
   * 5、通知主列表刷新
   */
  execSuccess = (res) => {
    const { btn } = this.props
    if (res && (res.ErrCode === 'S' || !res.ErrCode)) { // 执行成功
      notification.success({
        top: 92,
        message: res.ErrMesg || '执行成功',
        duration: btn.verify && btn.verify.stime ? btn.verify.stime : 2
      })
    } else if (res && res.ErrCode === 'Y') { // 执行成功
      Modal.success({
        title: res.ErrMesg || '执行成功'
      })
    } else if (res && res.ErrCode === '-1') { // 完成后不提示
    }
    this.setState({
      loading: false,
      loadingNumber: ''
    })
    if (btn.execSuccess !== 'never') {
      MKEmitter.emit('refreshByButtonResult', btn.$menuId, btn.execSuccess, btn)
    }
  }
  execPreError = (err, data) => {
    if (err.data && err.data.errors && err.data.errors[0]) {
      let error = err.data.errors[0]
      let message = error.detail
      if (error.source.pointer === '/api/persons/item' && error.error_code) {
        let list = [
          {val: 1, msg: '图片格式错误'},
          {val: 2, msg: '图片大小超出限制'},
          {val: 3, msg: '图片分辨率超出限制'},
          {val: 16, msg: '人脸无特征值'},
          {val: 17, msg: '入库无图片'},
          {val: 18, msg: '未检测到人脸'},
          {val: 19, msg: '检测到多个人脸'},
          {val: 20, msg: '人脸太小'},
          {val: 21, msg: '图片太亮'},
          {val: 22, msg: '图片太暗'},
          {val: 23, msg: '人脸太模糊'},
          {val: 24, msg: '人脸方向错误'},
          {val: 32, msg: '数据库满'},
          {val: 33, msg: '人脸图片重复'},
          {val: 34, msg: '人员编号重复'},
          {val: 35, msg: '卡号重复'},
          {val: 48, msg: '人员名称重复'},
          {val: -1, msg: '未知错误'},
          {val: -2141716476, msg: '系统参数错误,有可能是人员组不存在。'},
        ]
        list.forEach(item => {
          if (item.val === error.error_code) {
            message = (data ? data.person_name + ' - ' : '') + item.msg
          }
        })
      }
      this.execError({
        ErrCode: 'E',
        message: message
      })
    } else {
      this.execError({
        ErrCode: 'E',
        message: '请检查设备状态!'
      })
    }
  }
  /**
   * @description 操作失败后处理
   */
  execError = (res) => {
    const { btn } = this.props
    if (res.ErrCode === 'E') {
      Modal.error({
        title: res.message || res.ErrMesg,
      })
    } else if (res.ErrCode === 'N') {
      notification.error({
        top: 92,
        message: res.message || res.ErrMesg,
        duration: btn.verify && btn.verify.ntime ? btn.verify.ntime : 10
      })
    } else if (res.ErrCode === 'F') {
      notification.error({
        className: 'notification-custom-error',
        top: 92,
        message: res.message || res.ErrMesg,
        duration: btn.verify && btn.verify.ftime ? btn.verify.ftime : 10
      })
    } else if (res.ErrCode === 'NM') {
      message.error(res.message || res.ErrMesg)
    }
    this.setState({
      loading: false,
      loadingNumber: ''
    })
    if (btn.execError !== 'never') {
      MKEmitter.emit('refreshByButtonResult', btn.$menuId, btn.execError, btn)
    }
  }
  handleOk = () => {
    const { selectIp } = this.state
    if (!selectIp.ID) {
      notification.warning({
        top: 92,
        message: '请选择面板机!',
        duration: 5
      })
      return
    } else if (!selectIp.face_ip) {
      notification.warning({
        top: 92,
        message: '面板机IP缺失!',
        duration: 5
      })
      return
    } else if (!selectIp.face_uname) {
      notification.warning({
        top: 92,
        message: '面板机用户名缺失!',
        duration: 5
      })
      return
    } else if (!selectIp.face_pwd) {
      notification.warning({
        top: 92,
        message: '面板机密码缺失!',
        duration: 5
      })
      return
    }
    this.setState({
      visible: false
    })
    this.loginDevice()
  }
  getModels = () => {
    const { IpList, visible, selectIp } = this.state
    return (
      <Modal
        title="请选择面板机"
        maskClosable={false}
        getContainer={document.body}
        wrapClassName='ip-list-modal'
        visible={visible}
        width={700}
        onOk={this.handleOk}
        onCancel={() => this.setState({visible: false, loading: false})}
        destroyOnClose
      >
        {IpList.map(item => {
          return <div className={'ip-item' + (selectIp.ID === item.ID ? ' active' : '')} key={item.ID} onClick={() => {this.setState({selectIp: item})}}>
            <div className="ip">IP:{item.face_ip}</div>
            <div className="user">用户:{item.face_uname}</div>
            <div className="remark">备注:{item.Remark}</div>
          </div>
        })}
      </Modal>
    )
  }
  render() {
    const { btn, show } = this.props
    const { loading, disabled, hidden, loadingNumber } = this.state
    if (hidden) return null
    if (show === 'actionList') {
      return (
        <>
          <Button
            icon={btn.icon}
            loading={loading}
            disabled={disabled}
            title={disabled ? (btn.reason || '') : ''}
            className={'mk-btn mk-' + btn.class}
            onClick={(e) => {e.stopPropagation(); this.actionTrigger()}}
          >{(loadingNumber ? `(${loadingNumber})` : '') + btn.label}</Button>
          {this.getModels()}
        </>
      )
    } else { // icon、text、 all 卡片
      let label = ''
      let icon = ''
      if (show === 'button') {
        label = btn.label
        icon = btn.icon || ''
      } else if (show === 'link') {
        label = <span>{btn.label}{btn.icon ? <MkIcon style={{marginLeft: '8px'}} type={btn.icon}/> : ''}</span>
        icon = ''
      } else if (show === 'icon') {
        icon = btn.icon || ''
      } else {
        label = btn.label
      }
      return (
        <>
          <Button
            type="link"
            title={disabled ? (btn.reason || '') : (show === 'icon' ? btn.label : '')}
            loading={loading}
            disabled={disabled}
            style={btn.style}
            icon={icon}
            onClick={(e) => {e.stopPropagation(); this.actionTrigger()}}
          >{label}</Button>
          {this.getModels()}
        </>
      )
    }
  }
}
export default withRouter(FuncButton)
src/tabviews/zshare/actionList/funcMegvii/index.scss
New file
@@ -0,0 +1,26 @@
.ip-list-modal {
  .ip-item {
    display: inline-block;
    border: 1px solid #d9d9d9;
    padding: 10px;
    height: 100px;
    width: calc(33% - 20px);
    margin: 10px;
    cursor: pointer;
    .ip {
      color: #000000;
    }
    .remark {
      word-break: break-all;
      text-overflow: ellipsis;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 2;
      overflow: hidden;
    }
  }
  .ip-item.active {
    border-color: var(--antd-wave-shadow-color);
    box-shadow: 0 0 2px var(--antd-wave-shadow-color);
  }
}
src/tabviews/zshare/actionList/funcMegvii/mock.js
New file
@@ -0,0 +1,27 @@
export const mockdata = {
  data: [
    {
      "recognition_type": "staff",
      "id": "2226169",
      "type": "persons",
      "is_admin": "true",
      "person_name": "king",
      "card_number": "mk001",
      "person_code": "2018",
      "id_number": "130982193002054729",
      "group_list": "1",
      "face_data":""
    },
    {
      "recognition_type": "staff",
      "id": "3272487",
      "is_admin": 'false',
      "person_name": "jinfei",
      "card_number": "mk002",
      "person_code": "02",
      "id_number": "",
      "group_list": "1",
      "face_data": ""
    }
  ]
}
src/tabviews/zshare/actionList/index.jsx
@@ -14,6 +14,7 @@
const NewPageButton = asyncComponent(() => import('./newpagebutton'))
const ChangeUserButton = asyncComponent(() => import('./changeuserbutton'))
const PrintButton = asyncComponent(() => import('./printbutton'))
const FuncMegvii = asyncComponent(() => import('./funcMegvii'))
class ActionList extends Component {
  static propTpyes = {
@@ -37,7 +38,6 @@
  getButtonList = (actions) => {
    const { BID, BData, MenuID, Tab, columns, setting, ContainerId, selectedData, lock } = this.props
    return actions.map(item => {
      if (['exec', 'prompt', 'pop'].includes(item.OpenType)) {
        return (
@@ -113,7 +113,7 @@
        return (
          <NewPageButton
            key={item.uuid}
            show="actionList"
            show={item.show || 'actionList'}
            disabled={lock || false}
            btn={item}
            BData={BData}
@@ -151,6 +151,19 @@
              selectedData={selectedData}
            />
          )
        } else if (item.funcType === 'megvii') {
          return (
            <FuncMegvii
              key={item.uuid}
              show={item.show || 'actionList'}
              disabled={lock || false}
              BID={BID}
              Tab={Tab}
              btn={item}
              setting={setting}
              selectedData={selectedData}
            />
          )
        }
      }
      return null
src/tabviews/zshare/fileupload/index.jsx
@@ -39,6 +39,15 @@
    let filelist = []
    if (config.initval) {
      if (/^data:image/.test(config.initval)) {
        filelist = [{
          uid: '0',
          name: 'data:image/jpeg;base64',
          status: 'done',
          url: config.initval,
          origin: true
        }]
      } else {
      try {
        filelist = config.initval.split(',').map((url, index) => {
          return {
@@ -53,14 +62,19 @@
        filelist = []
      }
    }
    }
    let accept = ''
    let accepts = null
    let compress = false
    if (config.compress === 'true') {
    let maxFile = config.maxfile && config.maxfile > 0 ? config.maxfile : null
    if (config.compress === 'true' || config.compress === 'base64') {
      compress = true
      accepts = ['.jpg', '.png', '.gif', '.jpeg']
      accept = accepts.join(',')
      if (config.compress === 'base64') {
        maxFile = 1
      }
    } else if (config.suffix) {
      accepts = config.suffix.split(',').map(item => {
        if (!/^\./ig.test(item)) {
@@ -83,7 +97,7 @@
      filelist,
      compress,
      limit: config.limit || 2,
      maxFile: config.maxfile && config.maxfile > 0 ? config.maxfile : null,
      maxFile: maxFile,
      fileType: config.fileType || 'text'
    })
  }
@@ -281,6 +295,20 @@
          let param = {Base64Img: cvs.toDataURL('image/jpeg', compressRate)}
          if (this.props.config.compress === 'base64') {
            this.onUpdate(param.Base64Img)
            this.setState({
              percent: 100
            }, () => {
              setTimeout(() => {
                this.setState({
                  showprogress: false,
                  percent: 0
                })
              }, 200)
            })
          } else {
          if (rduri) {
            param.rduri = rduri
          }
@@ -310,6 +338,7 @@
            }
          })
        }
        }
        img.onerror = () => {
          this.onFail('图片读取失败!')
src/tabviews/zshare/normalTable/index.jsx
@@ -654,9 +654,11 @@
      let photos = ''
      if (item.field && record.hasOwnProperty(item.field)) {
        photos = record[item.field] + ''
        photos = photos.split(',').filter(Boolean)
      }
      if (/^data:image/.test(photos)) {
        photos = [photos]
      } else {
        photos = ''
        photos = photos.split(',').filter(Boolean)
      }
      let maxHeight = item.maxHeight || 128
@@ -857,7 +859,12 @@
          let photos = []
          try {
            photos = record[col.field] + ''
            if (/^data:image/.test(photos)) {
              photos = [photos]
            } else {
            photos = photos.split(',').filter(Boolean)
            }
          } catch (e) {
            photos = []
          }
src/templates/sharecomponent/actioncomponent/actionform/index.jsx
@@ -266,6 +266,8 @@
        }
      } else if (_funcType === 'closetab') {
        shows.push('refreshTab')
      } else if (_funcType === 'megvii') {
        shows.push('subFunc')
      }
    }
src/templates/sharecomponent/actioncomponent/dragaction/card.jsx
@@ -42,6 +42,8 @@
    }
  } else if (card.funcType === 'print') {
    hasProfile = true
  } else if (card.funcType === 'megvii') {
    hasProfile = true
  }
  return (
src/templates/sharecomponent/actioncomponent/index.jsx
@@ -24,6 +24,7 @@
const VerifyCard = asyncSpinComponent(() => import('@/templates/zshare/verifycard'))
const VerifyPrint = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifyprint'))
const VerifyExcelIn = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifyexcelin'))
const VerifyMegvii = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifymegvii'))
const VerifyExcelOut = asyncSpinComponent(() => import('@/templates/sharecomponent/actioncomponent/verifyexcelout'))
class ActionComponent extends Component {
@@ -858,6 +859,51 @@
    MKEmitter.removeListener('pasteButton', this.pasteButton)
  }
  getVerify = (card) => {
    const { config } = this.props
    const { dict } = this.state
    if (!card) return null
    if (['pop', 'prompt', 'exec'].includes(card.OpenType)) {
      return <VerifyCard
        card={card}
        dict={dict}
        config={config}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'excelIn') {
      return <VerifyExcelIn
        card={card}
        dict={dict}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'excelOut') {
      return <VerifyExcelOut
        card={card}
        dict={dict}
        config={config}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'funcbutton' && card.funcType === 'print') {
      return <VerifyPrint
        card={card}
        dict={dict}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    } else if (card.OpenType === 'funcbutton' && card.funcType === 'megvii') {
      return <VerifyMegvii
        card={card}
        dict={dict}
        columns={config.columns}
        wrappedComponentRef={(inst) => this.verifyRef = inst}
      />
    }
  }
  render() {
    const { config } = this.props
    const { actionlist, visible, card, dict, copying, profVisible } = this.state
@@ -921,39 +967,7 @@
          }}
          destroyOnClose
        >
          {card && !card.execMode && card.OpenType !== 'excelIn' && card.OpenType !== 'excelOut' ?
            <VerifyCard
              card={card}
              dict={dict}
              config={config}
              columns={config.columns}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {card && card.execMode ?
            <VerifyPrint
              card={card}
              dict={dict}
              columns={config.columns}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {card && card.OpenType === 'excelIn' ?
            <VerifyExcelIn
              card={card}
              dict={dict}
              columns={config.columns}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {card && card.OpenType === 'excelOut' ?
            <VerifyExcelOut
              card={card}
              dict={dict}
              config={config}
              wrappedComponentRef={(inst) => this.verifyRef = inst}
            /> : null
          }
          {this.getVerify(card)}
        </Modal>
      </div>
    )
src/templates/sharecomponent/actioncomponent/verifymegvii/index.jsx
New file
@@ -0,0 +1,249 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Tabs, Table, Row, Col, Button, notification, Modal, message, InputNumber } from 'antd'
import './index.scss'
const { TabPane } = Tabs
class MegviiCard extends Component {
  static propTpyes = {
    columns: PropTypes.array,  // 显示列
    dict: PropTypes.object,    // 字典项
    card: PropTypes.object,
  }
  state = {
    verify: {},
    activeKey: 'excelcolumn',
    excelColumns: [
      {
        title: '名称',
        dataIndex: 'field',
        width: '20%'
      },
      {
        title: '数据类型',
        dataIndex: 'datatype',
        width: '20%'
      },
      {
        title: '变量类型',
        dataIndex: 'type',
        width: '20%'
      },
      {
        title: '是否必须',
        dataIndex: 'required',
        width: '15%'
      },
      {
        title: '备注',
        dataIndex: 'remark',
        width: '25%',
      }
    ],
    excelData: [
      {field: 'id', datatype: 'string', type: 'nvarchar(50)', required: '必须', remark: '人员id'},
      {field: 'person_name', datatype: 'string', type: 'nvarchar(50)', required: '必须', remark: '人员名称'},
      {field: 'group_list', datatype: 'string', type: 'nvarchar(50)', required: '必须', remark: '绑定人员组的列表,多个使用逗号分隔,示例:\'1,2\''},
      {field: 'face_data', datatype: 'string', type: 'text', required: '必须', remark: 'Base64编码的照片数据(人脸信息)'},
      {field: 'face_idx', datatype: 'integer', type: 'int', required: '非必须', remark: '照片索引,默认为0'},
      {field: 'is_admin', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '是否启用管理员权限,默认为\'false\''},
      {field: 'recognition_type', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '人员类型,staff – 普通人员(默认),visitor – 访客,blacklist – 黑名单'},
      {field: 'password', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '密码使用6位数字(AES加密)'},
      {field: 'id_number', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '身份证号码(AES加密)'},
      {field: 'person_code', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '人员编号'},
      {field: 'card_number', datatype: 'string', type: 'nvarchar(50)', required: '非必须', remark: '卡号,最大20位数字'},
    ]
  }
  UNSAFE_componentWillMount() {
    const { card } = this.props
    let _verify = fromJS(card.verify || {}).toJS()
    this.setState({
      verify: _verify
    })
  }
  componentDidMount () {
  }
  handleConfirm = () => {
    const { verify } = this.state
    return Promise.resolve(verify)
  }
  showError = (errorType) => {
    if (errorType === 'S') {
      notification.success({
        top: 92,
        message: '执行成功!',
        duration: 2
      })
    } else if (errorType === 'Y') {
      Modal.success({
        title: '执行成功!'
      })
    } else if (errorType === 'F') {
      notification.error({
        className: 'notification-custom-error',
        top: 92,
        message: '执行失败!',
        duration: 10
      })
    } else if (errorType === 'N') {
      notification.error({
        top: 92,
        message: '执行失败!',
        duration: 10
      })
    } else if (errorType === 'E') {
      Modal.error({
        title: '执行失败!'
      })
    } else if (errorType === 'NM') {
      message.error('执行失败!')
    }
  }
  timeChange = (val, type) => {
    const { verify } = this.state
    this.setState({
      verify: {...verify, [type]: val}
    })
  }
  tabchange = (val) => {
    this.setState({activeKey: val})
  }
  render() {
    const { verify, excelColumns, excelData, activeKey } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div>
        <Tabs activeKey={activeKey} className="megvii-verify-card-box" onChange={this.tabchange}>
          <TabPane tab={
            <span>
              列设置
            </span>
          } key="excelcolumn">
            <Table
              bordered
              rowKey="field"
              className="custom-table"
              dataSource={excelData}
              columns={excelColumns}
              pagination={false}
            />
          </TabPane>
          <TabPane tab="信息提示" key="tip">
            <Form {...formItemLayout}>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> S </span>
                    <Button onClick={() => {this.showError('S')}} type="primary" size="small">
                      查看
                    </Button>
                  </Form.Item>
                </Col>
                <Col span={8}>
                  <Form.Item label={'停留时间'}>
                    <InputNumber defaultValue={verify.stime || 2} min={1} max={10000} precision={0} onChange={(val) => {this.timeChange(val, 'stime')}} />
                  </Form.Item>
                </Col>
              </Row>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> Y </span>
                    <Button onClick={() => {this.showError('Y')}} type="primary" size="small">
                      查看
                    </Button>
                  </Form.Item>
                </Col>
              </Row>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> N </span>
                    <Button onClick={() => {this.showError('N')}} type="primary" size="small">
                      查看
                    </Button>
                  </Form.Item>
                </Col>
                <Col span={8}>
                  <Form.Item label={'停留时间'}>
                    <InputNumber defaultValue={verify.ntime || 10} min={1} max={10000} precision={0} onChange={(val) => {this.timeChange(val, 'ntime')}} />
                  </Form.Item>
                </Col>
              </Row>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> F </span>
                    <Button onClick={() => {this.showError('F')}} type="primary" size="small">
                      查看
                    </Button>
                  </Form.Item>
                </Col>
                <Col span={8}>
                  <Form.Item label={'停留时间'}>
                    <InputNumber defaultValue={verify.ftime || 10} min={1} max={10000} precision={0} onChange={(val) => {this.timeChange(val, 'ftime')}} />
                  </Form.Item>
                </Col>
              </Row>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> E </span>
                    <Button onClick={() => {this.showError('E')}} type="primary" size="small">
                      查看
                    </Button>
                  </Form.Item>
                </Col>
              </Row>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> NM </span>
                    <Button onClick={() => {this.showError('NM')}} type="primary" size="small">
                      查看
                    </Button>
                  </Form.Item>
                </Col>
              </Row>
              <Row gutter={24}>
                <Col offset={6} span={6}>
                  <Form.Item label={'提示编码'}>
                    <span className="errorval"> -1 </span>
                    不提示
                  </Form.Item>
                </Col>
              </Row>
            </Form>
          </TabPane>
        </Tabs>
      </div>
    )
  }
}
export default Form.create()(MegviiCard)
src/templates/sharecomponent/actioncomponent/verifymegvii/index.scss
New file
@@ -0,0 +1,38 @@
.megvii-verify-card-box {
  .ant-tabs-nav-scroll {
    text-align: center;
  }
  .ant-tabs-content {
    min-height: 40vh;
  }
  table tr td {
    word-wrap: break-word;
    word-break: break-word;
  }
  .excel-custom-table {
    position: relative;
    top: -25px;
  }
  .errorval {
    display: inline-block;
    width: 30px;
  }
  .operation-btn {
    display: inline-block;
    font-size: 16px;
    padding: 0 5px;
    cursor: pointer;
  }
  .ant-tabs-tabpane {
    position: relative;
    .excel-col-add {
      position: relative;
      float: right;
      right: -9px;
      margin-right: 10px;
      top: 10px;
      z-index: 1;
    }
  }
}
src/templates/zshare/formconfig.jsx
@@ -953,7 +953,20 @@
      }, {
        value: 'closetab',
        text: '标签关闭'
      }, {
        value: 'megvii',
        text: '旷视面板机'
      }]
    },
    { // 旷视面板机接口 待扩展
      type: 'radio',
      key: 'subFunc',
      label: '接口名称',
      initVal: card.subFunc || 'addUser',
      required: true,
      options: [
        { value: 'addUser', text: '添加用户' },
      ]
    },
    {
      type: 'select',
@@ -3205,15 +3218,18 @@
    {
      type: 'radio',
      key: 'compress',
      label: '压缩',
      label: '文件处理',
      initVal: card.compress || 'false',
      tooltip: '文件压缩必须为图片,图片格式为jpg、png、gif 或 jpeg',
      tooltip: '文件压缩或base64必须为图片,图片格式为jpg、png、gif 或 jpeg。注:base64只可上传一张图片。',
      options: [{
        value: 'true',
        text: Formdict['model.true']
      }, {
        value: 'false',
        text: Formdict['model.false']
        text: '无'
      }, {
        value: 'true',
        text: '压缩'
      }, {
        value: 'base64',
        text: 'base64'
      }]
    },
    {
src/views/rolemanage/index.jsx
@@ -689,17 +689,17 @@
  }
  saveTree = () => {
    const { trees } = this.state
    // const { trees } = this.state
    const _this = this
    if (!trees || trees.length === 0) {
      notification.warning({
        top: 92,
        message: '未获取到权限信息!',
        duration: 5
      })
      return
    }
    // if (!trees || trees.length === 0) {
    //   notification.warning({
    //     top: 92,
    //     message: '未获取到权限信息!',
    //     duration: 5
    //   })
    //   return
    // }
    confirm({
      content: '确定执行吗?',