elementUI 需要验证的 table 表格

1,212 阅读4分钟

这个方案说实话不怎么样,只是能暂时解决问题,复用性比较差。 还是挺多人问这个源码的,所以今天重新去除了业务代码,理出了一个简单的demo, 源码如下

/*
 * @Author: Xuuu
 * @Last Modified by: Xuuu
 * @Last Modified time: 2018-07-16 15:25:17
 * @Function: 表格明细模板:由于复杂度特别高,和业务耦合性很高,无法抽象成通用组件,需根据业务更改此模板
 */
<template>
  <div class='detailed-table'>
    <el-form :rules="rules" :model="tableForm" ref='form' size="small">
      <div class='btn-row'>
        <el-button type='primary' size="mini" @click="addRow">添加一行</el-button> 
        <el-button size="mini" @click="deleteRow">删除</el-button>
      </div>
      <el-table border :data="tableData" show-summary @selection-change="handleSelectionChange" ref="table" :summary-method="getSummaries">
        <el-table-column
          type="selection"
          width="55">
        </el-table-column>
        <el-table-column
          prop="leaveDate"
          label="出发日期"
          min-width="160px"
        >
          <template slot-scope="scope">
            <el-form-item :prop='"leaveDate" + scope.row.id' :rules="rules.leaveDate" class="form-item__margin">
              <el-date-picker
                v-model="tableForm['leaveDate' + scope.row.id]"
                @input="changeValue('leaveDate', scope.$index, scope.row.id)"
                type="date"
                value-format="yyyy-MM-dd"
                placeholder="选择日期"
              >
              </el-date-picker>
            </el-form-item>
          </template>
        </el-table-column>

        <el-table-column
          prop="returnDate"
          label="到达日期"
          min-width="160px"
        >
          <template slot-scope="scope">
            <el-form-item :prop='"returnDate" + scope.row.id' :rules="rules.returnDate" class="form-item__margin">
              <el-date-picker
                v-model="tableForm['returnDate' + scope.row.id]"
                @input="changeValue('returnDate', scope.$index, scope.row.id)"
                type="date"
                value-format="yyyy-MM-dd"
                placeholder="选择日期">
              </el-date-picker>
            </el-form-item>
          </template>
        </el-table-column>
        <el-table-column
          prop="from"
          label="出发地"
        >
          <template slot-scope="scope">
            <el-form-item :prop='"from" + scope.row.id' :rules="rules.from" class="form-item__margin">
              <el-input
                v-model="tableForm['from' + scope.row.id]"
                @input="changeValue('from', scope.$index, scope.row.id)"
              >
              </el-input>
            </el-form-item>
          </template>
        </el-table-column>
        <el-table-column
          prop="to"
          label="到达地"
        >
          <template slot-scope="scope">
            <el-form-item :prop='"to" + scope.row.id' :rules="rules.to" class="form-item__margin">
              <el-input
                v-model="tableForm['to' + scope.row.id]"
                @input="changeValue('to', scope.$index, scope.row.id)"
              >
              </el-input>
            </el-form-item>
          </template>
        </el-table-column>
        <el-table-column
          prop="transport"
          label="交通工具"
        >
          <template slot-scope="scope">
            <el-form-item :prop='"transport" + scope.row.id' :rules="rules.transport" class="form-item__margin" ref='transport'>
              <el-select
                v-model="tableForm['transport' + scope.row.id]"
                placeholder="请选择"
                @input="changeValue('transport', scope.$index, scope.row.id)"
              >
                <el-option
                  v-for="item in vehicleList"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value">
                </el-option>
              </el-select>
            </el-form-item>
          </template>
        </el-table-column>
        <el-table-column
          prop="ticketCost"
          label="机票"
        >
          <template slot-scope="scope">
            <el-form-item :prop='"ticketCost" + scope.row.id' :rules="rules.ticketCost" class="form-item__margin">
              <el-input
                @input="changeValue('ticketCost', scope.$index, scope.row.id)"
                v-model="tableForm['ticketCost' + scope.row.id]"
                placeholder="">
              </el-input>
            </el-form-item>
          </template>
        </el-table-column>
        <el-table-column
          prop="transportCost"
          label="交通费"
        >
          <template slot-scope="scope">
            <el-form-item :prop='"transportCost" + scope.row.id' :rules="rules.transportCost" class="form-item__margin">
              <el-input
                @input="changeValue('transportCost', scope.$index, scope.row.id)"
                v-model="tableForm['transportCost' + scope.row.id]"
                placeholder="">
              </el-input>
            </el-form-item>
          </template>
        </el-table-column>
        <el-table-column
          prop="hotelCost"
          label="住宿费"
        >
          <template slot-scope="scope">
            <el-form-item :prop='"hotelCost" + scope.row.id' :rules="rules.hotelCost" class="form-item__margin">
              <el-input
                @input="changeValue('hotelCost', scope.$index, scope.row.id)"
                v-model="tableForm['hotelCost' + scope.row.id]"
                placeholder="">
              </el-input>
            </el-form-item>
          </template>
        </el-table-column>
        
      </el-table>
      <div class="total-row">
        <div class="money-item">合计人民币(大写):<span class="money"> {{formatMoney(total)}}</span></div>
        <div class="money-item">合计人民币(小写):<span class="money"> {{total}}</span></div>
      </div>
      <!-- <div class='result-row'><el-button @click="send" type="primary">发送</el-button></div> -->
    </el-form>
    <div>
      <el-button type="primary" @click="check">提 交</el-button>
    </div>
  </div>
</template>
<script>
import moment from 'moment'

// 初始化表格数据结构
const initData = {
  leaveDate: moment().format('YYYY-MM-DD'),
  returnDate: moment().format('YYYY-MM-DD'),
  from: '',
  to: '',
  transport: '',
  ticketCost: '',
  transportCost: '',
  hotelCost: ''
}
// 交通工具列表
const vehicleList = [
  {label: '飞机', value: 'airport'},
  {label: '火车', value: 'train'},
  {label: '汽车', value: 'bus'},
  {label: '自驾', value: 'drive'},
  {label: '其他', value: 'other'}
]

export default {
  // name: 'DetailedTable',
  data () {
    // 验证规则 --- 出发日期
    const validateStart = (rule, value, callback) => {
      const field = rule.field
      const index = field.slice(-13)
      const goalfiled = `returnDate${index}`
      const goal = this.tableForm[goalfiled]
      if (!goal) {
        callback()
      } else {
        if (moment(value).isAfter(goal)) {
          return callback(new Error('出发日期必须小于到达日期'))
        } else {
          callback()
        }
      }
    }
    // 验证规则 --- 到达日期
    const validateArrive = (rule, value, callback) => {
      const field = rule.field
      const index = field.slice(-13)
      const goalfiled = `leaveDate${index}`
      const goal = this.tableForm[goalfiled]
      if (!goal) {
        callback()
      } else {
        if (!moment(value).isBefore(goal)) {
          callback()
        } else {
          return callback(new Error('到达日期必须大于出发日期'))
        }
      }
    }
    // 验证规则 --- 是否是数字
    const validateNumber = (rule, value, callback) => {
      if (value && value !== '0' && !Number(value)) {
        callback(new Error('必须是数字'))
      }
    }
    return {
      tableData: [],  // 实际需要的表格数据
      vehicleList: [...vehicleList],  // 交通工具列表
      initData: {...initData},  // 初始化表单数据
      checkedRow: [], // 选中的行
      tableForm: {},  // 用于验证字段合法性
      total: null,  // 合计费用字段
      rules: { // 验证规则
        leaveDate: [
          {required: true, message: '不能为空', trigger: 'blur'},
          {validator: validateStart, trigger: ['blur', 'change']}
        ],
        returnDate: [
          {required: true, message: '不能为空', trigger: 'blur'},
          {validator: validateArrive, trigger: ['blur', 'change']}
        ],
        from: [
          {required: true, message: '不能为空', trigger: 'blur'}
        ],
        to: [
          {required: true, message: '不能为空', trigger: 'blur'}
        ],
        transport: [
          {required: true, message: '不能为空', trigger: 'change'}
        ],
        ticketCost: [
          {validator: validateNumber, message: '必须为数字', trigger: 'blur'}
        ],
        transportCost: [
          {validator: validateNumber, message: '必须为数字', trigger: 'blur'}
        ],
        hotelCost: [
          {validator: validateNumber, message: '必须为数字', trigger: 'blur'}
        ]
      }
    }
  },
  methods: {
    debounce (fn, delay) {
        let timerId;
        return function (...args) {
            if (timerId) {
                clearTimeout(timerId);
            }
            timerId = setTimeout(() => {
                fn(...args);
                timerId = null;
            }, delay);
        }
    },
    // 发送回调
    check () {
      this.$refs.form.validate()
      let result = {name: 'detailedTable', value: false}
      const promise = new Promise((resolve) => {
        this.$nextTick(() => {
          const node = document.querySelector('.detailed-table')
          const err = node.querySelectorAll('.el-form-item__error')
          if (err.length) {
            result.value = false
          } else {
            result.value = true
          }
          resolve(result)
        })
      })
      promise.then(result => {
        this.$emit('check', result)
      })
    },
    // 格式化大写金额
    formatMoney (num) {  
      let strOutput = ""
      let strUnit = '仟佰拾亿仟佰拾万仟佰拾元角分'
      num += "00"
      var intPos = num.indexOf('.')
      if (intPos >= 0)
        num = num.substring(0, intPos) + num.substr(intPos + 1, 2)
      strUnit = strUnit.substr(strUnit.length - num.length)
      for (let i=0; i < num.length; i++)  
        strOutput += '零壹贰叁肆伍陆柒捌玖'.substr(num.substr(i,1),1) + strUnit.substr(i,1)
        return strOutput.replace(/零角零分$/, '整').replace(/零[仟佰拾]/g, '零').replace(/零{2,}/g, '零').replace(/零([亿|万])/g, '$1').replace(/零+元/, '元').replace(/亿零{0,3}万/, '亿').replace(/^元/, "零元")
    },
    // 改变值去给 tableData 赋值
    changeValue (val, index, id) {
      this.debounce(this.$set(this.tableData[index], val, this.tableForm[val + id]), 300)
    },
    // 处理合计选项
    getSummaries (param) {
      const { columns, data } = param
      
      const sums = []
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = ''
          return
        }
        if (index === 1) {
          sums[index] = '总价'
          return
        }

        if (index < 6) {
          sums[index] = ''
          return
        }
        const values = data.map(item => Number(item[column.property]))
        if (!values.every(value => isNaN(value))) {
          sums[index] = values.reduce((prev, curr) => {
            const value = Number(curr)
            if (!isNaN(value)) {
              return prev + curr
            } else {
              return prev
            }
          }, 0)
          sums[index] = sums[index].toFixed(2)
        } else {
          sums[index] = 'N/A'
        }
      })
      const format = sums.map(item => Number(item))
      const result = format.reduce((prev, curr) => {
        if (!isNaN(curr)) {
          return prev + curr
        } else {
          return prev
        }
      }, 0)
      this.total = result.toFixed(2)
      return sums
    },
    // 选择选项
    handleSelectionChange (val) {
      this.checkedRow = val
    },
    // 添加行
    addRow () {
      // TODO: 添加行时,数据会重置
      const {tableData, initData} = this
      const id = +moment()
      const data = {...this.initData}
      for (let [key, value] of Object.entries(this.initData)) {
        this.$set(this.tableForm, `${key}${id}`, value)
      }
      data.id = id
      this.tableData.push(data)
    },
    // 删除选中行
    deleteRow () {
      const {checkedRow, tableData} = this
      if (!checkedRow.length) {
        this.$message({
          message: `没有选中项`,
          type: 'warning'
        })
        return
      }
      const ids = []
      checkedRow.forEach(item => {
        ids.push(item.id)
      })
      const newTable = tableData.filter(item => {
        return !ids.includes(item.id)
      })
      this.tableData = newTable
      for (let [key, value] of Object.entries(this.tableForm)) {
        for (let id of ids) {
          if (key.includes(id)) {
            delete this.tableForm[key]
          }
        }
      }
    },
    // 初始化
    init () {
      const id = +moment()
      const data = {...this.initData}
      for (let [key, value] of Object.entries(this.initData)) {
        this.$set(this.tableForm, `${key}${id}`, value)
      }
      data.id = id
      this.tableData.push(data)
    }
  },
  mounted () {
    this.init()
  },

}
</script>

<style lang='less'>
.detailed-table {
  padding: 20px;
  .btn-row {
    margin-bottom: 20px;
    text-align: right;
  }

  .result-row {
    margin-top: 30px;
    text-align: right;
    .el-button {
      padding: 12px 30px;
    }
  }

  .total-row {
    font-size: 14px;
    padding: 6px;
    display: flex;
    justify-content: flex-end;

    .money-item {
      margin-left: 20px;
      font-weight: 700;
      .money {
        color: #f05050;
      }
    }
  }
  .el-date-editor.el-input, .el-date-editor.el-input__inner {
    width: auto;
  }
  .form-item__margin {
    margin: 18px 0;
  }
  .el-table td {
    padding: 0;
  }

  .el-table__footer-wrapper {
    td, th {
      padding: 12px 0;
      color: #f05050;
    }
  }
}
</style>