效果大致如下,首先主页面会有个按钮
点击确定后选择文件,然后打开弹框编辑
工具栏和右键菜单可以自定义配置 点击确定后调用后端接口 后端处理后回显到页面
首先是引入插件,有两种方法,一种是cdn引入在线文件,但是资源加载缓慢,经常报错,这里建议使用下面这种
1、在这个网站拉取源码Luckysheet: 🚀Luckysheet ,一款纯前端类似excel的在线表格,功能强大、配置简单、完全开源。
2、执行 npm install 下载依赖
3、执行 npm run build 进行打包
4、把打包好的dist文件放在自己项目的public文件夹下,把dist里面除开index.html的所有文件复制到sheet文件夹下面
5、在根目录的html文件的head里面添加以下代码
<link rel="stylesheet" href="/sheet/plugins/css/pluginsCss.css" />
<link rel="stylesheet" href="/sheet/plugins/plugins.css" />
<link rel="stylesheet" href="/sheet/css/luckysheet.css" />
<link rel="stylesheet" href="/sheet/assets/iconfont/iconfont.css" />
<script src="/sheet/plugins/js/plugin.js"></script>
<script src="/sheet/luckysheet.umd.js"></script>
由于需要将本地文件转化为lucky要的格式,这里需要安装插件
npm install luckyexcel
一、下面正式开始,新建ExcelEdit.vue文件,下面是完整代码
<template>
<div class="dialog-mask" v-show="props.show">
<div class="dialog-container">
<div class="dialog-header">{{ `导入` }}</div>
<div class="dialog-body">
<div id="luckysheet" style="width: 100%; height: 380px"></div>
</div>
<div class="dialog-footer">
<button class="btn" @click="confirmForm">确定</button>
<button class="btn" @click="emit('update:show')">取消</button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {
computed,
onBeforeUpdate,
onMounted,
reactive,
ref,
watchEffect,
withDefaults,
watch,
unref,
onUpdated,
nextTick,
} from "vue";
import { useMessage } from "naive-ui";
import { useRouter } from "vue-router";
import { each, cloneDeep, filter, find, groupBy } from "lodash-es";
import { format } from "date-fns";
// 下面换成自己项目的保存api即可
import { importExcel } from "@/api/charts";
import LuckyExcel from "luckyexcel";
import exportExcel from "@/utils/exportExcel";
const message = useMessage();
interface IProps {
show?: boolean;
current?: any;
view?: boolean;
params?: any;
}
const props = withDefaults(defineProps<IProps>(), {
show: false,
view: false,
});
// 子组件向父组件传值
const emit = defineEmits<{
(e: "update:show"): void;
(e: "reloadTable"): void;
(e: "getStatisticsList"): void;
}>();
// 这里主要是为了解决弹框编辑聚焦框的层级问题
watch(
() => props.show,
() => {
nextTick(() => {
const luckysheetBox = document.querySelector(
".luckysheet-input-box"
) as any;
if (props.show && luckysheetBox) {
luckysheetBox.style.cssText = "z-index:9999";
} else if (luckysheetBox) {
luckysheetBox.style.cssText = "z-index:-1";
}
});
},
{
immediate: true,
deep: true,
}
);
// 数据绑定
function bindExcel(value: any, name = "测试数据") {
LuckyExcel.transformExcelToLuckyByUrl(value, name, (exportJson: any) => {
if (exportJson.sheets === null || exportJson.sheets.length === 0) {
message.warning(
"无法读取excel文件的内容,目前不支持xls文件,仅支持xlsx类型!"
);
nextTick(() => {
window.luckysheet.create({
container: "luckysheet", // 设定DOM容器的id
lang: "zh", // 设定表格语言
showinfobar: false,
});
});
return;
}
window.luckysheet.destroy();
nextTick(() => {
window.luckysheet.create({
container: "luckysheet", // luckysheet is the container id
lang: "zh", // 设定表格语言
row: 100, // 默认500行
showinfobar: false,
data: exportJson.sheets,
allowCopy: true, // 是否允许拷贝
allowEdit: true, // 是否允许前台编辑
enableAddRow: true, // 允许增加行
enableAddCol: true, // 允许增加列
showtoolbar: true,
showtoolbarConfig: {
undoRedo: true, //撤销重做,注意撤消重做是两个按钮,由这一个配置决定显示还是隐藏
paintFormat: false, //格式刷
currencyFormat: false, //货币格式
percentageFormat: false, //百分比格式
numberDecrease: false, // '减少小数位数'
numberIncrease: false, // '增加小数位数
moreFormats: false, // '更多格式'
font: true, // '字体'
fontSize: true, // '字号大小'
bold: false, // '粗体 (Ctrl+B)'
italic: false, // '斜体 (Ctrl+I)'
strikethrough: false, // '删除线 (Alt+Shift+5)'
underline: false, // '下划线 (Alt+Shift+6)'
textColor: true, // '文本颜色'
fillColor: true, // '单元格颜色'
border: false, // '边框'
mergeCell: false, // '合并单元格'
horizontalAlignMode: false, // '水平对齐方式'
verticalAlignMode: false, // '垂直对齐方式'
textWrapMode: false, // '换行方式'
textRotateMode: false, // '文本旋转方式'
image: false, // '插入图片'
link: false, // '插入链接'
chart: false, // '图表'(图标隐藏,但是如果配置了chart插件,右击仍然可以新建图表)
postil: false, //'批注'
pivotTable: false, //'数据透视表'
function: true, // '公式'
frozenMode: false, // '冻结方式'
sortAndFilter: true, // '排序和筛选'
conditionalFormat: false, // '条件格式'
dataVerification: false, // '数据验证'
splitColumn: false, // '分列'
screenshot: false, // '截图'
findAndReplace: false, // '查找替换'
protection: false, // '工作表保护'
print: false, // '打印'
},
showsheetbar: false, //是否显示底部sheet页按钮
showsheetbarConfig: {
add: false, //新增sheet
menu: false, //sheet管理菜单
sheet: false, //sheet页显示
},
cellRightClickConfig: {
copy: true, // 复制
copyAs: false, // 复制为
paste: true, // 粘贴
insertRow: true, // 插入行
insertColumn: true, // 插入列
deleteRow: true, // 删除选中行
deleteColumn: true, // 删除选中列
deleteCell: true, // 删除单元格
hideRow: false, // 隐藏选中行和显示选中行
hideColumn: false, // 隐藏选中列和显示选中列
rowHeight: true, // 行高
columnWidth: true, // 列宽
clear: true, // 清除内容
matrix: false, // 矩阵操作选区
sort: true, // 排序选区
filter: true, // 筛选选区
chart: false, // 图表生成
image: false, // 插入图片
link: false, // 插入链接
data: false, // 数据验证
cellFormat: false, // 设置单元格格式
},
});
});
});
}
async function confirmForm() {
onClickSaveFile();
emit("update:show");
}
// 保存文件
const onClickSaveFile = async () => {
const buf = await exportExcel(window.luckysheet.getAllSheets());
const blob = new Blob([buf]);
// 保存到后端 需要转为相应格式的二进制流
const file = new File([blob], Math.random() * 100 + ".xlsx", {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
handleUpload(file);
};
const handleUpload = async (file: any) => {
try {
// 创建formdata实例
let formData = new window.FormData();
// 将获取的文件通过append方法加入实例中
formData.append("file", file);
const { data } = ((await importExcel(formData)) || {}) as any;
if (data?.code === 0) {
message.success(data?.msg || "导入成功");
} else {
message.error(data?.msg || "导入失败");
}
} catch (error) {
console.log(error);
}
};
// 暴露方法给父组件使用
defineExpose({
bindExcel,
});
</script>
<style lang="scss">
.luckysheet-cols-menu {
z-index: 9999;
}
.dialog-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
}
.dialog-container {
width: 90%;
height: 560px;
border-radius: 8px;
background-color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
.dialog-header {
padding: 16px;
font-size: 18px;
font-weight: bold;
text-align: center;
border-bottom: 1px solid #ccc;
}
.dialog-body {
padding: 16px;
font-size: 14px;
text-align: center;
}
.dialog-footer {
padding: 16px;
text-align: center;
}
.btn {
appearance: none;
border: 1px solid black;
border-radius: 4px;
padding: 8px 16px;
margin-left: 8px;
color: black;
background-color: white;
cursor: pointer;
}
</style>
在弹框中使用会遇到一些问题,导致无法编辑,主要是使用ui库的弹框,暂时没有找到解决办法,这里没办法自己手写了个弹框,解决了这个问题
二、页面中使用
项目隐私只附上部分代码
<template>
<div>
<n-upload
action=""
:default-upload="false"
:show-file-list="false"
@change="handleUploadChange"
@before-upload="beforeUpload"
style="width: 84px"
>
<n-button>上传文件</n-button>
</n-upload>
<ExcelEdit
ref="excelEdit"
:show="excelModalMap.edit"
:current="excelCurrent"
:params="editParams"
@update:show="onToggleExcelModal('edit')"
@reload-table="loadData"
></ExcelEdit>
</div>
</template>
<script lang="ts" setup>
import {
h,
onMounted,
reactive,
ref,
watch,
computed,
unref,
onBeforeMount,
} from "vue";
import {
NSpace,
useMessage,
NSwitch,
NButton,
NDataTable,
NInput,
NSelect,
NTabs,
FormItemRule,
NForm,
NGrid,
NGi,
NFormItem,
NUpload,
} from "naive-ui";
import ExcelEdit from "./ExcelEdit.vue";
const excelModalMap = reactive<IModalMap>({
edit: false,
});
const excelCurrent = ref();
const onToggleExcelModal = (field: keyof IModalMap) => {
excelModalMap[field] = !excelModalMap[field];
if (["edit"].includes(field) && !excelModalMap[field]) {
excelCurrent.value = "";
}
};
const excelEdit = ref();
function beforeUpload(data: { file: any; fileList: any }) {
if (data.file.file?.type !== "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
message.error("只能上传xlsx格式的文件,请重新上传");
return false;
}
return true;
}
function handleUploadChange(data: any) {
const fileBlob = new Blob([data.file.file], { type: "application/x-xls" });
const fileurl = window.URL.createObjectURL(fileBlob);
excelCurrent.value = data.file.file;
const name = "目标数据";
// 调用子组件方法渲染excel表格
excelEdit.value?.bindExcel(fileurl, name);
// 打开弹框编辑器
onToggleExcelModal("edit");
}
</script>
<style lang="scss" scoped>
.tool-bar {
display: flex;
justify-content: flex-end;
padding: 6px 0;
}
</style>
注意:调用api保存的时候需要再headers中加入
headers: {
"Content-type": "multipart/form-data;charset=UTF-8",
}