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

news2024/11/24 3:37:11

一、导入

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/1936697.html

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

相关文章

【中项】系统集成项目管理工程师-第2章 信息技术发展-2.2新一代信息技术及应用-2.2.1物联网与2.2.2云计算

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…

iPhone手机上备忘录怎么设置字数显示

在日常生活和工作中&#xff0c;我经常会使用iPhone的备忘录功能来记录一些重要的想法、待办事项或临时笔记。备忘录的便捷性让我可以随时捕捉灵感&#xff0c;但有时候&#xff0c;我也会苦恼于不知道自己记录了多少内容&#xff0c;尤其是在需要控制字数的时候。 想象一下&a…

mysql的B+树索引结构介绍

一、B树 特性&#xff1a; 所有的叶子结点中包含了全部关键字的信息&#xff0c;非叶子节点只存储键值信息&#xff0c;及指向含有这些关键字记录的指针&#xff0c;且叶子结点本身依关键字的大小自小而大的顺序链接&#xff0c;所有的非终端结点可以看成是索引部分&#xff0…

达梦数据库 MPP集群搭建(带主备)

MPP集群搭建&#xff08;带主备&#xff09; 1.背景2.操作内容和要求3. 具体步骤3.1 搭建过程3.1.1 集群搭建3.1.2 准备工作3.1.2.1 初始化3.1.2.2 备份数据库 3.1.3 配置主库EP013.1.3.1 配置dm.ini3.1.3.2 配置dmmal.ini3.1.3.3 配置dmarch.ini3.1.3.4 配置dmmpp.ctl3.1.3.5 …

Linux NFS服务搭建及使用

一、NFS 服务器介绍 nfs &#xff08; Network File System &#xff09;即网络文件系统&#xff0c;其基于 UDP/IP使用 nfs 能够在不同计算机之间通过网络进行文件共享&#xff0c;能使使用者访问网络上其它计算机中的文件就像在访问自己的计算机一样。 二、NFS 服务器的特点 …

【Java】用队列实现栈 力扣

文章目录 题目链接题目描述思路代码 题目链接 225.用队列实现栈 题目描述 思路 一个队列在模拟栈弹出元素的时候只要将队列头部的元素&#xff08;除了最后一个元素外&#xff09; 重新添加到队列尾部&#xff0c;此时再去弹出元素就是栈的顺序了。 代码 class MyStack {Q…

C++那些事之依赖注入

C那些事之依赖注入 最近星球里面有个小伙伴让更新一下依赖注入&#xff0c;于是写出了这篇文章&#xff0c;来从实际的例子讲解&#xff0c;本文会讲解一些原理与实现&#xff0c;完整的实现代码懒人版放在星球中&#xff0c;我们开始正文。 大纲&#xff1a; 直接依赖接口依赖…

什么是长效住宅IP?

长效住宅IP的定义 长效住宅IP&#xff0c;简而言之&#xff0c;是指长期稳定、非动态更换的住宅网络IP地址。这类IP地址通常由互联网服务提供商&#xff08;ISP&#xff09;分配给居民家庭用户&#xff0c;用于上网、网络通信等日常网络活动。与传统的动态IP相比&#xff0c;长…

​前端Vue组件技术实践:打造自定义精美悬浮菜单按钮组件

随着前端技术的迅猛发展&#xff0c;复杂的应用场景和不断迭代的产品需求使得开发的复杂度日益提升。传统的整体式开发方式已经难以满足现代前端应用的灵活性和可维护性需求。在这样的背景下&#xff0c;组件化开发逐渐崭露头角&#xff0c;成为解决复杂前端应用问题的有效手段…

算法第十一天:leetcode707.设计链表

一、设计链表的题目描述与链接 707.设计链表的链接如下表所示&#xff0c;您可直接复制下面网址进入力扣学习&#xff0c;在观看下面的内容之前一定要先做一遍哦&#xff0c;这样才能印象深刻&#xff01; https://leetcode.cn/problems/design-linked-list/https://leetcode.…

Java_Docker

镜像和容器&#xff1a; 镜像仓库&#xff1a; 存储和管理镜像的平台&#xff0c;镜像仓库中有非常多常用软件的镜像&#xff0c;Docker官方维护了一个公共仓库​​​​​​:​Docker Hub 部署MySQL&#xff1a; docker run -d \--name mysql \-p 3306:3306 \-e TZAsia/Shang…

C/C++的堆栈内存分配详解

在C/C编程中&#xff0c;内存管理是至关重要的一个方面。理解内存的分配方式有助于编写高效、可靠的程序&#xff0c;C/C主要使用两种内存分配方式&#xff1a;堆&#xff08;heap&#xff09;和栈&#xff08;stack&#xff09;。这两者在管理方式、性能和使用场景上都有显著区…

RDMA软件架构

RDMA 的软件架构按层次可分成两部分&#xff0c;即 rdma-core 和内核 RDMA 子系统&#xff0c;分别运行在 Linux 系统中的用户态和内核态。整个软件架构适用于所有类型的 RDMA 网卡&#xff0c;不管网卡执行了哪种 RDMA 协议&#xff08;InfiniBand/RoCE/ iWARP&#xff09;。 …

[SUCTF 2019]EasySQL1

这是一个简单的SQL注入题&#xff0c;但是因为我的SQL基础约等于0&#xff0c;所以做起来很难。 首先试试引号是否被过滤 可以看到单引号、双引号都被过滤了&#xff0c;试试其他的盲注都不行&#xff0c;基本上可以确定不能用这种方法。 在测试的过程中发现&#xff0c;输入…

Python实现招聘数据采集 ,并做可视化分析

转眼秋招快到了&#xff0c; 今天来学习一下如何用Python采集全网招聘数据&#xff0c;并进行可视化分析&#xff0c;为就业准备~ 话不多说开始造 源码和详细的视频讲解我都打包好了&#xff0c;文末名片自取 准备工作 首先你需要准备这些 环境 Python 3.10 Pycharm 模块…

[解决方法]git上传的项目markdown文件的图片无法显示

应该有不少初学者会遇到这种情况 以下是本人摸索出的解决方法 我使用的是typora&#xff0c;首先设置typora的图片设置 文件>偏好设置>图像 如下&#xff1a; 选择这个就会在此文件的同级目录下创建一个assets文件夹来存放此markdown文件的所有图片 然后勾选优先使用相…

开机自启动设置

该方法优点是最简单且bug最少&#xff1a; 按键盘上的&#xff1a;CtrlR 打开"运行”&#xff0c;输入“shell:startup"并打开&#xff1b;下次重启即可自动启动软件了

CSRF防御及模拟CSRF攻击

CSRF&#xff08;Cross-Site Request Forgery&#xff0c;跨站请求伪造&#xff09;是一种攻击方式&#xff0c;攻击者可以诱使用户在已登录的应用中执行非本意的操作。为了防御这种攻击&#xff0c;许多Web应用会使用CSRF Token来验证请求的合法性。 0.csrf 攻击原理 2. 后端服…

2024 HNCTF PWN(hide_flag Rand_file_dockerfile Appetizers TTOCrv_)

文章目录 参考hide_flag思路exp Rand_file_dockerfile libc 2.31思路exp Appetizers glibc 2.35绕过关闭标准输出实例客户端 关闭标准输出服务端结果exp TTOCrv_&#x1f3b2; glibc 2.35逆向DT_DEBUG获得各个库地址随机数思路exp 参考 https://docs.qq.com/doc/p/641e8742c39…

HTTPS 的加密过程 详解

HTTP 由于是明文传输&#xff0c;所以安全上存在以下三个风险&#xff1a; 窃听风险&#xff0c;比如通信链路上可以获取通信内容。篡改风险&#xff0c;比如通信内容被篡改。冒充风险&#xff0c;比如冒充网站。 HTTPS 在 HTTP 与 TCP 层之间加入了 SSL/TLS 协议&#xff0c…