import {
Document,
Packer,
Paragraph,
TextRun,
ImageRun,
ExternalHyperlink,
WidthType,
VerticalAlign,
AlignmentType,
PageOrientation,
HeadingLevel,
Table,
TableRow,
TableCell,
BorderStyle,
} from 'docx';
import { saveAs } from 'file-saver';
import { ElMessageBox, ElMessage } from 'element-plus';
export function isEmpty(obj: any) {
if (typeof obj === 'undefined' || obj == null || obj === '') {
return true;
} else {
return false;
}
}
type DocxElement = Paragraph | Table | TextRun | ImageRun | ExternalHyperlink;
type ExportOptions = {
includeImages: boolean;
includeTables: boolean;
includeLists: boolean;
};
const includeImages = ref(true);
const includeTables = ref(true);
const includeLists = ref(true);
type StyleOptions = {
bold: boolean;
font: Object;
size: number;
id: String | null;
};
export const H_properties_A4 = {
page: {
size: {
width: 15840,
height: 12240,
},
},
};
export const Z_properties_A4 = {
page: {
size: {
width: 12240,
height: 15840,
},
orientation: PageOrientation.LANDSCAPE,
},
};
export const exportDocxFromHTML = async (htmlDom: any, filename: any) => {
let sections = [] as any;
let doms = htmlDom.querySelectorAll('.section');
try {
const options: ExportOptions = {
includeImages: includeImages.value,
includeTables: includeTables.value,
includeLists: includeLists.value,
};
let preorient = 'Z';
for (let i = 0; i < doms.length; i++) {
let dom = doms[i];
let orient = dom.getAttribute('orient');
let newpage = dom.getAttribute('newpage');
if (orient == preorient && newpage != 'true' && sections.length > 0) {
let childNodes = dom.childNodes;
let children = [];
for (let j = 0; j < childNodes.length; j++) {
const node = childNodes[j];
const result = await parseNode(node, options, null);
children.push(...result);
}
if (sections[sections.length - 1].children && children.length > 0) {
for (let c = 0; c < children.length; c++) {
let one = children[c];
sections[sections.length - 1].children.push(one);
}
}
} else {
let childNodes = dom.childNodes;
let children = [];
for (let j = 0; j < childNodes.length; j++) {
const node = childNodes[j];
const result = await parseNode(node, options, null);
children.push(...result);
}
let section = {
properties: orient == 'H' ? H_properties_A4 : Z_properties_A4,
children: children,
};
sections.push(section);
preorient = orient;
}
}
if (sections.length > 0) {
const doc = new Document({
styles: {
default: {
heading1: {
run: {
size: 44,
bold: true,
italics: true,
color: '000000',
font: '宋体',
},
paragraph: {
spacing: {
after: 120,
},
},
},
heading2: {
run: {
size: 36,
bold: true,
color: '000000',
font: '宋体',
},
paragraph: {
spacing: {
before: 240,
after: 120,
},
},
},
heading3: {
run: {
size: 28,
bold: true,
color: '000000',
font: '宋体',
},
paragraph: {
spacing: {
before: 240,
after: 120,
},
},
},
heading4: {
run: {
size: 24,
bold: true,
color: '000000',
font: '宋体',
},
paragraph: {
spacing: {
before: 240,
after: 120,
},
},
},
heading5: {
run: {
size: 20,
bold: true,
color: '000000',
font: '宋体',
},
paragraph: {
spacing: {
before: 240,
after: 120,
},
},
},
},
paragraphStyles: [
{
id: 'STx4Style',
name: '宋体小四号样式',
run: {
font: '宋体',
size: 24,
},
paragraph: {
spacing: {
line: 360,
},
},
},
{
id: 'THStyle',
name: '表头样式',
run: {
font: '等线',
size: 20.5,
},
paragraph: {
spacing: {
before: 240,
after: 120,
},
},
},
{
id: 'TDStyle',
name: '单元格样式',
run: {
font: '等线',
size: 20.5,
},
},
],
},
sections: sections,
});
await Packer.toBlob(doc).then((blob) => {
saveAs(blob, filename);
});
} else {
ElMessage.error('导出失败,该页面没有要导出的信息!');
}
} catch (error) {
console.error('导出失败:', error);
ElMessage.error('导出失败,请联系管理人员!');
}
};
export const parseNode = async (
node: Node,
options: ExportOptions,
style: any
): Promise<DocxElement[]> => {
const elements: DocxElement[] = [];
if (node.nodeType === Node.TEXT_NODE) {
const text = node.textContent?.trim();
if (!isEmpty(text)) {
const parent = node.parentElement;
if (style == null) {
let child = new TextRun({ text: text });
elements.push(child);
} else {
const isBold = style.bold ? true : parent?.tagName === 'STRONG' || parent?.tagName === 'B';
const Font = style.font ? style.font : '宋体';
const Size = style.size ? style.size : 24;
if (!isEmpty(style.id)) {
let child = new TextRun({ text: text, style: style.id });
elements.push(child);
} else {
let child = new TextRun({ text: text, bold: isBold, font: Font, size: Size });
elements.push(child);
}
}
}
return elements;
}
if (node.nodeType === Node.ELEMENT_NODE) {
const element = node as HTMLElement;
const tagName = element.tagName.toUpperCase();
const childNodes = element.childNodes;
let childElements: DocxElement[] = [];
for (let i = 0; i < childNodes.length; i++) {
const child = childNodes[i];
if (tagName == 'A') {
if (style == null) {
style = { id: 'Hyperlink' };
} else {
style.id = 'Hyperlink';
}
}
const childResult = await parseNode(child, options, style);
childElements = childElements.concat(childResult);
}
switch (tagName) {
case 'H1':
return [
newParagraph({
heading: HeadingLevel.HEADING_1,
alignment: AlignmentType.CENTER,
children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
}),
];
case 'H2':
return [
newParagraph({
heading: HeadingLevel.HEADING_2,
alignment: AlignmentType.CENTER,
children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
}),
];
case 'H3':
return [
newParagraph({
heading: HeadingLevel.HEADING_3,
alignment: AlignmentType.LEFT,
children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
}),
];
case 'H4':
return [
newParagraph({
heading: HeadingLevel.HEADING_4,
alignment: AlignmentType.LEFT,
children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
}),
];
case 'H5':
return [
newParagraph({
heading: HeadingLevel.HEADING_5,
alignment: AlignmentType.LEFT,
children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
}),
];
case 'P':
return [
newParagraph({
children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
style: 'STx4Style',
}),
];
case 'BR':
return [newTextRun({ text: '', break: 1 })];
case 'A':
const href = element.getAttribute('href');
if (href) {
return [
newParagraph({
children: [
newExternalHyperlink({
children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
link: href,
}),
],
}),
];
} else {
return childElements.filter((e) => e instanceof TextRun) as TextRun[];
}
case 'TABLE':
return getTable(element, options);
default:
return childElements;
}
}
return elements;
};
export const getTable = async (element: any, options: ExportOptions) => {
if (!options.includeTables) {
return [];
} else {
const rows = Array.from(element.rows);
const tableRows = rows.map((row: any) => {
const cells = Array.from(row.cells);
const tableCells = cells.map(async (cell: any, index: any) => {
let textAlign = cell.style.textAlign;
let width = (cell.style.width + '').replace('%', '');
let classlist = Array.from(cell.classList);
if (classlist && classlist.length > 0) {
if (classlist.indexOf('is-left') > -1) {
textAlign = 'left';
} else if (classlist.indexOf('is-center') > -1) {
textAlign = 'center';
} else if (classlist.indexOf('is-right') > -1) {
textAlign = 'right';
}
}
const cellChildren = [];
for (let i = 0; i < cell.childNodes.length; i++) {
let childNode = cell.childNodes[i];
if (cell.tagName == 'TH') {
const styleoption: StyleOptions = {
bold: true,
font: '等线',
size: 21,
id: null,
};
const result = await parseNode(childNode, options, styleoption);
cellChildren.push(
newParagraph({
alignment:
textAlign == 'center'
? AlignmentType.CENTER
: textAlign == 'right'
? AlignmentType.RIGHT
: AlignmentType.LEFT,
children: result,
style: 'THStyle',
})
);
} else {
const styleoption: StyleOptions = {
bold: false,
font: '等线',
size: 21,
id: null,
};
const result = await parseNode(childNode, options, styleoption);
cellChildren.push(
newParagraph({
alignment:
textAlign == 'center'
? AlignmentType.CENTER
: textAlign == 'right'
? AlignmentType.RIGHT
: AlignmentType.LEFT,
children: result,
style: 'TDStyle',
})
);
}
}
return newTableCell({
rowSpan: cell.rowSpan,
columnSpan: cell.colSpan,
verticalAlign: VerticalAlign.CENTER,
verticalMerge: cell.rowSpan > 1 ? 'restart' : undefined,
width: {
size: parseFloat(width),
type: WidthType.PERCENTAGE,
},
children: cellChildren.filter((e) => e instanceof Paragraph) as Paragraph[],
});
});
return Promise.all(tableCells).then((cells) => {
return newTableRow({ children: cells });
});
});
return Promise.all(tableRows).then((rows) => {
return [
newTable({
rows: rows,
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
}),
];
});
}
};