feat: 导出先更新一次

This commit is contained in:
2025-12-11 18:18:38 +08:00
parent 10747e9158
commit 1ac4807851
4 changed files with 338 additions and 13 deletions

105
src/utils/export.js Normal file
View File

@ -0,0 +1,105 @@
import { saveAs } from 'file-saver';
import html2pdf from 'html2pdf.js';
const isDOM = (obj) => obj instanceof HTMLElement;
/**
* 导出 PDF
* @param {HTMLElement} dom 目标 DOM 元素
* @param {string} name 文件名
*/
export const downloadPDF = async (dom, name = '导出文件') => {
if(!isDOM(dom)) return;
try {
// 等待一段时间确保所有图表完全渲染
await new Promise(resolve => setTimeout(resolve, 1500));
// 强制重新渲染所有图表
const resizeEvent = new Event('resize');
window.dispatchEvent(resizeEvent);
// 再次等待图表重绘完成
await new Promise(resolve => setTimeout(resolve, 1000));
// 获取内容的实际高度
const contentHeight = dom.scrollHeight;
// 计算需要的页面尺寸(单位:毫米)
// 将像素转换为毫米1px ≈ 0.264583mm
const a4HeightMm = 297; // A4纸高度
const contentHeightMm = contentHeight * 0.264583;
const opt = {
margin: [10, 10, 20, 10], // [top, right, bottom, left] 格式
filename: name + '.pdf',
image: {
type: 'png',
quality: 1
},
html2canvas: {
scale: 1.5,
useCoRs: true,
allowTaint: true,
foreignObjectRendering: false,
logging: false,
width: dom.scrollWidth,
height: dom.scrollHeight + 100, // 增加额外高度,包含底部内容
scrollX: 0,
scrollY: 0
},
jsPDF: {
unit: 'mm',
format: [210, contentHeightMm + 40], // 自定义页面高度,确保所有内容都在一页
orientation: "portrait"
},
pagebreak: {
mode: "avoid", // 尽量避免在元素中间分页
before: '.analysis-report-box h2', // 在h2标题前避免分页
after: '.analysis-report-box h2', // 在h2标题后避免分页
avoid: '.analysis-report-box p' // 避免在段落中间分页
}
};
await html2pdf().set(opt).from(dom).save();
return true;
} catch (error) {
console.error('PDF导出失败:', error);
return false;
}
}
// 带样式的下载方法
export function downloadDocWithStyle(textContent) {
if (typeof textContent !== 'string') return;
const wordDocument = `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>富文本导出</title>
<style>
/* 应用两端对齐样式 */
body {
text-align: justify;
text-justify: inter-character; /* 中文文本两端对齐 */
font-family: "Microsoft YaHei", Arial, sans-serif;
}
p {
text-align: justify;
text-justify: inter-character;
}
</style>
</head>
<body>
${textContent}
</body>
</html>
`;
const blob = new Blob([wordDocument], {
type: 'application/msword'
});
saveAs(blob, 'styled-document.doc');
};

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="analysis-report-box">
<!-- <div class="titleBox">
<PageTitle title="警情分析报告">
<el-button type="primary" @click="generatePDF()">
@ -23,7 +23,7 @@
<div style="background-color: #fff;color: black;padding: 15px;overflow: auto;" :style="{ height: tabHeight + 'px' }"
ref="tableBox">
<div style="border-bottom: 1px #ccc solid;padding-bottom: 30px;">
<h1 class="headline">{{ nd }}年度林芝市公安战术研判报告</h1>
<h1 style="text-align: center;color: red;">{{ nd }}年度林芝市公安战术研判报告</h1>
<div
style="display: flex;align-items: center;justify-content: space-between; color: red;margin-top: 30px;padding: 0 30px;font-size: 18px;font-weight: 700;">
<div>{{ deptId.name }}</div>
@ -113,7 +113,7 @@
<p style="min-height: 100px;" v-loading="loading">{{ textContent }} </p>
</div>
<el-button style="position: fixed;bottom: 0;left: 45%;" type="primary" size="default"
<el-button :loading="downLoading" style="position: absolute;bottom: 0;left: 45%;" type="primary" size="default"
@click="downloadWithStyles">下载</el-button>
</div>
@ -128,7 +128,9 @@ import { getItem, setItem } from '@/utils/storage'
import { fxbgDywdtj, getDictItem, fxbgJqlxtj, fxbgJqlytj, fxbgYdfx, fxbgXsfx, fxgbCljgf, fxgbCzlfx, fxbgTj } from '@/api/semanticAnalysis'
import { completions } from '@/api/semanticAnalysis'
import { reactive, ref, onMounted, getCurrentInstance, nextTick, computed, watch } from "vue";
import { downloadDocWithStyle } from '@/utils/export.js';
// import { downloadDocWithStyle } from '@/utils/export.js';
import { downloadDocWithStyle, downloadPDF } from "@/utils/export.js"
import { ElMessage } from "element-plus";
const props = defineProps({
// 数据
@ -138,6 +140,7 @@ const props = defineProps({
}
})
const loading = ref(false);
const downLoading = ref(false)
const { proxy } = getCurrentInstance();
const { D_GS_XS_LX } = proxy.$dict("D_GS_XS_LX"); //获取字典数据
const dictItemList = ref([])
@ -167,9 +170,74 @@ const tabHeightFn = () => {
tabHeightFn();
};
};
const downloadDocWithStyle = () => {
/** 以pdf形式下载 */
const downLoadPdf = async () => {
downLoading.value = true;
ElMessage.info('下载,会展开所有内容,是正常的。');
try {
// 强制刷新所有图表
const resizeEvent = new Event('resize');
window.dispatchEvent(resizeEvent);
// 等待图表重绘
await new Promise(resolve => setTimeout(resolve, 1000));
// 保存原始样式
const originalOverflow = tableBox.value.style.overflow;
const originalHeight = tableBox.value.style.height;
const originalPaddingBottom = tableBox.value.style.paddingBottom;
const originalMarginBottom = tableBox.value.style.marginBottom;
// 确保所有内容都可见,并增加底部间距
tableBox.value.style.overflow = 'visible';
tableBox.value.style.height = 'auto';
tableBox.value.style.paddingBottom = '50px'; // 增加底部内边距
tableBox.value.style.marginBottom = '30px'; // 增加底部外边距
// 为最后的总结分析部分添加特殊样式,避免分页截断
const summaryElements = tableBox.value.querySelectorAll('h2');
const lastH2 = summaryElements[summaryElements.length - 1];
if (lastH2 && lastH2.textContent.includes('总结分析')) {
lastH2.style.pageBreakBefore = 'always';
lastH2.style.pageBreakInside = 'avoid';
// 确保总结分析的段落不被分页截断
const nextP = lastH2.nextElementSibling;
if (nextP && nextP.tagName === 'P') {
nextP.style.pageBreakInside = 'avoid';
nextP.style.marginBottom = '30px';
}
}
await downloadPDF(tableBox.value, '警情分析报告');
// 恢复原始样式
tableBox.value.style.overflow = originalOverflow;
tableBox.value.style.height = originalHeight;
tableBox.value.style.paddingBottom = originalPaddingBottom;
tableBox.value.style.marginBottom = originalMarginBottom;
// 恢复总结分析部分的样式
if (lastH2) {
lastH2.style.pageBreakBefore = '';
lastH2.style.pageBreakInside = '';
const nextP = lastH2.nextElementSibling;
if (nextP && nextP.tagName === 'P') {
nextP.style.pageBreakInside = '';
nextP.style.marginBottom = '';
}
}
} catch (error) {
console.error('下载失败:', error);
} finally {
downLoading.value = false;
}
}
const downloadWithStyles = async () => {
if (!tableBox.value?.innerHTML) return;
downloadDocWithStyle(tableBox.value?.innerHTML)
// downloadDocWithStyle(tableBox.value?.innerHTML)
downLoadPdf()
}
const pageData = reactive({
@ -381,6 +449,7 @@ const Time = () => {
Time()
const funAll = () => {
// 使用Promise.all确保所有数据获取函数执行完成
downLoading.value = true
Promise.all([
getfxbgDywdtj(),
getfxbgJqlxtj(),
@ -391,6 +460,7 @@ const funAll = () => {
getfxbgTj()
]).then(() => {
extractTextContent()
downLoading.value = false
})
}
@ -422,7 +492,8 @@ watch(() => dictItemList.value, (val) => {
}, { deep: true })
getDictItemList()
const tableBox = ref(null);
const textContent = ref('')
const textContent = ref('');
const chartRefs = ref([]); // 存储所有图表组件的引用
// 获取p标签的所有文字内容去除所有<>包裹的内容
const extractTextContent = () => {
loading.value = true
@ -459,7 +530,7 @@ const extractTextContent = () => {
prompt: `# 角色定位\n你是一名资深警务人员尤其擅长对警情、案件、线索等非结构化文本数据进行阅读理解并总结各种对象之间的关联关系,对下面数据进行一个分析总结给出一个总结报告。\n${result}`,
max_tokens: 1000,
}).then(res => {
textContent.value = res.choices[0].text
textContent.value = res?.choices?.[0]?.text
}).finally(() => {
loading.value = false
})
@ -475,6 +546,10 @@ const extractTextContent = () => {
color: red;
}
.analysis-report-box {
position: relative;
}
p {
text-indent: 2em;
/* 首行缩进2个汉字 */