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

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

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

效果图:

首先我们创建一个node应用

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

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

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

初始化项目:

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

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

javascript 复制代码
{
  "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文件,功能如下 。

**/excelprocess**创建post接口
获取body传参循环操作xlsx-populate替换excel内容
接口返回文件流
javascript 复制代码
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

接口传参:

javascript 复制代码
[
  {"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代码到本地
javascript 复制代码
git clone https://gitee.com/mengshukeji/Luckysheet.git
2.流水线操作-下载依赖打包得到dist文件
bash 复制代码
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 内容如下

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 实列用沙箱隔离,保证独立性
javascript 复制代码
<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 获取得是二进制文件流,所以要转换。
javascript 复制代码
npm install luckyexcel --save
第二步:将我们准备得 luckysheet.html 文件引入到 iframe ,设置动态id
javascript 复制代码
  <iframe :id="iframeId" height="100%" width="100%" src="/luckysheet.html" class="no-scroll"></iframe>

const iframeId = `iframe${Math.random().toString().substring(2)}`;
第三步:onMounted 里面 初始化 luckysheet实列
javascript 复制代码
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接口
javascript 复制代码
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 ,加载表单数据
javascript 复制代码
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 完整代码 如下
javascript 复制代码
<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>
第六步:父组件引入调用
javascript 复制代码
<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>

效果展示:

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

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

javascript 复制代码
    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'
    })

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

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

相关推荐
腾讯TNTWeb前端团队4 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰7 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪7 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪7 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy8 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom9 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom9 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom9 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom9 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom9 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试