【vue、UI】使用 Vue2 和 Element UI 封装 CSV 文件上传组件,实现csv回显

news2024/9/22 5:31:13

文章目录

  • 前言
  • 组件功能概述
  • 实现效果
  • 组件模板结构
  • 组件的核心逻辑
    • 1.数据属性定义
    • 2.方法拆解
    • 3.CSV 文件解析方法
    • 4. 错误处理方法
  • 组件样式
  • 完整组件代码
  • 总结
  • 待优化的地方

前言

在 Vue2 项目中,我们经常需要封装一些可重用的组件来提升开发效率。本文将介绍如何使用 Vue2 和 Element UI 封装一个用于上传 CSV 文件并在对话框中回显其内容的公共组件。此组件共涉及两个接口:一个用于校验 CSV 文件内容是否合规,另一个用于上传经过校验的 CSV 文件。

组件功能概述

该组件主要包括以下功能:

  • 选择 CSV 文件并上传。
  • 校验文件内容是否符合要求。
  • 将文件内容以表格形式展示。
  • 支持对不合规内容进行标记和提示。
  • 用户可在确认内容无误后手动点击上传。

实现效果

在这里插入图片描述

组件模板结构

首先来看组件的模板部分。

<template>
  <el-dialog
    :visible.sync="visible"
    title="上传 CSV 文件"
    width="50%"
    :before-close="handleClose"
    :close-on-click-modal="false"
  >
    <el-upload
      :action="!validateStatus ? validate : action"
      :before-upload="beforeUpload"
      :show-file-list="false"
      :headers="headers"
      ref="upload"
      :on-success="handleAvatarSuccess"
      :auto-upload="!validateStatus"
      :file-list="fileList"
      :on-error="errorFn"
    >
      <el-button type="primary" @click="selectFile">选择 CSV 文件</el-button>
    </el-upload>

    <el-table
      v-if="tableData.length > 0"
      :data="tableData.slice(1)"
      style="width: 100%; margin-top: 20px"
      border
    >
      <el-table-column
        v-for="(header, index) in tableData[0]"
        :key="'header-' + index"
        :prop="'col-' + index"
        :label="header"
      >
        <template slot-scope="scope">
          <div :class="{ 'error-cell': scope.row[index].value.isError }">
            {{ scope.row[index].value.value }}
            <el-tooltip
              class="item"
              effect="dark"
              placement="top"
              v-if="scope.row[index].value.isError"
            >
              <template slot="content">
                {{ scope.row[index].value.errorMsg }}</template
              >
              <i class="el-icon-question"></i>
            </el-tooltip>
          </div>
        </template>
      </el-table-column>
    </el-table>

    <span slot="footer" class="dialog-footer">
      <el-button @click="handleClose">关 闭</el-button>
      <el-button
        type="primary"
        @click="handleConfirm"
        :disabled="!validateStatus"
        :title="!validateStatus ? '请上传文件并通过校验' : '点击上传文件'"
        >确定上传</el-button
      >
    </span>
  </el-dialog>
</template>

在这个模板中,使用了 Element UI 的 el-dialog 作为弹出框,el-upload 作为上传组件,el-table 显示上传的 CSV 文件内容。组件的主要逻辑操作通过各种方法(methods)来实现。

组件的核心逻辑

1.数据属性定义

以下是组件的数据属性,用于存储上传文件的状态、表格数据和 HTTP 请求头信息

data() {
  return {
    tableData: [], // 存储解析后的表格数据
    headers: {
      Authorization: "Bearer " + getToken(),
    },
    validateStatus: false,
    fileList: [], // 存储上传的文件
  };
}
  • tableData:存储解析后的 CSV 文件数据。
  • headers:HTTP 请求头,包含授权信息。
  • validateStatus:文件校验状态,决定文件是否可以被上传。
  • fileList:用于存储选择的 CSV 文件。

2.方法拆解

selectFile() 方法

selectFile() {
  this.validateStatus = false;
}

当用户点击“选择 CSV 文件”按钮时,重置 validateStatus 状态为 false,确保在选择新文件时校验状态被重置

validateData(data) 方法

validateData(data) {
  const allData = Object.values(data).flat();

  // 检查是否所有的 isError 都为 false
  const allValid = allData.every((item) => !item.isError);

  if (allValid) {
    this.validateStatus = true;
    this.$message.success("校验通过,可以上传");
  } else {
    this.validateStatus = false;
    this.$message.error("请按要求重新修改上传内容");
  }
}

该方法用于校验上传的数据,如果数据无误,则设置 validateStatustrue,并显示成功提示;否则显示错误提示。

handleAvatarSuccess(res, file) 方法

handleAvatarSuccess(res, file) {
  if (res.code === 500) {
    this.$message.error(res.msg);
    this.validateStatus = false;
    return;
  }

  if (res.data) {
    const csvHeaders = this.tableData[0];

    const fields = Object.keys(res.data);
    const dataLength = res.data[fields[0]].length;

    const result = Array.from({ length: dataLength }, (_, index) =>
      fields.map((field) => ({
        value: res.data[field][index],
        isError: false,
        errorMsg: "",
      }))
    );

    this.tableData = [csvHeaders, ...result];
    this.validateData(res.data);
  }
}

该方法在文件上传成功后调用,处理上传成功后的逻辑,包括数据解析和更新表格数据。

handleClose() 方法

handleClose() {
  this.tableData = [];
  this.$emit("update:visible", false);
}

关闭对话框时,清空表格数据并触发 visible 属性更新事件,关闭对话框。

handleConfirm() 方法

handleConfirm() {
  const formData = new FormData();
  formData.append("file", this.fileList[0]);

  const config = {
    headers: this.headers,
  };

  axios
    .post(this.action, formData, config)
    .then((response) => {
      if (response.data.code == 500 && response.data.msg == null) {
        this.$message.error("文件提交失败,未知原因");
      } else if (response.data.code == 200) {
        this.$message({
          dangerouslyUseHTMLString: true,
          message: response.data.msg,
          type: "success",
          duration: 5000,
        });
        this.$emit("upload-success");
      }
    })
    .catch((error) => {
      this.$message.error("文件提交失败");
    });
}

该方法在用户确认上传时调用,通过 Axios 发起 POST 请求将文件上传至后端接口。

3.CSV 文件解析方法

beforeUpload(file) 方法

beforeUpload(file) {
  if (!this.validateStatus) {
    const reader = new FileReader();
    reader.onload = (e) => {
      const decoder = new TextDecoder("gbk");
      const csvText = decoder.decode(e.target.result);
      this.tableData = this.parseCSV(csvText);

      if (this.tableData.length > 0) {
        return true;
      } else {
        return false;
      }
    };
    reader.readAsArrayBuffer(file);
    this.fileList = [file];
  } else {
    return true;
  }
}

该方法在文件上传之前执行,使用 FileReader 对象解析 CSV 文件内容,并将其转换为表格数据。

parseCSV(text) 方法

parseCSV(text) {
  const lines = text.split("\n").map((line) => line.trim());
  if (lines.length === 0) return [];

  const headers = this.parseLine(lines[0]);
  const data = lines.slice(1).map((line) => {
    const cells = this.parseLine(line);
    const row = headers.map((header, index) => ({
      value: cells[index] || "",
      isError: false,
      errorMsg: "",
    }));
    return row;
  });

  return [headers, ...data];
}

该方法用于解析 CSV 文件内容,按行拆分并解析每一行内容为表格所需的格式。

4. 错误处理方法

errorFn(err, file, fileList) 方法

errorFn(err, file, fileList) {
  console.log("🚀 ~ errorFn ~ err:", err);
}

该方法处理文件上传过程中的错误,当前仅简单地打印错误信息。

组件样式

<style scoped>
.el-table th,
.el-table td {
  text-align: center;
  padding: 10px;
}

.error-cell {
  background-color: #ffb1b1;
  color: black;
  padding: 5px;
}
</style>

此部分为组件的样式定义,确保表格居中对齐,并为有错误的单元格添加红色背景。

完整组件代码

完整的组件代码如下所示。这段代码结合了 Vue2 和 Element UI,封装了一个 CSV 文件上传与显示的功能组件。

<template>
  <el-dialog
    :visible.sync="visible"
    title="上传 CSV 文件"
    width="50%"
    :before-close="handleClose"
    :close-on-click-modal="false"
  >
    <el-upload
      :action="!validateStatus ? validate : action"
      :before-upload="beforeUpload"
      :show-file-list="false"
      :headers="headers"
      ref="upload"
      :on-success="handleAvatarSuccess"
      :auto-upload="!validateStatus"
      :file-list="fileList"
      :on-error="errorFn"
    >
      <el-button type="primary" @click="selectFile">选择 CSV 文件</el-button>
    </el-upload>

    <el-table
      v-if="tableData.length > 0"
      :data="tableData.slice(1)"
      style="width: 100%; margin-top: 20px"
      border
    >
      <el-table-column
        v-for="(header, index) in tableData[0]"
        :key="'header-' + index"
        :prop="'col-' + index"
        :label="header"
      >
        <template slot-scope="scope">
          <div :class="{ 'error-cell': scope.row[index].value.isError }">
            {{ scope.row[index].value.value }}
            <el-tooltip
              class="item"
              effect="dark"
              placement="top"
              v-if="scope.row[index].value.isError"
            >
              <template slot="content">
                {{ scope.row[index].value.errorMsg }}</template
              >
              <i class="el-icon-question"></i>
            </el-tooltip>
          </div>
        </template>
      </el-table-column>
    </el-table>

    <span slot="footer" class="dialog-footer">
      <el-button @click="handleClose">关 闭</el-button>
      <el-button
        type="primary"
        @click="handleConfirm"
        :disabled="!validateStatus"
        :title="!validateStatus ? '请上传文件并通过校验' : '点击上传文件'"
        >确定上传</el-button
      >
    </span>
  </el-dialog>
</template>

<script>
import { getToken } from "@/utils/auth";
import axios from "axios";
export default {
  props: {
    visible: {
      type: Boolean,
      required: true,
    },
    action: {
      type: String,
      required: true,
    },
    validate: {
      type: String,
      required: true,
    },
  },
  data() {
    return {
      tableData: [], // 存储解析后的表格数据
      headers: {
        Authorization: "Bearer " + getToken(),
      },
      validateStatus: false,
      fileList: [], // 存储上传的文件
    };
  },
  methods: {
    selectFile() {
      this.validateStatus = false;
    },
    validateData(data) {
      const allData = Object.values(data).flat();

      const allValid = allData.every((item) => !item.isError);

      if (allValid) {
        this.validateStatus = true;
        this.$message.success("校验通过,可以上传");
      } else {
        this.validateStatus = false;
        this.$message.error("请按要求重新修改上传内容");
      }
    },
    handleAvatarSuccess(res, file) {
      if (res.code === 500) {
        this.$message.error(res.msg);
        this.validateStatus = false;
        return;
      }

      if (res.data) {
        const csvHeaders = this.tableData[0];

        const fields = Object.keys(res.data);
        const dataLength = res.data[fields[0]].length;

        const result = Array.from({ length: dataLength }, (_, index) =>
          fields.map((field) => ({
            value: res.data[field][index],
            isError: false,
            errorMsg: "",
          }))
        );

        this.tableData = [csvHeaders, ...result];
        this.validateData(res.data);
      }
    },

    handleClose() {
      this.tableData = [];
      this.$emit("update:visible", false);
    },
    handleConfirm() {
      const formData = new FormData();
      formData.append("file", this.fileList[0]);

      const config = {
        headers: this.headers,
      };

      axios
        .post(this.action, formData, config)
        .then((response) => {
          if (response.data.code == 500 && response.data.msg == null) {
            this.$message.error("文件提交失败,未知原因");
          } else if (response.data.code == 200) {
            this.$message({
              dangerouslyUseHTMLString: true,
              message: response.data.msg,
              type: "success",
              duration: 5000,
            });
            this.$emit("upload-success");
          }
        })
        .catch((error) => {
          this.$message.error("文件提交失败");
        });
    },
    beforeUpload(file) {
      if (!this.validateStatus) {
        const reader = new FileReader();
        reader.onload = (e) => {
          const decoder = new TextDecoder("gbk");
          const csvText = decoder.decode(e.target.result);
          this.tableData = this.parseCSV(csvText);

          if (this.tableData.length > 0) {
            return true;
          } else {
            return false;
          }
        };
        reader.readAsArrayBuffer(file);
        this.fileList = [file];
      } else {
        return true;
      }
    },
    errorFn(err, file, fileList) {
      console.log("🚀 ~ errorFn ~ err:", err);
    },
    parseCSV(text) {
      const lines = text.split("\n").map((line) => line.trim());
      if (lines.length === 0) return [];

      const headers = this.parseLine(lines[0]);
      const data = lines.slice(1).map((line) => {
        const cells = this.parseLine(line);
        const row = headers.map((header, index) => ({
          value: cells[index] || "",
          isError: false,
          errorMsg: "",
        }));
        return row;
      });

      return [headers, ...data];
    },

    parseLine(line) {
      const result = [];
      let current = "";
      let inQuotes = false;

      for (let i = 0; i < line.length; i++) {
        const char = line[i];

        if (char === '"') {
          inQuotes = !inQuotes;
        } else if (char === "," && !inQuotes) {
          result.push(current.trim());
          current = "";
        } else {
          current += char;
        }
      }

      result.push(current.trim());

      return result;
    },
  },
};
</script>

<style scoped>
.el-table th,
.el-table td {
  text-align: center;
  padding: 10px;
}

.error-cell {
  background-color: #ffb1b1;
  color: black;
  padding: 5px;
}
</style>

总结

通过本文的介绍,了解了如何使用 Vue2 和 Element UI 封装一个 CSV 文件上传和回显的组件。该组件的设计充分考虑了数据校验和用户体验,使得上传和展示过程更加直观和友好。在实际项目中,可以根据业务需求对该组件进行扩展和定制。希望这篇文章对您有所帮助!

待优化的地方

文件handleAvatarSuccess可以优化,生成的数据有点冗余,过于嵌套了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2111402.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux工程管理文件Makefile-入门篇

1.Makefile简介 Makefile是在Linux环境下 C/C 程序开发必须要掌握的一个工程管理文件。当你使用make命令去编译一个工程项目时&#xff0c;make工具会首先到这个项目的根目录下去寻找Makefile文件&#xff0c;然后才能根据这个文件去编译程序。那Makefile在编译过程中到底起了…

T7:咖啡豆识别

T7&#xff1a;咖啡豆识别 **一、前期工作**1.设置GPU,导入库2.导入数据3.查看数据 **二、数据预处理**1.加载数据2.可视化数据3.配置数据集 **三、构建CNN网络模型**1、手动搭建2、直接调用官方模型 **四、编译模型****五、训练模型****六、模型评估****七、预测**八、暂时总结…

Spring-容器:IOC-基于注解管理Bean

目录 一、基于注解管理Bean&#xff08;重点&#xff09;1.1、概述1.2、开启组件扫描1.2.1、指定要排除的组件1.2.2、仅扫描指定组件 1.3、使用注解定义Bean1.4、使用Autowired注入1.4.1、属性注入1.4.2、set注入1.4.3、构造方法注入1.4.4、形参注入1.4.5、无注解注入1.4.6、联…

自幂数判断c++

题目描述 样例输入 3 152 111 153样例输出 F F T 代码如下&#xff1a; #include<bits/stdc.h> using namespace std; long long m,a; int main(){cin>>m;for(int i1;i<m;i){cin>>a;long long ta,n[10],cc0,s0;while(t!0){//求位数与拆位n[cc]t%10;tt/…

报错:Reached the max session limit(DM8 达梦数据库)

报错:Reached the max session limit - - DM8 达梦数据库 1 环境介绍2 数据库启动SYSTEM IS READY后面日志3 数据库刚启动日志4 达梦数据库学习使用列表 1 环境介绍 某项目无法连接数据库,报错:超过最大会话数限制 , 检查 dmdba ulimit -a openfiles 已改检查 dm.ini 其中 MAX…

中间代码例题

答案&#xff1a;D 知识点&#xff1a; 中间代码是一种简单且含义明确的记号系统&#xff0c;可以有若干形式&#xff0c;它们的共同特征是与机器无关。 最常见的中间代码有&#xff1a;后缀式&#xff0c;语法树&#xff0c;三地址码&#xff0c;四元式 这些往往是数据&am…

迪普防火墙接口故障处理

一、防火墙故障初查 一台捷普防火墙&#xff0c;突然间业务不通&#xff0c;接口UP&#xff0c;策略正常&#xff0c;区域正常&#xff0c;互联地址就是不能ping通。 如上&#xff0c;接口状态很正常。 如上&#xff0c;互联设备ping不通。 二、锁定故障问题点 检查arp表&…

卷轴模式系统APP源码之产品分析:探索其设计精髓与市场潜力

在移动互联网的浪潮中&#xff0c;各类创新应用层出不穷&#xff0c;其中&#xff0c;“卷轴模式系统APP”作为一种融合了传统文化元素与现代交互设计的产品&#xff0c;正逐渐引起市场的关注。本文将从产品设计的角度&#xff0c;深入分析卷轴模式系统APP的源码特性、用户体验…

CVPR 2024最新论文分享┆YOLO-World:一种实时开放词汇目标检测方法

论文分享简介 本推文主要介绍了CVPR 2024上的一篇论文《YOLO-World: Real-Time Open-Vocabulary Object Detection》&#xff0c;论文的第一作者为Tianheng Cheng和Lin Song&#xff0c;该论文提出了一种开放词汇目标检测的新方法&#xff0c;名为YOLO-World。论文通过引入视觉…

思科IP访问控制列表4

#网络安全技术实现# #任务四命名访问控制列表的控制4# #1配置计算机的IP 地址、子网掩码和网关 #2配置Router-A的主机名称及其接口IP地址 Router>enable Router#conf t Router(config)#hostname Router-A Router-A(config)#int g0/1 Router-A(config-if)#ip add 192.1…

如何在算家云搭建Flux.1(AI绘画)

一、Flux.1简介 Flux.1 是黑森林实验室发布的 AI 绘图模型&#xff0c;也可以说是 SD 的原班人马打造。其表现出的效果已经超越了 SD3、DALLE3(HD)和 Midjourney v6.0&#xff0c;具有强大的出图能力&#xff0c;相比 SD3 拥有更优秀的提示词理解能力&#xff0c;更强的文字生…

内网渗透—横向移动非约束委派约束委派

前言 今天依旧是横向移动的内容&#xff0c;委派主要分为三类非约束委派、约束委派、资源委派。今天主要讲前面两个内容&#xff0c;资源委派留到最后再讲。 实验环境 Web&#xff1a;192.168.145.137&#xff0c;192.168.22.28 DC&#xff1a;192.168.22.30 非约束委派 原…

6.科学计算模块Numpy(3)对ndarray数组的常用操作

引言 众所周知&#xff0c;numpy能作为python中最受欢迎的数据处理模块&#xff0c;脱离不了它最核心的部件——ndarray数组。那么&#xff0c;我们今天就来了解一下numpy中对ndarray的常用操作。 通过阅读本篇博客你可以&#xff1a; 1.掌握ndarray数组的切片和copy 2.学会…

wx.chooseMessageFile在pc端微信小程序失效解决方法

项目场景&#xff1a; 在uniapp上驱动微信开发者工具&#xff08;下图&#xff09; 在手机上和微信开发者工具中&#xff08;图1&#xff09;都可以上传成功&#xff0c; 打开pc端的微信小程序 在pc端打开小程序时点击上传没反应 问题描述 提示&#xff1a;这里描述项目中遇到…

css实现卡片右上角的状态

1、成品展示 2、html部分 <div class"itemBox"><div class"status">{{ statusList[item.status] }}</div> </div> 3、css部分 .itemBox {position: relative;overflow: hidden; } .status {height: 25px;line-height: 25px;bac…

ISO26262 功能安全对设计的要求以及评判安全等级的主要参数

ISO 26262 标准规定了道路车辆功能安全的相关要求&#xff0c;不同 ASIL 等级对控制器在设计、安全机制、验证确认和文档记录等方面有不同要求。高 ASIL 等级的控制器需满足更高的硬件和软件设计标准&#xff0c;具备更强的故障检测、诊断、容错和恢复能力&#xff0c;进行更严…

电脑回收站被清空,怎么恢复丢失数据?

回收站&#xff0c;这个看似不太起眼的电脑功能&#xff0c;实际上在关键时刻能够为我们挽回重大损失&#xff0c;帮助我们重新获得至关重要的文件和数据。对于经常与电脑打交道的朋友们来说&#xff0c;当某个文件被不小心删除时&#xff0c;回收站往往成为我们文件找回和恢复…

Vue 项目hash和history模式打包部署与服务器配置

你好&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏、评论和关注。 在开发 Vue 项目时&#xff0c;Vue Router 提供了两种模式来创建单页面应用&#xff08;SPA&#xff09;的 URL&#xff1a;hash 模式和 history 模式。 简单说下两者的主要区别&#xff1a; hash 模式下的…

2024 年高教社杯全国大学生数学建模竞赛B题—生产过程中的决策问题(讲解+代码+成品论文助攻)

2024数学建模国赛选题建议团队助攻资料-CSDN博客https://blog.csdn.net/qq_41489047/article/details/141925859本次国赛white学长团队选择的是比较擅长的BC题&#xff0c;选题建议和助攻资料可参见上面这篇博文&#xff0c;本篇博文主要介绍B题—生产过程中的决策问题&#xf…

反转二叉树(递归非递归解决)

给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 方法一&#xff1a;递归解法 递归是处理树结构问题的常用方式&#xff0c;二叉树的问题通…