vue3 + antd vue 纯前端 基于xlsx 实现导入excel 转 json,将json数据转换XLSX导出(模版下载)

news2024/12/29 8:51:24

一、导入

0、关键代码

// 安装插件
npm i xlsx/yarn add xlsx
// 导入xlsx
import * as XLSX from 'xlsx';

点击提交的时候才整理数据。上传的时候文件保存在  state.form.file[0] 中的

// 定义字段映射关系
const fieldMap = {
  sheet2json: {
    技能名称: 'skill_name',
    技能等级: 'skill_level',
    技能描述: 'skill_desc',
    技能类型: 'skill_type',
    技能效果: 'skill_effect',
    技能消耗: 'skill_cost',
    技能持续时间: 'skill_duration',
    技能范围: 'skill_range',
    技能范围: 'skill_range',
    技能目标: 'skill_target'
  }
};

// 提交 --- 点击提交的时候才整理数据。上传的时候文件保存的  state.form.file[0] 中的
const handleSummit = () => {
  formRef.value.validate().then(async () => {
    try {
      const data = await state.form.file[0].arrayBuffer(); // 使用 arrayBuffer 避免中文乱码
      const workbook = XLSX.read(data, { type: 'buffer' });
      const outdata = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);

      // 映射字段名并过滤掉不符合预期的数据
      const mappedData = outdata
        .map(row => {
          return Object.keys(row).reduce((targetMap, key) => {
            const mappedKey = fieldMap.sheet2json[key];
            if (mappedKey) {
              targetMap[mappedKey] = row[key];
            }
            return targetMap;
          }, {});
        })
        .filter(item => Object.keys(item).length > 0); // 过滤空对象

      console.log('------- 导入的数据 -------', mappedData);

      emits('submit', mappedData);
      handleClose();
    } catch (error) {}
  });
};

1、template

<a-form :model="state.form" name="form" ref="formRef" :label-col="{ style: { width: '120px' } }" autocomplete="off" :rules="rules">
  <a-form-item label="导入文件上传" name="file" :rules="rules.file">
    <div class="file-warp" style="position: relative">
      <a-upload
        style="margin-left: 20px"
        :file-list="state.form.file"
        name="file"
        :customRequest="upload"
        :beforeUpload="beforeUpload"
        @remove="handleRemove"
        accept=".xlsx, .xls">
        <a-button type="primary">
          <upload-outlined></upload-outlined>
          上传文件
        </a-button>
      </a-upload>
      <a-button type="primary" ghost style="position: absolute; top: 0; left: 150px" @click="handleDownload">
        <VerticalAlignBottomOutlined></VerticalAlignBottomOutlined>
        模版下载
      </a-button>
    </div>
  </a-form-item>
</a-form>

2、script

import * as XLSX from 'xlsx';
import { reactive, ref } from 'vue';

const state = reactive({
  form: {
    file: []
  }
});
const formRef = ref(null);
const open = ref(true);

const rules = {
  file: [{ required: true, message: '请选择文件', trigger: ['blur', 'change'] }]
};

// 定义字段映射关系
const fieldMap = {
  sheet2json: {
    技能名称: 'skill_name',
    技能等级: 'skill_level',
    技能描述: 'skill_desc',
    技能类型: 'skill_type',
    技能效果: 'skill_effect',
    技能消耗: 'skill_cost',
    技能持续时间: 'skill_duration',
    技能范围: 'skill_range',
    技能范围: 'skill_range',
    技能目标: 'skill_target'
  },
  json2sheet: {
    skill_name: '技能名称',
    skill_level: '技能等级',
    skill_desc: '技能描述',
    skill_type: '技能类型',
    skill_effect: '技能效果',
    skill_cost: '技能消耗',
    skill_duration: '技能持续时间',
    skill_range: '技能范围',
    skill_target: '技能目标'
  }
};

// 上传文件之前检测
const beforeUpload = file => {
  const isXlsxOrXls = file.name.split('.')[1] == 'xlsx' || file.name.split('.')[1] == 'xls';
  if (!isXlsxOrXls) {
    message.error('只允许上传xlsx, xls格式的文件!');
    return false;
  }
  const isLt10M = file.size / 1024 / 1024 < 10;
  if (!isLt10M) {
    message.error('文件不得大于10MB!');
    return false;
  }
  return isXlsxOrXls && isLt10M;
};

// 选择文件
const upload = file => {
  // 原本调用接口上传的
  // uplaodFile(file.file).then(res => {
  //   fileList.value.push({ name: res.data.originalFilename, url: viteConfig.baseUrl + res.data.fileName, fileUrl: res.data.fileName });
  //   formRef.value.clearValidate();
  // });
  state.form.file = [file.file];
  formRef.value.clearValidate();
};

// 移除文件
const handleRemove = file => {
  state.form.file = [];
};

// 提交 转换数据
const handleSummit = () => {
  formRef.value.validate().then(async () => {
    try {
      const data = await state.form.file[0].arrayBuffer(); // 使用 arrayBuffer 避免中文乱码
      const workbook = XLSX.read(data, { type: 'buffer' });
      const outdata = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);

      // 映射字段名并过滤掉不符合预期的数据
      const mappedData = outdata
        .map(row => {
          return Object.keys(row).reduce((targetMap, key) => {
            const mappedKey = fieldMap.sheet2json[key];
            if (mappedKey) {
              targetMap[mappedKey] = row[key];
            }
            return targetMap;
          }, {});
        })
        .filter(item => Object.keys(item).length > 0); // 过滤空对象

      console.log('------- 导入的数据 -------', mappedData);

      emits('submit', mappedData);
      handleClose();
    } catch (error) {}
  });
};

二、模板下载

1、script

// 定义字段映射关系
const fieldMap = {
  json2sheet: {
    skill_name: '技能名称',
    skill_level: '技能等级',
    skill_desc: '技能描述',
    skill_type: '技能类型',
    skill_effect: '技能效果',
    skill_cost: '技能消耗',
    skill_duration: '技能持续时间',
    skill_range: '技能范围',
    skill_target: '技能目标'
  }
};

// 模板数据
let templateData = [
  {
    skill_name: '大刀斩',
    skill_level: '5',
    skill_desc: '技能描述',
    skill_type: '大招',
    skill_effect: '亚瑟王那样的大招',
    skill_cost: '10000',
    skill_duration: '10',
    skill_range: '500',
    skill_target: '目标:亚瑟王'
  }
];

// 模版下载
const handleDownload = () => {
  // 映射字段名并过滤掉不符合预期的数据
  const list = templateData
    .map(row => {
      return Object.keys(row).reduce((targetMap, key) => {
        const mappedKey = fieldMap.json2sheet[key];
        if (mappedKey) {
          targetMap[mappedKey] = row[key];
        }
        return targetMap;
      }, {});
    })
    .filter(item => Object.keys(item).length > 0); // 过滤空对象;

  const workSheet = XLSX.utils.json_to_sheet(list);
  const workBook = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(workBook, workSheet, '技能表');

  // 生成Excel文件并下载
  XLSX.writeFile(workBook, '技能表模板.xlsx');
};

三、完整的文件

<!--
 * @Description: ------------ fileDescription -----------
 * @Author: snows_l snows_l@163.com
 * @Date: 2024-07-18 14:46:47
 * @LastEditors: snows_l snows_l@163.com
 * @LastEditTime: 2024-07-19 15:51:19
 * @FilePath: /digital-qiankun-you/cmdb/src/pages/ipSource/components/uploadFile.vue
-->
<template>
  <div class="upeate-field-warp">
    <a-modal width="800px" v-model:open="open" :z-index="10004" centered :title="'规划导入'">
      <template #footer>
        <a-button type="primary" @click="handleSummit">确认</a-button>
      </template>
      <div class="update-field-content-warp">
        <a-form :model="state.form" name="form" ref="formRef" :label-col="{ style: { width: '120px' } }" autocomplete="off" :rules="rules">
          <a-form-item label="导入文件上传" name="file" :rules="rules.file">
            <div class="file-warp" style="position: relative">
              <a-upload
                style="margin-left: 20px"
                :file-list="state.form.file"
                name="file"
                :customRequest="upload"
                :beforeUpload="beforeUpload"
                @remove="handleRemove"
                accept=".xlsx, .xls">
                <a-button type="primary">
                  <upload-outlined></upload-outlined>
                  上传文件
                </a-button>
              </a-upload>
              <a-button type="primary" ghost style="position: absolute; top: 0; left: 150px" @click="handleDownload">
                <VerticalAlignBottomOutlined></VerticalAlignBottomOutlined>
                模版下载
              </a-button>
            </div>
          </a-form-item>
        </a-form>
      </div>
    </a-modal>
  </div>
</template>

<script setup>
import { UploadOutlined, VerticalAlignBottomOutlined } from '@ant-design/icons-vue';
import { reactive, ref } from 'vue';
import * as XLSX from 'xlsx';

const emits = defineEmits(['submit']);

const state = reactive({
  form: {
    file: []
  }
});
const formRef = ref(null);
const open = ref(true);

const rules = {
  file: [{ required: true, message: '请选择文件', trigger: ['blur', 'change'] }]
};

// 上传文件之前检测
const beforeUpload = file => {
  const isXlsxOrXls = file.name.split('.')[1] == 'xlsx' || file.name.split('.')[1] == 'xls';
  if (!isXlsxOrXls) {
    message.error('只允许上传xlsx, xls格式的文件!');
    return false;
  }
  const isLt10M = file.size / 1024 / 1024 < 10;
  if (!isLt10M) {
    message.error('文件不得大于10MB!');
    return false;
  }
  return isXlsxOrXls && isLt10M;
};

// 选择文件
const upload = file => {
  // 原本调用接口上传的
  // uplaodFile(file.file).then(res => {
  //   fileList.value.push({ name: res.data.originalFilename, url: viteConfig.baseUrl + res.data.fileName, fileUrl: res.data.fileName });
  //   formRef.value.clearValidate();
  // });
  state.form.file = [file.file];
  formRef.value.clearValidate();
};

// 移除文件
const handleRemove = file => {
  state.form.file = [];
};

// 初始化
const init = () => {
  open.value = true;
};

// 关闭
const handleClose = () => {
  open.value = false;
};

// 定义字段映射关系
const fieldMap = {
  sheet2json: {
    技能名称: 'skill_name',
    技能等级: 'skill_level',
    技能描述: 'skill_desc',
    技能类型: 'skill_type',
    技能效果: 'skill_effect',
    技能消耗: 'skill_cost',
    技能持续时间: 'skill_duration',
    技能范围: 'skill_range',
    技能范围: 'skill_range',
    技能目标: 'skill_target'
  },
  json2sheet: {
    skill_name: '技能名称',
    skill_level: '技能等级',
    skill_desc: '技能描述',
    skill_type: '技能类型',
    skill_effect: '技能效果',
    skill_cost: '技能消耗',
    skill_duration: '技能持续时间',
    skill_range: '技能范围',
    skill_target: '技能目标'
  }
};

// 模板数据
let templateData = [
  {
    skill_name: '大刀斩',
    skill_level: '5',
    skill_desc: '技能描述',
    skill_type: '大招',
    skill_effect: '亚瑟王那样的大招',
    skill_cost: '10000',
    skill_duration: '10',
    skill_range: '500',
    skill_target: '目标:亚瑟王'
  }
];

// 提交
const handleSummit = () => {
  formRef.value.validate().then(async () => {
    try {
      const data = await state.form.file[0].arrayBuffer(); // 使用 arrayBuffer 避免中文乱码
      const workbook = XLSX.read(data, { type: 'buffer' });
      const outdata = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);

      // 映射字段名并过滤掉不符合预期的数据
      const mappedData = outdata
        .map(row => {
          return Object.keys(row).reduce((targetMap, key) => {
            const mappedKey = fieldMap.sheet2json[key];
            if (mappedKey) {
              targetMap[mappedKey] = row[key];
            }
            return targetMap;
          }, {});
        })
        .filter(item => Object.keys(item).length > 0); // 过滤空对象

      console.log('------- 导入的数据 -------', mappedData);

      emits('submit', mappedData);
      handleClose();
    } catch (error) {}
  });
};

// 模版下载
const handleDownload = () => {
  // 映射字段名并过滤掉不符合预期的数据
  const list = templateData
    .map(row => {
      return Object.keys(row).reduce((targetMap, key) => {
        const mappedKey = fieldMap.json2sheet[key];
        if (mappedKey) {
          targetMap[mappedKey] = row[key];
        }
        return targetMap;
      }, {});
    })
    .filter(item => Object.keys(item).length > 0); // 过滤空对象;

  const workSheet = XLSX.utils.json_to_sheet(list);
  const workBook = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(workBook, workSheet, '技能表');
  // 生成Excel文件并下载
  XLSX.writeFile(workBook, '技能表模板.xlsx');
};

defineExpose({
  init
});
</script>

<style lang="less" scoped>
.upeate-field-warp {
  width: 100%;
}
</style>

<style lang="less">
.update-field-content-warp {
  padding: 40px 20px;
  .field-item {
    display: flex;
    align-items: center;
    label {
      min-width: 80px;
    }
  }
}
</style>

四、效果图:

原数据(图1)

导入组件(图2)

导出整理后的数据(图3)

模板下载(图4)

模板下载之后的文件(图5)

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

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

相关文章

ROS配置并同时驱动多个UVC相机(含功能包)

配置并同时驱动多个UVC相机&#xff0c;并将数据保存为ROS话题形式的bag文件。 ROS可以同时驱动多个UVC相机。要实现这个目标并将数据保存成ROS话题的形式&#xff0c;再保存为bag文件&#xff0c;可以按照以下步骤操作&#xff1a; 1. 安装必要的包 sudo apt-get update sud…

vue3前端开发-小兔鲜项目-一些额外提醒的内容

vue3前端开发-小兔鲜项目-一些额外提醒的内容&#xff01;今天这一篇文章&#xff0c;是提醒大家&#xff0c;如果你正在学习小兔鲜这个前端项目&#xff0c;有些地方需要提醒大家&#xff0c;额外注意的地方。 第一个&#xff1a;就是大家在进入二级页面后&#xff0c;有一个分…

软考-软件设计师(2)-操作系统概述:多级索引、PV操作、段页式存储、磁盘管理、进程管理、有限自动机、I/O设备管理软件分层等高频考点

场景 软考-软件设计师-操作系统概述模块高频考点整理。 以下为高频考点、知识点汇总,不代表该模块所有知识点覆盖,请以官方教程提纲为准。 注: 博客:霸道流氓气质-CSDN博客 实现 知识点 文件系统多级索引 求文件系统多级索引的最大长度 二级索引=一级索引*一级索引…

Oracle中LISTAGG 函数的介绍以及使用详情

LISTAGG 函数介绍 listagg 函数是 Oracle 11.2 推出的新特性。 其主要功能类似于 wmsys.wm_concat 函数&#xff0c; 即将数据分组后&#xff0c; 把指定列的数据再通过指定符号合并。 LISTAGG 使用 listagg 函数有两个参数&#xff1a; 1、 要合并的列名 2、…

论文总结:A Survey on Evaluation of Large Language Models-鲁棒性相关内容

A Survey on Evaluation of Large Language Models 只取了鲁棒性相关的内容 LLMs&#xff1a;《A Survey on Evaluation of Large Language Models大型语言模型评估综述》理解智能本质(具备推理能力)、AI评估的重要性(识别当前算法的局限性设 3.2.1 Robustness鲁棒性&#xf…

R语言优雅的进行广义可加模型泊松回归分析

泊松回归&#xff08;Poisson regression&#xff09;是以结局变量为计数结果时的一种回归分析。泊松回归在我们的生活中应用非常广泛&#xff0c;例如&#xff1a;1分钟内过马路人数&#xff0c;1天内火车站的旅客流动数&#xff0c;1天内的银行取钱人数&#xff0c;一周内的销…

Unity UGUI 之Text 控件

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 1.Text是什么 UI里面写文本的&#xff08;注意是legacy Text&#xff0c;而不是TextmeshP…

c-periphery RS485串口库文档serial.md(serial.h)(非阻塞读)(VMIN、VTIME)

c-peripheryhttps://github.com/vsergeev/c-periphery 文章目录 NAMESYNOPSISENUMERATIONS关于奇偶校验枚举类型 DESCRIPTIONserial_new()serial_open()关于流控制软件流控制&#xff08;XON/XOFF&#xff09;硬件流控制&#xff08;RTS/CTS&#xff09;选择流控制方法 serial_…

文本编辑三巨头(grep)

目录 正则表达式 元字符 grep 案例 我在编写脚本的时候发现&#xff0c;三个文本编辑的命令&#xff08;grep、sed、awk&#xff0c;被称为文本编辑三剑客&#xff0c;我习惯叫它三巨头&#xff09;用的还挺多的&#xff0c;说实话我一开始学的时候也有些懵&#xff0c;主要…

异常处理和swagger使用

全局异常处理类 定义全局异常处理类&#xff0c;会将错误全部提交到这个异常处理类中进行处理&#xff0c;这个类会将处理的统一结果响应给前端&#xff0c;如果不添加异常处理类&#xff0c;异常不会按照统一的响应格式进行&#xff0c;前端无法识别&#xff0c;当然也可以在…

Windows10+vs 2017中创建WEB API教程

我们如果需要用到web api怎么办&#xff1f;一般来说可以自己开发和去使用别人开发好的api&#xff0c;今天我们来讲一下Windows10vs 2017中创建web Api的教程。目前本教程当中的方法在Win10 VS2017&#xff08;MVC5&#xff09;win server2016vs2017&#xff0c;vs2013 vs201…

MT2142 逆序(树状数组)

思路&#xff1a; 开始完全没有思路&#xff0c;还是想问题的方法不对&#xff08;应该先暴力模拟&#xff0c;然后再想可以优化的方法&#xff09;。 后来看了解析&#xff0c;用chang[]来存储每个元素删除后&#xff08;或者是该元素前面的元素删除后&#xff09;对record造成…

Linux应用——socket函数及TCP通信

网络通信实质上也是实现进程间通信&#xff0c;只是与之前进程间通信不同的是&#xff0c;现在在不同的计算机上进行进程间通信。比如&#xff1a;利用QQ工具实现聊天&#xff0c;在两个电脑上有不同的QQ进程之间在通信。而网络通信是如何使用进程间通信呢&#xff1f;采用的是…

【Unity】关于Luban的简单使用

最近看了下Luban导出Excel数据的方式&#xff0c;来记录下 【Unity】关于Luban的简单使用 安装Luban开始使用UnityLubanC# 扩展 安装Luban Luban文档&#xff1a;https://luban.doc.code-philosophy.com/docs/beginner/quickstart 1.安装dotnet sdk 8.0或更高版本sdk 2.githu…

windows安装redis设置密码、修改端口、提供外部访问

windows安装redis设置密码、修改端口、提供外部访问 一、前言1. 设置密码2. 修改端口3. 允许外部访问4. 注意事项 一、前言 设置Redis在Windows上设置密码、修改端口以及允许外部访问&#xff0c;需要进行以下步骤&#xff1a; 下载地址 https://github.com/tporadowski/redi…

unity2D游戏开发01项目搭建

1新建项目 选择2d模板,设置项目名称和存储位置 在Hierarchy面板右击&#xff0c;create Empty 添加组件 在Project视图中右键新建文件夹 将图片资源拖进来&#xff08;图片资源在我的下载里面&#xff09; 点击Player 修改属性&#xff0c;修好如下 点击Sprite Editor 选择第二…

机器人开源调度系统OpenTcs6二开-车辆表定义

前面已经知道opentcs 需要车辆的模型结构数据&#xff0c;将里面的数据结构化&#xff0c;已表的形式生成&#xff0c;再找一个开源的基础框架项目对车辆进行增删改的管理 表结构&#xff1a; CREATE TABLE Vehicle (id INT AUTO_INCREMENT PRIMARY KEY COMMENT 唯一标识符,n…

尝试带你理解 - 进程地址空间,写时拷贝

序言 在上一篇文章 进程概念以及进程状态&#xff0c;我们提到了 fork 函数&#xff0c;该函数可以帮我们创建一个子进程。在使用 fork 函数时&#xff0c;我们会发现一些奇怪的现象&#xff0c;举个栗子&#xff1a; 1 #include <stdio.h>2 #include <unistd.h>3 …

unity美术资源优化(资源冗余,主界面图集过多)

图片资源冗余&#xff1a; UPR unity的性能优化工具检查资源 1.检查纹理读/写标记 开启纹理资源的读/写标志会导致双倍的内存占用 检查Inspector -> Advanced -> Read/Write Enabled选项 2.检查纹理资源alpha通道 如果纹理的alpha通道全部为0&#xff0c;或者全部为2…

了解Selenium中的WebElement

Selenium中到处都使用WebElement来执行各种操作。什么是WebElement&#xff1f;这篇文章将详细讨论WebElement。 Selenium中的WebElement是一个表示网站HTML元素的Java接口。HTML元素包含一个开始标记和一个结束标记&#xff0c;内容位于这两个标记之间。 HTML元素的重命名 …