前端vue项目public/static/template目录下有个test.xlsx文件,需要对导出的模板文件内容进行更换,同时要保证其它文本、样式不变,目前用下面工具库可以实现替换内容导出,但是导出的内容font字体乱码了
这是原文件的样式,需要将required替换成必填,同时保证样式还是红色字体也不变
import XLSX from 'xlsx';
import XLSXStyle from "xlsx-style";
import {saveAs} from 'file-saver';async downloadTemplate1() {
try {
this.loading = true;
// 1. 获取模板文件
const workbook = await fetchTemplate(this.templatePath);
// 2. 替换国际化键值
const processedWorkbook = replaceI18nKeys(workbook);
// 3. 生成并下载文件
createExcel(processedWorkbook, this.title + this.$i('inkey.xdl.page.template'));
this.$message.success('模板下载成功');
} catch (error) {
console.error('模板下载失败:', error);
this.$message.error(`模板下载失败: ${error.message}`);
} finally {
this.loading = false;
}
},/**
* 从服务器获取模板文件(可选保留,模板解析用)
*/
export async function fetchTemplate(templatePath) {
try {
if (!templatePath || templatePath.trim() === '') {
throw new Error('模板文件路径为空,请检查templatePath配置');
}
console.log('开始获取模板文件:', templatePath);
const response = await fetch(templatePath);
if (!response.ok) {
throw new Error(`模板文件加载失败: 状态码${response.status},路径${templatePath}`);
}
const contentType = response.headers.get('content-type');
const validXlsxTypes = [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/octet-stream'
];
if (!validXlsxTypes.some(type => contentType?.includes(type))) {
throw new Error(`非标准XLSX文件,MIME类型:${contentType}`);
}
const arrayBuffer = await response.arrayBuffer();
if (!arrayBuffer || arrayBuffer.byteLength === 0) {
throw new Error('模板文件内容为空,文件可能损坏');
}
const uint8Array = new Uint8Array(arrayBuffer);
const workbook = XLSXStyle.read(uint8Array, {
type: 'buffer',
cellDates: true,
cellFormula: true,
cellNF: true,
cellStyles: true, // 保留模板样式
sheetStubs: true,
raw: false
});
console.log(workbook)
const fixedWorkbook = fixChineseFontNameDeep(workbook);
console.log(fixedWorkbook)
return fixedWorkbook;
} catch (error) {
console.error('获取模板文件失败:', error);
throw new Error(`获取模板文件失败: ${error.message || '文件解析异常,请检查模板文件格式'}`);
}
}
export function createExcel(workbook, fileName, options = {}) {
try {
// 在写入之前再次确保字体名称正确
const finalWorkbook = fixChineseFontName(workbook);
if (!finalWorkbook || !finalWorkbook.SheetNames || finalWorkbook.SheetNames.length === 0) {
throw new Error('工作簿无效,无工作表信息');
}
const writeOptions = {
bookType: 'xlsx',
type: 'binary',
bookSST: false,
compression: false,
Props: finalWorkbook.Props || {
Title: fileName || 'Excel文件',
Author: '系统',
CreatedDate: new Date()
},
cellStyles: true,
STYLES: true,
// 添加编码相关选项
codepage: 65001, // UTF-8 代码页
...options
};
// 使用修复后的工作簿
const excelData = XLSXStyle.write(finalWorkbook, writeOptions);
// 验证输出
console.log('生成Excel数据大小:', excelData.length, '字符');
const blob = new Blob([s2ab(excelData)], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"
});
saveAs(blob, `${fileName || 'ExcelTemplate'}.xlsx`);
console.log('Excel模板导出成功,样式已保留');
return true;
} catch (error) {
console.error('下载Excel模板失败:', error);
throw error;
}
}
/**
* 核心:修复Excel字体名称乱码(深度修复,模板解析/新建Excel通用)
*/
function fixChineseFontNameDeep(workbook) {
// 修复字体名称,直接替换乱码为正确的字体名
if (workbook.Styles && workbook.Styles.Fonts) {
workbook.Styles.Fonts.forEach(font => {
if (font.name && typeof font.name === 'string') {
// 直接映射常见乱码到正确字体
if (font.name === '\ç线') font.name = '等线';
else if (font.name === '微软雅黑') font.name = '微软雅黑';
else if (font.name === '宋ä½"') font.name = '宋体';
else if (font.name === '黑ä½"') font.name = '黑体';
else {
// 尝试解码 latin1 转 UTF-8
try {
const latin1Bytes = [];
for (let i = 0; i < font.name.length; i++) {
latin1Bytes.push(font.name.charCodeAt(i));
}
const decoder = new TextDecoder('utf-8');
const utf8Str = decoder.decode(new Uint8Array(latin1Bytes));
// 解码出中文则替换
if (/[\u4e00-\u9fa5]/.test(utf8Str)) font.name = utf8Str;
} catch (e) {
console.warn('字体解码失败:', font.name, e);
}
}
}
});
}
return workbook;
}
/**
* 二次修复字体:包含主题字体+单元格样式(写入前最终校验)
*/
function fixChineseFontName(workbook) {
// 修复样式表字体
if (workbook.Styles && workbook.Styles.Fonts) {
workbook.Styles.Fonts.forEach(font => {
if (font.name && typeof font.name === 'string') {
const original = font.name;
const fixed = fixEncoding(original);
if (fixed !== original) {
console.log(`修复样式字体: ${original} -> ${fixed}`);
font.name = fixed;
}
}
});
}
// 修复主题字体(东亚/拉丁)
if (workbook.Theme && workbook.Theme.themeElements) {
const theme = workbook.Theme.themeElements;
// 拉丁字体
if (theme.fontScheme && theme.fontScheme.latin) {
const original = theme.fontScheme.latin.typeface;
if (original) {
const fixed = fixEncoding(original);
if (fixed !== original) theme.fontScheme.latin.typeface = fixed;
}
}
// 东亚字体(中文核心)
if (theme.fontScheme && theme.fontScheme.ea) {
const original = theme.fontScheme.ea.typeface;
if (original) {
const fixed = fixEncoding(original);
if (fixed !== original) theme.fontScheme.ea.typeface = fixed;
}
}
}
return workbook;
}
/**
* 编码修复工具:UTF-8/GBK/硬编码映射(兜底)
*/
function fixEncoding(text) {
if (!text || typeof text !== 'string') return text;
// 已是中文直接返回
if (/[\u4e00-\u9fa5]/.test(text)) return text;
// 硬编码映射常见字体乱码/英文
const fontMapping = {
'ç线': '等线',
'微软雅黑': '微软雅黑',
'宋ä½"': '宋体',
'黑ä½"': '黑体',
'kaiti': '楷体',
'fangsong': '仿宋',
'simsun': '宋体',
'microsoft yahei': '微软雅黑',
'dengxian': '等线',
};
const lowerText = text.toLowerCase();
for (const [garbled, chinese] of Object.entries(fontMapping)) {
if (lowerText === garbled.toLowerCase()) return chinese;
}
// 尝试UTF-8解码
const tryUTF8Decode = (str) => {
try {
const decoded = decodeURIComponent(escape(str));
if (/[\u4e00-\u9fa5]/.test(decoded)) return decoded;
} catch (e) {}
return str;
};
let result = tryUTF8Decode(text);
if (/[\u4e00-\u9fa5]/.test(result)) return result;
// 尝试GBK/GB2312解码
const tryGBKDecode = (str) => {
try {
const bytes = [];
for (let i = 0; i < str.length; i++) bytes.push(str.charCodeAt(i) & 0xFF);
const encodings = ['gbk', 'gb2312', 'gb18030'];
for (const encoding of encodings) {
try {
const decoder = new TextDecoder(encoding);
const res = decoder.decode(new Uint8Array(bytes));
if (/[\u4e00-\u9fa5]/.test(res)) return res;
} catch (e) { continue; }
}
} catch (e) { console.warn('GBK解码失败:', e); }
return str;
};
result = tryGBKDecode(text);
return /[\u4e00-\u9fa5]/.test(result) ? result : text;
}
/**
* ArrayBuffer转换工具(xlsx-style写入必备)
*/
function s2ab(s) {
const buf = new ArrayBuffer(s.length);
const view = new Uint8Array(buf);
for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
return buf;
}
刚开始AI给的是import XLSX from 'xlsx';import XLSXStyle from "xlsx-style";这套方案,这个库怎么调都有问题,后来换成"exceljs": "^4.4.0"这个库可以了,但是这个库AI给的代码也是各种问题,富文本那个判断一直有问题,但也基本上人工调试通了,后面有些判断暂时没验证,导出的内容全部都替换,样式全部保留,目前看没什么问题,主要代码贴下面