封装一个h5移动端可以预览xlsx文件的组件

291 阅读3分钟
  • 通过xlsx依赖实现
  • npm install --save xlsx
 <template>
  <div class="page">
    <van-nav-bar title="附件预览EXCEL" left-arrow @click-left="onClickLeft" />
    <div class="excel">
      <div class="flex jc pdt-30 pdt-30" v-if="loading">
        <van-loading type="spinner" />
      </div>
      <div class="sheet">
        <span
          v-for="(item, index) in tableList"
          :key="index"
          v-html="item.name"
          :class="item.isSelected ? 'active' : ''"
          @click="onClickSwitchTab(index)"
        ></span>
      </div>
      <div
        class="table"
        v-for="(item, index) in tableList"
        :key="index"
        v-html="item.content"
        v-show="item.isSelected"
      ></div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import FileRequest from './request.js'
import * as XLSX from 'xlsx'
const emit = defineEmits(['onClose', 'onloadError'])

const loading = ref(true)
const fileUrl = ref('')
const activeName = ref(0)
const tableList = ref([])

/**
 * 初始化
 */
function init(url) {
  loading.value = true
  tableList.value = []
  fileUrl.value = url
  getFileContent()
}

/**
 * 获取excel内容
 */
async function getFileContent() {
  try {
    let res = await FileRequest.get(fileUrl.value)

    // 处理编码
    let binary = ''
    let bytes = new Uint8Array(res)
    let length = bytes.byteLength
    for (let i = 0; i < length; i++) {
      binary += String.fromCharCode(bytes[i])
    }

    // 读取内容
    let wb = XLSX.read(binary, { type: 'binary' })

    // sheet列表
    let sheetNameList = wb.SheetNames

    sheetNameList.forEach((item, index) => {
      let ws = wb.Sheets[item]
      let itemContent = ''
      try {
        itemContent = XLSX.utils.sheet_to_html(ws)
      } catch (error) {}
      tableList.value.push({
        name: item,
        isSelected: index == 0,
        content: itemContent
      })
      console.log(tableList.value)
    })
    loading.value = false
  } catch (error) {
    emit('onloadError')
  }
}

function onClickLeft() {
  emit('onClose')
}

function onClickSwitchTab(i) {
  tableList.value.forEach((item, index) => {
    item.isSelected = index == i
  })
}
defineExpose({ init })
</script>

<style lang="less">
.excel {
  flex: 1;
  overflow-x: scroll;
  align-items: center;
  padding: 24px;
  background-color: #f8f8f8;

  .sheet {
    display: flex;
    white-space: nowrap;
    padding-bottom: 30px;

    span {
      display: block;
      height: 72px;
      line-height: 72px;
      padding: 0 24px;
      background-color: #fff;
      font-size: 28px;
      box-shadow: 0px 4px 8px 6px rgba(204, 204, 204, 0.34);

      &.active {
        background-color: #ff6633;
        color: #fff;
      }
    }
  }

  .table {
    table {
      border-collapse: collapse !important;
      background-color: #fff;

      td {
        word-break: keep-all;
        white-space: nowrap;
        border: 2px solid #000;
        padding: 0px 16px;
        font-size: 24px;
        color: #666;
      }
    }
  }
}
::v-deep(.van-nav-bar) {
  height: 88px !important;
}
::v-deep(.van-nav-bar .van-nav-bar__title.van-ellipsis) {
  font-size: 34px !important;
}
::v-deep(.van-nav-bar .van-icon) {
  font-size: 44px;
  color: rgb(72, 72, 81);
}
.components-file-preview {
  height: 100%;

  .excel {
    height: 100vh;
    margin-top: 95px;

    .sheet {
      display: flex;
      white-space: nowrap;
      padding-bottom: 30px;
      box-shadow: 0px 4px 8px 16px rgba(204, 204, 204, 0.34);

      span {
        display: block;
        height: 72px;
        line-height: 72px;
        padding: 0 24px;
        background-color: #fff;
        font-size: 28px;
        // box-shadow: 0px 4px 8px 16px rgba(204, 204, 204, 0.34);

        &.active {
          background-color: #ff6d00;
          color: #fff;
        }
      }
    }

    .table {
      table {
        border-collapse: collapse !important;
        background-color: #fff;

        td {
          word-break: keep-all;
          white-space: nowrap;
          border: 2px solid #000;
          padding: 0px 16px;
          font-size: 24px;
          color: #666;
        }
      }
    }
  }
  }
  .van-nav-bar {
  position: fixed !important;
  top: 0;
  width: 100%;
}
.flex.jc {
  // -webkit-box-pack: center;
  justify-content: center;
}
.pdt-30 {
  padding-top: 60px;
}
</style>

  • request.js文件
/*
 * @description:
 * @author: liangyong016
 */

import { escape } from 'lodash-es'

class FileRequest {
  /**
   * 获取文件内容的数据请求
   */
  static get(url, responseType = 'arraybuffer') {
    return new Promise((resolve, reject) => {
      let xhr = new XMLHttpRequest()
      xhr.open('GET', url, true)
      xhr.responseType = responseType
      if (responseType == 'text') {
        xhr.setRequestHeader('Content-Type', 'text/text;charset=utf-8')
      }
      xhr.onload = () => {
        if (xhr.status == 200) {
          if (responseType == 'text') {
            let decodedData = decodeURIComponent(escape(xhr.responseText)) //解决中文乱码问题
            // resolve(xhr.responseText)
            resolve(decodedData)
          } else {
            resolve(xhr.response)
          }
        } else {
          reject()
        }
      }
      xhr.error = () => {
        reject()
      }
      xhr.send()
    })
  }
}

export default FileRequest

<template>
  <div id="imagePreview">
    <van-popup
      class="components-file-preview"
      v-model:show="show"
      position="bottom"
      get-container="body"
      :lazy-render="false"
    >
      <template v-if="isError">
        <Preview-error ref="" :msg="errMsg" @onClose="onClose"></Preview-error>
      </template>
      <template v-else>
        <Preview-excel
          ref="excel"
          v-if="cusFileType == 'excel'"
          @onClose="onClose"
          @onloadError="onloadError"
        >
        </Preview-excel>
      </template>
    </van-popup>
  </div>
</template>

<script setup>
import { ref, nextTick, getCurrentInstance } from 'vue'
import { CONSTS_FILE_TYPE_MAP } from './js/map'
import { showToast } from 'vant'

import PreviewExcel from './child/PreviewExcel.vue'
import PreviewError from './child/PreviewError.vue'

const { proxy } = getCurrentInstance()
const fileUrl = ref('')
const fileType = ref('')
const cusFileType = ref('')
const show = ref(false)
const errMsg = ref('暂不支持当前格式')
const isError = ref(false)
const images = ref([])
const showImage = ref(false)
const fileRef = ref(null)

function open(url, type) {
  if (!url) return showToast('暂无文件')
  fileUrl.value = url
  fileType.value = type
  getFileType()
}
// 获取url后面参数
function getUrlData(url) {
  let str = url.split('?')[1]
  let obj = {}
  let paramsArr = str.split('&')
  for (let i = 0, len = paramsArr.length; i < len; i++) {
    let arr = paramsArr[i].split('=')
    obj[arr[0]] = arr[1]
  }
  return obj
}

/**
 * 获取文件格式
 */
function getFileType() {
  let type = fileType.value
  if (!type) {
    let index = fileUrl.value.lastIndexOf('.')
    type = fileUrl.value.substr(index + 1)
    const types = ['xls', 'xlsx', 'doc', 'docx', 'pdf', 'jpeg', 'png', 'jpg', 'txt']
    if (!types.includes(type)) {
      let urlInfo = getUrlData(fileUrl.value)
      let suffix = urlInfo['response-content-disposition']
      let index = suffix.lastIndexOf('.')
      type = suffix.substr(index + 1)
    }
  }

  cusFileType.value = CONSTS_FILE_TYPE_MAP[type]
  fileRef.value = cusFileType.value

  if (!cusFileType.value) {
    isError.value = true
    show.value = true
    return
  }
  //图片
  if (cusFileType.value == 'image') {
    images.value = [`${fileUrl.value}`]
    showImage.value = true
    return
  }

  nextTick(() => {
    console.log(fileRef.value)
    proxy.$refs[fileRef.value].init(fileUrl.value)
    show.value = true
  })
}

function onClose() {
  isError.value = false
  errMsg.value = '暂不支持当前格式'
  show.value = false
}

function onloadError() {
  isError.value = true
  errMsg.value = '文件加载失败'
}

defineExpose({
  open
})
</script>

<style lang="less">

</style>