首先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文件,功能如下 。
/excelprocess
创建post接口获取body传参循环操作xlsx-populate替换excel内容
接口返回文件流
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'
})
我们把本地得表格添加边框,只替换其中得值,再次展示,实际开发中我们可以提前设置好模板得样式比如换行,合并单元格,这样我们只要替换其中得坐标值就可以了。
大家看到这里麻烦给个赞吧!!!