hiprint 打印预览正常,导出 Excel 报错
1. 设计模板
2. 预览效果
3. 导出excel(电话和地址合并到一起)
4. 配置表
5. 查看控制台发现报错了,合并重复了
地址的结束列6和电话的开始列4 没有错开,是不是改下这个列计算就可以。
getTextColByLeft(leftVal, firstTableLeft, firstTableCols) {
const relativeLeft = leftVal - firstTableLeft;
if (relativeLeft < 0) return 1;
if (firstTableCols.length === 0) return Math.max(1, Math.round(relativeLeft / 50));
let accumulated = 0;
for (let i = 0; i < firstTableCols.length; i++) {
accumulated += firstTableCols[i];
// console.log('accumulated:' + accumulated)
// console.log('relativeLeft:' + relativeLeft)
if (relativeLeft < accumulated) {
return i+1;
}
}
//左边超过表格 按照单元格宽度50换算
return firstTableCols.length + Math.round((relativeLeft - accumulated) / 50)
},
查了下这段列计算的逻辑:按照表格各列宽度累加,来判断文本的左边位置和右边位置分别落在表格的第几列。所以只要让文本元素的左右位置不再重叠,就不会再触发重复合并。理论上也可以在代码里加偏移计算来修正位置,但相比之下直接改设计模板更省事——把电话元素往右移,让它和地址错开。
然后导出再试下发现可以了。
预览导出代码
<template>
<el-dialog class="preview_dialog" ref="previewDialog" title="快速预览" :show-close="false" @opened="open" :modal="false"
:width="width + 'mm'" :visible.sync="previewDialogStatus">
<div id="preview_content" ref="myComponent"></div>
<span slot="footer" class="dialog-footer">
<el-button @click.native="previewDialogStatus = false" size="mini">{{$t('form.cancel')}}</el-button>
<el-button @click.native="printTemplate" size="mini" type="primary">{{$t('form.printBtn')}}</el-button>
<el-button @click="captureImage" size="mini">生成图片</el-button>
<el-button @click="exportToExcel1" size="mini">导出Excel(模板)</el-button>
<el-button @click="exportToPdf" size="mini">转Pdf</el-button>
</span>
</el-dialog>
</template>
<script>
import {
buildHiprintTemplate
} from './hiprint';
import {
getPrintModel,
addPrintCount
} from '@/api/hiprint/hiprint';
import html2canvas from "html2canvas";
import QRCode from 'qrcode';
import JsBarcode from 'jsbarcode';
import ExcelJS from 'exceljs';
export default {
name: "printPreview",
props: {},
data() {
return {
width: '250', //210
previewDialogStatus: false,
// 模板
hiprintTemplate: {},
// 数据
printData: {},
template: {}
}
},
methods: {
captureImage() {
const element = this.$refs.myComponent;
html2canvas(element).then((canvas) => {
const image = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = document.title + '.png'; // 图片名称
link.href = image;
link.click();
});
},
exportToPdf() {
this.hiprintTemplate.toPdf(this.printData, document.title + '.pdf');
},
async initImageToExcel(opts, textCol, row, worksheet, workbook, colSpan) {
const imageId = workbook.addImage({
base64: opts.src,
extension: 'png'
});
const codeSize = {
w: Math.max(opts.width, opts.height),
h: Math.max(opts.width, opts.height)
};
// 计算目标列宽:如果跨多列,均分宽度;否则按图片宽度
const targetColWidth = colSpan > 1 ? (codeSize.w / colSpan) + 10 : codeSize.w + 10;
for (let i = 0; i < colSpan; i++) {
worksheet.getColumn(textCol + i).width = targetColWidth;
}
worksheet.getRow(row).height = codeSize.h + 10;
// 跨列时先合并单元格,再插入图片
if (colSpan > 1) {
worksheet.mergeCells(row, textCol, row, textCol + colSpan - 1);
}
// 计算单元格可用宽度(Excel 列宽单位 ≈ 像素/7,行高单位 = 像素)
const cellWidthPx = targetColWidth * 7 * colSpan;
const cellHeightPx = codeSize.h + 10;
// 按比例缩放,以短边为限制
let drawW = codeSize.w;
let drawH = codeSize.h;
const scale = Math.min(cellWidthPx / drawW, cellHeightPx / drawH, 1);
if (scale < 1) {
drawW = drawW * scale;
drawH = drawH * scale;
}
// 水平居中偏移(像素转 Excel 列偏移:1 列宽 ≈ 7px)
const offsetXPx = (cellWidthPx - drawW) / 2;
const offsetCol = offsetXPx / 7 / targetColWidth;
worksheet.addImage(imageId, {
tl: {
col: textCol - 1 + offsetCol,
row: row - 1 + 0.05
},
ext: {
width: drawW,
height: drawH
}
});
},
async initQrCodeToExcel(opts, textCol, row, worksheet, workbook, colSpan) {
let imageBase64 = await QRCode.toDataURL(opts.testData, {
width: Math.round(opts.width * 2),
margin: 1,
errorCorrectionLevel: 'M'
});
const imageId = workbook.addImage({
base64: imageBase64,
extension: 'png'
});
const CODE_SCALE = 2;
const codeSize = {
w: Math.max(opts.width, opts.height) * CODE_SCALE,
h: Math.max(opts.width, opts.height) * CODE_SCALE
};
const cellWidth = codeSize.w;
const titleHeight = opts.title ? 20 : 0;
const bottomPadding = 6;
const rowHeight = codeSize.h + titleHeight + bottomPadding;
worksheet.getRow(row).height = rowHeight;
worksheet.getColumn(textCol).width = Math.max(worksheet.getColumn(textCol).width || 20, cellWidth /
7.5);
worksheet.addImage(imageId, {
tl: {
col: textCol - 1 + 0.9,
row: row - 1 + 0.05
},
ext: {
width: codeSize.w,
height: codeSize.h
}
});
if (opts.title) {
const titleCell = worksheet.getRow(row).getCell(textCol);
titleCell.value = opts.title;
titleCell.font = {
name: '宋体',
size: opts.fontSize,
bold: false
};
titleCell.alignment = {
horizontal: 'center',
vertical: 'bottom',
wrapText: true
};
}
if (colSpan > 1) {
try {
worksheet.mergeCells(row, textCol, row, textCol + colSpan - 1);
} catch (e) {
/* 跳过 */
}
}
},
async initBarCodeToExcel(opts, textCol, row, worksheet, workbook, colSpan) {
const canvas = document.createElement('canvas');
JsBarcode(canvas, opts.testData, {
format: 'CODE128',
width: 1,
height: Math.round(opts.height),
displayValue: false
});
let imageBase64 = canvas.toDataURL('image/png');
const imageId = workbook.addImage({
base64: imageBase64,
extension: 'png'
});
const CODE_SCALE = 2;
const codeSize = {
w: opts.width * CODE_SCALE,
h: opts.height * CODE_SCALE
};
const cellWidth = codeSize.w;
const titleHeight = opts.title ? 20 : 0;
const bottomPadding = 6;
const rowHeight = codeSize.h + titleHeight + bottomPadding;
worksheet.getRow(row).height = rowHeight;
worksheet.getColumn(textCol).width = Math.max(worksheet.getColumn(textCol).width || 20, cellWidth /
7.5);
worksheet.addImage(imageId, {
tl: {
col: textCol - 1 + 0.25,
row: row - 1 + 0.1
},
ext: {
width: codeSize.w,
height: codeSize.h
}
});
// 文字放在同一单元格底部
if (opts.title) {
const titleCell = worksheet.getRow(row).getCell(textCol);
titleCell.value = opts.title;
titleCell.font = {
name: '宋体',
size: opts.fontSize,
bold: false
};
titleCell.alignment = {
horizontal: 'center',
vertical: 'bottom',
wrapText: true
};
}
if (colSpan > 1) {
try {
worksheet.mergeCells(row, textCol, row, textCol + colSpan - 1);
} catch (e) {
/* 跳过 */
}
}
},
async initTextToExcel(textBeforeTable, worksheet, row, textDataSource, tableElements, workbook) {
// 按 top 排序
textBeforeTable.sort((a, b) => {
const aTop = (a.options && a.options.top) || 0;
const bTop = (b.options && b.options.top) || 0;
return aTop - bTop;
});
// 获取第一个表格的列宽信息和起始 left,用于文本 left 换算列位置
const firstTableCols = [];
let firstTableLeft = 0;
if (tableElements.length > 0) {
const firstOpts = tableElements[0].options || {};
firstTableLeft = firstOpts.left || 0;
const firstColumns = firstOpts.columns[0] || [];
console.log('firstColumns:', firstColumns)
for (const col of firstColumns) {
if (col.checked === false) continue;
firstTableCols.push(col.width);
}
}
console.log('firstTableCols:', firstTableCols)
let rows = [];
for (let i = 0; i < textBeforeTable.length; i++) {
const top1 = textBeforeTable[i].options.top
let cols = [textBeforeTable[i]];
for (let j = i + 1; j < textBeforeTable.length; j++) {
const top2 = textBeforeTable[j].options.top
if (top2 - top1 <= 5) {
cols.push(textBeforeTable[j])
i = j;
} else {
break;
}
}
rows.push(cols);
}
//console.log('rows:' + JSON.stringify(rows))
// 处理表格前文本
for (let i = 0; i < rows.length; i++) {
let cols = rows[i];
for (const textEl of cols) {
const opts = textEl.options || {};
const peType = (textEl.printElementType && textEl.printElementType.type) || '';
const textField = (typeof opts.field === 'string' && opts.field) || '';
const title = (opts.hideTitle && opts.hideTitle === true) ? '' : (opts.title || '');
const fieldValue = (textField && textDataSource[textField] != null) ? textDataSource[textField] : '';
let text = title ? (textField ? title + ':' + String(fieldValue) : title) : String(fieldValue);
// 将 替换为普通空格
text = text.replace(/ /gi, ' ');
const textLeft = opts.left || 0;
const textWidth = opts.width || 200;
const textHeight = opts.height || 20;
const fontSize = opts.fontSize || 10;
const formatter = opts.formatter;
const fontWeight = opts.fontWeight;
const borderLeft = opts.borderLeft;
const borderRight = opts.borderRight;
const borderBottom = opts.borderBottom;
const borderTop = opts.borderTop;
const vCenter=opts.vCenter;
const hCenter=opts.hCenter;
const startCol = this.getTextColByLeft(textLeft, firstTableLeft, firstTableCols);
const endCol = this.getTextColByLeft(textLeft + textWidth, firstTableLeft, firstTableCols);
const colSpan = Math.max(1, endCol - startCol + 1);
console.log('startCol:', startCol, 'endCol:', endCol, 'colSpan:', colSpan, 'text:', text, opts)
// 二维码/条形码:图片在上文字在下,同一单元格
if (peType === 'qrcode') {
await this.initQrCodeToExcel(opts, startCol, row, worksheet, workbook, colSpan)
} else if (peType === 'barcode') {
await this.initBarCodeToExcel(opts, startCol, row, worksheet, workbook, colSpan)
} else if (peType == 'image') {
await this.initImageToExcel(opts, startCol, row, worksheet, workbook, colSpan)
} else {
if (colSpan > 1) {
try {
let value = '';
for (let i = startCol; i <= endCol; i++) {
value = value + (worksheet.getRow(row).getCell(i).value ? worksheet.getRow(row).getCell(i).value : '')
}
worksheet.mergeCells(row, startCol, row, endCol);
worksheet.getRow(row).getCell(startCol).value = value;
} catch (e) {
}
}
const cell = worksheet.getRow(row).getCell(startCol);
if (text.indexOf(':') > -1) {
cell.value = text + (cell.value ? cell.value : '');
} else {
cell.value = (cell.value ? cell.value : '') + text;
}
if (formatter) {
console.log('formatter:' + formatter)
cell.value = undefined;
const formattedValue = this.formatTextValue(formatter);
console.log('formattedValue:' + formattedValue)
cell.value = formattedValue != null ? formattedValue : '';
}
cell.font = {
name: '宋体',
size: fontSize,
bold: fontWeight === 'bold' || fontWeight === '700' || opts.bold === true
};
cell.alignment = {
horizontal: opts.textAlign || 'left',
vertical: 'middle',
wrapText: true
};
// 根据 opts 设置边框
const borderStyle = {
style: 'thin',
color: {
argb: 'FF000000'
}
};
cell.border = {
left: borderLeft ? borderStyle : undefined,
right: borderRight ? borderStyle : undefined,
top: borderTop ? borderStyle : undefined,
bottom: borderBottom ? borderStyle : undefined
};
}
}
row++
}
return row
},
getTextColByLeft(leftVal, firstTableLeft, firstTableCols) {
const relativeLeft = leftVal - firstTableLeft;
if (relativeLeft < 0) return 1;
if (firstTableCols.length === 0) return Math.max(1, Math.round(relativeLeft / 50));
let accumulated = 0;
for (let i = 0; i < firstTableCols.length; i++) {
accumulated += firstTableCols[i];
// console.log('accumulated:' + accumulated)
// console.log('relativeLeft:' + relativeLeft)
if (relativeLeft < accumulated) {
return i+1;
}
}
//左边超过表格 按照单元格宽度50换算
return firstTableCols.length + Math.round((relativeLeft - accumulated) / 50)
},
// 根据 template 配置导出表格到 Excel
async exportToExcel1() {
const template = this.template;
console.log('=== exportToExcel1 ===:' + JSON.stringify(template));
if (!template || !template.panels || template.panels.length === 0) {
this.$message.warning('没有可导出的模板配置');
return;
}
const workbook = new ExcelJS.Workbook();
const sheetName = String(template.panels[0].name || 'Sheet1');
const worksheet = workbook.addWorksheet(sheetName);
const COL_WIDTH_RATIO = 5; // 列宽比例:col.width / COL_WIDTH_RATIO
// 收集所有表格元素(保留原始索引用于排序)printElementType.type=='table'
//导出有问题解决方案:表格用detailTable这个组件,禁用detailTable2,mainTable,剩下所有字段排版到表格前面或者后面
const tableFields = ['detailTable', 'detailTable2', 'mainTable'];
const tableElements = [];
for (const panel of template.panels) {
const elements = panel.printElements || [];
for (const pe of elements) {
const opts = pe.options || {};
if (tableFields.includes(opts.field)) {
tableElements.push(pe);
}
}
}
if (tableElements.length === 0) {
this.$message.warning('未找到表格配置');
return;
}
// 收集文本、二维码、条形码元素(type === 'text'/'qrcode'/'barcode'),根据 top 分为表格前和表格后
const firstTableTop = tableElements[0].options ? (tableElements[0].options.top || 0) : 0;
const textBeforeTable = [];
const textAfterTable = [];
for (const panel of template.panels) {
const elements = panel.printElements || [];
for (const pe of elements) {
const peOpts = pe.options || {};
const peType = (pe.printElementType && pe.printElementType.type) || '';
if (peType === 'text' || peType === 'qrcode' || peType === 'barcode' || peType === 'image') {
const textTop = peOpts.top || pe.top || 0;
if (textTop < firstTableTop) {
textBeforeTable.push(pe);
} else {
textAfterTable.push(pe);
}
}
}
}
// 数据源(用于文本字段值替换)
let item = this.printData && this.printData.mainTable && this.printData.mainTable[0] ? this.printData
.mainTable[0] : {}
const textDataSource = {
...item,
...this.printData,
}
let row = 1;
row = await this.initTextToExcel(textBeforeTable, worksheet, row, textDataSource, tableElements, workbook);
// 处理表格元素
for (const tableEl of tableElements) {
const opts = tableEl.options || {};
const tableField = opts.field;
let tableData = (this.printData && this.printData[tableField]) || [];
const columns = (opts.columns && opts.columns[0]) || [];
const footerFormatter = opts.footerFormatter || '';
// 收集可见列
const visibleCols = [];
for (const col of columns) {
if (col.checked === false) continue;
visibleCols.push(col);
}
console.log('visibleCols:' + visibleCols.length)
if (visibleCols.length === 0) continue;
if (!Array.isArray(tableData) || tableData.length === 0) continue;
// 判断是否显示表头(detailTable 显示表头,mainTable 不显示)
const showHeader = (tableField === 'detailTable' || tableField === 'detailTable2') ? true : false;
if (showHeader) {
row++;
const headerRow = worksheet.getRow(row);
headerRow.height = 20;
let colIdx = 1;
for (const col of visibleCols) {
const cell = headerRow.getCell(colIdx);
cell.value = col.title || '';
cell.font = {
name: '宋体',
size: 10,
bold: true
};
cell.alignment = {
horizontal: 'center',
vertical: 'middle',
wrapText: true
};
cell.border = this.getThinBorder();
worksheet.getColumn(colIdx).width = col.width / COL_WIDTH_RATIO;
colIdx++;
}
row++;
}
// 数据行
tableData = tableData.filter(item => Object.keys(item).length > 0);
for (let ri = 0; ri < tableData.length; ri++) {
const rowData = tableData[ri];
const dataRow = worksheet.getRow(row);
dataRow.height = 18;
let dc = 1;
for (const col of visibleCols) {
const cell = dataRow.getCell(dc);
if (col.tableTextType === 'sequence') {
cell.value = ri + 1;
} else if (col.formatter2) {
cell.value = this.formatCellValue2(col.formatter2, col.field, rowData);
} else if (col.formatter) {
cell.value = this.formatCellValue(col.formatter, col.field, rowData);
} else {
cell.value = rowData[col.field];
}
if (cell.value == null || cell.value === 'null') {
cell.value = '';
} else if (typeof cell.value === 'string') {
cell.value = cell.value.replace(/null|undefined/g, '').trim();
}
cell.font = {
name: '宋体',
size: 9
};
if (tableField === 'mainTable') {
cell.alignment = {
horizontal: col.halign || col.align || 'left',
vertical: 'middle',
wrapText: true
};
} else {
cell.alignment = {
horizontal: col.halign || col.align || 'center',
vertical: 'middle',
wrapText: true
};
}
cell.border = this.getThinBorder();
dc++;
}
row++;
}
// 处理 rowsColumnsMerge
const rowsColumnsMergeFn = opts.rowsColumnsMerge;
if (rowsColumnsMergeFn && typeof rowsColumnsMergeFn === 'string') {
try {
const fnBody = rowsColumnsMergeFn.replace(/^function\s*\([^)]*\)\s*\{|\}$/g, '').trim();
const mergeFn = new Function('data', 'col', 'colIndex', 'rowIndex', 'tableData', 'printData',
'"use strict"; ' + fnBody);
const dataStartRow = row - tableData.length;
for (let ri = 0; ri < tableData.length; ri++) {
const excelRow = dataStartRow + ri;
let ci = 0;
while (ci < visibleCols.length) {
const col = visibleCols[ci];
const result = mergeFn(tableData[ri], col, ci, ri, tableData, this.printData);
const rowspan = result ? (result[0] || 0) : 0;
const colspan = result ? (result[1] || 0) : 0;
if (rowspan > 1 || colspan > 1) {
try {
worksheet.mergeCells(excelRow, ci + 1, excelRow + rowspan - 1, ci + colspan);
} catch (e) {
/* 跳过 */
}
}
ci += Math.max(colspan, 1);
}
}
} catch (e) {
console.error('rowsColumnsMerge 处理失败:', e);
}
}
// 合计行
if (footerFormatter && typeof footerFormatter === 'string') {
const footerHtml = this.extractFooterHtml(footerFormatter, tableData, visibleCols);
if (footerHtml) {
const footerRow = worksheet.getRow(row);
footerRow.height = 20;
const tdRegex = /<td[^>]*>([\s\S]*?)<\/td>/gi;
let match;
let fc = 1;
const totalCols = visibleCols.length;
while ((match = tdRegex.exec(footerHtml)) !== null && fc <= totalCols) {
let tdContent = match[1].replace(/<[^>]+>/g, '').trim();
const colspanMatch = match[0].match(/colspan\s*=\s*["']?(\d+)/i);
const colspan = colspanMatch ? parseInt(colspanMatch[1]) : 1;
if (colspan > 1 && fc + colspan - 1 <= totalCols) {
try {
worksheet.mergeCells(row, fc, row, fc + colspan - 1);
} catch (e) {
/* 跳过 */
}
}
const cell = footerRow.getCell(fc);
cell.value = tdContent;
cell.font = {
name: '宋体',
size: 10,
bold: true
};
cell.alignment = {
horizontal: 'center',
vertical: 'middle',
wrapText: true
};
cell.border = this.getThinBorder();
fc += colspan;
}
while (fc <= totalCols) {
footerRow.getCell(fc).border = this.getThinBorder();
fc++;
}
row++;
}
}
}
row = await this.initTextToExcel(textAfterTable, worksheet, row, textDataSource, tableElements, workbook)
// 下载
try {
const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = (template.modelName || document.title || '导出报表') + '.xlsx';
link.click();
URL.revokeObjectURL(link.href);
this.$message.success('导出成功');
} catch (error) {
console.error('导出失败:', error);
this.$message.error('导出失败: ' + error.message);
}
},
// 从 footerFormatter 函数字符串中提取 HTML 内容
extractFooterHtml(footerFormatter, tableData, columns) {
try {
// 构建执行上下文
const rows = tableData || [];
const getPrintTotal = (_options, _rows, title) => {
const col = columns.find(c => c.title === title);
if (!col) return 0;
return rows.reduce((sum, r) => sum + (Number(r[col.field]) || 0), 0);
};
const getPrintTotalNotNull = (_options, _rows, title) => {
const col = columns.find(c => c.title === title);
if (!col) return '';
const total = rows.reduce((sum, r) => sum + (Number(r[col.field]) || 0), 0);
return total === 0 ? '' : String(total);
};
const getCnMoney = (num) => this.numberToChinese(num);
// 执行函数体,提取 return 语句中的 HTML
const trimmed = footerFormatter.trim();
// 跳过前面可能存在的注释,找到 function 关键字的位置
const fnStart = trimmed.search(/function\s*(?:\w+\s*)?\(/);
if (fnStart === -1) return null;
const fnBody = trimmed.slice(fnStart)
.replace(/^function\s*(?:\w+\s*)?\([^)]*\)\s*\{/, '')
.replace(/\}\s*$/, '')
.trim();
const fn = new Function('options', 'rows', 'data', 'pageData',
'getPrintTotal', 'getPrintTotalNotNull', 'getCnMoney',
'"use strict";' + fnBody);
// 调用获取 HTML:pageData 默认用 rows(未分页时保持一致)
const options = columns;
const html = fn(options, rows, {}, rows, getPrintTotal, getPrintTotalNotNull, getCnMoney);
// 提取 return 后面的内容
if (typeof html === 'string') return html;
// 如果函数最后是 return 语句,重新提取
const returnMatch = fnBody.match(/return\s+(`[^`]*`|'[^']*'|"[^"]*")/s);
if (returnMatch) {
let ret = returnMatch[1];
if (ret.startsWith('`')) ret = ret.slice(1, -1);
else ret = ret.slice(1, -1);
return ret;
}
} catch (e) {
console.error('解析 footerFormatter 失败:', e);
}
return null;
},
// 数字转中文大写
numberToChinese(num) {
if (isNaN(num) || num === 0) return '零元整';
const units = ['', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿'];
const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
const radices = ['', '角', '分'];
let numStr = Math.abs(num).toFixed(2);
let integerPart = parseInt(numStr.split('.')[0]);
let decimalPart = numStr.split('.')[1];
let result = '';
let unitIndex = 0;
let zeroFlag = false;
while (integerPart > 0) {
const digit = integerPart % 10;
if (digit === 0) {
if (!zeroFlag && result !== '') {
result = '零' + result;
zeroFlag = true;
}
} else {
result = digits[digit] + units[unitIndex] + result;
zeroFlag = false;
}
integerPart = Math.floor(integerPart / 10);
unitIndex++;
}
result += '元';
if (decimalPart === '00') {
result += '整';
} else {
for (let i = 0; i < decimalPart.length; i++) {
const d = parseInt(decimalPart[i]);
if (d > 0) result += digits[d] + radices[i + 1];
}
}
return result;
},
// 处理 formatter2(函数字符串格式)
formatCellValue2(formatter2, field, rowData, index = 0, options = {}) {
console.log('field', field)
console.log('rowData', rowData)
if (!formatter2) return rowData[field] != null ? rowData[field] : '';
try {
const value = rowData[field] != null ? rowData[field] : '';
const row = rowData;
const parseFloat = (v) => {
const n = Number(v);
return isNaN(n) ? 0 : n;
};
// 使用 Function 构造器创建函数并执行
const fn = new Function('value', 'row', 'index', 'options', 'parseFloat',
'"use strict"; return (' + formatter2 + ')');
const func = fn(value, row, index, options, parseFloat);
if (typeof func === 'function') {
return func(value, row, index, options);
}
return func;
} catch (e) {
return rowData[field] != null ? String(rowData[field]) : '';
}
},
// 格式化单元格值(处理 formatter)
formatCellValue(formatter, field, rowData) {
if (!formatter) return rowData[field] != null ? rowData[field] : '';
try {
// 构建安全的求值上下文
const value = rowData[field] != null ? rowData[field] : '';
const row = rowData;
const parseFloat = (v) => {
const n = Number(v);
return isNaN(n) ? 0 : n;
};
// 使用 Function 构造器执行 formatter
const fn = new Function('value', 'row', 'parseFloat', '"use strict"; return (' + formatter + ')');
return fn(value, row, parseFloat);
} catch (e) {
// formatter 执行失败,回退到原始值
return rowData[field] != null ? String(rowData[field]) : '';
}
},
formatTextValue(formatter) {
if (!formatter) return '';
try {
const fn = new Function('"use strict"; return (' + formatter + ')');
const func = fn();
if (typeof func === 'function') {
return func();
}
return func;
} catch (e) {
return '';
}
},
// 获取细线边框
getThinBorder() {
const borderStyle = {
style: 'thin',
color: {
argb: 'FF000000'
}
};
return {
top: borderStyle,
right: borderStyle,
bottom: borderStyle,
left: borderStyle
};
},
show(hiprintTemplate, printData, width = this.width, template) {
this.previewDialogStatus = true;
this.width = width
this.hiprintTemplate = hiprintTemplate
this.printData = printData
this.template = template
},
async preview(panelId, printData, width = this.width) {
if (printData.billNo === '' && printData.mainTable && Array.isArray(printData.mainTable) && printData
.mainTable.length > 0) {
if (printData.mainTable[0].billNo) {
printData.billNo = printData.mainTable[0].billNo;
}
}
if (!panelId) {
this.$message.warning("请先选择预览的模板")
return
}
const option = {}
let res = await getPrintModel(panelId);
if (res.data.success && res.data.data) {
option.template = JSON.parse(res.data.data.modelJson)
console.log('template:' + JSON.stringify(option.template))
this.template = option.template;
}
this.hiprintTemplate = buildHiprintTemplate(option);
this.hiprintTemplate.design("#preview_content", false);
this.show(this.hiprintTemplate, printData, width, this.template)
//console.log('template:', this.hiprintTemplate)
},
open() {
$("#preview_content").html(
this.hiprintTemplate.getHtml(this.printData)
);
},
printTemplate() {
this.hiprintTemplate.print(this.printData);
this.previewDialogStatus = false;
//记录打印次数
if (this.printData.id) {
addPrintCount({
sourceBillId: this.printData.id,
tranType: this.printData.tranType,
}).then(res => {
if (res.data.success) {
}
})
}
},
async print(panelId, printData) {
if (!panelId) {
this.$message.warning("请先选择预览的模板")
return
}
const option = {}
await getPrintModel(panelId)
.then(res => {
if (res.data.success && res.data.data) {
option.template = JSON.parse(res.data.data.modelJson)
}
})
this.hiprintTemplate = buildHiprintTemplate(option);
this.hiprintTemplate.print2(printData)
}
}
}
</script>
<style scoped>
</style>