UNPKG

lay-excel

Version:

简单快捷的导出插件,导出仅需一句话

1,030 lines (992 loc) 33.8 kB
/* * @Author: Jeffrey Wang * @Desc: 整理强大的 SheetJS 功能,依赖 XLSX.js 和 FileSaver * @Date: 2018-03-24 09:54:17 * @Last Modified by: Jeffrey Wang */ import Blob from 'blob'; import FileSaver from 'file-saver'; import {merge} from "lodash" import XLSX from './xlsx.js'; function make_lay_excel(global) { // default if (!global) { global = {}; } global = { /** * 合并对象 */ objectExtend: function() { return merge(...arguments); // if (typeof Object.assign != 'function') { // 'use strict'; // if (target == null) { // throw new TypeError('Cannot convert undefined or null to object'); // } // target = Object(target); // for (var index = 1; index < arguments.length; index++) { // var source = arguments[index]; // if (source != null) { // for (var key in source) { // if (Object.prototype.hasOwnProperty.call(source, key)) { // target[key] = source[key]; // } // } // } // } // return target; // } else { // return Object.assign.apply(this, arguments) // } }, /** * 遍历对象 * @param object * @param callback */ each: function (object, callback) { for (var k in object) { if (object.hasOwnProperty(k)) { if (Array.isArray(object)) { k = parseInt(k); } callback.apply(this, [k, object[k]]) } } }, /** * 兼容老版本的导出函数 * @param {[type]} data [description] * @param {[type]} filename [description] * @param {[type]} type [description] * @return {[type]} [description] */ downloadExl: function(data, filename, type) { type = type ? type : 'xlsx'; this.exportExcel({sheet1: data}, filename+'.'+type, type, null); }, /** * 导出Excel并弹出下载框,具体使用方法和范围请参考文档 * @param data object * @param {[type]} filename [description] * @param {[type]} type [description] * @param {[type]} opt [description] * @return {[type]} [description] */ exportExcel : function(data, filename, type, opt) { type = type ? type : 'xlsx'; filename = filename ? filename : '导出数据.'+type; // 创建一个 XLSX 对象 var wb = XLSX.utils.book_new(); // 1. 定义excel对的基本属性 var Props = { Title: filename, Subject: 'Export From web browser', Author: "excel.wj2015.com", Manager: '', Company: '', Category: '', Keywords: '', Comments: '', LastAuthor: '', CreatedData: new Date(), }; opt && opt.Props && (Props = this.objectExtend(Props, opt.Props)); // 默认进行压缩 wb.compression = opt ? opt.compression : true if(wb.compression !== false) { wb.compression = true } wb.Props = Props; // 特殊属性实现,比如合并单元格 var wbExtend = { '!merges': null ,'!margins': null ,'!cols': null ,'!rows': null ,'!protect': null ,'!autofilter': null }; opt && opt.extend && (wbExtend = this.objectExtend(wbExtend, opt.extend)); // 清理空配置 for (var key in wbExtend) { if (!wbExtend.hasOwnProperty(key)) { continue; } if (!wbExtend[key]) { delete wbExtend[key]; } } // 判断 data 如果是 sheet 级别数据,自动加 sheet1 if (Array.isArray(data)) { data = {sheet1: data}; } for(var sheet_name in data) { if (!data.hasOwnProperty(sheet_name)) { continue; } var content = data[sheet_name]; // 2. 设置sheet名称 wb.SheetNames.push(sheet_name); // 3. 分配工作表对象到 sheet var is_aoa = false; if (content.length && content[0] && Array.isArray(content[0])) { is_aoa = true; } if (is_aoa) { ws = XLSX.utils.aoa_to_sheet(content); } else { var option = {}; if (content.length) { option.headers = content.unshift(); option.skipHeader = true; // 分离并重组样式 var splitRes = this.splitContent(content); } var ws = XLSX.utils.json_to_sheet(content, option); // 合并样式 if (typeof splitRes !== 'undefined') { this.mergeCellOpt(ws, splitRes.style); } } // 特殊属性,支持单独设置某个sheet的属性 if (wbExtend[sheet_name]) { this.objectExtend(ws, wbExtend[sheet_name]); } else { this.objectExtend(ws, wbExtend); } wb.Sheets[sheet_name] = ws; }; var writeOpt = {bookType: type, type: 'binary', cellStyles: true, compression: wb.compression}; if (opt && typeof opt === 'object' && typeof opt.writeOpt === 'object' && opt.writeOpt) { this.objectExtend(writeOpt, opt.writeOpt) } // 4. 输出工作表 var wbout = XLSX.write(wb, writeOpt); // 5. 跨浏览器支持,采用 FileSaver 三方库 FileSaver.saveAs(new Blob([this.s2ab(wbout)], {type: "application/octet-stream"}), filename); }, /** * 分离内容和样式 * @param {[type]} content [description] * @return {[type]} [description] */ splitContent: function(content) { var styleContent = {}; // 扫描每个单元格,如果是对象则等表格转换完毕后分离出来重新赋值 for (var line = 0; line < content.length; line++) { var lineData = content[line]; var rowIndex = 0; for (var row in lineData) { if (!lineData.hasOwnProperty(row)) { continue; } var rowData = lineData[row]; if (typeof rowData === 'object') { // typeof null == object if (rowData !== null) { styleContent[this.numToTitle(rowIndex+1)+(parseInt(line)+1)] = rowData; } else { lineData[row] = ''; } } else { // JeffreyWang 2019-03-10针对 0 的hack处理 if (rowData === 0) { rowData = { v: '0', s: { alignment: { horizontal: 'right' } } } } styleContent[this.numToTitle(rowIndex+1)+(parseInt(line)+1)] = rowData; } rowIndex++; } } return { content: content, style: styleContent }; }, /** * 合并内容和样式 * @param {[type]} ws [description] * @param {[type]} style [description] * @return {[type]} [description] */ mergeCellOpt: function(ws, style) { for (var row in style) { if (!style.hasOwnProperty(row)) { continue; } var rowOpt = style[row]; if (ws[row]) { // 其他属性做一个初始化 var otherOpt = ['t', 'w', 'f', 'r', 'h', 'c', 'z', 'l', 's']; for (var i = 0; i < otherOpt.length; i++) { ws[row][otherOpt[i]] = ws[row][otherOpt[i]]; } this.objectExtend(ws[row], rowOpt); } } }, /** * 将table转换为JSON数据 * @param dom */ tableToJson: function(dom) { if (!dom || !dom.querySelectorAll) { return []; } var that = this; var handleLineNode = function (lineDomList) { var res = []; that.each(lineDomList, function (key, val) { var line = []; that.each(val.querySelectorAll('td,th'), function (k, v) { line.push(v.innerText); }); res.push(line); }) return res; }; var headDom = dom.querySelectorAll('thead > tr'); var bodyDom = dom.querySelectorAll('tbody > tr'); var head = handleLineNode(headDom); var body = handleLineNode(bodyDom); return { head: head, body: body } }, // 测试代码: // for(i=1;i<100;i++){var change = layui.excel.numToTitle(i);console.log(i, change, layui.excel.titleToNum(change));} // numsToTitle备忘录提效 numsTitleCache: {}, // titleToTitle 备忘录提效 titleNumsCache: {}, /** * 将数字(从1开始)转换为 A、B、C...AA、AB,内藏规律,解码为0代表A * @param num int [description] * @return string [description] */ numToTitle: function(num) { if (num <= 0) { return ''; } var remainder = num % 26; var left = Math.floor(num / 26); if (remainder === 0) { remainder = 26; left -= 1; } var ans = String.fromCharCode(64 + remainder); if (left > 0) { ans = this.numToTitle(left) + ans; } this.numsTitleCache[num] = ans; this.titleNumsCache[ans] = num; return ans; }, /** * 将A、B、AA、ABC转换为 1、2、3形式的数字 * @param {[type]} title [description] * @return {number} [description] */ titleToNum: function(title) { if (this.titleNumsCache[title]) { return this.titleNumsCache[title]; } var len = title.length; var total = 0; for (var index in title) { if (!title.hasOwnProperty(index)) { continue; } var char = title[index]; var code = char.charCodeAt() - 64; total += code * Math.pow(26, len - index - 1); } this.numsTitleCache[total] = title; this.titleNumsCache[title] = total; return total; }, /** * 获取数据范围内有效范围 * @param data array sheet级别的数据 * @param range 范围字符串,如 A1:C12,默认从左上角到右下角 */ getDefaultRange: function(data, range) { // 以 rowIndex 为键,field 为值 var fieldKeys = Object.keys(data[0]); var maxCol = fieldKeys.length - 1; var maxRow = data.length -1; // 默认 A1 ~ 右下角 var startPos = {c: 0, r: 0}; var endPos = {c: maxCol, r: maxRow}; if (range && typeof range === 'string') { var rangeArr = range.split(':'); if (rangeArr[0].length) { startPos = this.splitPosition(rangeArr[0]); } if (typeof rangeArr[1] !== 'undefined' && rangeArr[1] !== '') { endPos = this.splitPosition(rangeArr[1]); } } else { // pass } // position范围限制 - 考虑到特殊情况取消此限制 // startPos.c = startPos.c < maxCol ? startPos.c : maxCol; // endPos.c = endPos.c < maxCol ? endPos.c : maxCol; // startPos.r = startPos.r < maxRow ? startPos.r : maxRow; // endPos.r = endPos.r < maxRow ? endPos.r : maxRow; if (startPos.c > endPos.c) { console.error('开始列不得大于结束列'); } if (startPos.r > endPos.r) { console.error('开始行不得大于结束行'); } return { startPos: startPos, endPos: endPos, fieldKeys: fieldKeys } }, /** * 根据 startPos endPos 遍历设置单元格属性,支持 filter 回调处理 * @param data array sheet级别数据 * @param startPos object {c: 开始列索引, r: 开始行索引} * @param endPos object {c: 结束列索引, r: 结束行索引} * @param fieldKeys ['第一列属性Key', '第二列属性Key'] * @param config object {s: {样式}, v: '值'} * @param filter callable 回调函数,入参 cell(原cell),newCell(新cell),row(当前行),config(配置), currentRow(当前行索引), currentCol(当前列索引-数字),currentColKey(当前列索引-对象) */ setCellStyle: function (data, startPos, endPos, fieldKeys, config, filter) { // 遍历范围内的数据,进行样式设置,按从上到下从左到右按行遍历 for (var currentRow = startPos.r; currentRow <= endPos.r; currentRow++) { for (var currentCol = startPos.c; currentCol <= endPos.c; currentCol++) { // 如果有回调则执行回调判断,否则全部更新,如果遇到超出数据范围的,自动置空 var row = data[currentRow]; if (!row) { row = {}; for (var key = 0; key < fieldKeys.length; key++) { row[fieldKeys[key]] = ''; } data[currentRow] = row; } var cell = row[fieldKeys[currentCol]]; var newCell = null; if (cell === null || cell === undefined) { cell = ''; } // 手工合并(相同的则以当前函数config为准) if (typeof cell === 'object') { newCell = this.objectExtend({}, cell, config); } else { newCell = this.objectExtend({}, {v: cell}, config); } if ( typeof filter === 'function' ) { newCell = filter(cell, newCell, row, config, currentRow, currentCol, fieldKeys[currentCol]); } else { } // 回写 data[currentRow][fieldKeys[currentCol]] = newCell; } } }, /** * 设置范围内环绕的边框 * @param data [sheet级别的数据] * @param range [范围字符串,如 A1:C12,默认从左上角到右下角] * @param config [border 上下左右属性配置信息(对角线的三个属性被下放到left/right/top/bottom下),如:{top: {xxx}, bottom: {}, left: {}, right: {}}] */ setRoundBorder: function(data, range, config) { if (typeof data !== 'object' || !data.length || !data[0] || !Object.keys(data[0]).length) { return []; } var rangeObj = this.getDefaultRange(data, range); var startPos = rangeObj.startPos; var endPos = rangeObj.endPos; var fieldKeys = rangeObj.fieldKeys; // 顶部 border 属性取 config.top // console.warn("top,", "from:", this.numToTitle(startPos.c)+startPos.r, ",to:", this.numToTitle(endPos.c)+startPos.r); this.setCellStyle(data, startPos, { c: endPos.c, r: startPos.r }, fieldKeys, { s: { border: { top: config.top, diagonal: config.top.diagonal, diagonalUp: config.top.diagonalUp, diagonalDown: config.top.diagonalDown } } }); // 右侧 border 属性取 config.right // console.warn("right,", "from:", this.numToTitle(endPos.c)+startPos.r, ",to:", this.numToTitle(endPos.c)+endPos.r); this.setCellStyle(data, { c: endPos.c, r: startPos.r }, endPos, fieldKeys, { s: { border: { right: config.right, diagonal: config.right.diagonal, diagonalUp: config.right.diagonalUp, diagonalDown: config.right.diagonalDown } } }) // 底部 border 属性取 config.bottom // console.warn("bottom,", "from:", this.numToTitle(startPos.c)+endPos.r, ",to:", this.numToTitle(endPos.c)+endPos.r); this.setCellStyle(data, { c: startPos.c, r: endPos.r }, endPos, fieldKeys, { s: { border: { bottom: config.bottom, diagonal: config.bottom.diagonal, diagonalUp: config.bottom.diagonalUp, diagonalDown: config.bottom.diagonalDown } } }) // 左侧 border 属性取 config.left // console.warn("left,", "from:", this.numToTitle(startPos.c)+startPos.r, ",to:", this.numToTitle(startPos.c)+endPos.r); this.setCellStyle(data, startPos, { c: startPos.c, r: endPos.r }, fieldKeys, { s: { border: { left: config.left, diagonal: config.left.diagonal, diagonalUp: config.left.diagonalUp, diagonalDown: config.left.diagonalDown } } }) }, /** * 批量设置单元格属性 * @param {array} data [sheet级别的数据] * @param {string} range [范围字符串,比如 A1:C12,开始位置默认 A1,结束位置默认整个表格右下角] * @param {object} config [批量设置的单元格属性] * @param {function} filter [回调函数,传递函数生效,返回值作为新的值(可用于过滤、规则替换样式等骚操作)] * @return {array} [重新渲染后的 sheet 数据] */ setExportCellStyle: function(data, range, config, filter) { if (typeof data !== 'object' || !data.length || !data[0] || !Object.keys(data[0]).length) { return []; } var rangeObj = this.getDefaultRange(data, range); var startPos = rangeObj.startPos; var endPos = rangeObj.endPos; var fieldKeys = rangeObj.fieldKeys; this.setCellStyle(data, startPos, endPos, fieldKeys, config, filter); return data; }, /** * 合并单元格快速生成配置的函数 传入 [ ['开始坐标 A1', '结束坐标 D2'], ['开始坐标 B2', '结束坐标 E3'] ] * @param {[type]} origin [description] * @return {[type]} [description] */ makeMergeConfig: function(origin) { var merge = []; for (var index = 0; index < origin.length; index++) { merge.push({ s: this.splitPosition(origin[index][0]), e: this.splitPosition(origin[index][1]) }); } return merge; }, /** * 自动生成列宽配置 * @param {$ObjMap} data [A、B、C的宽度映射] * @param {number} defaultNum [description] * @return {$ObjMap} [description] */ makeColConfig: function(data, defaultNum) { defaultNum = defaultNum > 0 ? defaultNum : 50; // 将列的 ABC 转换为 index var change = []; var startIndex = 0; for (var index in data) { if (!data.hasOwnProperty(index)) { continue; } var item = data[index]; if (index.match && index.match(/[A-Z]*/)) { var currentIndex = this.titleToNum(index) - 1; // 填充未配置的单元格 while (startIndex < currentIndex) { change.push({wpx: defaultNum}); startIndex++; } startIndex = currentIndex+1; change.push({wpx: item > 0 ? item : defaultNum}); } }; return change; }, /** * 自动生成列高配置 * @param {[type]} data [description] * @param {[type]} defaultNum [description] * @return {[type]} [description] */ makeRowConfig: function(data, defaultNum) { defaultNum = defaultNum > 0 ? defaultNum : 10; // 将列的 ABC 转换为 index var change = []; var startIndex = 0; for (var index in data) { if (!data.hasOwnProperty(index)) { continue; } var item = data[index]; if (index.match && index.match(/[0-9]*/)) { var currentIndex = parseInt(index) - 1; // 填充未配置的行 while (startIndex < currentIndex) { change.push({hpx: defaultNum}); startIndex++; } startIndex = currentIndex+1; change.push({hpx: item > 0 ? item : defaultNum}); } }; return change; }, /** * 将A1分离成 {c: 0, r: 0} 格式的数据 * @param {string} pos [description] * @return {{r: number, c: number}} [description] */ splitPosition: function(pos) { var res = pos.match('^([A-Z]+)([0-9]+)$'); if (!res) { return {c: 0, r: 0}; } // 转换结果相比需要的结果需要减一转换 return { c: this.titleToNum(res[1]) - 1, r: parseInt(res[2]) - 1 } }, /** * 将二进制数据转为8位字节 * @param {[type]} s [description] * @return {[type]} [description] */ s2ab: function(s) { var buf = new ArrayBuffer(s.length); var view = new Uint8Array(buf); for (var i = 0; i < s.length; i++) { view[i] = s.charCodeAt(i) & 0xFF; } return buf; }, /** * 将导出的数据格式,转换为可以aoa导出的格式 * @return {[type]} [description] */ filterDataToAoaData: function(filterData){ var aoaData = []; this.each(filterData, function(index, item) { var itemData = []; for (var i in item) { if (!item.hasOwnProperty(i)) { continue; } itemData.push(item[i]); } aoaData.push(itemData); }); return aoaData; }, /** * 梳理导出的数据,包括字段排序和多余数据过滤,具体功能请参见文档 * @param {[type]} data [需要梳理的数据] * @param {[type]} fields [支持数组和对象,用于映射关系和字段排序] * @return {[type]} [description] */ filterExportData: function(data, fields) { // PS:之所以不直接引用 data 节省内存,是因为担心如果 fields 可能存在如下情况: { "id": 'test_id', 'test_id': 'new_id' },会导致处理异常 var exportData = []; var true_fields = []; // filed 支持两种模式,数组则单纯排序,对象则转换映射关系,为了统一处理,将数组转换为符合要求的映射关系对象 if (Array.isArray(fields)) { for (var i = 0; i< fields.length; i++) { true_fields[fields[i]] = fields[i]; } } else { true_fields = fields; } for (var i = 0; i < data.length; i++) { var item = data[i]; exportData[i] = {}; for (var key in true_fields) { if (!true_fields.hasOwnProperty(key)) { continue; } var new_field_name = key; var old_field_name = true_fields[key]; // 如果传入的是回调,则回调的值则为新值 if (typeof old_field_name === 'function' && old_field_name.apply) { exportData[i][new_field_name] = old_field_name.apply(window, [item[new_field_name], item, data, i, new_field_name]); } else { if (typeof item[old_field_name] !== 'undefined') { exportData[i][new_field_name] = item[old_field_name]; } else { exportData[i][new_field_name] = ''; } } } } return exportData; }, /** * 梳理导入的数据,参数意义可参考 filterExportData * @param {[type]} data [description] * @param {[type]} fields [description] * @return {[type]} [description] */ filterImportData: function(data, fields) { var that = this; this.each(data, function(fileindex, xlsx) { this.each(xlsx, function(sheetname, content) { xlsx[sheetname] = that.filterExportData(content, fields); }); }); return data; }, /** * 读取Excel,支持多文件多表格读取 * @param {[type]} files [description] * @param {[type]} opt [description] * @param {Function} callback [description] * @return {[type]} [description] */ importExcel: function(files, opt, callback) { var option = { header: 'A', range: null, fields: null, checkMime: true, }; this.objectExtend(option, opt); var that = this; if (files.length < 1) { throw {code: 999, 'message': '传入文件为空'}; } var supportReadMime = [ 'application/vnd.ms-excel', 'application/msexcel', 'application/x-msexcel', 'application/x-ms-excel', 'application/x-excel', 'application/x-dos_ms_excel', 'application/xls', 'application/x-xls', 'application/vnd-xls', 'application/csv', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/wps-office.xlsx', '' ]; if (option.checkMime) { this.each(files, function(index, item) { if (supportReadMime.indexOf(item.type) === -1) { throw {code: 999, message: item.name+'('+item.type+')为不支持的文件类型'}; } }); } delete option.checkMime; // 按照二进制读取 var data = {}; var book = {}; this.each(files, function(index, item) { var reader = new FileReader(); if (!reader) { throw {code: 999, message: '不支持FileReader,请更换更新的浏览器'}; } // 读取excel表格对象 reader.onload = function(ev) { var wb = XLSX.read(ev.target.result, { type: 'binary' }); var excelData = {}; that.each(wb.Sheets, function(sheet, sheetObj) { // 全为空的去掉 if (wb.Sheets.hasOwnProperty(sheet)) { var opt = { header: option.header, defval: '' }; if (option.range) { opt.range = option.range; } excelData[sheet] = XLSX.utils.sheet_to_json(sheetObj, opt); // 支持梳理数据 if (option.fields) { excelData[sheet] = that.filterExportData(excelData[sheet], option.fields); } } }); data[index] = excelData; book[index] = wb; // 全部读取完毕才执行,考虑后来的文件先解析完,判断长度更合理 if (Object.values(data).length === files.length) { callback && callback.apply && callback.apply(window, [data, book]); } }; reader.readAsBinaryString(item); }); }, /** * EXCEL日期码转换为Date对象 * @param code double excel中存储的日期格式码 */ dateCodeToDate: function(code) { var obj = XLSX.SSF.parse_date_code(code); return (new Date(obj.y + '-' + obj.m + '-' + obj.d + ' ' + obj.H + ':' + obj.M + ':' + obj.S)); }, /** * 字符补全函数 * @param str * @param maxLength * @param padString * @returns {*} */ strPad: function(str, maxLength, padString) { str = str + '' if (typeof maxLength === 'undefined') { maxLength = 2 } if (typeof padString === 'undefined') { padString = '0' } if (padString.length <= 0) { console.error('strPad error'); return str; } if (str.length < maxLength) { var repeatCount = Math.floor((maxLength - str.length) / padString.length); var exceptStr = ''; if (repeatCount * padString.length < maxLength - 1) { exceptStr = padString.substr(0, maxLength - 1 - repeatCount * padString.length) } return padString * repeatCount + exceptStr + str } else { return str } }, /** * 简易格式转换 * @param date Date 待转换时间 * @param format String 日期格式 YYYY-MM-DD HH:ii:ss */ dateFormat: function(date, format) { if (!(date instanceof Date)) { console.error(date+'需要是时间日期对象'); } if (typeof format === 'undefined') { format = 'YYYY-MM-DD HH:ii:ss'; } // 制造 format 相关参数 var YYYY = date.getFullYear(); var YY = (YYYY + '').substr(2, 2) var M = date.getMonth() + 1; var MM = this.strPad(M, 2, '0'); var D = date.getDate(); var DD = this.strPad(D, 2, '0'); var H = date.getHours(); var HH = this.strPad(H, 2, '0'); var i = date.getMinutes(); var ii = this.strPad(i, 2, '0'); var s = date.getSeconds(); var ss = this.strPad(s, 2, '0'); var config = { 'YYYY': YYYY, 'YY': YY, 'MM': MM, 'M': M, 'DD': DD, 'D': D, 'HH': HH, 'H': H, 'ii': ii, 'i': i, 'ss': ss, 's': s }; for (var key in config) { if (!config.hasOwnProperty(key)) { continue; } var reg = RegExp(key, 'g'); format = format.replace(reg, config[key]); } return format; }, /** * excel的日期CODE格式化 * @param code * @param format * @returns {*|void|string} */ dateCodeFormat: function (code, format) { return this.dateFormat(this.dateCodeToDate(code), format) }, /** * * 提取 url 中的后缀 * @param {string} url * @returns */ getUrlSuffix(url) { const match = /\.(\w+)(\?.*|#.*)?$/.exec(url); return match ? match[1] : ''; }, /** * * 远程下载图片,可能有跨域问题,IE 可能报 SCRIPT5022: SecurityError * @param {string} url * @param {string} ext * @returns */ imageUrlToBase64(url, ext="") { if (!ext) { ext = getUrlSuffix(url); } return new Promise((resolve,reject) => { let canvas=document.createElement("canvas"); const ctx=canvas.getContext("2d"); let img=new Image(); img.crossOrigin="Anonymous"; //解决Canvas.toDataURL 图片跨域问题 img.src=url; img.onload=function() { canvas.height=img.height; canvas.width=img.width; ctx.drawImage(img,0,0,img.width,img.height); //参数可自定义 const dataURL=canvas.toDataURL(`image/${ext}`,1); //获取Base64编码 resolve(dataURL); canvas=null; //清除canvas元素 img=null; //清除img元素 }; img.onerror=function() { reject(new Error("Could not load image at "+url)); }; }); }, /** * EMU 单位转换辅助函数 * EMU (English Metric Units) 是 Office 文档中的度量单位 * @param {number} value 输入值 * @param {string} fromUnit 输入单位:'inch'(英寸), 'cm'(厘米), 'px'(像素), 'emu' * @param {string} toUnit 输出单位:'inch'(英寸), 'cm'(厘米), 'px'(像素), 'emu' * @returns {number} 转换后的值 */ convertEMU(value, fromUnit, toUnit = 'emu') { // EMU 转换常数 const EMU_PER_INCH = 914400; const EMU_PER_CM = 360000; const EMU_PER_PIXEL = 9525; // 基于 96 DPI // 先转换为 EMU let emuValue; switch (fromUnit.toLowerCase()) { case 'inch': case 'in': emuValue = value * EMU_PER_INCH; break; case 'cm': emuValue = value * EMU_PER_CM; break; case 'px': case 'pixel': emuValue = value * EMU_PER_PIXEL; break; case 'emu': emuValue = value; break; default: throw new Error('不支持的输入单位: ' + fromUnit + '。支持的单位: inch, cm, px, emu'); } // 从 EMU 转换为目标单位 switch (toUnit.toLowerCase()) { case 'inch': case 'in': return emuValue / EMU_PER_INCH; case 'cm': return emuValue / EMU_PER_CM; case 'px': case 'pixel': return emuValue / EMU_PER_PIXEL; case 'emu': return Math.round(emuValue); // 返回整数 default: throw new Error('不支持的输出单位: ' + toUnit + '。支持的单位: inch, cm, px, emu'); } }, /** * 创建 oneCellAnchor 类型的图片位置配置 * @param {string|object} position 位置,如 "D2" 或 {c: 3, r: 1} * @param {number} width 宽度 * @param {number} height 高度 * @param {string} unit 尺寸单位,默认 'emu' * @param {object} options 可选配置 {colOff, rowOff} * @returns {object} oneCellAnchor 位置配置对象 */ createOneCellAnchor(position, width, height, unit = 'emu', options = {}) { const { colOff = 0, rowOff = 0 } = options; // 处理位置参数 let from; if (typeof position === 'string') { from = this.splitPosition(position); } else if (typeof position === 'object' && position.c !== undefined && position.r !== undefined) { from = position; } else { throw new Error('position 参数格式错误,应为字符串(如 "D2")或对象(如 {c: 3, r: 1})'); } // 转换尺寸为 EMU const cx = this.convertEMU(width, unit, 'emu'); const cy = this.convertEMU(height, unit, 'emu'); return { type: "oneCellAnchor", from: { c: from.c, r: from.r, colOff: colOff, rowOff: rowOff }, ext: { cx: cx, cy: cy } }; } } return global; } if (typeof layui !== 'undefined') { layui.define([], function(exports){ exports('excel', make_lay_excel()); }); } if(typeof exports !== 'undefined') {make_lay_excel(exports);} else if(typeof module !== 'undefined' && module.exports) make_lay_excel(module.exports); else if(typeof define === 'function' && define.amd) define('lay-excel', function() { return make_lay_excel(); }); window.LAY_EXCEL = make_lay_excel(); export default make_lay_excel();