前端界面在线excel编辑器 。node编写post接口获取文件流,使用传参替换表格内容展示、前后端一把梭。

news2025/4/24 0:51:20

首先luckysheet插件是支持在线替换excel内容编辑得但是浏览器无法调用本地文件,如果只是展示,让后端返回文件得二进制文件流就可以了,直接使用luckysheet展示。

这里我们使用xlsx-populate得node简单应用来调用本地文件,自己写一个接口,让自己对后端有更一步得了解。

效果图:

首先我们创建一个node应用

1、只是想展示文件,直接让后端返回文件流可以跳过直接往下拉,看如何在线展示excel编辑器

2、node下载得二进制文件流,不需要展示得可以直接导出下载

前置条件:已经安装node16以上版本

初始化项目:

1.创建项目目录 :一个文件夹名为 my-xlsx-populate
2.文件内右键打开cmd  使用命令创建   生成package.json:(-y使用默认配置)
npm init -y

3.package.json文件我们需要得依赖复制一下,大家直接npm i 就可以了

{
  "name": "my-xlsx-populate", // 项目唯一标识符
  "version": "1.0.0", // 初始版本号
  "description": "基于Excel文件操作的Node.js服务框架",
  "main": "server.js", // 主入口文件
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1", 
    "start": "node server.js" // 启动服务脚本[2](@ref)
  },
  "keywords": [], // 关键词列表
 
  "license": "ISC", // 开源协议
  "dependencies": {
    "cors": "^2.8.5", // 跨域资源共享中间件,用于处理跨域请求
    "express": "^4.21.2", // Node.js核心Web框架,提供HTTP服务基础能力
    "multer": "^1.4.5-lts.1", // 文件上传处理中间件,支持multipart/form-data
    "xlsx-populate": "^1.21.0" // Excel文件操作库,支持读写xlsx文件
  }
}

4.创建server.js文件,功能如下 。

  1. /excelprocess创建post接口
  2. 获取body传参循环操作xlsx-populate替换excel内容
  3. 接口返回文件流
const express = require('express');
const XlsxPopulate = require('xlsx-populate');
const path = require('path');
const fs = require('fs');
const cors = require('cors'); // 引入 cors 中间件

const app = express();

// 使用 cors 中间件,允许所有来源的请求
app.use(cors());

// 解析 JSON 请求体
app.use(express.json());

// 检查 excelTemplates 目录是否存在,不存在则创建
const templateDir = path.join(__dirname, 'excelTemplates');
if (!fs.existsSync(templateDir)) {
    fs.mkdirSync(templateDir);
}

// 处理静态文件,使得前端页面可以被访问
app.use(express.static(__dirname));

// 处理文件处理请求
app.post('/excelprocess', async (req, res) => {
  try {
    // 获取 excelTemplates 目录下的所有文件
    const files = fs.readdirSync(templateDir);
    if (files.length === 0) {
      console.error('excelTemplates 目录中没有文件');
      return res.status(400).send('excelTemplates 目录中没有文件');
    }

    // 选择第一个文件作为模板
    const selectedFile = path.join(templateDir, files[0]);
    console.log('选定模板文件:', selectedFile);

    // 检查文件扩展名
    const fileExtension = path.extname(selectedFile).toLowerCase();
    if (fileExtension !== '.xlsx') {
      console.error('文件扩展名不正确:', fileExtension);
      return res.status(400).send('仅支持 .xlsx 文件');
    }

    // 检查文件是否可读
    fs.accessSync(selectedFile, fs.constants.R_OK);

    // 读取 Excel 文件
    const workbook = await XlsxPopulate.fromFileAsync(selectedFile);
    console.log('成功读取文件:', selectedFile);
    const sheet = workbook.sheet(0);

    // 获取传入的修改数据
    const modifications = req.body;

    // 根据传入的数据修改 Excel 文件内容
    for (const [cellAddress, value] of Object.entries(modifications)) {
      sheet.cell(cellAddress).value(value);
    }

    // 生成修改后的文件路径
    const defaultFileName = 'output.xlsx';
    const outputPath = path.join(__dirname, defaultFileName);

    // 将修改后的文件保存到磁盘
    await workbook.toFileAsync(outputPath);
    console.log('文件已保存到:', outputPath);

    // 对文件名进行严格编码
    const originalFileName = req.query.filename || '修改后.xlsx'; // 使用查询参数中的文件名或默认文件名
    const encodedFileName = encodeURIComponent(originalFileName).replace(/'/g, '%27');

    // 设置响应头并发送文件流
    res.setHeader('Content-Disposition', `attachment; filename="${encodedFileName}"`);
    res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');

    // 使用流的方式发送文件
    const fileStream = fs.createReadStream(outputPath);
    fileStream.pipe(res);

    // 监听文件流的错误
    fileStream.on('error', (err) => {
      console.error('文件流发送时出错:', err);
      res.status(500).send('文件流发送时出错');
    });


  } catch (error) {
    console.error('处理文件时出错:', error.message);
    res.status(500).send(`处理文件时出错: ${error.message}`);
  }
});

// 启动服务器
const port = 3000;
app.listen(port, () => {
    console.log(`服务器运行在端口 http://localhost:${port}/`);
});

5.创建excelTemplates文件夹用于存放我们要操作得本地文件,在里放入一个excel文件

全部目录如下:

excel模板sheet设置如下:

6.运行node 服务

7.打开接口调试工具

接口类型: post

接口地址 : http://localhost:3000/excelprocess

接口传参:

[
  {"sheetName": "Sheet1", "data": {"A1": "新值1"}},
  {"sheetName": "Sheet2", "data": {"A1": "新值2"}},
  {"sheetName": "Sheet3", "data": {"A1": "新值3"}}
]

点击下载:

打开文件内容如下:

替换成功,我们获取到了一个替换后得二进制文件流

xlsx-populate更多具体配置请看我得另一篇文章:

前端插件使用xlsx-populate,花样配置excel内容,根据坐添加标替换excel内容,修改颜色,合并单元格...。_xlsxpopulate-CSDN博客

接下来我们将二进制文件展示到前端接口

接下来我们使用luckysheet插件展示操作完得excel文件

使用luckysheet插件

1.克隆官方gite代码到本地
git clone https://gitee.com/mengshukeji/Luckysheet.git
2.流水线操作-下载依赖打包得到dist文件
npm install
npm install gulp -g

npm run build

3.来到VUE项目根目录下创建   public/Luckysheet
  将dist里面得文件复制到public/Luckysheet目录下
4.来到VUE项目根目录下luckysheet.html 

将public/Luckysheet里面得文件引入到luckysheet.html 

luckysheet.html 内容如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>luckysheet-wrapper</title>

    <link rel="stylesheet" href="/Luckysheet/plugins/css/pluginsCss.css">
    <link rel="stylesheet" href="/Luckysheet/plugins/plugins.css">
    <link rel="stylesheet" href="/Luckysheet/css/luckysheet.css">
    <link rel="stylesheet" href="/Luckysheet/assets/iconfont/iconfont.css">
    <script src="/Luckysheet/plugins/js/plugin.js"></script>
    <script src="/Luckysheet/luckysheet.umd.js"></script>
</head>
<body>
<noscript>
    <strong>We're sorry but luckysheet-wrapper doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="sheetContainer" style="margin:0px;padding:0px;position:absolute;width:100%;height:95%;left: 0px;top: 0px;"></div>
<!-- built files will be auto injected -->
</body>
</html>

目录结构如下:

5.界面引用Luckysheet

这里我们使用  iframe 官方得方法是把Luckysheet实列挂在到windows实列上,初始化为windows.luckysheet  但是我想一个界面同时存在多个编辑器时候,就会出现只能有一个excel展示,我们使用iframe把luckysheet 实列用沙箱隔离,保证独立性

<iframe :id="iframeId" height="100%" width="100%" src="/luckysheet.html" class="no-scroll"></iframe>

我得另一篇具体文章详情:解决Luckysheet在线预览编辑Excel、PDF.....无法在同一个界面创建多个luckysheet实列问题-CSDN博客

什么是iframe,火爆的微前端解决方案方案,教你快速看懂使用-CSDN博客

有兴趣大家可以去看看哈~

6.这里为了编辑器逻辑隔离,也为了可以创建多个实列,我们把编辑器封装为一个子组件使用:

功能逻辑:

第一步:这里我们需要引入luckyexcel ,因为luckysheet只支持展示json格式得文件,我们从node接口http://localhost:3000/excelprocess 获取得是二进制文件流,所以要转换。
npm install luckyexcel --save
第二步:将我们准备得 luckysheet.html 文件引入到 iframe ,设置动态id
  <iframe :id="iframeId" height="100%" width="100%" src="/luckysheet.html" class="no-scroll"></iframe>

const iframeId = `iframe${Math.random().toString().substring(2)}`;

第三步:onMounted 里面 初始化 luckysheet实列
const $sheet = ref(null); // 存储luckysheet实例
onMounted(() => {
  const frame = document.querySelector(`#${iframeId}`);
  frame.onload = () => {
    $sheet.value = frame.contentWindow.luckysheet;
    const container = frame.contentDocument.createElement('div');
    container.id = 'sheetContainer';
    frame.contentDocument.body.appendChild(container);

    $sheet.value.create({container: container.id});
    initLuckysheet(); //调用后端接口
  };

});
第四步:调用node接口
const initLuckysheet = async () => {
  try {

    const response = await axios.post('http://localhost:3000/excelprocess',
        [
          {"sheetName": "Sheet1", "data": {"A1": "新值1"}},
          {"sheetName": "Sheet2", "data": {"A1": "新值2"}},
          {"sheetName": "Sheet3", "data": {"A1": "新值3"}}
        ] , {
      responseType: 'blob'
    }).then(response => {
      const file = new File([response.data], 'XXX.xlsx', {type: response.data.type});
      console.log(file);
      var files = [];
      files.push(file);
      uploadExcel(files); //加载表单数据
    });
  } catch (error) {
    console.error('加载并解析 Excel 失败:', error);
  }
};

第五步:接口返回数据使用 luckyexcel 转换为json格式 初始化  luckysheet ,加载表单数据
const uploadExcel = (files) => {
  LuckyExcel.transformExcelToLucky(files[0], function (exportJson, luckysheetfile) {
    if (exportJson.sheets == null || exportJson.sheets.length == 0) return alert('读取excel文件内容失败, 目前不支持XLS文件!');
    $sheet.value.destroy();
    $sheet.value.create({
      data: exportJson.sheets,
      title: exportJson.info.name,
      userInfo: exportJson.info.name.creator,
      container: 'sheetContainer', // 设定DOM容器的id
      showtoolbar: false, // 是否显示工具栏
      showinfobar: false, // 是否显示顶部信息栏
      showstatisticBar: true, // 是否显示底部计数栏
      sheetBottomConfig: false, // sheet页下方的添加行按钮和回到顶部按钮配置
      allowEdit: false, // 是否允许前台编辑
      enableAddRow: false, // 是否允许增加行
      enableAddCol: false, // 是否允许增加列
      sheetFormulaBar: true, // 是否显示公式栏
      enableAddBackTop: false, // 返回头部按钮
      showsheetbar: true, // 是否显示底部sheet页按钮
      // 自定义配置底部sheet页按钮
      showsheetbarConfig: {
        add: false,
        menu: false,
      },
    });
  });
};
子组件eftExcel.vue 完整代码 如下
<template>
  <div style="height: 100%; overflow: hidden;">
    <div class="controls">
    </div>
    <iframe :id="iframeId" height="100%" width="100%" src="/luckysheet.html" class="no-scroll"></iframe>
  </div>
</template>

<script setup>
import {ref, onMounted, defineProps} from 'vue';
import axios from 'axios';
import * as LuckyExcel from 'luckyexcel';
import { ElMessage } from 'element-plus'
const iframeId = `iframe${Math.random().toString().substring(2)}`;
const $sheet = ref(null); // 存储luckysheet实例

onMounted(() => {
  const frame = document.querySelector(`#${iframeId}`);
  frame.onload = () => {
    $sheet.value = frame.contentWindow.luckysheet;
    const container = frame.contentDocument.createElement('div');
    container.id = 'sheetContainer';
    frame.contentDocument.body.appendChild(container);

    $sheet.value.create({container: container.id});
    initLuckysheet();
  };

});

const initLuckysheet = async () => {
  try {

    const response = await axios.post('http://localhost:3000/excelprocess',
        [
          {"sheetName": "Sheet1", "data": {"A1": "新值1"}},
          {"sheetName": "Sheet2", "data": {"A1": "新值2"}},
          {"sheetName": "Sheet3", "data": {"A1": "新值3"}}
        ] , {
      responseType: 'blob'
    }).then(response => {
      const file = new File([response.data], 'XXX.xlsx', {type: response.data.type});
      console.log(file);
      var files = [];
      files.push(file);
      uploadExcel(files);
    });
  } catch (error) {
    console.error('加载并解析 Excel 失败:', error);
  }
};
const uploadExcel = (files) => {
  LuckyExcel.transformExcelToLucky(files[0], function (exportJson, luckysheetfile) {
    if (exportJson.sheets == null || exportJson.sheets.length == 0) return alert('读取excel文件内容失败, 目前不支持XLS文件!');
    $sheet.value.destroy();
    $sheet.value.create({
      data: exportJson.sheets,
      title: exportJson.info.name,
      userInfo: exportJson.info.name.creator,
      container: 'sheetContainer', // 设定DOM容器的id
      showtoolbar: false, // 是否显示工具栏
      showinfobar: false, // 是否显示顶部信息栏
      showstatisticBar: true, // 是否显示底部计数栏
      sheetBottomConfig: false, // sheet页下方的添加行按钮和回到顶部按钮配置
      allowEdit: false, // 是否允许前台编辑
      enableAddRow: false, // 是否允许增加行
      enableAddCol: false, // 是否允许增加列
      sheetFormulaBar: true, // 是否显示公式栏
      enableAddBackTop: false, // 返回头部按钮
      showsheetbar: true, // 是否显示底部sheet页按钮
      // 自定义配置底部sheet页按钮
      showsheetbarConfig: {
        add: false,
        menu: false,
      },
    });
  });
};

</script>

<style scoped>
.controls {
  margin-bottom: 20px;
}

.no-scroll {
  overflow: hidden; /* 隐藏水平和垂直滚动条 */
}
</style>
第六步:父组件引入调用
<template>
<div class="index_body">
  <div class="container">
    <el-main style="height: calc(100% - 80px); position: relative; padding: 0px">
      <LeftExcel ></LeftExcel>
    </el-main>
  </div>
</div>
</template>

<script setup>
import LeftExcel from './leftExcel.vue'
import { ref, onMounted, onUnmounted } from 'vue'

</script>

<style lang="scss" scoped>
.index_body{
margin-top: 50px;

.container {
  display: flex;
  justify-content: space-between; /* 根据需要调整 */
  align-items: stretch; /* 根据需要调整 */
  height: 100vh; /* 根据需要调整 */
}

.left-component {
  flex: 1; /* 根据需要调整 */
  margin-right: 10px; /* 根据需要调整 */
}

.right-component {
  flex: 1; /* 根据需要调整 */
  margin-left: 10px; /* 根据需要调整 */
}
}
</style>

效果展示:

第六步:我们操作一下功能

接口传参中我们可以自己修改为需要得内容

    const response = await axios.post('http://localhost:3000/excelprocess',
        [
          {"sheetName": "Sheet1", "data": {"A1": "新值1"}},
          {"sheetName": "Sheet2", "data": {"A1": "新值2"}},
          {"sheetName": "Sheet3", "data": {"A1": "新值3"}}
        ] , {
      responseType: 'blob'
    })

我们把本地得表格添加边框,只替换其中得值,再次展示,实际开发中我们可以提前设置好模板得样式比如换行,合并单元格,这样我们只要替换其中得坐标值就可以了。

大家看到这里麻烦给个赞吧!!!

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

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

相关文章

核心知识——Spark核心数据结构:RDD

引入 通过前面的学习&#xff0c;我们对于Spark已经有一个基本的认识&#xff0c;并且搭建了一个本地的练习环境&#xff0c;因为本专栏的主要对象是数仓和数分&#xff0c;所以就不花大篇幅去写环境搭建等内容&#xff0c;当然&#xff0c;如果感兴趣的小伙伴可以留言&#x…

【数据分享】中国3254座水库集水区特征数据集(免费获取)

水库在水循环、碳通量、能量平衡中扮演关键角色&#xff0c;实实在在地影响着我们的生活。其功能和环境影响高度依赖于地理位置、上游流域属性&#xff08;如地形、气候、土地类型&#xff09;和水库自身的动态特征&#xff08;如水位、蒸发量&#xff09;。但在此之前一直缺乏…

我用Axure画了一个富文本编辑器,还带交互

最近尝试用Axure RP复刻了一个富文本编辑器&#xff0c;不仅完整还原了工具栏的各类功能&#xff0c;还通过交互设计实现了接近真实编辑器操作体验。整个设计过程聚焦功能还原与交互流畅性&#xff0c;最终成果令人惊喜。 编辑器采用经典的三区布局&#xff1a;顶部工具栏集成了…

Uniapp自定义TabBar组件全封装实践与疑难问题解决方案

前言 在当前公司小程序项目中&#xff0c;我们遇到了一个具有挑战性的需求&#xff1a;根据不同用户身份动态展示差异化的底部导航栏&#xff08;TabBar&#xff09; 。这种多角色场景下的UI适配需求&#xff0c;在提升用户体验和实现精细化运营方面具有重要意义。 在技术调研…

【PCB工艺】软件是如何控制硬件的发展过程

软件与硬件的关系密不可分&#xff0c;软件的需求不断推动硬件的发展&#xff0c;而硬件的进步又为软件创新提供了基础。 时光回溯到1854年&#xff0c;亨利戈培尔发明了电灯泡&#xff08;1879年&#xff0c;托马斯阿尔瓦爱迪生找到了更合适的材料研制出白炽灯。&#xff09;…

【教程】如何利用bbbrisk一步一步实现评分卡

利用bbbrisk一步一步实现评分卡 一、什么是评分卡1.1.什么是评分卡1.2.评分卡有哪些 二、评分卡怎么弄出来的2.1.如何制作评分卡2.2.制作评分卡的流程 三、变量的分箱3.1.数据介绍3.2.变量自动分箱3.3.变量的筛选 四、构建评分卡4.1.评分卡实现代码4.2.评分卡表4.3.阈值表与分数…

丝杆,同步带,链条选型(我要自学网)

这里的选型可以70%的正确率&#xff0c;正确率不高&#xff0c;但是选型速度会比较快。 1.丝杆选型 后面还有一堆计算公式&#xff0c;最终得出的结果是导程25&#xff0c;轴径25mm的丝杆。 丝杆选择长度时&#xff0c;还要注意细长比&#xff0c;长度/直径 一般为30到50。 2…

【YOLO系列】基于YOLOv8的无人机野生动物检测

基于YOLOv8的无人机野生动物检测 1.前言 在野生动物保护、生态研究和环境监测领域&#xff0c;及时、准确地检测和识别野生动物对于保护生物多样性、预防人类与野生动物的冲突以及制定科学的保护策略至关重要。传统的野生动物监测方法通常依赖于地面巡逻、固定摄像头或无线传…

一文详细讲解Python(详细版一篇学会Python基础和网络安全)

引言 在当今数字化时代&#xff0c;Python 作为一种简洁高效且功能强大的编程语言&#xff0c;广泛应用于各个领域&#xff0c;从数据科学、人工智能到网络安全等&#xff0c;都能看到 Python 的身影。而网络安全作为保障信息系统和数据安全的关键领域&#xff0c;其重要性不言…

【Java】Hibernate的一级缓存

Session是有一个缓存, 又叫Hibernate的一级缓存 session缓存是由一系列的Java集合构成的。当一个对象被加入到Session缓存中&#xff0c;这个对象的引用就加入到了java的集合中&#xff0c;以后即使应用程序中的引用变量不再引用该对象&#xff0c;只要Session缓存不被清空&…

学习笔记--(6)

import numpy as np import matplotlib.pyplot as plt from scipy.special import erfc# 设置参数 rho 0.7798 z0 4.25 # 确保使用大写 Z0&#xff0c;与定义一致def calculate_tau(z, z_prime, rho, s_values):return np.log(rho * z * z_prime * s_values / 2)# 定义 chi_…

JWT在线解密/JWT在线解码 - 加菲工具

JWT在线解密/JWT在线解码 首先进入加菲工具 选择 “JWT 在线解密/解码” https://www.orcc.top 或者直接进入JWT 在线解密/解码 https://www.orcc.top/tools/jwt 进入功能页面 使用 输入对应的jwt内容&#xff0c;点击解码按钮即可

飞桨PP系列新成员PP-DocLayout开源,版面检测加速大模型数据构建,超百页文档图像一秒搞定

背景介绍 文档版面区域检测技术通过精准识别并定位文档中的标题、文本块、表格等元素及其空间布局关系&#xff0c;为后续文本分析构建结构化上下文&#xff0c;是文档图像智能处理流程的核心前置环节。随着大语言模型、文档多模态及RAG&#xff08;检索增强生成&#xff09;等…

飞速(FS)HPC无损组网:驱动AI高性能计算网络转型升级

案例亮点 部署低功耗、高密度飞速&#xff08;FS&#xff09;以太网交换机&#xff0c;紧凑机身设计节省70%机房空间&#xff0c;冗余电源和智能风扇确保系统高可用性&#xff0c;有效优化散热和降低能耗。 支持25G/40G/100G多速率自适应交换架构&#xff0c;构建超低时延企业…

git 常用操作整理

一.git 的概念 Git 是一个分布式版本控制系统&#xff0c;用于跟踪文件的更改历史&#xff0c;帮助开发者管理代码的版本。以下是关于 Git 的一些基本概念&#xff1a; 1. 仓库&#xff08;Repository&#xff09; - **本地仓库**&#xff1a;在你的计算机上存储的项目文件及…

JAVA数据库增删改查

格式 Main.java(测试类) package com.example;import com.example.dao.UserDao; import com.example.model.User;public class Main {public static void main(String[] args) {UserDao userDao new UserDao();// 测试添加用户System.out.println(" 添加用户 ");Us…

上海某海外视频平台Android高级工程师视频一面

问的问题比较细&#xff0c;有很多小细节在里面&#xff0c;平时真不一定会注意到&#xff0c;做一个备忘&#xff1a; 1.Object类里面有哪些方法&#xff1f; Object 类是 Java 中所有类的根类&#xff0c;它定义了一些基本方法&#xff0c;供所有类继承和重写1. 常用方法 1…

前后端数据序列化:从数组到字符串的旅程(附优化指南)

&#x1f310; 前后端数据序列化&#xff1a;从数组到字符串的旅程&#xff08;附优化指南&#xff09; &#x1f4dc; 背景&#xff1a;为何需要序列化&#xff1f; 在前后端分离架构中&#xff0c;复杂数据类型&#xff08;如数组、对象&#xff09;的传输常需序列化为字符…

idea报错:程序包不存在

这里的程序包是我们项目里自己写的&#xff0c;idea却报错不存在。 解决方法: 参考这位大佬的方法&#xff0c;OK。

spring boot 整合redis

1.在pom文件中添加spring-boot-starter-data-redis依赖启动器 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 2.编写三个实体类 RedisHash("p…