vue 导入组件

bash 复制代码
<template>
  <el-dialog :title="getTitle" :visible.sync="isOpen" width="50%" append-to-body :close-on-click-modal="false" @close="cancel">

    <el-steps :active="stepActive" :align-center="true" style="margin-bottom: 20px">
      <el-step title="选择Excel" />
      <el-step title="预览数据" />
      <el-step title="配置表单" />
    </el-steps>
    <div v-show="stepActive===0">
      <div class="upload-model">
        <span>导入模式:</span>
        <el-radio-group v-model="importMode">
          <el-radio :label="1">仅新增数据</el-radio>
          <el-radio :label="2">仅更新数据</el-radio>
          <el-radio :label="3">新增和更新数据</el-radio>
        </el-radio-group>
      </div>
      <div class="upload-tip">
        <ul>
          <li>为保证数据导入顺利,推荐您使用 <el-link type="primary" :underline="false" @click="importTemplate"> 标准模板 </el-link></li>
          <li>仅允许导入xls、xlsx格式文件</li>
          <li>文件中数据不能超过10000行、200列</li>
        </ul>
      </div>
      <el-upload
        ref="upload"
        class="full-width-upload"
        :limit="1"
        accept=".xlsx, .xls"
        :headers="headers"
        action="#"
        :disabled="isUploading"
        :on-exceed="handleExceed"
        :on-error="handleError"
        :before-upload="beforeUpload"
        :on-change="handleChange"
        :auto-upload="false"
        drag
      >
        <i class="el-icon-upload" />
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      </el-upload>
    </div>
    <div v-show="stepActive===1">
      <div style="margin-bottom: 5px">
        <span>工作表:</span>
        <el-select v-model="sheetActive">
          <el-option v-for="dict in sheetNames" :key="dict" :label="dict" :value="dict" />
        </el-select>
        <span style="margin-left: 10px;color: red">选中任意一行可将其设置为标题行,标题行之前的数据不导入。</span>
      </div>
      <el-table v-if="excelList[sheetActive]" ref="table1" :data="excelList[sheetActive].list" :height="tableHeight" highlight-current-row @current-change="handleCurrentChange">
        <el-table-column type="index" width="50" />
        <el-table-column v-for="(col,index) in excelList[sheetActive].header" :key="index" :prop="col" :label="col" min-width="120" show-overflow-tooltip />
      </el-table>
    </div>
    <div v-show="stepActive===2">
      <div style="margin-bottom: 15px;text-align: right">
        <el-alert style="width: 200px;display: initial" :title="`导入${getConfigCol.length}列/共${excelList[sheetActive]?excelList[sheetActive].header.length:0}列`" type="warning" show-icon :closable="false" />
      </div>
      <el-table v-if="excelList[sheetActive]" ref="table2" :data="configList" :height="tableHeight">
        <el-table-column align="center" width="100">
          <template slot-scope="scope">
            <span v-if="scope.$index === 0">表单字段</span>
            <span v-else>{{ scope.$index }}</span>
          </template>
        </el-table-column>
        <el-table-column v-for="(col,index) in excelList[sheetActive].header" :key="index" :property="col" :label="col" min-width="150" show-overflow-tooltip>
          <template slot-scope="scope">
            <el-select v-if="scope.$index === 0" v-model="scope.row[col]" class="width100">
              <el-option v-for="dict in fieldList" :key="dict.key" :label="dict.value" :value="dict.key" :disabled="getFieldDisabled(dict.key)" />
            </el-select>
            <span v-else>{{ scope.row[col] }}</span>
          </template>
        </el-table-column>
      </el-table>
    </div>

    <div slot="footer" class="dialog-footer">
      <el-button v-if="stepActive>0" type="primary" @click="stepActive--"> 上一步 </el-button>
      <el-button v-if="stepActive<2" type="primary" @click="stepActive++"> 下一步 </el-button>
      <el-button v-if="stepActive===2" type="primary" @click="submitFileForm"> 导 入 </el-button>
      <el-button @click="cancel"> 取 消 </el-button>
    </div>
  </el-dialog>
</template>

<script>
import { getToken } from '@/utils/auth'
import request from '@/utils/request'
import * as XLSX from 'xlsx'

export default {
  props: {
    // 是否打开
    value: {
      type: Boolean,
      default: false
    },
    // 导入地址
    url: {
      type: String,
      default: ''
    },
    // 导入字段地址
    fieldUrl: {
      type: String,
      default: ''
    },
    // 模板下载地址
    templateUrl: {
      type: String,
      default: ''
    },
    // 模板下载参数
    templateParams: {
      type: Object,
      default: () => { return {} }
    },
    // 标题
    title: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      // 步骤
      stepActive: 0,
      // 工作簿
      sheetActive: null,
      // 设置上传的请求头部
      headers: { Authorization: 'Bearer ' + getToken() },
      // 导入模型 1仅新增数据 2仅更新数据 3新增和更新数据
      importMode: 1,
      // 是否打开
      isOpen: this.open,
      // 是否禁用上传
      isUploading: false,
      // 列集合
      fieldList: [],
      // excel数据集
      excelList: {},
      // excel表集合
      sheetNames: [],
      // 当前选中行
      currentRow: null,
      // 配置数据
      configList: [],
      // 表格高度
      tableHeight: 280
    }
  },
  computed: {
    getConfigCol: function() {
      const t = this.configList.length > 0 ? this.configList[0] : []
      const list = []
      Object.keys(t).forEach(key => {
        if (t[key] !== '0') {
          list.push(t[key])
        }
      })
      return list
    },
    getFieldDisabled() {
      return function(key) {
        return this.getConfigCol.find((t) => t === key) !== undefined
      }
    },
    getTitle() {
      switch (this.importMode) {
        case 1:
          return `${this.title}导入【仅新增数据】`
        case 2:
          return `${this.title}导入【仅更新数据】`
        case 3:
          return `${this.title}导入【新增和更新数据】`
      }
      return `${this.title}导入`
    }
  },
  watch: {
    value: {
      handler(val) {
        this.isOpen = val
      },
      deep: true,
      immediate: true
    },
    dialogFullScreen: {
      handler(val) {
        if (val) {
          const height = window.innerHeight
          if (this.$refs.table1) {
            const rect = this.$refs.table1.$el.getBoundingClientRect()
            if (rect.y > 0) this.tableHeight = height - rect.y + 90
          }
          if (this.$refs.table2) {
            const rect = this.$refs.table2.$el.getBoundingClientRect()
            if (rect.y > 0) this.tableHeight = height - rect.y + 90
          }
        } else {
          this.tableHeight = 270
        }
      },
      deep: true,
      immediate: true
    },
    fieldUrl: {
      handler(val) {
        if (val) {
          request({ url: val, method: 'get' }).then((resp) => {
            this.fieldList = resp.data
          })
        } else {
          this.fieldList = []
        }
      },
      deep: true,
      immediate: true
    },
    sheetActive: {
      handler(val) {
        this.currentRow = null
      },
      deep: true,
      immediate: true
    },
    stepActive: {
      handler(val) {
        if (val === 1 && this.sheetNames.length === 0) {
          this.$modal.msgError('请先选择要导入的excel文件')
          this.stepActive--
        }
        if (val === 1 && this.sheetActive && this.currentRow) {
          this.$nextTick(() => {
            this.$refs.table1.setCurrentRow(this.currentRow)
            this.configList = []
          })
        }
        if (val === 2 && this.currentRow === null) {
          this.$modal.msgError('请先选中任意一行可将其设置为标题行')
          this.stepActive--
        }
        if (val === 2 && this.currentRow) {
          const sliceIndex = this.excelList[this.sheetActive].list.indexOf(this.currentRow)
          this.configList = this.excelList[this.sheetActive].list.slice(sliceIndex + 1)
          const topRow = this.excelList[this.sheetActive].header.reduce((obj, key) => {
            obj[key] = '0'
            return obj
          }, {})
          this.configList.unshift(topRow)
        }
      },
      deep: true,
      immediate: true
    }
  },
  methods: {
    /** 下载模板操作 */
    importTemplate() {
      this.download(this.templateUrl, this.templateParams, `${this.title}_模板_${new Date().getTime()}.xlsx`)
    },
    /** 文件上传检测*/
    beforeUpload(file) {
      const isExcel = /\.(xlsx|xls)$/.test(file.name)
      if (!isExcel) {
        this.$modal.msgError('只能上传.xlsx、.xls 文件!')
        return false
      }
      return true
    },
    /** 文件改变 */
    handleChange(file, fileList) {
      this.$modal.loading('正在解析数据中,请稍等......')
      this.excelList = []
      this.sheetActive = ''
      const reader = new FileReader()
      reader.onload = (e) => {
        const data = new Uint8Array(e.target.result)
        const workbook = XLSX.read(data, { type: 'array' })
        this.sheetNames = workbook.Workbook.Sheets.filter((t) => t.Hidden === 0).map((t) => { return t.name })
        this.sheetNames.forEach((t, sindex) => {
          const sheet = { list: [], header: [] }
          const worksheet = workbook.Sheets[t]
          if (sindex === 0) {
            this.sheetActive = t
          }
          const json = XLSX.utils.sheet_to_json(worksheet, { header: 1, blankrows: false })
          json.forEach((t, index) => {
            if (index === 0) {
              sheet.header = t
              const obj = t.reduce((obj, key) => {
                obj[key] = key
                return obj
              }, {})
              sheet.list.push(obj)
            } else {
              const obj = sheet.header.reduce((obj, key, index) => {
                obj[key] = t[index]
                return obj
              }, {})
              sheet.list.push(obj)
            }
          })
          this.excelList[t] = sheet
        })
        this.stepActive++
        this.$modal.closeLoading()
      }
      reader.readAsArrayBuffer(file.raw)
    },
    /** 文件上传失败处理*/
    handleError() {
      this.$modal.closeLoading()
    },
    /** 文件个数超出*/
    handleExceed() {
      this.$modal.msgError(`上传文件数量不能超过1个!`)
    },
    /** 提交上传文件*/
    submitFileForm() {
      const colKeyValue = {}
      const newList = []
      const colOne = this.configList[0]
      let importCols = 0
      Object.keys(colOne).forEach(key => {
        if (colOne[key] !== '0') {
          colKeyValue[key] = colOne[key]
          importCols++
        }
      })
      if (importCols === 0) {
        this.$modal.msgError('请先配置要导入列的表单字段')
        return
      }
      this.$modal.loading('正在导入数据中,请稍候...')
      this.configList.forEach((t, index) => {
        if (index !== 0) {
          const obj = {}
          Object.keys(t).forEach(key => {
            if (colKeyValue[key]) {
              obj[colKeyValue[key]] = t[key]
            }
          })
          newList.push(obj)
        }
      })
      request({
        url: this.url + '/' + this.importMode,
        method: 'post',
        headers: {
          repeatSubmit: true
        },
        data: newList
      }).then((resp) => {
        this.$modal.closeLoading()
        this.isUploading = false
        this.$refs.upload.clearFiles()
        this.cancel()
        this.$modal.closeLoading()
        this.$emit('importComple')
        this.$modal.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + resp.data + '</div>', '导入结果', { dangerouslyUseHTMLString: true })
      }).catch(() => {
        this.cancel()
        this.$modal.closeLoading()
      })
    },
    /** 取消 */
    cancel() {
      // 步骤
      this.stepActive = 0
      // 工作簿
      this.sheetActive = null
      // 导入模型 1仅新增数据 2仅更新数据 3新增和更新数据
      this.importMode = 1
      // 是否打开
      this.isOpen = false
      // excel数据集
      this.excelList = {}
      // excel表集合
      this.sheetNames = []
      // 当前选中行
      this.currentRow = null
      // 配置数据
      this.configList = []
      this.$refs.upload.clearFiles()
      this.$emit('input', this.isOpen)
    },
    /** 当前选中行 */
    handleCurrentChange(val) {
      this.currentRow = val
    }
  }
}
</script>

<style scoped lang="scss">
.full-width-upload {
  width: 100%;
  display: grid;
  ::v-deep{
    .el-upload-dragger {
      width: 100%;
    }
  }

}
.full-screen{
  float: right;
  margin: 0 35px 0 0;
  cursor: pointer;
  i{
    border-radius: 50px 25px 50px 0;
    background: linear-gradient(50deg, #3A71A8, #83D483);
    color: #E2FFFE;
    font-style: italic;
    font-weight: bold;
  }
}
.upload-model{}
.upload-tip{
  background: #e1e9e9;
  margin: 10px 0;
  padding: 5px;;
  ul{
    padding-inline-start: 15px;
    li{
      font-size: 15px;
      .el-link{
        font-size: 15px;
        vertical-align: baseline
      }
    }
  }
}
</style>

使用

bash 复制代码
  <el-button   @click="handleImport" >导入</el-button>
   <ExcelImport
      v-model="upload.open"
      :title="upload.title"
      :url="upload.url"
      :template-url="upload.templateUrl"
      :field-url="upload.fieldUrl"
      @importComple="importComple"
    />
     // 导入参数
      upload: {
        // 标题
        title: '',
        // 是否显示弹出层
        open: false,
        // 上传的地址
        url: 'a',
        // 模板下载地址
        templateUrl: '',
        // 字段地址
        fieldUrl: '',
        // 其它参数
        params: {}
      }
    /** 导入按钮操作 */
    handleImport() {
      // 标题
      this.upload.title = '数采点'
      // 导入的地址
      this.upload.url = '/importData'
      // 模板下载地址
      this.upload.templateUrl = '/importTemplate'
      // 导入字段地址
      this.upload.fieldUrl = '/imporField'
      // 其它参数
      this.upload.params = {}
      this.upload.open = true
    },
相关推荐
沉默是金~几秒前
Vue+Elementui el-tree树只能选择子节点并且支持检索
javascript·vue.js·elementui
0_113 分钟前
让你的网页变的高大上:国际化Vue-i18n
前端·javascript·vue.js
谷隐凡二16 分钟前
fiddler安卓雷电模拟器配置踩坑篇
前端·测试工具·fiddler
哆啦美玲18 分钟前
养成编程思维——栈与队列的运用
前端·javascript·算法
类人_猿32 分钟前
ASP.NET Web(.Net Framework)POST无法正常接收数据
前端·asp.net·.net·post·post请求
小黄编程快乐屋41 分钟前
electron-updater软件自动检测更新 +无服务器本地测试
javascript·electron·serverless
城沐小巷1 小时前
外卖点餐系统小程序
前端·后端·微信小程序
东方隐侠安全团队-千里1 小时前
网安瞭望台第6期 :XMLRPC npm 库被恶意篡改、API与SDK的区别
前端·网络·网络安全·npm·node.js
Answer_ism1 小时前
【CSS】一篇掌握CSS
前端·css·html
Layue000001 小时前
学习HTML第三十四天
前端·笔记·学习·html