vue3 + Luckysheet的使用
- 功能列表:
- 导入excel
- 导出excel
- 设置数据格式
- 自动保存(表头修改时不能自动保存。所以,目前编写模板不做自动保存功能。填报时,做自动保存功能)
- 冻结区间
- 工作表保护 及 区域范围
- 数据验证 如数字 数据验证
- 。。。
- 问题修复
- 修改 luckysheet 源代码。问题修复:设置单元格格式为数字的千分位格式时,数据验证,type设置为数字时,验证不通过
注:修改完luckysheet源码后,需要重新打包再引用到项目中。
一、 Luckysheet的介绍
Luckysheet,一款纯前端类似excel的在线表格,功能强大、配置简单、完全开源。
二、Luckysheet简单使用
luckysheet源码地址:https://gitee.com/mengshukeji/Luckysheet/
- 下载源码,查看package.json文件,打包生成dist文件夹
npm run build
- 将打包生成的dist文件,重命名为luckysheet_dist,放在 根目录/public文件夹下
- 在index.html中引入luckysheet
<link rel='stylesheet' href='/luckysheet_dist/plugins/css/pluginsCss.css' />
<link rel='stylesheet' href='/luckysheet_dist/plugins/plugins.css' />
<link rel='stylesheet' href='/luckysheet_dist/css/luckysheet.css' />
<link rel='stylesheet' href='/luckysheet_dist/assets/iconfont/iconfont.css' />
<script src="/luckysheet_dist/plugins/js/plugin.js"></script>
<script src="/luckysheet_dist/luckysheet.umd.js"></script>
2.1 填报页面:配置文件
// src/utils/luckysheetConfig.js文件
import { updateTemplate } from "@/api/api.js";
/** luckysheet 职能预算填报 start */
/** 整体配置 start */
//配置项 作用于整个表格。特别的,单个sheet的配置项需要在options.data数组中
export const options = {
container: "luckysheet", // 设定DOM容器的id:luckysheet为容器id
title: "职能预算", // 设置表格名称
lang: "zh", // 设定表格语言
// 更多其它设置...
data: [],
column: 18, // 空表格默认的列数量
row: 36, //空表格默认的行数据量
showtoolbar: false, //是否显示工具栏
showtoolbarConfig: {
//自定义配置工具栏
undoRedo: false, // 撤销重做,注意撤销重做是两个按钮,由这一个配置决定显示还是隐藏
paintFormat: false, //格式刷
currencyFormat: false, //货币格式
percentageFormat: false, //百分比格式
numberDecrease: false, // '减少小数位数'
numberIncrease: false, // '增加小数位数'
moreFormats: false, // '更多格式'
font: false, // '字体'
fontSize: false, // '字号大小'
bold: false, // '粗体(Ctrl + B)'
italic: false, // '斜体(Ctrl + I)'
strikethrough: false, //删除线(Alt + Shift + 5)
underline: false, // 下划线(Alt + Shift + 6)
textColor: false, //文本颜色
fillColor: false, // 单元格颜色
border: false, // 边框
mergeCell: false, //合并单元格
horizontalAlignMode: false, //水平对齐方式
verticalAlignMode: false, //垂直对齐方式
textWrapMode: false, //换行方式
textRotateMode: false, //文本旋转方式
image: false, //插入图片
link: false, //插入链接
chart: false, //图表(图标隐藏,但是如果配置了chart插件,右击仍然可以新建图表)
postil: false, //批注
pivotTable: false, //数据透视表
function: false, //公式
frozenMode: false, //冻结方式
sortAndFilter: false, //排序和筛选
conditionalFormat: false, //条件格式
dataVerification: false, //数据验证
splitColumn: false, //分列
screenshot: false, //截图
findAndReplace: false, //查找替换
protection: false, // 工作表保护
print: false, //打印
},
showinfobar: false, //是否显示顶部信息栏
showsheetbar: false, //是否显示底部sheet页按钮
showsheetbarConfig: {
//自定义配置底部sheet页按钮
add: false, //新增sheet
menu: false, //sheet管理菜单
sheet: false, //sheet页显示
},
showstatisticBar: false, //是否显示底部计数栏
showstatisticBarConfig: {
//自定义配置底部计数栏
count: true, //计数栏
view: true, //打印视图
zoom: true, //缩放
},
enableAddRow: false, //允许添加行
enableAddBackTop: false, //允许回到顶部
userInfo: false, //右上角的用户信息展示
userMenuItem: [], //点击右上角的用户信息弹出的菜单
myFolderUrl: "", //左上角 < 返回按钮的链接
functionButton: "", //右上角功能按钮
cellRightClickConfig: {
//自定义配置单元格右击菜单
copy: false, //复制
copyAs: false, //复制为
paste: false, //粘贴
insertRow: false, //插入行
insertColumn: false, //插入列
deleteRow: false, //删除选中行
deleteColumn: false, //删除选中列
deleteCell: false, //删除单元格
hideRow: false, //隐藏选中行和显示选中行
hideColumn: false, //隐藏选中列和显示选中列
rowHeight: false, //行高
columnWidth: false, //列宽
clear: false, //清除内容
matrix: false, //矩阵操作选区
sort: false, //排序选区
filter: false, //筛选选区
chart: false, //图表生成
image: false, //插入图片
link: false, //插入链接
data: false, //数据验证
cellFormat: false, //设置单元格格式
},
sheetRightClickConfig: {
//自定义配置sheet页右击菜单
delete: false, //删除
copy: false, //复制
rename: false, //重命名
color: false, //更改颜色
hide: false, //隐藏,取消隐藏
move: false, //向左移,向右移
},
rowHeaderWidth: 46, //行标题区域的宽度,如果设置为0,则表示隐藏行标题
columnHeaderHeight: 20, //列标题区域的高度,如果设置为0,则表示隐藏列标题
sheetFormulaBar: true, //是否显示公示栏
defaultFontSize: 11, //初始化默认字体大小
limitSheetNameLength: true, //工作表重命名等场景下是否限制工作表名称的长度
defaultSheetNameMaxLength: 31, //默认允许的工作表名最大长度
// pager: { //分页器按钮设置
// pageIndex: 1, //当前的页码
// pageSize: 10, //每页显示多少行数据
// total: 50, //数据总行数
// selectOption: [10, 20], //允许设置每页行数的选项
// },
hook: {
//钩子函数
/** 单元格 start */
/**进入单元格编辑模式之前触发。在选中了某个单元格且在非编辑状态下,通常有以下三种常规方法触发进入编辑模式 1.双击单元格 2.敲Enter键 3.使用API:enterEditMode
* 参数:{ Array }[range]: 当前选区范围
*/
cellEditBefore: (range) => {
console.log("进入单元格编辑模式之前触发:cellEditBefore");
console.log(`当前选区范围:`, range);
},
/**更新这个单元格值之前触发,return false 则不执行后续的更新。在编辑状态下修改了单元格之后,退出编辑模式并进行数据更新之前触发这个钩子
* 参数:
* {Number}[r]:单元格所在行数
* {Number}[c]:单元格所在列数
* {Object | String | Number}[value]:要修改的单元格内容
* {Boolean}[isRefresh]:是否刷新整个表格
*/
cellUpdateBefore: (r, c, value, isRefresh) => {
console.log("更新这个单元格值之前触发:cellUpdateBefore");
console.log(
`单元格所在行:${r};单元格所在列:${c};是否刷新整个表格:${isRefresh};要修改的单元格内容: `,
value
);
},
/** 更新这个单元格后触发 */
cellUpdated: (r, c, oldValue, newValue, isRefresh) => {
console.log("cellUpdated:", r, c, oldValue, newValue);
// 获取单元格的值
const value = luckysheet.getCellValue(r, c, { type: "v" });
console.log("更新后的值:", value, "对值进行数据验证");
},
/**单元格渲染前触发 return false 则不渲染该单元格
* 参数:
* {Object}[cell]:单元格对象
* {Object}[position]:
* {Number}[r]:单元格所在行号
* {Number}[c]:单元格所在列号
* {Number}[start_r]:单元格左上角的水平坐标
* {Number}[start_c]:单元格左上角的垂直坐标
* {Number}[end_r]:单元格右下角的水平坐标
* {Number}[end_c]:单元格右下角的垂直坐标
* {Object}[sheet]:当前sheet对象
* {Object}[ctx]:当前画布的context
*/
cellRenderBefore: (cell, position, sheet, ctx) => {
// console.log('单元格渲染前触发:cellRenderBefore');
// console.log('单元格对象:', cell, '单元格所在行号:', position.r, '单元格所在列号:', position.c, '单元格左上角的水平坐标:',
// position.start_r, '单元格左上角的垂直坐标:', position.start_c, '单元格右下角的水平坐标:', position.end_r, '单元格右下角的垂直坐标:', position.end_c,
// '当前sheet对象:', sheet, '当前画布的context:', ctx);
},
/**单元格渲染结束后触发,return false 则不渲染该单元格
* 参数:
* {Object} [cell]:单元格对象
* {Object} [position]:
* {Number} [r]:单元格所在行号
* {Number} [c]:单元格所在列号
* {Number} [start_r]:单元格左上角的水平坐标
* {Number} [start_c]:单元格左上角的垂直坐标
* {Number} [end_r]:单元格右下角的水平坐标
* {Number} [end_c]:单元格右下角的垂直坐标
* {Object} [sheet]:当前sheet对象
* {Object} [ctx]:当前画布的context
*
*/
cellRenderAfter: () => {},
/**所有单元格渲染之前执行的方法
* 参数:
* {Object}[data]:当前工作表二维数组数据
* {Object}[sheet]:当前sheet对象
* {Object}[ctx]:当前画布的context
*/
cellAllRenderBefore: (data, sheet, ctx) => {
// console.log('当前工作表二维数组数据:', data);
},
/**行标题单元格渲染前触发,return false 则不渲染行标题
* 参数:
* {String}[rowNum]:行号
* {Object}[position]:
* {Number}[r]:单元格所在行号
* {Number}[top]:单元格左上角的垂直坐标
* {Number}[width]:单元格宽度
* {Number}[height]:单元格高度
* {Object}[ctx]:当前画布的context
*/
rowTitleCellRenderBefore: () => {},
/** 行标题单元格渲染后触发 return false 则不渲染行标题*/
rowTitleCellRenderAfter: () => {},
/** 列标题单元格渲染前触发 return false 则不渲染列标题 */
columnTitleCellRenderBefore: () => {},
/** 列标题单元格渲染后触发,return false 则不渲染列标题 */
columnTitleCellRenderAfter: () => {},
/** 单元格 end */
/** 鼠标钩子 start */
/** 单元格点击前的事件,return false 则终止之后的点击操作 */
cellMousedownBefore: (cell, position, sheet, ctx) => {},
/** 单元格点击后的事件,return false 则终止之后的点击操作 */
cellMousedownAfter: (cell, position, sheet, ctx) => {},
/** 鼠标移动事件,可通过cell判断鼠标停留在哪个单元格 */
sheetMousemove: () => {},
/** 鼠标按钮释放事件,可通过cell判断鼠标停留在哪个单元格 */
sheetMouseup: () => {},
/** 鼠标滚动事件 */
scroll: (position) => {},
/** 鼠标拖拽文件到Luckysheet内部的结束事件 */
cellDragStop: () => {},
/** 鼠标钩子 end */
/** 选区操作 (包括单元格) start */
/** 框选或者设置选区后触发 参数:{Object}[sheet]:当前选区对象 {Object | Array}[range]:选区范围,可能为多个选区 */
rangeSelect: (sheet, range) => {
console.log("选区范围:", range);
},
/** 移动选区前,包括单个单元格 */
rangeMoveBefore: (range) => {},
/** 移动选区后,包括单个单元格 */
rangeMoveAfter: (range) => {},
/** 选区修改前 */
rangeEditBefore: (range, data) => {},
/** 选区修改后 */
rangeEditAfter: (range, oldData, newData) => {},
/** 选区复制前 */
rangeCopyBefore: (range, data) => {},
/** 选区复制后 */
rangeCopyAfter: (range, data) => {},
/** 选区粘贴前 */
rangePasteBefore: (range, data) => {},
/** 选区粘贴后 */
rangePasteAfter: (range, originData, pasteData) => {},
/** 选区剪切前 */
rangeCutBefore: (range, data) => {},
/** 选区剪切后 */
rangeCutAfter: (range, data) => {},
/** 选区删除前 */
rangeDeleteBefore: (range, data) => {},
/** 选区删除后 */
rangeDeleteAfter: (range, data) => {},
/** 选区清除前 */
rangeClearBefore: (range, data) => {},
/** 选区清除后 */
rangeClearAfter: (range, data) => {},
/** 选区下拉前 */
rangePullBefore: (range) => {},
/** 选区下拉后 */
rangePullAfter: (range) => {},
/** 选区操作 (包括单元格) end */
/** 工作表 start */
/** 创建sheet页前触发,sheet页新建也包含数据透视表新建 */
sheetCreatekBefore: () => {},
/** 创建sheet页后触发,sheet页新建也包含数据透视表新建 */
sheetCreateAfter: (sheet) => {},
/** sheet移动前 */
sheetMoveBefore: (i, order) => {},
/** sheet移动后 */
sheetMoveAfter: (i, oldOrder, newOrder) => {},
/** sheet删除前 */
sheetDeleteBefore: (sheet) => {},
/** sheet删除后 */
sheetDeleteAfter: (sheet) => {},
/** sheet修改名称前 */
sheetEditNameBefore: (i, name) => {},
/** sheet修改名称后 */
sheetEditNameAfter: (i, oldName, newName) => {},
/** sheet修改颜色前 */
sheetEditColorBefore: (i, color) => {},
/** sheet修改颜色后 */
sheetEditColorAfter: (i, oldColor, newColor) => {},
/** sheet缩放前 */
sheetZoomBefore: (i, zoom) => {},
/** sheet缩放后 */
sheetZoomAfter: (i, oldZoom, newZoom) => {},
/** 激活工作表前 */
sheetActivate: (i, isPivotInitial, isNewSheet) => {},
/** 工作表从活动状态转为非活动状态前 */
sheetDeactivateBefore: (i) => {},
/** 工作表从活动状态转为非活动状态后 */
sheetDeactivateAfter: (i) => {},
/** 工作表 end */
/** 工作薄 start */
/** 表格创建之前触发 */
workbookCreateBefore: (book) => {},
/** 表格创建之后触发 */
workbookCreateAfter: (book) => {},
/** 表格销毁之前触发 */
workbookDestroyBefore: (book) => {},
/** 表格销毁之后触发 */
workbookDestroyAfter: (book) => {},
/** 协同编辑中的每次操作后执行的方法,监听表格内容变化,即客户端每执行一次表格操作,Luckysheet将这次操作存到历史记录中后触发,撤销重做时因为也算一次操作,也会触发此钩子函数 参数:{Object}[operate]:本次操作的历史记录信息,根据不同的操作,会有不同的历史记录 */
updated: (operate) => {
console.log("luckysheetUpdatedHook", operate);
// 监听更新,并在3s后自动保存
// if (autoSave) {
// console.log(autoSave, "autoSave");
// clearTimeout(autoSave);
// $(luckysheet_info_detail_save).text("已修改");
// autoSave = setTimeout(() => {
// const excel = luckysheet.getAllSheets();
// // 去除临时数据,减小体积
// for (const i in excel) {
// excel[i].ddata = undefined;
// }
// // $.post('http://127.0.0.0:8081/setWorkBook', {
// // jsonExcel: JSON.stringify(excel)
// // }, () => {
// // $(luckysheet_info_detail_save).text('已保存')
// // })
// }, 1 * 300);
// return true;
// }
},
/** resize 执行之后 */
resized: (size) => {},
/** 工作薄 end */
/** 冻结 start */
/** 设置冻结前 */
frozenCreateBefore: (frozen) => {},
/** 设置冻结后 */
frozenCreateAfter: (frozen) => {},
/** 取消冻结前 */
frozenCancelBefore: (frozen) => {},
/** 取消冻结后 */
frozenCancelAfter: (frozen) => {},
/** 冻结 end */
/** 分页器 start */
/** 点击分页按钮回调函数,返回当前页码 */
onTogglePager: (page) => {},
/** 分页器 end */
},
/** 协同编辑 start */
// loadUrl: gridKey:工作簿的唯一标识, 加载luckysheet数据的地址
// loadUrl: 'http://localhost:8081/getWorkBook?gridKey=1',
// loadUrl: "/baseURL/excel",
// updateUrl: '', //后台websocket地址
allowUpdate: true,
/** 协同编辑 end */
};
/** 整体配置 end */
/** luckysheet 职能预算填报 end */
2.2 填报页面:页面使用
// luckysheet/index.vue页面
<template>
<div class="lucky">
<div class="operate" @click="exitEditMode">
<span>{{ luckysheet_save_text }}</span>
<el-button type="primary">保存</el-button>
<el-button @click="exportExcel" type="primary">导出excel</el-button>
</div>
<div id="luckysheet"></div>
</div>
</template>
<script setup>
import { onMounted, reactive, onUnmounted, ref } from "vue";
import { options } from "@/utils/luckysheetConfig.js";
import { getAuth, updateTemplate } from "@/api/api.js";
import axios from "axios";
import { globalName } from "../../utils/global";
// const Excel = require("exceljs");
// import Excel from "exceljs";
import ExcelJS from "exceljs";
import { saveAs } from "file-saver";
import LuckySheet from "../../components/LuckySheet.vue";
let templateData = reactive([]);
onMounted(() => {
// $(function () {
// 初始化表格
// luckysheet.create(options);
// })
getTemplateJson();
// 接口请求已调通 ~~~
// getAuth().then(res => {
// console.log(res, 2322)
// })
});
onUnmounted(() => {
exitEditMode();
});
// 获取模板文件 获取public文件夹下指定json文件内容
const getTemplateJson = () => {
axios.get(`/${globalName}/json/luckysheet.json`).then((res) => {
if (res.status === 200) {
templateData = res.data;
createLuckysheet();
init();
}
});
};
// 创建luckysheet
const createLuckysheet = () => {
templateData[0] = {
...templateData[0],
// frozen: { //冻结行列配置
// type: 'rangeBoth',
// range: { row_focus: 0, column_focus: 9 } //冻结行列到'B2'选区
// },
// // row: 90, //行数
// // column: 26, //列数
// config: {
// ...templateData[0].config,
// authority: {
// //工作表保护
// //当前工作表的权限配置
// selectLockedCells: 1, //选定锁定单元格
// selectunLockedCells: 1, //选定解除锁定的单元格
// formatCells: 0, //设置单元格格式
// formatColumns: 0, //设置列格式
// formatRows: 0, //设置行格式
// insertColumns: 0, //插入列
// insertRows: 0, //插入行
// insertHyperlinks: 0, //插入超链接
// deleteColumns: 0, //删除列
// deleteRows: 0, //删除行
// sort: 0, //排序
// filter: 1, //使用自动筛选
// usePivotTablereports: 0, //使用数据透视表和报表
// editObjects: 0, //编辑对象
// editScenarios: 0, //编辑方案
// sheet: 1, //如果为1或true,则该工作表受到保护;如果为0或false,则该工作表不受保护。
// hintText: "", //弹窗提示的文字
// algorithmName: "None", //加密方案:MD2,MD4,MD5,RIPEMD-128,RIPEMD-160,SHA-1,SHA-256,SHA-384,SHA-512,WHIRLPOOL
// saltValue: null, //密码解密的盐参数,为一个自己定的随机数值
// allowRangeList: [
// {
// //区域保护
// name: "area", //名称
// password: "", //密码
// hintText: "", //提示文字
// algorithmName: "None", //加密方案:MD2,MD4,MD5,RIPEMD-128,RIPEMD-160,SHA-1,SHA-256,SHA-384,SHA-512,WHIRLPOOL
// saltValue: null, //密码解密的盐参数,为一个自己定的随机数值
// sqref: "$O$1:$Z$18", //区域范围
// },
// ],
// },
// },
// 数据验证
// dataVerification: {
// "1_14": {
// type: "number_decimal",
// type2: "bw",
// value1: "0",
// value2: "100000",
// checked: false, //是否勾选中复选框;type为checkbox时需配置;
// remote: false, //自动远程获取选项
// prohibitInput: false, //输入数据无效时禁止输入;默认为false;
// hintShow: false, //选中单元格时显示提示语;默认为false;
// hintText: "", //提示语文本,hintShow为true时需配置;
// },
// "1_15": {
// type: "number", //数字
// type2: "bw",
// value1: "0", //最小值为0
// value2: "100000",//最大值为1000000
// checked: false, //是否勾选中复选框;type为checkbox时需配置;
// remote: false, //自动远程获取选项
// prohibitInput: false, //输入数据无效时禁止输入;默认为false;
// hintShow: true, //选中单元格时显示提示语;默认为false;
// hintText: "请输入1到100000之间的数字", //提示语文本,hintShow为true时需配置;
// },
// }
};
luckysheet.create({
...options,
data: templateData,
hook: {
updated: handleUpdate,
},
});
};
// 初始化
const init = () => {
getAllSheets();
const sheetData = getSheetData();
// console.log("sheetData", sheetData);
transToCellData();
getConfig();
getSheetCellValue();
// setDataVerification();
};
// 所有工作表的配置信息
const getAllSheets = () => {
// 获取所有工作表的配置信息
const allSheets = luckysheet.getAllSheets();
console.log("所有工作表的配置信息:", allSheets);
return allSheets;
};
// 工作表数据
const getSheetData = () => {
const luckysheetdata = luckysheet.getSheetData({ order: 0 });
console.log("工作表数据", luckysheetdata);
return luckysheetdata;
};
// data => celldata,data二维数组数据转化成 {r,c,v}格式一维数组
const transToCellData = () => {
const cellData = luckysheet.transToCellData(getSheetData());
console.log("data=>celldata", cellData);
return cellData;
};
// celldata => data, celldata一维数组数据转化成表格所需二维数组
const transToData = () => {
const data = luckysheet.transToData(transToCellData());
console.log("celldata=>data", data);
};
// 工作表配置
const getConfig = () => {
const config = luckysheet.getConfig({ order: 0 });
console.log("工作表配置", config);
return config;
};
// 单元格的值
const getCellValue = (row = 0, col = 0) => {
const cellValue = luckysheet.getCellValue(row, col, { type: "v" });
console.log("单元格的值", cellValue);
return getCellValue;
};
// 获取表中每个单元格的值
const getSheetCellValue = () => {
const allSheets = getAllSheets();
const rows = allSheets[0].data.length; //行
const cols = allSheets[0].data[0].length; //列
// 获取每个单元格的值
const arr = new Array(rows);
for (let i = 0; i < rows; i++) {
arr[i] = new Array(cols);
}
// 追个追加到二维数组中
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
arr[i][j] = luckysheet.getCellValue(i, j, { type: "v" });
}
}
console.log("每个单元格的值", arr);
return arr;
};
// 指定工作表范围设置数据验证功能,并设置参数
const setDataVerification = () => {
const optionItem = {
type: "number", //数字 number_decimal:小数
type2: "bw",
value1: "0", //最小值为0
value2: "1000000", //最大值为1000000
checked: false, //是否勾选中复选框;type为checkbox时需配置;
remote: false, //自动远程获取选项
prohibitInput: false, //输入数据无效时禁止输入;默认为false;
hintShow: false, //选中单元格时显示提示语;默认为false;
hintText: "请输入0到1000000之间的整数", //提示语文本,hintShow为true时需配置;
};
const setting = {
range: { row: [1, 74], column: [14, 26] }, //区间
};
luckysheet.setDataVerification(optionItem, setting, (res) => {
console.log(res, 1);
});
};
// toJson
const toJson = () => {
const json = luckysheet.toJson();
};
const closeWebsocket = () => {
luckysheet.closeWebsocket();
};
// 返回所有表格数据结构的一维数组
const getLuckysheetfile = () => {
// 调试使用,不适用初始化表格
const luckysheetfile = luckysheet.getLuckysheetfile();
};
// 退出编辑模式
const exitEditMode = () => {
// 自动退出编辑模式的操作,主要是为了触发自动保存单元格
luckysheet.exitEditMode();
};
const luckysheet_save_text = ref("已保存");
// 自动保存
const autoSave = () => {
//调用luckysheet提供的API获取当前编辑的表格数据
const allSheets = getAllSheets();
// 去除临时数据,减小体积
for (const i in allSheets) {
allSheets[i].data = undefined;
}
let params = {
jsonData: JSON.stringify(allSheets),
targetFilePath:
process.env.NODE_ENV === "development"
? "D://hhkj_project//function-budget//public//json//luckysheet.json"
: "D://apache-tomcat-7.0.64-8080-mis//webapps//functionalBudget//json//template.json",
};
updateTemplate(params).then((res) => {
if (res.success) {
// $(luckysheet_info_detail_save).text("已保存");
luckysheet_save_text.value = "已保存";
}
});
};
const handleUpdate = (operate) => {
console.log("绑定更新处理函数", operate);
luckysheet_save_text.value = "保存中...";
autoSave();
};
/**
* 协同
* loadUrl:
* $.post(loadurl,{"gridKey":server.gridKey},function(d){})
*/
/** 导出excel 功能 start 已完成 */
//导出
const exportExcel = async () => {
try {
//获取luckysheet的数据
// const sheetsData = luckysheet.getLuckysheetfile()[0].sheets;
const sheetsData = luckysheet.getAllSheets();
// 创建工作簿
const workbook = new ExcelJS.Workbook();
// 遍历每张表单数据,添加到工作簿
sheetsData.forEach((sheet) => {
if (sheet.data.length === 0) return; // 跳过空表
const worksheet = workbook.addWorksheet(sheet.name);
setStyleAndValue(sheet.data, worksheet);
setMerge(sheet.config.merge, worksheet);
setBorder(sheet.config.borderInfo, worksheet);
});
// 将工作簿写入Buffer
const buffer = await workbook.xlsx.writeBuffer();
// 使用file-saver保存文件
saveAs(
new Blob([buffer], { type: "application/octet-stream" }),
"output.xlsx"
);
} catch (error) {
console.error("导出Excel时发生错误:", error);
}
};
//合并单元格
const setMerge = (luckyMerge = {}, worksheet) => {
const mergearr = Object.values(luckyMerge);
mergearr.forEach(function (elem) {
// elem格式:{r: 0, c: 0, rs: 1, cs: 2}
// 按开始行,开始列,结束行,结束列合并(相当于 K10:M12)
worksheet.mergeCells(
elem.r + 1,
elem.c + 1,
elem.r + elem.rs,
elem.c + elem.cs
);
});
};
//边框
const setBorder = (luckyBorderInfo, worksheet) => {
if (!Array.isArray(luckyBorderInfo)) return;
luckyBorderInfo.forEach(function (elem) {
let border = borderConvert();
worksheet.getCell(
elem.value.row_index + 1,
elem.value.col_index + 1
).border = border;
});
};
//表格样式
const setStyleAndValue = (cellArr, worksheet) => {
if (!Array.isArray(cellArr)) return;
cellArr.forEach(function (row, rowid) {
row.every(function (cell, columnid) {
if (!cell) return true;
let fill = fillConvert(cell.bg);
let font = fontConvert(
cell.ff,
cell.fc,
cell.bl,
cell.it,
cell.fs,
cell.cl,
cell.ul
);
let alignment = alignmentConvert(
cell.vt,
cell.ht,
cell.tb,
cell.tr
);
let value;
if (cell.f) {
value = { formula: cell.f, result: cell.v };
} else {
// value = cell.v;
value = luckysheet.getCellValue(rowid, columnid, {
type: "v",
});
}
let target = worksheet.getCell(rowid + 1, columnid + 1);
target.fill = fill;
target.font = font;
target.alignment = alignment;
// target.value = value;
return true;
});
});
};
const fillConvert = (bg) => {
// if (!bg) {
// return {};
// }
let fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFFFFF" }, // 默认白色背景
};
if (bg) fill.fgColor = { argb: bg.replace("#", "") };
return fill;
};
const fontConvert = (
ff = 0,
fc = "#000000",
bl = 0,
it = 0,
fs = 10,
cl = 0,
ul = 0
) => {
// luckysheet:ff(样式), fc(颜色), bl(粗体), it(斜体), fs(大小), cl(删除线), ul(下划线)
const luckyToExcel = {
0: "微软雅黑",
1: "宋体(Song)",
2: "黑体(ST Heiti)",
3: "楷体(ST Kaiti)",
4: "仿宋(ST FangSong)",
5: "新宋体(ST Song)",
6: "华文新魏",
7: "华文行楷",
8: "华文隶书",
9: "Arial",
10: "Times New Roman ",
11: "Tahoma ",
12: "Verdana",
num2bl: function (num) {
return num === 0 ? false : true;
},
};
let font = {
name: luckyToExcel[ff],
family: 1,
size: fs,
color: { argb: fc.replace("#", "") },
bold: luckyToExcel.num2bl(bl),
italic: luckyToExcel.num2bl(it),
underline: luckyToExcel.num2bl(ul),
strike: luckyToExcel.num2bl(cl),
};
return font;
};
var alignmentConvert = function (
vt = "default",
ht = "default",
tb = "default",
tr = "default"
) {
// luckysheet:vt(垂直), ht(水平), tb(换行), tr(旋转)
const luckyToExcel = {
vertical: {
0: "middle",
1: "top",
2: "bottom",
default: "top",
},
horizontal: {
0: "center",
1: "left",
2: "right",
default: "left",
},
wrapText: {
0: false,
1: false,
2: true,
default: false,
},
textRotation: {
0: 0,
1: 45,
2: -45,
3: "vertical",
4: 90,
5: -90,
default: 0,
},
};
let alignment = {
vertical: luckyToExcel.vertical[vt],
horizontal: luckyToExcel.horizontal[ht],
wrapText: luckyToExcel.wrapText[tb],
textRotation: luckyToExcel.textRotation[tr],
};
return alignment;
};
const borderConvert = (
borderType = "border-all",
style = 1,
color = "#000000"
) => {
// 对应luckysheet的config中borderinfo的的参数
// if (!borderType) {
// return {};
// }
const luckyToExcel = {
type: {
"border-all": "all",
"border-top": "top",
"border-right": "right",
"border-bottom": "bottom",
"border-left": "left",
},
style: {
0: "none",
1: "thin",
2: "hair",
3: "dotted",
4: "dashDot", // 'Dashed',
5: "dashDot",
6: "dashDotDot",
7: "double",
8: "medium",
9: "mediumDashed",
10: "mediumDashDot",
11: "mediumDashDotDot",
12: "slantDashDot",
13: "thick",
},
};
let template = {
style: luckyToExcel.style[style],
color: { argb: color.replace("#", "") },
};
let border = {};
if (luckyToExcel.type[borderType] === "all") {
border["top"] = template;
border["right"] = template;
border["bottom"] = template;
border["left"] = template;
} else {
border[luckyToExcel.type[borderType]] = template;
}
return border;
};
/** 导出excel 功能 end */
</script>
<!-- scss配置已生效 -->
<style lang="scss" scoped>
.lucky {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.operate {
margin: 0 10px 10px;
position: absolute;
top: 10px;
right: 0;
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
span {
margin-right: 10px;
}
}
#luckysheet {
margin: 0px;
padding: 0px;
position: absolute;
width: 100%;
height: 100%;
height: calc(100% - 50px);
left: 0px;
top: 50px;
}
}
</style>
2.3 模板页面:配置文件
// src/utils/templateConfig.js
import { saveAs } from "file-saver";
/** template 模板 start */
/** 工作表数据及配置 start */
export const sheetData = [
{
name: "sheet1", //工作表名称
color: "", //工作表颜色,工作表名称下方会有一条底部边框
index: 0, //工作表索引,作为唯一key值使用,不是工作表顺序,和order区分开。
status: 1, //激活状态,仅有一个激活状态的工作表,其它工作表为0
order: 0, //工作表的下标
hide: 0, //是否隐藏 0不隐藏 1隐藏
row: 36, //行数
column: 18, //列数
celldata: [],
config: {},
},
];
/** 工作表数据及配置 end */
/** 整体配置 start */
//配置项 作用于整个表格。特别的,单个sheet的配置项需要在options.data数组中
export const options = {
container: "luckysheet", // 设定DOM容器的id:luckysheet为容器id
title: "Luckysheet Demo", // 设置表格名称
lang: "zh", // 设定表格语言
// 更多其它设置...
data: sheetData,
column: 18, // 空表格默认的列数量
row: 36, //空表格默认的行数据量
showtoolbar: true, //是否显示工具栏
showtoolbarConfig: {
//自定义配置工具栏
print: false, //打印
},
showinfobar: false, //是否显示顶部信息栏
showsheetbar: false, //是否显示底部sheet页按钮
showsheetbarConfig: {
//自定义配置底部sheet页按钮
add: false, //新增sheet
menu: false, //sheet管理菜单
sheet: false, //sheet页显示
},
showstatisticBar: false, //是否显示底部计数栏
showstatisticBarConfig: {
//自定义配置底部计数栏
count: true, //计数栏
view: false, //打印视图
zoom: true, //缩放
},
enableAddRow: false, //允许添加行
enableAddBackTop: false, //允许回到顶部
userInfo: false, //右上角的用户信息展示
userMenuItem: [], //点击右上角的用户信息弹出的菜单
myFolderUrl: "", //左上角 < 返回按钮的链接
functionButton: "", //右上角功能按钮
cellRightClickConfig: {
//自定义配置单元格右击菜单
copy: true, //复制
copyAs: false, //复制为
paste: true, //粘贴
insertRow: true, //插入行
insertColumn: true, //插入列
deleteRow: true, //删除选中行
deleteColumn: true, //删除选中列
deleteCell: true, //删除单元格
hideRow: true, //隐藏选中行和显示选中行
hideColumn: true, //隐藏选中列和显示选中列
rowHeight: true, //行高
columnWidth: true, //列宽
clear: true, //清除内容
matrix: true, //矩阵操作选区
sort: true, //排序选区
filter: true, //筛选选区
chart: true, //图表生成
image: true, //插入图片
link: true, //插入链接
data: true, //数据验证
cellFormat: true, //设置单元格格式
},
sheetRightClickConfig: {
//自定义配置sheet页右击菜单
delete: true, //删除
copy: true, //复制
rename: true, //重命名
color: false, //更改颜色
hide: false, //隐藏,取消隐藏
move: false, //向左移,向右移
},
rowHeaderWidth: 46, //行标题区域的宽度,如果设置为0,则表示隐藏行标题
columnHeaderHeight: 20, //列标题区域的高度,如果设置为0,则表示隐藏列标题
sheetFormulaBar: true, //是否显示公示栏
defaultFontSize: 11, //初始化默认字体大小
limitSheetNameLength: true, //工作表重命名等场景下是否限制工作表名称的长度
defaultSheetNameMaxLength: 31, //默认允许的工作表名最大长度
// pager: { //分页器按钮设置
// pageIndex: 1, //当前的页码
// pageSize: 10, //每页显示多少行数据
// total: 50, //数据总行数
// selectOption: [10, 20], //允许设置每页行数的选项
// },
hook: {
//钩子函数
/** 单元格 start */
/**进入单元格编辑模式之前触发。在选中了某个单元格且在非编辑状态下,通常有以下三种常规方法触发进入编辑模式 1.双击单元格 2.敲Enter键 3.使用API:enterEditMode
* 参数:{ Array }[range]: 当前选区范围
*/
cellEditBefore: (range) => {
console.log("进入单元格编辑模式之前触发:cellEditBefore");
console.log(`当前选区范围:`, range);
},
/**更新这个单元格值之前触发,return false 则不执行后续的更新。在编辑状态下修改了单元格之后,退出编辑模式并进行数据更新之前触发这个钩子
* 参数:
* {Number}[r]:单元格所在行数
* {Number}[c]:单元格所在列数
* {Object | String | Number}[value]:要修改的单元格内容
* {Boolean}[isRefresh]:是否刷新整个表格
*/
cellUpdateBefore: (r, c, value, isRefresh) => {
console.log("更新这个单元格值之前触发:cellUpdateBefore");
console.log(
`单元格所在行:${r};单元格所在列:${c};是否刷新整个表格:${isRefresh};要修改的单元格内容: `,
value
);
},
/** 更新这个单元格后触发 */
cellUpdated: (r, c, oldValue, newValue, isRefresh) => {
console.log("cellUpdated:", r, c, oldValue, newValue);
// 获取单元格的值
const value = luckysheet.getCellValue(r, c, { type: "v" });
console.log("更新后的值:", value, "对值进行数据验证");
},
/**单元格渲染前触发 return false 则不渲染该单元格
* 参数:
* {Object}[cell]:单元格对象
* {Object}[position]:
* {Number}[r]:单元格所在行号
* {Number}[c]:单元格所在列号
* {Number}[start_r]:单元格左上角的水平坐标
* {Number}[start_c]:单元格左上角的垂直坐标
* {Number}[end_r]:单元格右下角的水平坐标
* {Number}[end_c]:单元格右下角的垂直坐标
* {Object}[sheet]:当前sheet对象
* {Object}[ctx]:当前画布的context
*/
cellRenderBefore: (cell, position, sheet, ctx) => {
// console.log('单元格渲染前触发:cellRenderBefore');
// console.log('单元格对象:', cell, '单元格所在行号:', position.r, '单元格所在列号:', position.c, '单元格左上角的水平坐标:',
// position.start_r, '单元格左上角的垂直坐标:', position.start_c, '单元格右下角的水平坐标:', position.end_r, '单元格右下角的垂直坐标:', position.end_c,
// '当前sheet对象:', sheet, '当前画布的context:', ctx);
},
/**单元格渲染结束后触发,return false 则不渲染该单元格
* 参数:
* {Object} [cell]:单元格对象
* {Object} [position]:
* {Number} [r]:单元格所在行号
* {Number} [c]:单元格所在列号
* {Number} [start_r]:单元格左上角的水平坐标
* {Number} [start_c]:单元格左上角的垂直坐标
* {Number} [end_r]:单元格右下角的水平坐标
* {Number} [end_c]:单元格右下角的垂直坐标
* {Object} [sheet]:当前sheet对象
* {Object} [ctx]:当前画布的context
*
*/
cellRenderAfter: () => {},
/**所有单元格渲染之前执行的方法
* 参数:
* {Object}[data]:当前工作表二维数组数据
* {Object}[sheet]:当前sheet对象
* {Object}[ctx]:当前画布的context
*/
cellAllRenderBefore: (data, sheet, ctx) => {
// console.log('当前工作表二维数组数据:', data);
},
/**行标题单元格渲染前触发,return false 则不渲染行标题
* 参数:
* {String}[rowNum]:行号
* {Object}[position]:
* {Number}[r]:单元格所在行号
* {Number}[top]:单元格左上角的垂直坐标
* {Number}[width]:单元格宽度
* {Number}[height]:单元格高度
* {Object}[ctx]:当前画布的context
*/
rowTitleCellRenderBefore: () => {},
/** 行标题单元格渲染后触发 return false 则不渲染行标题*/
rowTitleCellRenderAfter: () => {},
/** 列标题单元格渲染前触发 return false 则不渲染列标题 */
columnTitleCellRenderBefore: () => {},
/** 列标题单元格渲染后触发,return false 则不渲染列标题 */
columnTitleCellRenderAfter: () => {},
/** 单元格 end */
/** 鼠标钩子 start */
/** 单元格点击前的事件,return false 则终止之后的点击操作 */
cellMousedownBefore: (cell, position, sheet, ctx) => {},
/** 单元格点击后的事件,return false 则终止之后的点击操作 */
cellMousedownAfter: (cell, position, sheet, ctx) => {},
/** 鼠标移动事件,可通过cell判断鼠标停留在哪个单元格 */
sheetMousemove: () => {},
/** 鼠标按钮释放事件,可通过cell判断鼠标停留在哪个单元格 */
sheetMouseup: () => {},
/** 鼠标滚动事件 */
scroll: (position) => {},
/** 鼠标拖拽文件到Luckysheet内部的结束事件 */
cellDragStop: () => {},
/** 鼠标钩子 end */
/** 选区操作 (包括单元格) start */
/** 框选或者设置选区后触发 参数:{Object}[sheet]:当前选区对象 {Object | Array}[range]:选区范围,可能为多个选区 */
rangeSelect: (sheet, range) => {
console.log("选区范围:", range);
},
/** 移动选区前,包括单个单元格 */
rangeMoveBefore: (range) => {},
/** 移动选区后,包括单个单元格 */
rangeMoveAfter: (range) => {},
/** 选区修改前 */
rangeEditBefore: (range, data) => {},
/** 选区修改后 */
rangeEditAfter: (range, oldData, newData) => {},
/** 选区复制前 */
rangeCopyBefore: (range, data) => {},
/** 选区复制后 */
rangeCopyAfter: (range, data) => {},
/** 选区粘贴前 */
rangePasteBefore: (range, data) => {},
/** 选区粘贴后 */
rangePasteAfter: (range, originData, pasteData) => {},
/** 选区剪切前 */
rangeCutBefore: (range, data) => {},
/** 选区剪切后 */
rangeCutAfter: (range, data) => {},
/** 选区删除前 */
rangeDeleteBefore: (range, data) => {},
/** 选区删除后 */
rangeDeleteAfter: (range, data) => {},
/** 选区清除前 */
rangeClearBefore: (range, data) => {},
/** 选区清除后 */
rangeClearAfter: (range, data) => {},
/** 选区下拉前 */
rangePullBefore: (range) => {},
/** 选区下拉后 */
rangePullAfter: (range) => {},
/** 选区操作 (包括单元格) end */
/** 工作表 start */
/** 创建sheet页前触发,sheet页新建也包含数据透视表新建 */
sheetCreatekBefore: () => {},
/** 创建sheet页后触发,sheet页新建也包含数据透视表新建 */
sheetCreateAfter: (sheet) => {},
/** sheet移动前 */
sheetMoveBefore: (i, order) => {},
/** sheet移动后 */
sheetMoveAfter: (i, oldOrder, newOrder) => {},
/** sheet删除前 */
sheetDeleteBefore: (sheet) => {},
/** sheet删除后 */
sheetDeleteAfter: (sheet) => {},
/** sheet修改名称前 */
sheetEditNameBefore: (i, name) => {},
/** sheet修改名称后 */
sheetEditNameAfter: (i, oldName, newName) => {},
/** sheet修改颜色前 */
sheetEditColorBefore: (i, color) => {},
/** sheet修改颜色后 */
sheetEditColorAfter: (i, oldColor, newColor) => {},
/** sheet缩放前 */
sheetZoomBefore: (i, zoom) => {},
/** sheet缩放后 */
sheetZoomAfter: (i, oldZoom, newZoom) => {},
/** 激活工作表前 */
sheetActivate: (i, isPivotInitial, isNewSheet) => {},
/** 工作表从活动状态转为非活动状态前 */
sheetDeactivateBefore: (i) => {},
/** 工作表从活动状态转为非活动状态后 */
sheetDeactivateAfter: (i) => {},
/** 工作表 end */
/** 工作薄 start */
/** 表格创建之前触发 */
workbookCreateBefore: (book) => {},
/** 表格创建之后触发 */
workbookCreateAfter: (book) => {},
/** 表格销毁之前触发 */
workbookDestroyBefore: (book) => {},
/** 表格销毁之后触发 */
workbookDestroyAfter: (book) => {},
/** 协同编辑中的每次操作后执行的方法,监听表格内容变化,即客户端每执行一次表格操作,Luckysheet将这次操作存到历史记录中后触发,撤销重做时因为也算一次操作,也会触发此钩子函数 参数:{Object}[operate]:本次操作的历史记录信息,根据不同的操作,会有不同的历史记录 */
updated: (operate) => {},
/** resize 执行之后 */
resized: (size) => {},
/** 工作薄 end */
/** 冻结 start */
/** 设置冻结前 */
frozenCreateBefore: (frozen) => {},
/** 设置冻结后 */
frozenCreateAfter: (frozen) => {},
/** 取消冻结前 */
frozenCancelBefore: (frozen) => {},
/** 取消冻结后 */
frozenCancelAfter: (frozen) => {},
/** 冻结 end */
/** 分页器 start */
/** 点击分页按钮回调函数,返回当前页码 */
onTogglePager: (page) => {},
/** 分页器 end */
},
/** 协同编辑 start */
// loadUrl: gridKey:工作簿的唯一标识, 加载luckysheet数据的地址
// loadUrl: 'http://localhost:8081/getWorkBook?gridKey=1',
// loadUrl: "/baseURL/excel",
// updateUrl: '', //后台websocket地址
allowUpdate: false,
/** 协同编辑 end */
};
/** 整体配置 end */
/** template 模板 end */
2.4. 模板页面:页面使用
//luckysheet/template.vue
<!-- 模板填写 -->
<template>
<div class="lucky">
<div class="operate">
<input
type="file"
style="font-size: 12px"
ref="fileInput"
@change="handleFileChange" />
<el-button @click="handleSubmit" type="primary">提交模板</el-button>
</div>
<div id="luckysheet"></div>
</div>
</template>
<script setup>
import LuckyExcel from "luckyexcel";
import { onMounted, reactive, onUnmounted, ref } from "vue";
import { saveAs } from "file-saver";
import { options } from "@/utils/templateConfig.js";
import axios from "axios";
import { globalName } from "../../utils/global";
import { getAuth, updateTemplate } from "@/api/api.js";
import { ElMessage } from "element-plus";
let templateData = reactive([]);
onMounted(() => {
getTemplateJson();
});
onUnmounted(() => {
exitEditMode();
});
// 获取模板文件 获取public文件夹下指定json文件内容
const getTemplateJson = () => {
axios.get(`/${globalName}/json/template.json`).then((res) => {
if (res.status === 200) {
templateData = res.data;
createLuckysheet();
init();
}
});
};
// 创建luckysheet
const createLuckysheet = () => {
templateData[0] = {
...templateData[0],
};
luckysheet.create({
...options,
data: templateData,
});
};
// 初始化
const init = () => {
getAllSheets();
};
// 选择文件 将excel文件转为luckysheet的数据
const handleFileChange = (event) => {
const file = event.target.files[0];
LuckyExcel.transformExcelToLucky(
file,
(exportJson, luckysheetfile) => {
// 转换后获取工作表数据
console.log("excel转换为luckysheet的数据:", exportJson);
// 销毁原来的表格
luckysheet.destroy();
// 重新创建表格
luckysheet.create({
...options,
data: exportJson.sheets,
title: exportJson.info.name,
});
},
(error) => {
// 如果抛出任何错误,则处理错误
console.log(error);
}
);
};
// 所有工作表的配置信息
const getAllSheets = () => {
// 获取所有工作表的配置信息
const allSheets = luckysheet.getAllSheets();
console.log("所有工作表的配置信息:", allSheets);
return allSheets;
};
// 工作表数据
const getSheetData = () => {
const luckysheetdata = luckysheet.getSheetData({ order: 0 });
console.log("工作表数据", luckysheetdata);
return luckysheetdata;
};
// 处理冻结错位
const handleFrozen = () => {
//调用luckysheet提供的API获取当前编辑的表格数据
let allSheets = getAllSheets();
/** 解决这三种情况下,保存数据,重新渲染时,冻结行列错位问题 start */
// 冻结行到选区
if (allSheets[0].frozen) {
if (allSheets[0].frozen.type === "rangeRow") {
allSheets[0].frozen.range.row_focus =
allSheets[0].frozen.range.row_focus - 1;
}
// 冻结列到选区
if (allSheets[0].frozen.type === "rangeColumn") {
allSheets[0].frozen.range.column_focus =
allSheets[0].frozen.range.column_focus - 1;
}
// 冻结行列到选区
if (allSheets[0].frozen.type === "rangeBoth") {
allSheets[0].frozen.range.column_focus =
allSheets[0].frozen.range.column_focus - 1;
allSheets[0].frozen.range.row_focus =
allSheets[0].frozen.range.row_focus - 1;
}
}
return allSheets;
/** 解决这三种情况下,保存数据,重新渲染时,冻结行列错位问题 end */
};
//保存数据到本地文件 (供模拟)
const saveToLocalFile = (data) => {
const content = JSON.stringify(allSheets);
const blob = new Blob([content], {
type: "text/plain;charset=utf-8",
});
saveAs(blob, "template.json");
};
// 退出编辑模式
const exitEditMode = () => {
// 自动退出编辑模式的操作,主要是为了触发自动保存单元格
luckysheet.exitEditMode();
};
// 自动保存
const autoSave = () => {
let allSheets = handleFrozen();
// 去除临时数据,减小体积
for (const i in allSheets) {
allSheets[i].data = undefined;
}
let params = {
jsonData: JSON.stringify(allSheets),
targetFilePath:
process.env.NODE_ENV === "development"
? "D://hhkj_project//function-budget//public//json//template.json"
: "D://apache-tomcat-7.0.64-8080-mis//webapps//functionalBudget//json//template.json",
};
updateTemplate(params).then((res) => {
if (res.success) {
// $(luckysheet_info_detail_save).text("已保存");
ElMessage({
message: "保存成功",
type: "success",
});
}
});
};
// 提交模板
const handleSubmit = () => {
autoSave();
};
/**
* 如何解决冻结行列区不准的问题
* 1. 改源码 - 耗时且不准
* 2. 假设在填写模板时,去掉设置冻结的操作
* 在填写数据页面,把设置冻结的操作放开。然后供填写时设置方便。
* 提交时,去除冻结的配置。以防止回显时,冻结列现实错乱的问题。
*/
/**
* 综合考虑:
* 模板页面不需要自动保存功能,因为会涉及到工具栏的操作。
* 目前没有采用实时官方提供的loadurl,updateurl,allowupdate的这种方式去更新,需要用到websocket,后台数据难存储,所以,采用普通的方式保存
*/
</script>
<!-- scss配置已生效 -->
<style lang="scss" scoped>
.lucky {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.operate {
margin: 0 10px 10px;
position: absolute;
top: 10px;
right: 0;
width: 100%;
display: flex;
justify-content: flex-end;
}
#luckysheet {
margin: 0px;
padding: 0px;
position: absolute;
width: 100%;
height: 100%;
height: calc(100% - 50px);
left: 0px;
top: 50px;
}
}
</style>
三、界面效果
- 模板页面:供制作填报模板,有头部工具栏
- 填报页面:供填报 处部分数据可填写外,其它只读,且不可更改模板结构样式等。