最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

前端docx庫(kù)實(shí)現(xiàn)將html頁(yè)面導(dǎo)出word的詳細(xì)過(guò)程

 更新時(shí)間:2025年09月14日 11:44:10   作者:進(jìn)階的小木樁  
將HTML轉(zhuǎn)為Word可以通過(guò)多種方法實(shí)現(xiàn),包括使用在線(xiàn)工具、編程庫(kù)以及Office軟件等,下面這篇文章主要介紹了前端docx庫(kù)實(shí)現(xiàn)將html頁(yè)面導(dǎo)出word的詳細(xì)過(guò)程,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言:

最近遇到一個(gè)需求,需要將頁(yè)面的html導(dǎo)出為word文檔,并且包含橫向和豎向頁(yè)面,并且可以進(jìn)行混合方向?qū)С觥=?jīng)過(guò)一段時(shí)間的實(shí)驗(yàn),發(fā)現(xiàn)只有docx這個(gè)庫(kù)滿(mǎn)足這個(gè)要求。在這里記錄一下實(shí)現(xiàn)思路以及代碼。

docx官網(wǎng)

一、效果展示

頁(yè)面內(nèi)容:

導(dǎo)出樣式:

二、解決思路

1、首先是需要在頁(yè)面上設(shè)置哪些部分是需要橫向?qū)С觯男┎糠质切枰Q向?qū)С龅?。以方便后面進(jìn)行解析。

2、根據(jù)頁(yè)面樣式以及各類(lèi)html標(biāo)簽進(jìn)行解析。然后以docx的形式生成,最后導(dǎo)出來(lái)。

三、實(shí)現(xiàn)代碼

1、index.vue

這里 class 中的 section 代表了docx中的一節(jié),也就是一個(gè)頁(yè)面。同時(shí)newpage屬性控制了是不是要換一個(gè)新頁(yè),orient屬性是頁(yè)面橫向縱向的標(biāo)識(shí)(Z縱向H橫向)。也可以根據(jù)自己的需求自行添加屬性,在后面自己進(jìn)行對(duì)應(yīng)的解析。

<template>
	<div>
		<el-row>
			<el-col :span="24">
				<div>
					<el-button type="primary" @click="exportToWord" style="float: right">導(dǎo)出</el-button>
				</div>
				<div style="overflow-y: auto; height: calc(85vh)" id="export">
					<div class="section" orient="Z">
						<h1 style="text-align: center">這里是標(biāo)題1</h1>
					</div>
          <div class="section" orient="Z" newpage="true">
						<h2 style="text-align: center">這里是標(biāo)題2</h2>
						<h3 style="text-align: center">這里是標(biāo)題3</h3>
					</div>
          <div class="section" orient="Z">
						<p>這里是一段文字內(nèi)容</p>
					</div>
					<div class="section" orient="Z">
						<el-table :data="tableData" :span-method="arraySpanMethod" border style="width: 100%">
							<el-table-column prop="id" label="ID" width="180" header-align="center" align="left"/>
							<el-table-column prop="name" label="姓名" width="" header-align="center" align="left"/>
							<el-table-column prop="amount1"  label="列 1" width="" header-align="center" align="center"/>
							<el-table-column prop="amount2"  label="列 2" width="" header-align="center" align="right"/>
							<el-table-column prop="amount3"  label="列 3" width="" header-align="center" align="left"/>
						</el-table>
					</div>
					<div class="section" orient="H">
						<p>這里是橫向頁(yè)面內(nèi)容</p>
					</div>
          	<div class="section" orient="Z">
						<p>這里是縱向頁(yè)面內(nèi)容</p>
					</div>
				</div>
			</el-col>
		</el-row>
	</div>
</template>
<script lang="ts" setup="" name="">
//導(dǎo)出用
import * as htmlDocx from 'html-docx-js-typescript';
import { saveAs } from 'file-saver';
import { exportDocxFromHTML } from '@/utils/exportWord';
//導(dǎo)出word
const exportToWord = async () => {
	let contentElement = document.getElementById('export') as HTMLElement;
	// 克隆元素 操作新元素
	let newDiv = contentElement.cloneNode(true) as HTMLElement;
	// 這里可以對(duì)newDiv進(jìn)行一些操作...
	exportDocxFromHTML(newDiv, `test.docx`);
};
import type { TableColumnCtx } from 'element-plus'

interface User {
  id: string
  name: string
  amount1: string
  amount2: string
  amount3: number
}

interface SpanMethodProps {
  row: User
  column: TableColumnCtx<User>
  rowIndex: number
  columnIndex: number
}

const tableData:User[] = [
  {
    id: '12987122',
    name: 'Tom',
    amount1: '234',
    amount2: '3.2',
    amount3: 10,
  },
  {
    id: '12987123',
    name: 'Tom',
    amount1: '165',
    amount2: '4.43',
    amount3: 12,
  },
  {
    id: '12987124',
    name: 'Tom',
    amount1: '324',
    amount2: '1.9',
    amount3: 9,
  },
  {
    id: '12987125',
    name: 'Tom',
    amount1: '621',
    amount2: '2.2',
    amount3: 17,
  },
  {
    id: '12987126',
    name: 'Tom',
    amount1: '539',
    amount2: '4.1',
    amount3: 15,
  },
];
const arraySpanMethod = ({
  row,
  column,
  rowIndex,
  columnIndex,
}: SpanMethodProps) => {
  if (rowIndex % 2 === 0) {
    if (columnIndex === 0) {
      return [1, 2]
    } else if (columnIndex === 1) {
      return [0, 0]
    }
  }
}

onMounted(async () => {});
</script>

<style lang="scss" scoped></style>

2、exportWord.ts

這個(gè)部分是進(jìn)行了html轉(zhuǎn)換成docx形式的拼接組合。可以根據(jù)理解自行調(diào)整樣式以及解析過(guò)程。

import {
	Document,
	Packer,
	Paragraph,
	TextRun,
	ImageRun,
	ExternalHyperlink,
	WidthType,
	VerticalAlign,
	AlignmentType,
	PageOrientation,
	HeadingLevel,
	Table,
	TableRow,
	TableCell,
	BorderStyle,
} from 'docx';
import { saveAs } from 'file-saver';
/**
 * 字符串是否為空
 * @param {*} obj
 * @returns
 */
export function isEmpty(obj:any) {
  if (typeof obj == 'undefined' || obj == null || obj === '') {
    return true
  } else {
    return false
  }
};
import { ElMessageBox, ElMessage } from 'element-plus';
// 定義類(lèi)型
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);
//保存樣式對(duì)象
type StyleOptions = {
	bold: boolean; //是否加粗
	font: Object; //字體樣式
	size: number; //字體大小
	id: String | null; //樣式id
};
//橫向A4
export const H_properties_A4 = {
	page: {
		size: {
			width: 15840, // A4 橫向?qū)挾?(11英寸)
			height: 12240, // A4 橫向高度 (8.5英寸)
		},
	},
};
//縱向A4
export const Z_properties_A4 = {
	page: {
		size: {
			width: 12240, // A4 縱向?qū)挾?(8.5英寸 * 1440 twip/inch)
			height: 15840, // A4 縱向高度 (11英寸 * 1440)
		},
		orientation: PageOrientation.LANDSCAPE,
	},
};
//根據(jù)html生成word文檔
export const exportDocxFromHTML = async (htmlDom: any, filename: any) => {
	let sections = [] as any; //頁(yè)面數(shù)據(jù)
	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) {
				//方向一致且不分頁(yè),繼續(xù)從上一個(gè)section節(jié)添加
				// 獲取子節(jié)點(diǎn)
				let childNodes = dom.childNodes;
				// 遞歸處理所有節(jié)點(diǎn)
				let children = [];
				for (let i = 0; i < childNodes.length; i++) {
					const node = childNodes[i];
					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 {
				//否則則新開(kāi)一個(gè)section節(jié)
				// 獲取子節(jié)點(diǎn)
				let childNodes = dom.childNodes;
				// 遞歸處理所有節(jié)點(diǎn)
				let children = [];
				for (let i = 0; i < childNodes.length; i++) {
					const node = childNodes[i];
					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) {
			// 創(chuàng)建Word文檔
			const doc = new Document({
				styles: {
					default: {
						heading1: {
							//宋體 二號(hào)
							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: {
							//宋體 四號(hào)
							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', // 樣式ID
							name: '宋體小四號(hào)樣式', // 可讀名稱(chēng)
							run: {
								font: '宋體', // 字體
								size: 24, // 字號(hào)
							},
							paragraph: {
								spacing: { line: 360 }, // 1.5倍行距(240*1.5=360)
								indent: { firstLine: 400 }, // 首行縮進(jìn)400twips(約2字符)
							},
						},
						{
							id: 'THStyle', // 樣式ID
							name: '表頭樣式', // 可讀名稱(chēng)
							run: {
								font: '等線(xiàn)', // 字體
								size: 20.5, // 字號(hào)
							},
							paragraph: {
								spacing: {
									before: 240,
									after: 120,
								},
							},
						},
						{
							id: 'TDStyle', // 樣式ID
							name: '單元格樣式', // 可讀名稱(chēng)
							run: {
								font: '等線(xiàn)', // 字體
								size: 20.5, // 字號(hào)
							},
							// paragraph: {
							// 	spacing: {
							// 		before: 240,
							// 		after: 120,
							// 	},
							// },
						},
					],
				},
				sections: sections, //.filter(Boolean) as (Paragraph | Table)[],
			});
			// 生成并下載文檔
			await Packer.toBlob(doc).then((blob) => {
				saveAs(blob, filename);
			});
		} else {
			ElMessage.error('導(dǎo)出失敗,該頁(yè)面沒(méi)有要導(dǎo)出的信息!');
		}
	} catch (error) {
		console.error('導(dǎo)出失敗:', error);
		ElMessage.error('導(dǎo)出失敗,請(qǐng)聯(lián)系管理人員!'); //查看控制臺(tái)獲取詳細(xì)信息!');
	} finally {
	}
};
// 遞歸轉(zhuǎn)換 DOM 節(jié)點(diǎn)為 docx 元素
export const parseNode = async (node: Node, options: ExportOptions, style: any): Promise<DocxElement[]> => {
	const elements: DocxElement[] = [];
	// 1、處理文本節(jié)點(diǎn)
	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 isItalic = parent?.tagName === 'EM' || parent?.tagName === 'I';
				// const isUnderline = parent?.tagName === 'U';
				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;
	}
	// 2、處理元素節(jié)點(diǎn)
	if (node.nodeType === Node.ELEMENT_NODE) {
		const element = node as HTMLElement;
		const tagName = element.tagName.toUpperCase();
		const childNodes = element.childNodes;
		
		// 遞歸處理子節(jié)點(diǎn)
		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);
		}
		// 根據(jù)標(biāo)簽類(lèi)型創(chuàng)建不同的docx元素
		switch (tagName) {
			case 'H1':
				return [
					new Paragraph({
						heading: HeadingLevel.HEADING_1,
						alignment: AlignmentType.CENTER,
						children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
					}),
				];

			case 'H2':
				return [
					new Paragraph({
						heading: HeadingLevel.HEADING_2,
						alignment: AlignmentType.CENTER,
						children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
					}),
				];

			case 'H3':
				return [
					new Paragraph({
						heading: HeadingLevel.HEADING_3,
						alignment: AlignmentType.LEFT,
						children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
					}),
				];

			case 'H4':
				return [
					new Paragraph({
						heading: HeadingLevel.HEADING_4,
						alignment: AlignmentType.LEFT,
						children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
					}),
				];

			case 'H5':
				return [
					new Paragraph({
						heading: HeadingLevel.HEADING_5,
						alignment: AlignmentType.LEFT,
						children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
					}),
				];

			case 'P':
				return [
					new Paragraph({
						children: childElements.filter((e) => e instanceof TextRun) as TextRun[],
						style: 'STx4Style', // 應(yīng)用樣式ID
					}),
				];

			case 'BR':
				return [new TextRun({ text: '', break: 1 })];

			case 'A':
				const href = element.getAttribute('href');
				if (href) {
					return [
						new Paragraph({
							children: [
								new ExternalHyperlink({
									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);

			// case 'IMG':
			// 	if (!options.includeImages) {
			// 		return [];
			// 	} else {
			// 		const src = element.getAttribute('src');
			// 		if (src) {
			// 			try {
			// 				const response = await fetch(src);
			// 				const arrayBuffer = await response.arrayBuffer();
			// 				// return [
			// 				// 	new ImageRun({
			// 				// 		data: arrayBuffer,
			// 				// 		transformation: {
			// 				// 			width: 400,
			// 				// 			height: 300,
			// 				// 		},
			// 				// 	}),
			// 				// ];
			// 				return [];
			// 			} catch (e) {
			// 				console.error('圖片加載失敗:', e);
			// 				return [
			// 					new TextRun({
			// 						text: '[圖片加載失敗]',
			// 						color: 'FF0000',
			// 					}),
			// 				];
			// 			}
			// 		} else {
			// 			return [];
			// 		}
			// 	}

			// case 'I':
			// 	return childElements.map((e) => {
			// 		if (e instanceof TextRun) {
			// 			return new TextRun({
			// 				...e.options,
			// 				italics: true,
			// 			});
			// 		}
			// 		return e;
			// 	});

			// case 'U':
			// 	return childElements.map((e) => {
			// 		if (e instanceof TextRun) {
			// 			return new TextRun({
			// 				...e.options,
			// 				underline: {},
			// 			});
			// 		}
			// 		return e;
			// 	});

			default:
				return childElements;
		}
	}

	return elements;
};
//獲取一個(gè)表格
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: '等線(xiàn)',
							size: 21,
							id: null,
						};
						const result = await parseNode(childNode, options, styleoption);
						cellChildren.push(
							new Paragraph({
								alignment: textAlign == 'center' ? AlignmentType.CENTER : textAlign == 'right' ? AlignmentType.RIGHT : AlignmentType.LEFT, // 水平居中/居右/居左
								children: result,
								style: 'THStyle',
							})
						);
					} else {
						const styleoption: StyleOptions = {
							bold: false,
							font: '等線(xiàn)',
							size: 21,
							id: null,
						};
						const result = await parseNode(childNode, options, styleoption);
						cellChildren.push(
							new Paragraph({
								alignment: textAlign == 'center' ? AlignmentType.CENTER : textAlign == 'right' ? AlignmentType.RIGHT : AlignmentType.LEFT, // 水平居中/居右/居左
								children: result,
								style: 'TDStyle',
							})
						);
					}
				}
				// 動(dòng)態(tài)判斷是否合并
				//const isMergedStart = cell.rowSpan > 1 || cell.colSpan > 1;
				return new TableCell({
					rowSpan: cell.rowSpan,
					columnSpan: cell.colSpan,
					verticalAlign: VerticalAlign.CENTER,
					verticalMerge: cell.rowSpan > 1 ? 'restart' : undefined,
					width: {
						size: parseFloat(width), // 設(shè)置第一列寬度為250
						type: WidthType.PERCENTAGE, //WidthType.DXA, // 單位為twip (1/20 of a point)
					},
					children: cellChildren.filter((e) => e instanceof Paragraph) as Paragraph[],
				});
				// return new TableCell({
				// 	children: cellChildren.filter((e) => e instanceof Paragraph) as Paragraph[],
				// });
			});

			return Promise.all(tableCells).then((cells) => {
				return new TableRow({
					children: cells,
				});
			});
		});
		return Promise.all(tableRows).then((rows) => {
			return [
				new Table({
					rows: rows,
					width: { size: 100, type: WidthType.PERCENTAGE },
				}),
			];
		});
	}
};


總結(jié) 

到此這篇關(guān)于前端docx庫(kù)實(shí)現(xiàn)將html頁(yè)面導(dǎo)出word的文章就介紹到這了,更多相關(guān)docx庫(kù)將html頁(yè)面導(dǎo)出word內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

天长市| 濉溪县| 嘉善县| 奎屯市| 宜章县| 博罗县| 宁陵县| 沂南县| 雷山县| 万宁市| 静宁县| 景宁| 区。| 顺义区| 靖远县| 永清县| 错那县| 文昌市| 伊春市| 广西| 崇州市| 彭泽县| 班戈县| 许昌市| 温州市| 曲松县| 上杭县| 台州市| 吴旗县| 沂南县| 宜川县| 南通市| 年辖:市辖区| 利辛县| 阿勒泰市| 陆川县| 偏关县| 商城县| 泸西县| 石楼县| 山东省|