king
2024-04-18 8f78c97ffcde201e2a4d1ffa7ccb97a13f0f045d
2024-04-18
17个文件已修改
10个文件已添加
2291 ■■■■■ 已修改文件
src/assets/img/invoice.png 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/balcony/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/cardcellcomponent/elementform/index.jsx 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/data-card/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/double-data-card/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/card/prop-card/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/carousel/data-card/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/carousel/prop-card/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/module/invoice/index.jsx 419 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/module/invoice/index.scss 258 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/module/invoice/options.jsx 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/timeline/normal-timeline/index.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/datasource/verifycard/index.jsx 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/datasource/verifycard/settingform/index.jsx 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/datasource/verifycard/utils.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/debug/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/menushell/card.jsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modulesource/option.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/module/invoice/index.jsx 377 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/module/invoice/index.scss 260 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/module/invoice/invoiceTable/index.jsx 394 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/module/invoice/invoiceTable/index.scss 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/module/invoice/subTable/index.jsx 245 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/module/invoice/subTable/index.scss 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/tree/antd-tree/index.jsx 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/index.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelin/index.jsx 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/invoice.png
src/menu/components/card/balcony/index.scss
@@ -32,7 +32,7 @@
  }
  .card-item {
    overflow: hidden;
    // overflow: hidden;
    position: relative;
    background-color: #ffffff;
    background-position: center center;
src/menu/components/card/cardcellcomponent/elementform/index.jsx
@@ -118,12 +118,14 @@
        } else if (item.key === 'formula') {
          item.fields = []
          config.columns.forEach(col => {
            item.fields.push(col.field)
          })
          if (config.subColumns) {
            config.subColumns.forEach(col => {
          if (side === 'sub') {
            if (config.subColumns) {
              config.subColumns.forEach(col => {
                item.fields.push(col.field)
              })
            }
          } else {
            config.columns.forEach(col => {
              item.fields.push(col.field)
            })
          }
src/menu/components/card/data-card/index.scss
@@ -20,7 +20,7 @@
  }
  .card-item {
    overflow: hidden;
    // overflow: hidden;
    position: relative;
    background-color: #ffffff;
    background-position: center center;
src/menu/components/card/double-data-card/index.scss
@@ -20,7 +20,7 @@
  }
  .card-item, .card-item-wrap {
    overflow: hidden;
    // overflow: hidden;
    position: relative;
    background-color: #ffffff;
    background-position: center center;
src/menu/components/card/prop-card/index.scss
@@ -19,7 +19,7 @@
  }
  .card-item {
    overflow: hidden;
    // overflow: hidden;
    position: relative;
    background-color: #ffffff;
    background-position: center center;
src/menu/components/carousel/data-card/index.scss
@@ -19,7 +19,7 @@
  }
  .card-item {
    overflow: hidden;
    // overflow: hidden;
    position: relative;
    background-position: center center;
    background-repeat: no-repeat;
src/menu/components/carousel/prop-card/index.scss
@@ -19,7 +19,7 @@
  }
  .card-item {
    overflow: hidden;
    // overflow: hidden;
    position: relative;
    background-position: center center;
    background-repeat: no-repeat;
src/menu/components/module/invoice/index.jsx
New file
@@ -0,0 +1,419 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Popover, Button } from 'antd'
import { EditOutlined, ToolOutlined, DeleteOutlined, FontColorsOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons'
import moment from 'moment'
import Utils from '@/utils/utils.js'
import asyncIconComponent from '@/utils/asyncIconComponent'
import { getTables, checkComponent } from '@/utils/utils-custom.js'
import MKEmitter from '@/utils/events.js'
import getWrapForm from './options'
import './index.scss'
const NormalForm = asyncIconComponent(() => import('@/components/normalform'))
const SettingComponent = asyncIconComponent(() => import('@/menu/datasource'))
class Invoice extends Component {
  static propTpyes = {
    card: PropTypes.object,
    deletecomponent: PropTypes.func,
    updateConfig: PropTypes.func,
  }
  state = {
    card: null,
    date: moment().format('YYYY年MM月')
  }
  UNSAFE_componentWillMount () {
    const { card } = this.props
    if (card.isNew) {
      let _card = {
        uuid: card.uuid,
        type: card.type,
        format: 'object',   // 组件属性 - 数据格式
        pageable: false,    // 组件属性 - 是否可分页
        switchable: false,  // 组件属性 - 数据是否可切换
        width: card.width || 24,
        name: '发票',
        subtype: card.subtype,
        wrap: { name: '发票', width: card.width || 24, datatype: 'static' },
        style: { paddingLeft: '20px', paddingRight: '20px', paddingTop: '10px', paddingBottom: '10px' },
        setting: { interType: 'system' },
        columns: [],
        scripts: [],
        buyer: {
          pageable: true,
          format: 'array',
          subtype: 'invTable',
          setting: { interType: 'system' },
          columns: [],
          scripts: [],
          search: [
            {field: 'from_to_name', label: '企业名称', initval: '', type: 'text', match: 'like', uuid: Utils.getuuid()},
            {field: 'from_to_tax_no', label: '企业税号', initval: '', type: 'text', match: 'like', uuid: Utils.getuuid()},
          ],
        },
        detail: {
          pageable: true,
          format: 'array',
          subtype: 'invTable',
          setting: { interType: 'system' },
          columns: [],
          scripts: [],
          search: [
            {field: 'productname', label: '商品名称', initval: '', type: 'text', match: 'like', uuid: Utils.getuuid()},
            {field: 'productcode', label: '商品编码', initval: '', type: 'text', match: 'like', uuid: Utils.getuuid()},
          ],
        }
      }
      let buys = [
        ['企业名称', 'from_to_name'],
        ['企业税号', 'from_to_tax_no'],
        ['地址', 'from_to_addr', 'Nvarchar(100)'],
        ['电话', 'from_to_tel'],
        ['开户行', 'from_to_bank_name'],
        ['银行账号', 'from_to_account_no'],
        ['手机号', 'from_to_mob'],
        ['邮箱', 'from_to_email'],
        ['企业编码', 'from_to_code']
      ]
      buys.forEach((cell, index) => {
        _card.buyer.columns.push({$index: index + 1, datatype: 'Nvarchar(50)', field: cell[1], label: cell[0], uuid: Utils.getuuid()})
      })
      let details = [
        ['id', 'id'],
        ['商品编码', 'productcode'],
        ['商品名称', 'productname'],
        ['规格型号', 'spec'],
        ['描述', 'Description'],
        ['计量单位', 'unit'],
        ['单价', 'unitprice', 'Decimal(18,10)'],
        ['税务分类编码', 'tax_classify_code'],
        ['税务分类名称', 'tax_classify_name'],
        ['税率', 'tax_rate'],
      ]
      details.forEach((cell, index) => {
        _card.detail.columns.push({$index: index + 1, datatype: cell[2] || 'Nvarchar(50)', field: cell[1], label: cell[0], uuid: Utils.getuuid()})
      })
      let cols = [
        ['发票种类', 'invoice_type'],
        ['购买方名称', 'from_to_name'],
        ['购买方税号', 'from_to_tax_no'],
        ['购买方地址', 'from_to_addr', 'Nvarchar(100)'],
        ['购买方电话', 'from_to_tel'],
        ['购买方开户行', 'from_to_bank_name'],
        ['购买方银行账号', 'from_to_account_no'],
        ['购买方手机号', 'from_to_mob'],
        ['购买方邮箱', 'from_to_email'],
        ['购买方编码', 'from_to_code'],
        ['销售方名称', 'orgname'],
        ['销售方税号', 'tax_no'],
        ['销售方地址', 'addr', 'Nvarchar(100)'],
        ['销售方电话', 'tel'],
        ['销售方开户行', 'bank_name'],
        ['销售方银行账号', 'account_no'],
        ['备注', 'remark', 'Nvarchar(512)'],
        ['收款人', 'payee'],
        ['复核人', 'reviewer'],
        ['开票人', 'drawer'],
        ['商品编码', 'productcode'],
        ['商品名称', 'productname'],
        ['规格型号', 'spec'],
        ['计量单位', 'unit'],
        ['数量', 'bill_count', 'Decimal(18,10)'],
        ['单价', 'unitprice', 'Decimal(18,10)'],
        ['金额', 'amount_line', 'Decimal(18,2)'],
        ['税务分类编码', 'tax_classify_code'],
        ['税务分类名称', 'tax_classify_name'],
        ['税率', 'tax_rate', 'Decimal(18,2)'],
        ['税额', 'tax_amount', 'Decimal(18,2)'],
      ]
      cols.forEach((cell, index) => {
        _card.columns.push({$index: index + 1, datatype: cell[2] || 'Nvarchar(50)', field: cell[1], label: cell[0], uuid: Utils.getuuid()})
      })
      this.updateComponent(_card)
    } else {
      let _card = fromJS(card).toJS()
      // _card.buyer.format = 'array'
      // _card.detail.format = 'array'
      this.setState({
        card: _card
      })
    }
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  /**
   * @description 卡片行外层信息更新(数据源,样式等)
   */
  updateComponent = (card) => {
    card.width = card.wrap.width
    card.name = card.wrap.name
    if (window.GLOB.styling && card.errors) { // 样式修改时不做筛查
      this.setState({
        card: card
      })
      this.props.updateConfig(card)
      return
    }
    card.$c_ds = card.wrap.datatype === 'dynamic'
    card.errors = checkComponent(card)
    if (card.errors.length === 0) {
      if (card.buyer.setting.interType === 'system' && card.buyer.setting.execute !== 'false' && !card.buyer.setting.dataresource) {
        card.errors.push({ level: 0, detail: '-购买方 未设置数据源!'})
      } else if (card.buyer.setting.interType === 'system' && card.buyer.setting.execute === 'false' && card.buyer.scripts.filter(script => script.status !== 'false').length === 0) {
        card.errors.push({ level: 0, detail: '-购买方 数据源中无可用脚本!'})
      }
    }
    if (card.errors.length === 0) {
      if (card.detail.setting.interType === 'system' && card.detail.setting.execute !== 'false' && !card.detail.setting.dataresource) {
        card.errors.push({ level: 0, detail: '-明细 未设置数据源!'})
      } else if (card.detail.setting.interType === 'system' && card.detail.setting.execute === 'false' && card.detail.scripts.filter(script => script.status !== 'false').length === 0) {
        card.errors.push({ level: 0, detail: '-明细 数据源中无可用脚本!'})
      }
    }
    if (card.errors.length === 0) {
      card.$tables = getTables(card)
      card.$tables = [...card.$tables, ...getTables(card.buyer)]
      card.$tables = [...card.$tables, ...getTables(card.detail)]
    }
    this.setState({
      card: card
    })
    this.props.updateConfig(card)
  }
  changeStyle = () => {
    const { card } = this.state
    MKEmitter.emit('changeStyle', ['background', 'border', 'padding', 'margin', 'shadow', 'clear'], card.style, this.getStyle)
  }
  getStyle = (style) => {
    let _card = {...this.state.card, style}
    this.updateComponent(_card)
  }
  getWrapForms = () => {
    const { card } = this.state
    return getWrapForm(card.wrap)
  }
  updateWrap = (res) => {
    this.updateComponent({...this.state.card, wrap: res})
  }
  render() {
    const { card, date } = this.state
    let style = {...card.style}
    if (card.wrap.invColor) {
      style['--inv-color'] = card.wrap.invColor
    }
    return (
      <div className="menu-invoice-box" style={style} id={card.uuid}>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            <NormalForm title="基本设置" width={800} update={this.updateWrap} getForms={this.getWrapForms}>
              <EditOutlined style={{color: '#1890ff'}} title="编辑"/>
            </NormalForm>
            <FontColorsOutlined className="style" title="调整样式" onClick={this.changeStyle}/>
            <DeleteOutlined className="close" title="删除组件" onClick={() => this.props.deletecomponent(card.uuid)} />
            {card.wrap.datatype === 'dynamic' ? <SettingComponent config={card} updateConfig={this.updateComponent} /> : <SettingOutlined style={{color: '#eeeeee', cursor: 'not-allowed'}}/>}
          </div>
        } trigger="hover">
          <ToolOutlined />
        </Popover>
        <div className="inv-action">
          <Button className="mk-bill">保存单据</Button>
          <Button className="mk-submit">提交开票</Button>
        </div>
        <div className="inv-header">
          <div className="mk-select">请选择发票种类</div>
          <div className="date">开票日期:{date}</div>
        </div>
        <div className="inv-body">
          <div className="inv-main-content">
            <div className="inv-buyer">
              <div className="inv-label">购买方</div>
              <div className="inv-content">
                <div className="mk-input">
                  <label>名称:</label>
                  <span className="content">请输入购买方名称</span>
                  <span className="extra">
                    <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
                      <div className="mk-popover-control">
                        <SettingComponent config={card.buyer} updateConfig={(res) => this.updateComponent({...card, buyer: res})} />
                      </div>
                    } trigger="hover">
                      <EllipsisOutlined />
                    </Popover>
                  </span>
                </div>
                <div className="mk-input">
                  <label>纳税人识别号:</label>
                  <span className="content">请输入购买方纳税人识别号</span>
                </div>
                <div className="mk-input">
                  <label>地址、电话:</label>
                  <span className="content">请输入购买方地址</span>
                  <span className="content">请输入购买方电话</span>
                </div>
                <div className="mk-input">
                  <label>开户行及账号:</label>
                  <span className="content">请输入购买方开户行</span>
                  <span className="content">请输入购买方账号</span>
                </div>
              </div>
            </div>
            <div className="inv-notice">
              <div className="inv-label">通知到</div>
              <div className="inv-content">
                <div className="mk-input">
                  <label>手机号:</label>
                  <span className="content">请输入购买方手机号</span>
                </div>
                <div className="mk-input">
                  <label>邮箱:</label>
                  <span className="content">请输入购买方邮箱</span>
                </div>
              </div>
            </div>
          </div>
          <div className="inv-details">
            <div className="detail-wrap">
              <div className="mk-th">
                <div className="mk-td">货物或应税劳务、服务名称</div>
                <div className="mk-td">规格型号</div>
                <div className="mk-td">单位</div>
                <div className="mk-td">数量</div>
                <div className="mk-td">单价(含税)</div>
                <div className="mk-td">金额(含税)</div>
                <div className="mk-td">税率</div>
                <div className="mk-td">税额</div>
              </div>
              <div className="mk-tr">
                <div className="mk-td mk-left">
                  <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
                    <div className="mk-popover-control">
                      <SettingComponent config={card.detail} updateConfig={(res) => this.updateComponent({...card, detail: res})} />
                    </div>
                  } trigger="hover">
                    <EllipsisOutlined />
                  </Popover>
                </div>
                <div className="mk-td mk-left"></div>
                <div className="mk-td mk-left"></div>
                <div className="mk-td mk-right"></div>
                <div className="mk-td mk-right"></div>
                <div className="mk-td mk-right"></div>
                <div className="mk-td mk-right"></div>
                <div className="mk-td mk-right"></div>
              </div>
              <div className="mk-total">
                <div className="mk-td">合计</div>
                <div className="mk-td"></div>
                <div className="mk-td"></div>
                <div className="mk-td"></div>
                <div className="mk-td"></div>
                <div className="mk-td">¥12</div>
                <div className="mk-td"></div>
                <div className="mk-td">¥1</div>
              </div>
              <div className="mk-upcase">
                <div className="mk-td">价税合计(大写)</div>
                <div className="mk-td"></div>
                <div className="mk-td">(小写)¥12</div>
              </div>
            </div>
          </div>
          <div className="inv-main-content">
            <div className="inv-buyer">
              <div className="inv-label">销售方</div>
              <div className="inv-content">
                <div className="mk-input">
                  <label>名称:</label>
                  <span className="content">请输入销售方名称</span>
                </div>
                <div className="mk-input">
                  <label>纳税人识别号:</label>
                  <span className="content">请输入销售方纳税人识别号</span>
                </div>
                <div className="mk-input">
                  <label>地址、电话:</label>
                  <span className="content">请输入销售方地址</span>
                  <span className="content">请输入销售方电话</span>
                </div>
                <div className="mk-input">
                  <label>开户行及账号:</label>
                  <span className="content">请输入销售方开户行</span>
                  <span className="content">请输入销售方账号</span>
                </div>
              </div>
            </div>
            <div className="inv-notice">
              <div className="inv-label">备注</div>
              <div className="inv-content" style={{paddingTop: '30px'}}>
                <div className="mk-input">
                  <span className="content" style={{height: '80px'}}>请输入备注</span>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div className="inv-tail">
          <div className="mk-input">
            <label>收款人:</label>
            <span className="content">收款人</span>
          </div>
          <div className="mk-input">
            <label>复核人:</label>
            <span className="content">复核人</span>
          </div>
          <div className="mk-input">
            <label>开票人:</label>
            <span className="content">开票人</span>
          </div>
        </div>
      </div>
    )
  }
}
export default Invoice
src/menu/components/module/invoice/index.scss
New file
@@ -0,0 +1,258 @@
.menu-invoice-box {
  position: relative;
  box-sizing: border-box;
  background: #ffffff;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  color: #000000;
  --inv-color: #13509c;
  .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);
  }
  .inv-action {
    text-align: right;
    margin-right: 30px;
    .ant-btn {
      margin-left: 15px;
      margin-bottom: 5px;
      height: 30px;
    }
    .mk-bill:hover, .mk-bill:active, .mk-bill:focus {
      color: var(--mk-sys-color);
      border-color: var(--mk-sys-color);
    }
    .mk-submit, .mk-submit:hover, .mk-submit:active, .mk-submit:focus {
      color: #fff;
      background-color: var(--mk-sys-color);
      border-color: var(--mk-sys-color);
    }
  }
  .inv-header {
    text-align: center;
    position: relative;
    height: 70px;
    margin-right: 30px;
    .mk-select {
      position: relative;
      display: inline-block;
      width: 390px;
      font-size: 25px;
      text-align: center;
      font-family: kaiti;
      color: var(--inv-color, #13509c);
    }
    .mk-select::before, .mk-select::after {
      content: '';
      display: block;
      width: 100%;
      position: absolute;
      border-top: var(--inv-color, #13509c) 1px solid;
      border-bottom: var(--inv-color, #13509c) 1px solid;
      height: 1px;
    }
    .mk-select::before {
      bottom: -10px;
    }
    .mk-select::after {
      bottom: -15px;
    }
    .date {
      position: absolute;
      right: 100px;
      top: 5px;
      color: var(--inv-color, #13509c);
    }
  }
  .mk-input {
    position: relative;
    height: 28px;
    font-size: 13px;
    background: transparent;
    display: flex;
    padding: 0 5px 0 15px;
    margin-bottom: 5px;
    label {
      display: inline-block;
      height: 28px;
      line-height: 28px;
      text-align: right;
      color: var(--inv-color, #13509c);
    }
    span {
      display: inline-block;
      height: 28px;
      line-height: 28px;
    }
    .extra {
      position: absolute;
      right: 0px;
      .anticon-ellipsis {
        padding: 0 15px;
      }
    }
    .content {
      flex: 1;
      border-bottom: 1px solid #d9d9d9;
      color: #b8b8b8;
      transition: all 0.3s;
      padding: 0px 10px;
    }
  }
  .mk-input:hover .content {
    border-color: var(--inv-color, #13509c);
  }
  .inv-body {
    border: var(--inv-color, #13509c) 1px solid;
    font-size: 13px;
    margin-right: 30px;
    .inv-main-content {
      display: flex;
      .inv-buyer, .inv-notice {
        width: 50%;
        display: flex;
        .inv-label {
          color: var(--inv-color, #13509c);
          width: 6.25%;
          display: flex;
          flex-direction: column;
          writing-mode: vertical-rl;
          justify-content: center;
          align-items: center;
          letter-spacing: 5px;
          border-right: var(--inv-color, #13509c) 1px solid;
        }
        .inv-content {
          flex: 1;
          padding: 8px 0;
        }
      }
      .inv-buyer {
        border-right: var(--inv-color, #13509c) 1px solid;
        label {
          width: 95px;
          min-width: 95px;
        }
      }
      .inv-notice {
        .inv-content {
          padding-top: 45px;
        }
        label {
          width: 75px;
          min-width: 75px;
        }
      }
    }
    .inv-details {
      border-top: var(--inv-color, #13509c) 1px solid;
      border-bottom: var(--inv-color, #13509c) 1px solid;
    }
  }
  .inv-tail {
    display: flex;
    padding-top: 10px;
    margin-right: 30px;
    .mk-input {
      display: flex;
      flex: 1;
      label {
        width: 40%;
        color: rgba(0, 0, 0, 0.85);
      }
      span {
        width: 40%;
        flex: none;
      }
    }
  }
  .detail-wrap {
    position: relative;
    .mk-th, .mk-tr, .mk-total {
      position: relative;
      display: flex;
      border-bottom: var(--inv-color, #13509c) 1px solid;
      .mk-td {
        width: 10%;
        height: 40px;
        display: flex;
        justify-content: center;
        align-items: center;
        padding: 0 3px;
        position: relative;
      }
      .mk-td:not(:last-child) {
        border-right: var(--inv-color, #13509c) 1px solid;
      }
      .mk-td:first-child {
        width: 20%;
      }
      .mk-td:nth-child(2), .mk-td:nth-child(6) {
        width: 15%;
      }
      .mk-left {
        justify-content: start;
      }
      .mk-right {
        justify-content: end;
      }
      .anticon-ellipsis {
        padding: 0 10px;
        position: absolute;
        right: 0;
      }
    }
    .mk-tr.active, .mk-tr:hover {
      background: var(--mk-sys-color1);
    }
    .mk-upcase {
      display: flex;
      .mk-td {
        width: 40%;
        height: 40px;
        display: flex;
        align-items: center;
        padding: 0 20px;
      }
      .mk-td:first-child {
        width: 20%;
        padding: 0;
        border-right: var(--inv-color, #13509c) 1px solid;
        justify-content: center;
      }
      .mk-td:last-child {
        justify-content: end;
      }
    }
  }
}
.menu-invoice-box::after {
  display: block;
  content: ' ';
  clear: both;
}
.menu-invoice-box:hover {
  z-index: 1;
  box-shadow: 0px 0px 4px #1890ff;
}
src/menu/components/module/invoice/options.jsx
New file
@@ -0,0 +1,81 @@
/**
 * @description Wrap表单配置信息
 */
export default function (wrap) {
  let menu = window.GLOB.customMenu
  let books = []
  let bookids = []
  menu.components.forEach(item => {
    if (item.subtype === 'account') {
      books.push({
        value: item.uuid,
        label: item.name
      })
      bookids.push(item.uuid)
    }
  })
  const wrapForm = [
    {
      type: 'radio',
      field: 'datatype',
      label: '类型',
      initval: wrap.datatype || 'static',
      required: true,
      options: [
        {value: 'static', label: '新增发票'},
        {value: 'dynamic', label: '查看单据'},
      ]
    },
    {
      type: 'text',
      field: 'name',
      label: '组件名称',
      initval: wrap.name || '',
      tooltip: '用于组件间的区分。',
      required: true
    },
    {
      type: 'number',
      field: 'width',
      label: '宽度',
      initval: wrap.width || 24,
      tooltip: '栅格布局,每行等分为24列。',
      min: 1,
      max: 24,
      precision: 0,
      required: true
    },
    {
      type: 'select',
      field: 'supBook',
      label: '账套',
      initval: wrap.supBook || '',
      required: true,
      options: books,
      allowClear: true
    },
    {
      type: 'radio',
      field: 'business_type',
      label: '发票类型',
      initval: wrap.business_type || 'sell',
      required: true,
      options: [
        {value: 'sell', label: '销项发票'},
        {value: 'buy', label: '进项发票'},
      ]
    },
    {
      type: 'color',
      field: 'invColor',
      label: '样式',
      initval: wrap.invColor || '',
      tooltip: '发票组件中边框以及文字的颜色。',
      allowClear: true,
      required: false
    },
  ]
  return wrapForm
}
src/menu/components/timeline/normal-timeline/index.scss
@@ -23,7 +23,7 @@
  }
  .card-item {
    overflow: hidden;
    // overflow: hidden;
    position: relative;
    background-color: #ffffff;
    background-position: center center;
src/menu/datasource/verifycard/index.jsx
@@ -678,7 +678,8 @@
      if (config.subtype === 'dualdatacard') {
        _columns = [...columns, ...subColumns]
      }
      let r = SettingUtils.getDebugSql(setting, _scripts, _columns, searches, config.type)
      let r = SettingUtils.getDebugSql(setting, _scripts, _columns, searches, config.subtype)
      let _debugId = md5(r.sql)
      
@@ -958,7 +959,8 @@
  }
  copyColumns = () => {
    const { columns } = this.state
    const { columns, setting } = this.state
    let m = []
    let n = []
    let s = []
@@ -982,8 +984,27 @@
    }
    let oInput = document.createElement('input')
    oInput.value = `/*${m.join(',')}*/
      ${n.join(',')}`
    oInput.value = `create table #${setting.tableName || 'tb'}
    (${m.join(',')},sort_id INT IDENTITY(1,1))
    insert into #${setting.tableName || 'tb'}
    (${n.join(',')})
    select ${n.join(',')}
    from ${setting.dataresource ? `(${setting.dataresource}) tb` : setting.tableName || 'tb'}
    order by @orderBy@
    declare @mk_total int
    set @mk_total = 0
    select @mk_total = count(1) from #${setting.tableName || 'tb'}
      declare @pageIndex_top int
      set @pageIndex_top=(@pageIndex@-1)*@pageSize@
      if @mk_total > @pageIndex@*@pageSize@
        delete #${setting.tableName || 'tb'} where sort_id > @pageIndex@*@pageSize@
      if @pageIndex_top > 0
        delete #${setting.tableName || 'tb'} where sort_id <= @pageIndex_top`
    document.body.appendChild(oInput)
    oInput.select()
    document.execCommand('Copy')
@@ -1134,7 +1155,7 @@
              wrappedComponentRef={(inst) => this.settingForm = inst}
            /> : null}
          </TabPane>
          {config.subtype !== 'basetable' ? <TabPane tab={
          {!['basetable', 'invoice', 'invTable'].includes(config.subtype) ? <TabPane tab={
            <span>
              字段集
              {columns.length ? <span className="count-tip">{columns.length}</span> : null}
src/menu/datasource/verifycard/settingform/index.jsx
@@ -375,7 +375,7 @@
              </Form.Item>
            </Col> : null}
            {setting.interType === 'system' ? <Col span={24} className="data-source" style={{paddingLeft: '7px'}}>
              {window.GLOB.process ? <span className="process-btn" onClick={this.addProcess}>工作流</span> : null}
              {window.GLOB.process && !['invoice', 'invTable'].includes(config.subtype) ? <span className="process-btn" onClick={this.addProcess}>工作流</span> : null}
              <Form.Item labelCol={{xs: { span: 24 }, sm: { span: 2 }}} wrapperCol={ {xs: { span: 24 }, sm: { span: 22 }} } label={
                <Tooltip placement="topLeft" title={`使用系统函数时,需填写数据源。注:数据权限替换符 $@ -> /* 或 ''、 @$ -> */ 或 ''。`}>
                  <QuestionCircleOutlined className="mk-form-tip" />
@@ -409,7 +409,7 @@
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            <Col span={8}>
            {!['invTable'].includes(config.subtype) ? <Col span={8}>
              <Form.Item label="主键">
                {getFieldDecorator('primaryKey', {
                  initialValue: setting.primaryKey || ''
@@ -423,7 +423,7 @@
                  </Select>
                )}
              </Form.Item>
            </Col>
            </Col> : null}
            {/* 数组数据,需设置排序规则 */}
            {config.format === 'array' ? <Col span={8}>
              <Form.Item label={
@@ -454,7 +454,7 @@
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            {!['navbar', 'balcony', 'menubar'].includes(config.type) && (!config.wrap || config.wrap.supType !== 'multi') && MenuType !== 'billPrint' ? <Col span={8}>
            {!['balcony', 'menubar', 'commonbar', 'tabbar', 'invTable'].includes(config.subtype) && (!config.wrap || config.wrap.supType !== 'multi') && MenuType !== 'billPrint' ? <Col span={8}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={'该组件如果受其他组件控制,请选项相应的组件,没有时选“无”。'}>
                  <QuestionCircleOutlined className="mk-form-tip" />
@@ -516,7 +516,7 @@
              </Form.Item>
            </Col> : null}
            {/* 1、不分页且不存在上级模块 */}
            {!['navbar', 'interface', 'calendar'].includes(config.type) && !['editable', 'basetable', 'dualdatacard'].includes(config.subtype) && (!config.pageable || (config.pageable && setting.laypage === 'false')) && (setting.supModule.length === 0 || setting.supModule[0] === 'empty') && setting.interType === 'system' && setting.onload !== 'false' ? <Col span={8}>
            {!['navbar', 'interface', 'calendar'].includes(config.type) && !['editable', 'basetable', 'dualdatacard', 'invoice', 'invTable'].includes(config.subtype) && (!config.pageable || (config.pageable && setting.laypage === 'false')) && (setting.supModule.length === 0 || setting.supModule[0] === 'empty') && setting.interType === 'system' && setting.onload !== 'false' ? <Col span={8}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={'初始化加载时,是否与其他组件一同加载数据,注:如菜单未使用后端缓存,则查询语句大于8000字符时无效。'}>
                  <QuestionCircleOutlined className="mk-form-tip" />
@@ -533,7 +533,7 @@
                )}
              </Form.Item>
            </Col> : null}
            {!['navbar', 'balcony', 'menubar'].includes(config.type) && !['basetable'].includes(config.subtype) ? <Col span={8}>
            {!['navbar', 'balcony', 'menubar'].includes(config.type) && !['basetable', 'invoice', 'invTable'].includes(config.subtype) ? <Col span={8}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={config.type === 'interface' ? '单独搜索组件可作为公共数据源的搜索条件。' : '优先使用同级的搜索条件组件,同级搜索不存在时,依次向上选取,与当前组件的搜索条件一同用作数据过滤(当前组件的搜索条件优先)。'}>
                  <QuestionCircleOutlined className="mk-form-tip" />
src/menu/datasource/verifycard/utils.jsx
@@ -75,7 +75,7 @@
      {reg: /@sum\$|\$sum@/ig, value: ''},
    ]
    if (window.GLOB.process) {
    if (window.GLOB.process && type !== 'invoice') {
      regs.push({reg: /@works_flow_code@/ig, value: `'${getuuid()}'`})
    }
@@ -101,7 +101,7 @@
      `
    }
    if (window.GLOB.urlFields) {
    if (window.GLOB.urlFields && type !== 'invoice') {
      window.GLOB.urlFields.forEach(field => {
        let reg = new RegExp('@' + field + '@', 'ig')
        _dataresource = _dataresource.replace(reg, `'0'`)
src/menu/debug/index.jsx
@@ -1521,7 +1521,7 @@
      `
    }
    sql = `create table #${sheet} (${declarefields.join(',')},jskey nvarchar(50),BID nvarchar(50) )
    sql = `create table #${sheet} (${declarefields.join(',')},jskey nvarchar(50),BID nvarchar(50))
      Declare @UserName nvarchar(50),@FullName nvarchar(50),@RoleID nvarchar(512),@mk_departmentcode nvarchar(512),@mk_organization nvarchar(512),@mk_user_type nvarchar(20),@mk_nation nvarchar(50),@mk_province nvarchar(50),@mk_city nvarchar(50),@mk_district nvarchar(50),@mk_address nvarchar(100),@ErrorCode nvarchar(50),@retmsg nvarchar(4000),@tbid Nvarchar(512)
      
      Select  @ErrorCode='', @retmsg='', @UserName='${userName}', @FullName='${fullName}', @RoleID='${RoleID}', @mk_departmentcode='${departmentcode}', @mk_organization='${organization}', @mk_user_type='${mk_user_type}', @mk_nation='${nation}', @mk_province='${province}', @mk_city='${city}', @mk_district='${district}', @mk_address='${address}'
src/menu/menushell/card.jsx
@@ -30,6 +30,7 @@
const Timeline = asyncComponent(() => import('@/menu/components/timeline/normal-timeline'))
const Voucher = asyncComponent(() => import('@/menu/components/module/voucher'))
const Account = asyncComponent(() => import('@/menu/components/module/account'))
const Invoice = asyncComponent(() => import('@/menu/components/module/invoice'))
const Iframe = asyncComponent(() => import('@/menu/components/iframe'))
const AntvG6 = asyncComponent(() => import('@/menu/components/chart/antv-G6'))
const AntvX6 = asyncComponent(() => import('@/menu/components/chart/antv-X6'))
@@ -135,6 +136,8 @@
      return (<Voucher card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'module' && card.subtype === 'account') {
      return (<Account card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'module' && card.subtype === 'invoice') {
      return (<Invoice card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
  }
src/menu/modulesource/option.jsx
@@ -32,6 +32,7 @@
import Voucher from '@/assets/mobimg/voucher.png'
import Account from '@/assets/mobimg/account.png'
import canlendar from '@/assets/mobimg/canlendar.png'
import Invoice from '@/assets/img/invoice.png'
// 组件配置信息
export const menuOptions = [
@@ -72,4 +73,5 @@
  { type: 'menu', url: Iframe, component: 'iframe', subtype: 'iframe', title: 'iframe', width: 24, forbid: ['billPrint'] },
  { type: 'menu', url: Account, component: 'module', subtype: 'account', title: '账套', width: 24, forbid: ['billPrint'] },
  { type: 'menu', url: Voucher, component: 'module', subtype: 'voucher', title: '凭证', width: 24, forbid: ['billPrint'] },
  { type: 'menu', url: Invoice, component: 'module', subtype: 'invoice', title: '发票', width: 24, forbid: ['billPrint'] },
]
src/tabviews/custom/components/module/invoice/index.jsx
New file
@@ -0,0 +1,377 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Select, Form, Input, Button, Modal } from 'antd'
import { EllipsisOutlined } from '@ant-design/icons'
import moment from 'moment'
// import Api from '@/api'
// import Utils from '@/utils/utils.js'
import MKEmitter from '@/utils/events.js'
import InvoiceTable from './invoiceTable'
import SubTable from './subTable'
import './index.scss'
class InvoiceModule extends Component {
  static propTpyes = {
    config: PropTypes.object
  }
  state = {
    BID: '',
    invTypes: [
      {value: '1', label: '电子发票(增值税专用发票)'},
      {value: '2', label: '电子发票(普通发票)'},
      {value: '3', label: '增值税纸质专用发票'},
      {value: '4', label: '增值税纸质普通发票'},
      {value: '5', label: '增值税电子普通发票'},
      {value: '6', label: '增值税电子专用发票'},
    ],
    invoice_type: '',
    date: moment().format('YYYY年MM月DD日'),
    from_to_name: '',
    from_to_tax_no: '',
    from_to_addr: '',
    from_to_tel: '',
    from_to_bank_name: '',
    from_to_account_no: '',
    from_to_mob: '',
    from_to_email: '',
    from_to_code: '',
    orgname: '',
    tax_no: '',
    addr: '',
    tel: '',
    bank_name: '',
    account_no: '',
    remark: '',
    payee: '',
    reviewer: '',
    drawer: '',
    details: []
  }
  UNSAFE_componentWillMount () {
    const { config } = this.props
    let _config = fromJS(config).toJS()
    let BID = ''
    let BData = ''
    if (_config.setting.supModule) {
      BData = window.GLOB.CacheData.get(_config.setting.supModule)
    } else {
      BData = window.GLOB.CacheData.get(_config.$pageId)
    }
    if (BData) {
      BID = BData.$BID || ''
    }
    if (_config.wrap.invColor) {
      _config.style['--inv-color'] = _config.wrap.invColor
    }
    _config.buyer = this.formatSetting(_config.buyer, 'buyer')
    _config.detail = this.formatSetting(_config.detail, 'detail')
    this.setState({
      BID: BID || '',
      config: _config
    })
  }
  componentDidMount () {
    this.loadData()
    MKEmitter.addListener('reloadData', this.reloadData)
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('reloadData', this.reloadData)
  }
  formatSetting = (item, type) => {
    item.setting.arr_field = item.columns.map(col => col.field).join(',')
    item.setting.laypage = item.setting.laypage === 'true'
    if (type === 'buyer') {
      item.columns = item.columns.map(cell => {
        if (['from_to_tel', 'from_to_account_no', 'from_to_code'].includes(cell.field)) {
          cell.Hide = 'true'
        } else if (['from_to_email', 'from_to_mob'].includes(cell.field)) {
          cell.Width = 80
        }
        return cell
      })
    } else {
      item.columns = item.columns.map(cell => {
        if (['Description', 'id'].includes(cell.field)) {
          cell.Hide = 'true'
        } else if (['spec'].includes(cell.field)) {
          cell.Width = 150
        } else if (['unit', 'unitprice', 'tax_rate'].includes(cell.field)) {
          cell.Width = 80
        }
        return cell
      })
    }
    if (item.setting.interType !== 'system') {
      item.setting.dataresource = ''
      return item
    }
    let regs = [
      { reg: /@userName@/ig, value: `'${sessionStorage.getItem('User_Name') || ''}'` },
      { reg: /@fullName@/ig, value: `'${sessionStorage.getItem('Full_Name') || ''}'` }
    ]
    if (window.GLOB.externalDatabase !== null) {
      regs.push({
        reg: /@db@/ig,
        value: window.GLOB.externalDatabase
      })
    }
    let _customScript = ''
    let _tailScript = ''
    item.scripts && item.scripts.forEach(script => {
      if (script.status === 'false') return
      if (script.position !== 'back') {
        _customScript += `
        ${script.sql}
        `
      } else {
        _tailScript += `
        ${script.sql}
        `
      }
    })
    delete item.scripts
    item.setting.execute = item.setting.execute !== 'false'
    if (!item.setting.execute) {
      item.setting.dataresource = ''
    }
    if (/\s/.test(item.setting.dataresource)) {
      item.setting.dataresource = '(' + item.setting.dataresource + ') tb'
    }
    if (sessionStorage.getItem('dataM') === 'true') { // 数据权限
      item.setting.dataresource = item.setting.dataresource.replace(/\$@/ig, '/*').replace(/@\$/ig, '*/').replace(/@datam@/ig, '\'Y\'')
      _customScript = _customScript.replace(/\$@/ig, '/*').replace(/@\$/ig, '*/').replace(/@datam@/ig, '\'Y\'')
      _tailScript = _tailScript.replace(/\$@/ig, '/*').replace(/@\$/ig, '*/').replace(/@datam@/ig, '\'Y\'')
    } else {
      item.setting.dataresource = item.setting.dataresource.replace(/@\$|\$@/ig, '').replace(/@datam@/ig, '\'\'')
      _customScript = _customScript.replace(/@\$|\$@/ig, '').replace(/@datam@/ig, '\'\'')
      _tailScript = _tailScript.replace(/@\$|\$@/ig, '').replace(/@datam@/ig, '\'\'')
    }
    regs.forEach(cell => {
      item.setting.dataresource = item.setting.dataresource.replace(cell.reg, cell.value)
      _customScript = _customScript.replace(cell.reg, cell.value)
      _tailScript = _tailScript.replace(cell.reg, cell.value)
    })
    item.setting.customScript = _customScript // 整理后自定义脚本
    item.setting.tailScript = _tailScript     // 后置自定义脚本
    item.setting.custompage = /@pageSize@|@orderBy@/i.test(item.setting.dataresource + item.setting.customScript)
    return item
  }
  reloadData = (menuId) => {
    // const { config } = this.props
    // const { activeItem } = this.state
    // if (config.uuid !== menuId) return
    // if (activeItem) {
    //   MKEmitter.emit('resetSelectLine', config.uuid, activeItem.id, activeItem)
    // } else {
    //   this.loadData()
    // }
  }
  loadData = () => {
    // let param = {
    //   func: 's_get_fcc_book_data',
    //   dataM: sessionStorage.getItem('dataM') === 'true' ? 'Y' : '',
    //   mk_organization: sessionStorage.getItem('organization') || ''
    // }
    // Api.genericInterface(param).then(res => {
    //   if (!res.status) {
    //     notification.warning({
    //       top: 92,
    //       message: res.message,
    //       duration: 5
    //     })
    //     return
    //   }
    //   let books = res.book || []
    //   let activeItem = null
    //   let map = new Map()
    //   books = books.filter(item => {
    //     if (!item.id) return false
    //     if (map.has(item.id)) return false
    //     map.set(item.id, true)
    //     if (item.selected === 'true' && !activeItem) {
    //       activeItem = item
    //     }
    //     if (item.months) {
    //       item.date = item.months.replace('-', '年') + '月'
    //     }
    //     return true
    //   })
    //   this.setState({books, activeItem})
    //   if (activeItem) {
    //     MKEmitter.emit('resetSelectLine', this.props.config.uuid, activeItem.id, activeItem)
    //   }
    // })
  }
  changeType = (val) => {
    this.setState({invoice_type: val})
  }
  changeBuyer = (item) => {
    this.setState({
      visible: false,
      from_to_name: item.from_to_name || '',
      from_to_tax_no: item.from_to_tax_no || '',
      from_to_addr: item.from_to_addr || '',
      from_to_tel: item.from_to_tel || '',
      from_to_bank_name: item.from_to_bank_name || '',
      from_to_account_no: item.from_to_account_no || '',
      from_to_mob: item.from_to_mob || '',
      from_to_email: item.from_to_email || '',
      from_to_code: item.from_to_code || '',
    })
  }
  render() {
    const { config, invTypes, date, from_to_name, from_to_tax_no, from_to_addr, from_to_tel, from_to_bank_name, from_to_account_no, from_to_mob, from_to_email, orgname, tax_no, addr, tel, bank_name, account_no, remark, reviewer, drawer, payee, details, visible } = this.state
    return (
      <div className="menu-invoice-wrap" style={config.style}>
        <div className="inv-action">
          <Button className="mk-bill">保存单据</Button>
          <Button className="mk-submit">提交开票</Button>
        </div>
        <div className="inv-header">
          <Select placeholder="请选择发票种类" onChange={this.changeType} dropdownClassName="inv-type-select">
            {invTypes.map(item => (
              <Select.Option key={item.value} value={item.value}>{item.label}</Select.Option>
            ))}
          </Select>
          <div className="date">开票日期:{date}</div>
        </div>
        <div className="inv-body">
          <div className="inv-main-content">
            <div className="inv-buyer">
              <div className="inv-label">购买方</div>
              <div className="inv-content">
                <Form.Item label={<>名<span></span>称</>} extra={<EllipsisOutlined onClick={() => this.setState({visible: true})}/>}>
                  <Input placeholder="请输入购买方名称" allowClear value={from_to_name} autoComplete="off" onChange={(e) => this.setState({from_to_name: e.target.value})}/>
                </Form.Item>
                <Form.Item label="纳税人识别号">
                  <Input placeholder="请输入购买方纳税人识别号" allowClear value={from_to_tax_no} autoComplete="off" onChange={(e) => this.setState({from_to_tax_no: e.target.value})}/>
                </Form.Item>
                <Form.Item className="mutil-input" label={<>地<span></span>址<span></span>、<span></span>电<span></span>话</>}>
                  <Input placeholder="请输入购买方地址" allowClear value={from_to_addr} autoComplete="off" onChange={(e) => this.setState({from_to_addr: e.target.value})}/>
                  <Input placeholder="请输入购买方电话" allowClear value={from_to_tel} autoComplete="off" onChange={(e) => this.setState({from_to_tel: e.target.value})}/>
                </Form.Item>
                <Form.Item className="mutil-input" label="开户行及账号">
                  <Input placeholder="请输入购买方开户行" allowClear value={from_to_bank_name} autoComplete="off" onChange={(e) => this.setState({from_to_bank_name: e.target.value})}/>
                  <Input placeholder="请输入购买方账号" allowClear value={from_to_account_no} autoComplete="off" onChange={(e) => this.setState({from_to_account_no: e.target.value})}/>
                </Form.Item>
              </div>
            </div>
            <div className="inv-notice">
              <div className="inv-label">通知到</div>
              <div className="inv-content">
                <Form.Item label={<>手<span></span>机<span></span>号</>}>
                  <Input placeholder="请输入购买方手机号" allowClear value={from_to_mob} autoComplete="off" onChange={(e) => this.setState({from_to_mob: e.target.value})}/>
                </Form.Item>
                <Form.Item label={<>邮<span></span>箱</>}>
                  <Input placeholder="请输入购买方邮箱" allowClear value={from_to_email} autoComplete="off" onChange={(e) => this.setState({from_to_email: e.target.value})}/>
                </Form.Item>
              </div>
            </div>
          </div>
          <div className="inv-details">
            <InvoiceTable data={details} config={config.detail} onChange={(details) => this.setState({details})}/>
          </div>
          <div className="inv-main-content">
            <div className="inv-buyer">
              <div className="inv-label">销售方</div>
              <div className="inv-content">
                <Form.Item label={<>名<span></span>称</>}>
                  <Input placeholder="请输入销售方名称" value={orgname} autoComplete="off" onChange={(e) => this.setState({orgname: e.target.value})}/>
                </Form.Item>
                <Form.Item label="纳税人识别号">
                  <Input placeholder="请输入销售方纳税人识别号" disabled value={tax_no} autoComplete="off"/>
                </Form.Item>
                <Form.Item className="mutil-input" label={<>地<span></span>址<span></span>、<span></span>电<span></span>话</>}>
                  <Input placeholder="请输入销售方地址" value={addr} autoComplete="off" onChange={(e) => this.setState({addr: e.target.value})}/>
                  <Input placeholder="请输入销售方电话" value={tel} autoComplete="off" onChange={(e) => this.setState({tel: e.target.value})}/>
                </Form.Item>
                <Form.Item className="mutil-input" label="开户行及账号">
                  <Input placeholder="请输入销售方开户行" value={bank_name} autoComplete="off" onChange={(e) => this.setState({bank_name: e.target.value})}/>
                  <Input placeholder="请输入销售方账号" value={account_no} autoComplete="off" onChange={(e) => this.setState({account_no: e.target.value})}/>
                </Form.Item>
              </div>
            </div>
            <div className="inv-notice">
              <div className="inv-label">备注</div>
              <div className="inv-content" style={{paddingTop: '30px'}}>
                <Form.Item label="">
                  <Input.TextArea placeholder="请输入备注" autoSize={{ minRows: 4, maxRows: 4 }} value={remark} autoComplete="off" onChange={(e) => this.setState({remark: e.target.value})}/>
                </Form.Item>
              </div>
            </div>
          </div>
        </div>
        <div className="inv-tail">
          <Form.Item label="收款人">
            <Input placeholder="收款人" value={payee} autoComplete="off" onChange={(e) => this.setState({payee: e.target.value})}/>
          </Form.Item>
          <Form.Item label="复核人">
            <Input placeholder="复核人" value={reviewer} autoComplete="off" onChange={(e) => this.setState({reviewer: e.target.value})}/>
          </Form.Item>
          <Form.Item label="开票人">
            <Input placeholder="开票人" value={drawer} autoComplete="off" onChange={(e) => this.setState({drawer: e.target.value})}/>
          </Form.Item>
        </div>
        <Modal
          title="客户信息"
          visible={visible}
          width="70vw"
          maskClosable={false}
          onCancel={() => { this.setState({ visible: false }) }}
          footer={null}
        >
          <SubTable config={config.buyer} onChange={this.changeBuyer}/>
        </Modal>
      </div>
    )
  }
}
export default InvoiceModule
src/tabviews/custom/components/module/invoice/index.scss
New file
@@ -0,0 +1,260 @@
.menu-invoice-wrap {
  position: relative;
  box-sizing: border-box;
  background: #ffffff;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  color: #000000;
  --inv-color: #13509c;
  .inv-action {
    text-align: right;
    margin-right: 30px;
    .ant-btn {
      margin-left: 15px;
      margin-bottom: 5px;
      height: 30px;
    }
    .mk-bill:hover, .mk-bill:active, .mk-bill:focus {
      color: var(--mk-sys-color);
      border-color: var(--mk-sys-color);
    }
    .mk-submit, .mk-submit:hover, .mk-submit:active, .mk-submit:focus {
      color: #fff;
      background-color: var(--mk-sys-color);
      border-color: var(--mk-sys-color);
    }
  }
  .inv-header {
    text-align: center;
    position: relative;
    height: 70px;
    margin-right: 30px;
    .ant-select {
      width: 390px;
      border: none;
      .ant-select-selection {
        border: none;
        box-shadow: none;
        font-size: 25px;
        text-align: center;
        background-color: transparent;
        .ant-select-selection-selected-value {
          float: none;
          text-align: center;
          font-family: kaiti;
          color: var(--inv-color, #13509c);
        }
        .ant-select-selection__placeholder {
          text-align: center;
          font-family: kaiti;
        }
      }
    }
    .ant-select::before, .ant-select::after {
      content: '';
      display: block;
      width: 100%;
      position: absolute;
      border-top: var(--inv-color, #13509c) 1px solid;
      border-bottom: var(--inv-color, #13509c) 1px solid;
      height: 1px;
    }
    .ant-select::before {
      bottom: -10px;
    }
    .ant-select::after {
      bottom: -15px;
    }
    .date {
      position: absolute;
      right: 100px;
      top: 5px;
      color: var(--inv-color, #13509c);
    }
  }
  .ant-input {
    border-top: none;
    border-left: none;
    border-right: none;
    border-radius: 0;
    box-shadow: none!important;
    height: 28px;
    font-size: 13px;
    background: transparent;
  }
  .ant-input:hover, .ant-input:active, .ant-input:focus {
    border-color: var(--inv-color, #13509c)!important;
  }
  .ant-input.ant-input-disabled {
    cursor: text;
    background: transparent!important;
  }
  .ant-input-number {
    border-top: none;
    border-left: none;
    border-right: none;
    border-radius: 0;
    box-shadow: none!important;
    height: 28px;
    font-size: 13px;
    .ant-input-number-handler-wrap {
      display: none;
    }
  }
  .ant-input-number:hover, .ant-input-number:active, .ant-input-number:focus {
    border-color: var(--inv-color, #13509c)!important;
  }
  .inv-body {
    border: var(--inv-color, #13509c) 1px solid;
    font-size: 13px;
    margin-right: 30px;
    .inv-main-content {
      display: flex;
      .inv-buyer, .inv-notice {
        width: 50%;
        display: flex;
        .inv-label {
          color: var(--inv-color, #13509c);
          width: 6.25%;
          display: flex;
          flex-direction: column;
          writing-mode: vertical-rl;
          justify-content: center;
          align-items: center;
          letter-spacing: 5px;
          border-right: var(--inv-color, #13509c) 1px solid;
        }
        .inv-content {
          flex: 1;
          padding: 6px 0;
          .ant-form-item {
            display: flex;
            padding: 0 5px 0 15px;
            font-size: 13px;
            margin-bottom: 5px;
            .ant-form-item-label {
              line-height: 30px;
              text-align: justify;
              label {
                display: flex;
                color: var(--inv-color, #13509c);
                span {
                  display: inline-block;
                  flex: 1;
                }
              }
            }
            .ant-form-item-control-wrapper {
              width: 100%;
              .ant-form-item-control {
                line-height: 30px;
                .ant-form-extra {
                  position: absolute;
                  top: 1px;
                  right: 10px;
                  font-size: 16px;
                  .anticon {
                    cursor: pointer;
                  }
                }
                .ant-input-affix-wrapper:not(:hover) {
                  .ant-input-suffix {
                    opacity: 0;
                  }
                }
              }
            }
          }
          .ant-form-item.mutil-input {
            .ant-form-item-children {
              display: flex;
            }
          }
        }
      }
      .inv-buyer {
        border-right: var(--inv-color, #13509c) 1px solid;
        .ant-form-item-label {
          width: 95px;
          min-width: 95px;
        }
      }
      .inv-notice {
        .inv-content {
          padding-top: 45px;
        }
        .ant-form-item-label {
          width: 75px;
          min-width: 75px;
        }
      }
    }
    .inv-details {
      border-top: var(--inv-color, #13509c) 1px solid;
      border-bottom: var(--inv-color, #13509c) 1px solid;
    }
  }
  .inv-tail {
    display: flex;
    padding-top: 10px;
    margin-right: 30px;
    .ant-form-item {
      flex: 1;
      display: flex;
      font-size: 13px;
      .ant-form-item-label, .ant-form-item-control-wrapper {
        width: 40%;
      }
    }
  }
}
.inv-table {
  .ant-table .ant-table-tbody tr:hover td {
    background-color: var(--mk-sys-color1);
  }
}
.tb-search-wrap {
  .search-item {
    display: flex;
    float: left;
    width: 350px;
    align-items: center;
    justify-content: flex-end;
    .ant-input {
      width: 200px;
    }
  }
  .ant-btn {
    margin-bottom: 20px;
    margin-left: 50px;
    background-color: var(--mk-sys-color);
    border-color: var(--mk-sys-color);
    color: #ffffff;
  }
  .ant-btn:hover, .ant-btn:active {
    background-color: var(--mk-sys-color);
    border-color: var(--mk-sys-color);
    color: #ffffff;
    opacity: 0.9;
  }
}
.inv-type-select {
  .ant-select-dropdown-menu-item {
    text-align: center;
  }
}
src/tabviews/custom/components/module/invoice/invoiceTable/index.jsx
New file
@@ -0,0 +1,394 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Input, InputNumber, notification, Modal } from 'antd'
import { EllipsisOutlined } from '@ant-design/icons'
import Utils from '@/utils/utils.js'
import MKEmitter from '@/utils/events.js'
import SubTable from '../subTable'
import './index.scss'
class DetailLine extends React.Component {
  state = {
    bill_count: 0,
    unitprice: 0,
    amount_line: 0,
    visible: false
  }
  UNSAFE_componentWillMount() {
    let { line } = this.props
    this.setState({
      bill_count: line.bill_count || '',
      unitprice: line.unitprice || '',
      amount_line: line.amount_line || ''
    })
  }
  onChange = (value, key) => {
    let line = {...this.props.line}
    if (['bill_count', 'unitprice', 'amount_line'].includes(key)) {
      line[key] = value || 0
      if (line[key]) {
        if (key === 'bill_count') {
          line[key] = Math.round(line[key] * 10000000000) / 10000000000
          if (line.unitprice) {
            line.amount_line = Math.round(line.unitprice * line.bill_count * 100) / 100
          }
        } else if (key === 'unitprice') {
          line[key] = Math.round(line[key] * 10000000000) / 10000000000
          if (line.bill_count) {
            line.amount_line = Math.round(line.unitprice * line.bill_count * 100) / 100
          }
        } else if (key === 'amount_line') {
          line[key] = Math.round(line[key] * 100) / 100
          if (line.bill_count) {
            line.unitprice = Math.round(line.amount_line / line.bill_count * 10000000000) / 10000000000
          } else if (line.unitprice) {
            line.bill_count = Math.round(line.amount_line / line.unitprice * 10000000000) / 10000000000
          }
        }
      }
    } else {
      line[key] = value
    }
    this.setState({
      bill_count: line.bill_count || '',
      unitprice: line.unitprice || '',
      amount_line: line.amount_line || ''
    })
    this.props.changeLine(line, key)
  }
  render() {
    const { line, delLine, trigger } = this.props
    const { bill_count, unitprice, amount_line } = this.state
    return <div className="mk-tr active">
      <div className="mk-td">
        <div className="mk-input">{line.productname || ''}<EllipsisOutlined onClick={trigger}/></div>
      </div>
      <div className="mk-td">
        <Input defaultValue={line.spec || ''} onChange={(e) => this.onChange(e.target.value, 'spec')}/>
      </div>
      <div className="mk-td">
        <Input defaultValue={line.unit || ''} onChange={(e) => this.onChange(e.target.value, 'unit')}/>
      </div>
      <div className="mk-td">
        <InputNumber value={bill_count} onChange={(val) => this.setState({bill_count: val})} onBlur={(e) => this.onChange(e.target.value, 'bill_count')}/>
      </div>
      <div className="mk-td">
        <InputNumber value={unitprice} onChange={(val) => this.setState({unitprice: val})} onBlur={(e) => this.onChange(e.target.value, 'unitprice')}/>
      </div>
      <div className="mk-td">
        <InputNumber value={amount_line} onChange={(val) => this.setState({amount_line: val})} onBlur={(e) => this.onChange(e.target.value, 'amount_line')}/>
      </div>
      <div className="mk-td">{line.tax_name}</div>
      <div className="mk-td mk-right">{line.tax_amount} <span className="del-line" onClick={() => delLine(line.uuid)}></span> </div>
    </div>
  }
}
class InvoiceTable extends Component {
  static propTpyes = {
    config: PropTypes.object,
    data: PropTypes.any,
    onChange: PropTypes.func
  }
  state = {
    data: [],
    editKey: '',
    total: {}
  }
  UNSAFE_componentWillMount () {
    const { data } = this.props
    let _data = fromJS(data).toJS()
    if (!_data.length) {
      _data = [{uuid: Utils.getguid(), productname: '', spec: '', unit: '', bill_count: '', unitprice: 0, amount_line: 0, tax_rate: '', tax_name: '', tax_amount: 0}]
    }
    this.setState({
      data: _data
    }, () => {
      this.getTotal(_data)
    })
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  componentDidMount () {
    MKEmitter.addListener('resetDetails', this.resetDetails)
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('resetDetails', this.resetDetails)
  }
  changeMoneyToChinese = (money) => {
    let cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
    let cnIntRadice = ['', '拾', '佰', '仟']
    let cnIntUnits = ['', '万', '亿', '兆']
    let cnDecUnits = ['角', '分', '毫', '厘']
    let cnInteger = '整'
    let cnIntLast = '元'
    let maxNum = 999999999999999.9999 // 最大处理的数字
    let IntegerNum = null
    let DecimalNum = null
    let ChineseStr = ''
    let parts = null // 分离金额后用的数组,预定义
    let Symbol = ''  // 正负值标记
    if (money === 0) return ''
    if (money >= maxNum) return '超出最大处理数字'
    if(money < 0) {
      money = -money
      Symbol = '负'
    }
    money = money.toString() // 转换为字符串
    if (money.indexOf('.') === -1) {
      IntegerNum = money
      DecimalNum = ''
    } else {
      parts = money.split('.')
      IntegerNum = parts[0]
      DecimalNum = parts[1].substr(0, 4)
    }
    if (parseInt(IntegerNum, 10) > 0) { // 获取整型部分转换
      let zeroCount = 0
      let IntLen = IntegerNum.length
      for (let i = 0; i < IntLen; i++) {
        let n = IntegerNum.substr(i, 1)
        let p = IntLen - i - 1
        let q = p / 4
        let m = p % 4
        if (n === '0') {
          zeroCount++
        } else {
          if (zeroCount > 0) {
            ChineseStr += cnNums[0]
          }
          zeroCount = 0 // 归零
          ChineseStr += cnNums[parseInt(n)] + cnIntRadice[m]
        }
        if (m === 0 && zeroCount < 4) {
          ChineseStr += cnIntUnits[q]
        }
      }
      ChineseStr += cnIntLast
    }
    if (DecimalNum !== '') { // 小数部分
      let decLen = DecimalNum.length
      for (let i = 0; i < decLen; i++) {
        let n = DecimalNum.substr(i, 1)
        if (n !== '0') {
          ChineseStr += cnNums[Number(n)] + cnDecUnits[i]
        }
      }
    }
    if (ChineseStr === '') {
      ChineseStr += cnNums[0] + cnIntLast + cnInteger
    } else if (DecimalNum === '') {
      ChineseStr += cnInteger
    }
    ChineseStr = Symbol + ChineseStr
    return ChineseStr
  }
  getTotal = (data) => {
    let price = 0
    let tax = 0
    data.forEach(item => {
      if (!item.productcode) return
      price += item.amount_line
      tax += item.tax_amount
    })
    this.setState({total: {price, tax, sum: price + tax, sumName: this.changeMoneyToChinese(price + tax)}})
  }
  resetDetails = (data) => {
    let _data = fromJS(data).toJS()
    if (!_data.length) {
      _data = [{uuid: Utils.getguid(), productname: '', spec: '', unit: '', bill_count: '', unitprice: 0, amount_line: 0, tax_rate: '', tax_name: '', tax_amount: 0}]
    }
    this.setState({data: _data}, () => {
      this.getTotal(_data)
    })
  }
  addLine = () => {
    const { data } = this.state
    let line = {uuid: Utils.getguid(), productname: '', spec: '', unit: '', bill_count: '', unitprice: 0, amount_line: 0, tax_rate: '', tax_name: '', tax_amount: 0}
    this.setState({data: [...data, line]})
  }
  delLine = () => {
    const { editKey, data } = this.state
    if (data.length === 1) {
      notification.warning({
        top: 92,
        message: '至少保留一行明细!',
        duration: 3
      })
      return
    }
    let _data = data.filter(item => item.uuid !== editKey)
    this.setState({data: _data}, () => {
      this.getTotal(_data)
    })
    this.props.onChange(_data)
  }
  changeLine = (record) => {
    const { editKey, data } = this.state
    let _data = data.map(item => {
      if (item.uuid === editKey) {
        return record
      } else {
        return item
      }
    })
    this.setState({data: _data}, () => {
      this.getTotal(_data)
    })
    this.props.onChange(_data)
  }
  checkLine = (uuid) => {
    this.setState({editKey: uuid})
  }
  changeDetail = (prod) => {
    const { editKey, data } = this.state
    let _data = data.map(item => {
      if (item.uuid === editKey) {
        item.productname = prod.productname
        item.spec = prod.spec
        item.unit = prod.unit
        item.unitprice = prod.unitprice
        item.tax_rate = prod.tax_rate
        item.tax_name = prod.tax_rate
        item.productcode = prod.productcode
        item.Description = prod.Description
        item.tax_classify_code = prod.tax_classify_code
        item.tax_classify_name = prod.tax_classify_name
        if (item.bill_count && item.unitprice) {
          item.amount_line = Math.round(item.unitprice * item.bill_count * 100) / 100
        }
        // item.tax_amount = prod.productname
      }
      return item
    })
    this.setState({data: _data, editKey: '', visible: false}, () => {
      this.setState({editKey: editKey})
      this.getTotal(_data)
    })
    this.props.onChange(_data)
  }
  render() {
    const { config } = this.props
    const { editKey, data, total, visible } = this.state
    return (
      <div className="detail-wrap">
        <span className="plus-line" onClick={this.addLine}></span>
        <div className="mk-th">
          <div className="mk-td">货物或应税劳务、服务名称</div>
          <div className="mk-td">规格型号</div>
          <div className="mk-td">单位</div>
          <div className="mk-td">数量</div>
          <div className="mk-td">单价(含税)</div>
          <div className="mk-td">金额(含税)</div>
          <div className="mk-td">税率</div>
          <div className="mk-td">税额</div>
        </div>
        {data.map(item => {
          if (editKey === item.uuid) {
            return <DetailLine key={item.uuid} line={item} changeLine={this.changeLine} delLine={this.delLine} trigger={() => this.setState({visible: true})}/>
          }
          return <div className="mk-tr" key={item.uuid} onClick={() => this.checkLine(item.uuid)}>
            <div className="mk-td mk-left">{item.productname || '**'}</div>
            <div className="mk-td mk-left">{item.spec || ''}</div>
            <div className="mk-td mk-left">{item.unit || ''}</div>
            <div className="mk-td mk-right">{item.bill_count || ''}</div>
            <div className="mk-td mk-right">{item.unitprice || ''}</div>
            <div className="mk-td mk-right">{item.amount_line || ''}</div>
            <div className="mk-td mk-right">{item.tax_name}</div>
            <div className="mk-td mk-right">{item.tax_amount}</div>
          </div>
        })}
        <div className="mk-total">
          <div className="mk-td">合计</div>
          <div className="mk-td"></div>
          <div className="mk-td"></div>
          <div className="mk-td"></div>
          <div className="mk-td"></div>
          <div className="mk-td">¥{total.price}</div>
          <div className="mk-td"></div>
          <div className="mk-td">¥{total.tax}</div>
        </div>
        <div className="mk-upcase">
          <div className="mk-td">价税合计(大写)</div>
          <div className="mk-td">{total.sumName}</div>
          <div className="mk-td">(小写)¥{total.sum}</div>
        </div>
        <Modal
          title="商品信息"
          visible={visible}
          width="75vw"
          maskClosable={false}
          onCancel={() => { this.setState({ visible: false }) }}
          footer={null}
        >
          <SubTable config={config} onChange={this.changeDetail}/>
        </Modal>
      </div>
    )
  }
}
export default InvoiceTable
src/tabviews/custom/components/module/invoice/invoiceTable/index.scss
New file
@@ -0,0 +1,127 @@
.detail-wrap {
  position: relative;
  .plus-line, .del-line {
    position: absolute;
    right: -40px;
    top: 5px;
    font-size: 26px;
    border: 1px solid #d9d9d9;
    border-radius: 32px;
    width: 30px;
    height: 30px;
    transition: all 0.3s;
    cursor: pointer;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .plus-line::before, .del-line::before {
    content: ' ';
    position: absolute;
    top: 13px;
    width: 12px;
    height: 1px;
    background: #b8b8b8;
    transition: all 0.3s;
  }
  .plus-line::after {
    content: ' ';
    position: absolute;
    top: 13px;
    width: 12px;
    height: 1px;
    background: #b8b8b8;
    transform: rotate(90deg);
    transition: all 0.3s;
  }
  .plus-line:hover {
    border-color: #26C281;
  }
  .plus-line:hover::before, .plus-line:hover::after {
    background: #26C281;
  }
  .del-line:hover {
    border-color: #ff4d4f;
  }
  .del-line:hover::before {
    background: #ff4d4f;
  }
  .mk-th, .mk-tr, .mk-total {
    position: relative;
    display: flex;
    border-bottom: var(--inv-color, #13509c) 1px solid;
    .mk-td {
      width: 10%;
      height: 40px;
      display: flex;
      justify-content: center;
      align-items: center;
      padding: 0 3px;
    }
    .mk-td:not(:last-child) {
      border-right: var(--inv-color, #13509c) 1px solid;
    }
    .mk-td:first-child {
      width: 20%;
    }
    .mk-td:nth-child(2), .mk-td:nth-child(6) {
      width: 15%;
    }
    .mk-left {
      justify-content: start;
    }
    .mk-right {
      justify-content: end;
    }
    .mk-input {
      position: relative;
      height: 28px;
      width: 100%;
      background: #ffffff;
      border-bottom: 1px solid #d9d9d9;
      transition: all 0.3s;
      .anticon-ellipsis {
        position: absolute;
        right: 0px;
        padding: 5px 10px;
        cursor: pointer;
      }
    }
    .mk-input:hover {
      border-color: var(--inv-color, #13509c);
    }
    .ant-input {
      background: #ffffff;
    }
  }
  .mk-tr.active, .mk-tr:hover {
    background: var(--mk-sys-color1);
  }
  .mk-upcase {
    display: flex;
    .mk-td {
      width: 40%;
      height: 40px;
      display: flex;
      align-items: center;
      padding: 0 20px;
    }
    .mk-td:first-child {
      width: 20%;
      padding: 0;
      border-right: var(--inv-color, #13509c) 1px solid;
      justify-content: center;
    }
    .mk-td:last-child {
      justify-content: end;
    }
  }
}
src/tabviews/custom/components/module/invoice/subTable/index.jsx
New file
@@ -0,0 +1,245 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Table, Input, Button } from 'antd'
import Api from '@/api'
import UtilsDM from '@/utils/utils-datamanage.js'
// import './index.scss'
class SearchWrap extends Component {
  static propTpyes = {
    search: PropTypes.array,
    onChange: PropTypes.func
  }
  state = {
    search: []
  }
  UNSAFE_componentWillMount () {
    const { search } = this.props
    this.setState({
      search: fromJS(search).toJS()
    })
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  searchChange = (val, key) => {
    const { search } = this.state
    this.setState({search: search.map(item => {
      if (item.field === key) {
        item.value = val
      }
      return item
    })})
  }
  trigger = () => {
    const { search } = this.state
    this.props.onChange(search)
  }
  render() {
    const { search } = this.state
    return (
      <div className="tb-search-wrap">
        {search.map(item => (
          <div className="search-item" key={item.field}>
            {item.label}:
            <Input autoComplete="off" onChange={(e) => this.searchChange(e.target.value, item.field)}/>
          </div>
        ))}
        <Button onClick={this.trigger}>搜索</Button>
      </div>
    )
  }
}
class SubTable extends Component {
  static propTpyes = {
    config: PropTypes.object
  }
  state = {
    pageIndex: 1,         // 初始页面索引
    pageSize: 10,         // 每页数据条数
    columns: null,        // 显示列
    pageOptions: [],
    search: []
  }
  UNSAFE_componentWillMount () {
    const { config } = this.props
    let _columns = []
    config.columns.forEach(item => {
      if (item.Hide === 'true') return
      _columns.push({
        align: 'center',
        dataIndex: item.field,
        title: item.label,
        sorter: false,
        width: item.Width || 120
      })
    })
    let size = (config.setting.pageSize || 10) + ''
    let pageOptions = ['10', '25', '50', '100', '500', '1000']
    if (!pageOptions.includes(size)) {
      pageOptions.push(size)
      pageOptions = pageOptions.sort((a, b) => a - b)
    }
    this.setState({
      pageSize: config.pageSize || 10,
      pageOptions,
      search: fromJS(config.search).toJS(),
      columns: _columns
    })
  }
  componentDidMount() {
    const { config } = this.props
    if (config.setting.onload === 'true') {
      this.loadData()
    }
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  /**
   * @description 数据加载
   */
  async loadData () {
    const { config } = this.props
    const { search, pageIndex, pageSize } = this.state
    this.setState({
      loading: true
    })
    let searches = search.map(item => ({
      key: item.field,
      match: item.match,
      type: item.type,
      value: item.value || ''
    }))
    let param = UtilsDM.getQueryDataParams(config.setting, searches, config.setting.order, pageIndex, pageSize, '')
    this.requestId = config.uuid + new Date().getTime()
    let result = await Api.genericInterface(param, config.setting.js_script, '', this.requestId)
    if (result.status) {
      if (result.$requestId && this.requestId !== result.$requestId) return
      let start = pageSize * (pageIndex - 1) + 1
      let data = result.data.map((item, index) => {
        item.key = index
        item.$Index = start + index + ''
        return item
      })
      let total = result.total || 0
      if (config.setting.custompage && data.length) {
        total = data[data.length - 1].mk_total || 0
      }
      this.setState({
        data: data,
        total: total,
        loading: false
      })
      UtilsDM.querySuccess(result)
    } else {
      this.setState({
        loading: false
      })
      UtilsDM.queryFail(result)
    }
  }
  changeTable = (pagination) => {
    this.setState({
      pageIndex: pagination.current,
      pageSize: pagination.pageSize,
    }, () => {
      this.loadData()
    })
  }
  doubleClickLine = (record) => {
    this.props.onChange(record)
  }
  searchChange = (search) => {
    this.setState({search: search}, () => {
      this.loadData()
    })
  }
  render() {
    const { pageIndex, pageSize, pageOptions, columns, total, loading, data, search } = this.state
    return (
      <>
        <SearchWrap search={search} onChange={this.searchChange}/>
        <Table
          className="inv-table"
          columns={columns}
          dataSource={data}
          bordered={true}
          loading={loading}
          onRow={(record) => {
            return {
              onDoubleClick: () => {this.doubleClickLine(record)}
            }
          }}
          onChange={this.changeTable}
          pagination={{
            current: pageIndex,
            pageSize: pageSize,
            pageSizeOptions: pageOptions,
            showSizeChanger: true,
            total: total || 0,
            showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条`
          }}
        />
      </>
    )
  }
}
export default SubTable
src/tabviews/custom/components/module/invoice/subTable/index.scss
src/tabviews/custom/components/tree/antd-tree/index.jsx
@@ -353,9 +353,9 @@
    let hasSelectKey = false
    data.forEach(item => {
      let pval = item[config.wrap.parentField]
      let val = item[config.wrap.valueField]
      let uuid = item[config.setting.primaryKey] || ''
      let pval = item[config.wrap.parentField] + ''
      let val = item[config.wrap.valueField] + ''
      let uuid = item[config.setting.primaryKey] + ''
      if (!val || logMap.has(val)) return
src/tabviews/custom/index.jsx
@@ -41,6 +41,7 @@
const AntvX6 = asyncComponent(() => import('./components/chart/antv-X6'))
const Voucher = asyncComponent(() => import('./components/module/voucher'))
const Account = asyncComponent(() => import('./components/module/account'))
const Invoice = asyncComponent(() => import('./components/module/invoice'))
const Iframe = asyncComponent(() => import('./components/iframe'))
const Calendar = asyncComponent(() => import('./components/calendar'))
const DebugTable = asyncComponent(() => import('@/tabviews/debugtable'))
@@ -1698,6 +1699,12 @@
            <Account config={item}/>
          </Col>
        )
      } else if (item.type === 'module' && item.subtype === 'invoice') {
        return (
          <Col span={item.width} style={style} key={item.uuid}>
            <Invoice config={item}/>
          </Col>
        )
      } else if (item.type === 'iframe') {
        return (
          <Col span={item.width} style={style} key={item.uuid}>
src/templates/sharecomponent/actioncomponent/verifyexcelin/index.jsx
@@ -722,33 +722,32 @@
          let cols = _verify.columns.map(col => col.Column.toLowerCase())
          cols = Array.from(new Set(cols))
          let error = ''
          if (_verify.columns.length === 0) {
            notification.warning({
              top: 92,
              message: '请设置Excel列字段!',
              duration: 5
            })
            return
            error = '请设置Excel列字段!'
          } else if (_verify.columns.length > cols.length) {
            notification.warning({
              top: 92,
              message: 'Excel列字段名,不可重复!',
              duration: 5
            })
            return
            error = 'Excel列字段名,不可重复!'
          } else if (cols.includes('bid')) {
            error = 'bid字段为保留字,不可使用!'
          } else if (cols.includes('jskey')) {
            error = 'jskey字段为保留字,不可使用!'
          } else if (_verify.range === 1) {
            let tEmptys = _verify.columns.filter(op => !op.Text)
            if (tEmptys.length > 0) {
              notification.warning({
                top: 92,
                message: '忽略首行时,会使用Text值校验Excel首行内容,Text值与Excel表首行内容相同,且均不可为空!',
                duration: 5
              })
              return
              error = '忽略首行时,会使用Text值校验Excel首行内容,Text值与Excel表首行内容相同,且均不可为空!'
            }
          }
          if (error) {
            notification.warning({
              top: 92,
              message: error,
              duration: 5
            })
            return
          }
          _verify.columns.sort((a, b) => {
            if (a.import === 'init' && b.import !== 'init') {
              return 1