CSV文件预览及中文乱码问题

1,190 阅读2分钟

最近做项目时遇到了一些需求,要实现pdf,excel,csv等文件格式的预览。在网上搜索了下,关于pdf,excel预览的比较多,关于csv的就比较少,所以我想将我开发csv预览的过程整理一下。

gitHub源码地址:github.com/seapack-hub…

第一步,先了解csv文件的格式。

csv全称“Comma-Separated Values”,是一种逗号分隔值格式的文件,是一种用来存储数据的纯文本格式文件。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串。
了解之后发现csv是一种纯文本格式文件,因此在进行预览的时,可以将文本转成对应的数组格式,然后将数组填充到创建的表格中。

第二步,解析csv文件。

将获取到的文件转化为二进制文件。

//获取文件
async handleChange(e) {
    this.loading = true;
    try {
      //提取出前端浏览器解析出的file文件
      const [file] = e.target.files;
      //解析file文件,生成二进制数据
      const arrayBuffer = await readBuffer(file);
      this.loading = false;
      this.last = await this.displayResult(arrayBuffer, file);
    } catch (e) {
      console.error(e);
    } finally {
      this.loading = false;
    }
  },

//将解析好的文件放入界面中
  displayResult(buffer, file) {
      // 取得文件名
      const { name } = file;
      // 取得扩展名
      const extend = getExtend(name);
      // 输出目的地
      const { output } = this.$refs;
      // 生成新的dom
      const node = document.createElement("div");
      // 添加孩子,防止vue实例替换dom元素
      if (this.last) {
        output.removeChild(this.last.$el);
        this.last.$destroy();
      }
      const child = output.appendChild(node);
      // 调用渲染方法进行渲染
      return new Promise((resolve, reject) =>
        render(buffer, extend, child).then(resolve).catch(reject)
      );
    },

通过FileReader 按照文件的编码将二进制文件转化为Text文本

export async function readText(buffer) {
  return new Promise((resolve,reject)=>{
    let readerBs64 = new FileReader();
    readerBs64.readAsDataURL(new Blob([buffer]));
    readerBs64.onload = () => {
      let str = atob(readerBs64.result.split(';base64,')[1])
      // 要用二进制格式,获取文件编码
      let encoding = jschardet.detect(str)
      encoding = encoding.encoding;
      console.log('获取文件编码',encoding)
      let reader = new FileReader();
      if(encoding == 'GB2312'){
        reader.readAsText(new Blob([buffer]),'gbk')
      }else{
        reader.readAsText(new Blob([buffer]),encoding)
      }
      reader.onload = (loadEvent)=>{
        const {result} = loadEvent.target;
        resolve(result);
      }
    };
    readerBs64.onerror = e => reject(e);
  })
}

将csv文本转化为数组

export function  csvToArray(csv){
    let table = [];
    //将字符串以换行符隔开。
    let allRows = csv.split(/\r\n/);
    for(let i=0; i<allRows.length; i++){
        if(allRows[i] != ""){
            table.push(allRows[i].split(","));
        }
    }
    return table;
}

将数组展示在vue模板中
新建vue实例

import Vue from 'vue';
import Table from './Table';
import {readText} from "@/components/util.js";
import ElementUI from 'element-ui'; // 2.1引入结构
import 'element-ui/lib/theme-chalk/index.css'; // 2.2引入样式
Vue.use(ElementUI); // 3.安装

export default async function renderCsv(buffer, target) {
    const table  = await readText(buffer);
    return new Vue({
        render: h => h(Table, { props: { value: table } }),
    }).$mount(target)
}

将数组转化为模板可以识别的数据形式。

<template>
<div>
<!--  {{value}}-->
  <el-table class="container-table" ref="table" :data="rows" border height="100%" :header-cell-style="{'background':'#778da8','color':'#fff'}">
    <el-table-column v-for="(column, index) in columns" :key="index" :prop="column" :label="column">
      <template slot-scope="scope">
        <div>
          {{ scope.row[column] }}
        </div>
      </template>
    </el-table-column>
  </el-table>
</div>
</template>

<script>
import {readText,csvToArray} from "./util";
export default {
  name: "Table",
  props: {
    value: {
      type: String,
      default:''
    }
  },
  data(){
    return {
      rows: [],
      columns: [],
    }
  },
  watch:{
    value:{
      immediate:true,
      handler(value){
        this.setTable(value);
      }
    }
  },
  methods:{
    setTable(value){
      let data = csvToArray(value);
      for(let i=0;i<data.length;i++){
        if(i==0){
          this.columns = data[i];
        }else{
          let obj = {};
          data[i].forEach((e,index)=>{
            obj[data[0][index]]= e;
          })
          this.rows.push(obj);
        }
      }
    }
  }
}
</script>

<style scoped>

</style>

实现效果:

image.png

拓展:如何解决csv展示中出现的中文乱码问题

为什么会产生中文乱码呢?
如果表格内容有中文的话,就是个大问题了。因为一般网页的编码是UTF8,导出的表格也会是UTF8编码格式,如果不修改直接上传则为UTF8。但是如果修改,Windows平台下的常用表格软件包括Office和WPS全都将其转换成GBK编码。如果程序没有自动识别编码处理,将有很大概率导致乱码。
另一方面,如果网页使用GBK编码格式下载,也不能确保用户上传的文件就一定是GBK,因为MAC系统用的是UTF8,可能本来GBK的在修改后就成了UTF8了。

解决方案:先将服务器上的文件获取到,然后用FileReader根据编码格式不同按不同的编码格式读取到文件数据。
可以通过jschardet插件来获取文件的编码格式。

export async function readText(buffer) {
  return new Promise((resolve,reject)=>{
    let readerBs64 = new FileReader();
    readerBs64.readAsDataURL(new Blob([buffer]));
    readerBs64.onload = () => {
      let str = atob(readerBs64.result.split(';base64,')[1])
      // 要用二进制格式,获取文件编码
      let encoding = jschardet.detect(str)
      encoding = encoding.encoding;
      console.log('获取文件编码',encoding)
      let reader = new FileReader();
      if(encoding == 'GB2312'){
        reader.readAsText(new Blob([buffer]),'gbk')
      }else{
        reader.readAsText(new Blob([buffer]),encoding)
      }
      reader.onload = (loadEvent)=>{
        const {result} = loadEvent.target;
        resolve(result);
      }
    };
    readerBs64.onerror = e => reject(e);
  })
}

参考文档:www.freesion.com/article/212…